From 81313007b08094cfa67a6b24fcb0034cc24e5b61 Mon Sep 17 00:00:00 2001 From: Adrien Bourmault Date: Wed, 21 Aug 2019 10:26:17 +0200 Subject: [PATCH] Releasing 3.1.2 --- CNIRevelator.py | 124 ++++ Invert.png | Bin 0 -> 553 bytes OCR.png | Bin 0 -> 512 bytes background.png | Bin 0 -> 83685 bytes background.svg | 235 ++++++++ critical.py | 66 +++ downloader.py | 214 +++++++ globs.py | 71 +++ id-card.ico | Bin 0 -> 16121 bytes id-card.ico.png | Bin 0 -> 71145 bytes id-card.svg | 279 +++++++++ ihm.py | 375 ++++++++++++ lang.py | 1473 ++++++++++++++++++++++++++++++++++++++++++++++ launcher.py | 57 ++ logger.py | 81 +++ main.py | 1076 +++++++++++++++++++++++++++++++++ mrz.py | 562 ++++++++++++++++++ pytesseract.py | 326 ++++++++++ rotateLeft.png | Bin 0 -> 750 bytes rotateLeft1.png | Bin 0 -> 450 bytes rotateRight.png | Bin 0 -> 738 bytes rotateRight1.png | Bin 0 -> 428 bytes updater.py | 420 +++++++++++++ zoomIn.png | Bin 0 -> 772 bytes zoomIn20.png | Bin 0 -> 1245 bytes zoomIn50.png | Bin 0 -> 1217 bytes zoomOut.png | Bin 0 -> 735 bytes zoomOut20.png | Bin 0 -> 1013 bytes zoomOut50.png | Bin 0 -> 1147 bytes 29 files changed, 5359 insertions(+) create mode 100644 CNIRevelator.py create mode 100644 Invert.png create mode 100644 OCR.png create mode 100644 background.png create mode 100644 background.svg create mode 100644 critical.py create mode 100644 downloader.py create mode 100644 globs.py create mode 100644 id-card.ico create mode 100644 id-card.ico.png create mode 100644 id-card.svg create mode 100644 ihm.py create mode 100644 lang.py create mode 100644 launcher.py create mode 100644 logger.py create mode 100644 main.py create mode 100644 mrz.py create mode 100644 pytesseract.py create mode 100644 rotateLeft.png create mode 100644 rotateLeft1.png create mode 100644 rotateRight.png create mode 100644 rotateRight1.png create mode 100644 updater.py create mode 100644 zoomIn.png create mode 100644 zoomIn20.png create mode 100644 zoomIn50.png create mode 100644 zoomOut.png create mode 100644 zoomOut20.png create mode 100644 zoomOut50.png diff --git a/CNIRevelator.py b/CNIRevelator.py new file mode 100644 index 0000000..cf491c5 --- /dev/null +++ b/CNIRevelator.py @@ -0,0 +1,124 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application launcher & updater * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" +# Import critical files +import os +import threading + +import lang # lang.py +import logger # logger.py +import globs # globs.py +import critical # critical.py + +# Import all other files and crash if necessary +try: + + import launcher # launcher.py" + import updater # updater.py + import pytesseract # pytesseract.py + import ihm # ihm.py + from tkinter.messagebox import * + from tkinter import * +except: + critical.crashCNIR() + +# Global handler +logfile = logger.logCur + +## MAIN FUNCTION OF CNIREVELATOR +def main(): + logfile.printdbg('*** CNIRevelator LOGFILE. Hello World ! ***') + + mainw = mainWindow() + + try: + os.environ['PATH'] = globs.CNIRFolder + '\\Tesseract-OCR5\\' + os.environ['TESSDATA_PREFIX'] = globs.CNIRFolder + '\\Tesseract-OCR5\\tessdata' + tesser_version = pytesseract.get_tesseract_version() + except Exception as e: + logfile.printerr('ERROR WITH TESSERACT MODULE ' + str(e)) + else: + text = 'Tesseract version ' + str(tesser_version) + ' Licensed Apache 2004 successfully initiated\n' + mainw.logOnTerm(text) + + mainw.logOnTerm('\n\n{} \n'.format(lang.all[globs.CNIRlang]["Please type a MRZ or open a scan"])) + + # changelog + if globs.CNIRNewVersion: + mainw.after_idle(mainw.showChangeLog) + + logfile.printdbg('main() : **** Launching App_main() ****') + try: + mainw.mainloop() + except Exception as e: + showerror(lang.all[globs.CNIRlang]["CNIRevelator Fatal Error"], "{} : {}".format(lang.all[globs.CNIRlang]["An error has occured"],e), parent=mainw) + logfile.printdbg('main() : **** Ending App_main() ****') + + logfile.printdbg('*** CNIRevelator LOGFILE. Goodbye World ! ***') + logfile.close() + + +## BOOTSTRAP OF CNIREVELATOR +try: + + try: + # LANGUAGE + lang.readLang() + except: + critical.crashCNIR() + updater.exitProcess(1) + + from main import * # main.py + # GO + try: + launcherThread = threading.Thread(target=updater.umain, daemon=False) + launcher.lmain(launcherThread) + except Exception: + critical.crashCNIR(False) + + if updater.UPDATE_IS_MADE: + # Launch app ! + args = updater.UPATH + '\\CNIRevelator.exe' + " DELETE " + globs.CNIRFolder + cd = updater.UPATH + for i in range(0,3): + try: + updater.spawnProcess(args, cd) + except: + time.sleep(3) + continue + break + updater.exitProcess(0) + + # Here we go ! + try: + main() + except Exception as e: + critical.crashCNIR() + updater.exitProcess(1) + +except: + critical.crashCNIR() + +updater.exitProcess(0) \ No newline at end of file diff --git a/Invert.png b/Invert.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b3fb3c11b40fe1cb64b936cdffe679868134d7 GIT binary patch literal 553 zcmV+^0@nSBP)@B~OuQCJj& zJO@t$Dc~7MYNSv*MG&IJk`r`7iIOcB(nY8WUbb?_Vy za==F-T66UX_jABQu~c$(7u_82LRAOuieBfc5Tn)_d=Hy*fDbUr0e3}Y=1j-ANTI)T zz_-O(T+0Ce!bdYUjae!4v-TEbpi7JytIP9eGR3^6n=@00000NkvXXu0mjfk&*I4 literal 0 HcmV?d00001 diff --git a/OCR.png b/OCR.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc81ea2a85b09cf9473673458392d76071a1f04 GIT binary patch literal 512 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

J&ka6!f;Hqzuv~kK<+x`OqKGCT16I~ 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

SH~_W$sURp* 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/critical.py b/critical.py new file mode 100644 index 0000000..5e8af5c --- /dev/null +++ b/critical.py @@ -0,0 +1,66 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Critical Stuff for CNIRevelator * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" +from tkinter.messagebox import * +from importlib import reload +from tkinter import * +import webbrowser +import traceback +import psutil +import os + +import lang # lang.py +import logger # logger.py +import globs # globs.py + +def crashCNIR(shutdown=True): + """ + very last solution + """ + + try: + root = Tk() + root.withdraw() + logfile = logger.logCur + logfile.printerr("FATAL ERROR : see traceback below.\n{}".format(traceback.format_exc())) + showerror(lang.all[globs.CNIRlang]["CNIRevelator Fatal Eror"], lang.all[globs.CNIRlang]["CNIRevelator crashed because a fatal error occured. View log for more infos and please open an issue on Github"]) + res = askquestion(lang.all[globs.CNIRlang]["CNIRevelator Fatal Eror"], lang.all[globs.CNIRlang]["Would you like to open the log file ?"]) + if res == "yes": + webbrowser.open_new(globs.CNIRErrLog) + res = askquestion(lang.all[globs.CNIRlang]["CNIRevelator Fatal Eror"], lang.all[globs.CNIRlang]["Would you like to open an issue on Github to report this bug ?"]) + if res == "yes": + webbrowser.open_new("https://github.com/neox95/CNIRevelator/issues") + root.destroy() + + # Quit ? + if not shutdown: + return + + # Quit totally without remain in memory + for process in psutil.process_iter(): + if process.pid == os.getpid(): + process.terminate() + sys.exit(arg) + except: + traceback.print_exc() \ No newline at end of file diff --git a/downloader.py b/downloader.py new file mode 100644 index 0000000..3faa243 --- /dev/null +++ b/downloader.py @@ -0,0 +1,214 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application launcher download stuff * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" + +import base64, hashlib +import os +from pypac import PACSession +from requests.auth import HTTPProxyAuth +from Crypto import Random +from Crypto.Cipher import AES +from requests import Session +from time import time + +import critical # critical.py +import logger # logger.py +import globs # globs.py +import ihm # ihm.py +import lang # lang.py + +class AESCipher(object): + + def __init__(self, key): + self.bs = 32 + self.key = hashlib.sha256(key.encode()).digest() + + def encrypt(self, raw): + raw = self._pad(raw) + iv = Random.new().read(AES.block_size) + cipher = AES.new(self.key, AES.MODE_CBC, iv) + return base64.b64encode(iv + cipher.encrypt(raw)) + + def decrypt(self, enc): + enc = base64.b64decode(enc) + iv = enc[:AES.block_size] + cipher = AES.new(self.key, AES.MODE_CBC, iv) + return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8') + + def _pad(self, s): + return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs) + + @staticmethod + def _unpad(s): + return s[:-ord(s[len(s) - 1:])] + + +class newcredentials: + def __init__(self): + + logfile = logger.logCur + + self.login = '' + self.password = '' + self.valid = False + self.readInTheBooks = False + self.trying = 0 + + while True: + session = PACSession(proxy_auth=(HTTPProxyAuth(self.login, self.password))) + self.trying += 1 + + try: + sessionAnswer = session.get('https://www.google.com') + except Exception as e: + logfile.printdbg('Network Error : ' + str(e)) + sessionAnswer = '' + + logfile.printdbg("Session Answer : " + str(sessionAnswer)) + + if str(sessionAnswer) == '': + logfile.printdbg('Successfully connected to the Internet !') + self.sessionHandler = session + self.valid = True + return + + if str(sessionAnswer) != '' and self.trying > 2: + # because sometimes the proxy does not return an error (especially if we do not provide either credentials) + logfile.printerr('Network Error, or need a proxy !') + return + + if self.trying > 4: + logfile.printerr('Invalid credentials : access denied, a maximum of 3 trials have been raised !') + return + + logfile.printdbg('Invalid credentials : access denied') + + # Deleting the root of Evil if needed + if self.readInTheBooks: + os.remove(globs.CNIRConfig) + logfile.printdbg("Deleting the root of Evil") + + try: + with open(globs.CNIRConfig, 'rb') as (configFile): + self.readInTheBooks = True + # Decrypt the config file + AESObj = AESCipher(globs.CNIRCryptoKey) + try: + # Reading it + reading = AESObj.decrypt(configFile.read()) + # Parsing it + if reading != '||': + if reading.find('||') != -1: + # TADAAA + self.login, self.password = reading.split('||')[0:2] + else: + # UPS + logfile.printerr('Cryptokey is bad !') + return + + except Exception as e: + raise IOError(str(e)) + + except FileNotFoundError: + logfile.printdbg('We will ask for credentials then') + + launcherWindow = ihm.launcherWindowCur + + # Parameters for the password invite + invite = ihm.LoginDialog(launcherWindow) + invite.transient(launcherWindow) + invite.grab_set() + + launcherWindow.wait_window(invite) + + # Getting the credentials + self.login = invite.login + self.password = invite.key + + AESObj = AESCipher(globs.CNIRCryptoKey) + try: + os.mkdir(globs.CNIRFolder + '\\config') + except: + pass + with open(globs.CNIRConfig, 'wb+') as (configFile): + logfile.printdbg('Saving credentials in encrypted config file') + configFile.write(AESObj.encrypt(self.login + '||' + self.password)) + + + return + +class newdownload: + def __init__(self, credentials, urlFile, destinationFile, title): + self.urlFile = urlFile + self.destinationFile = destinationFile + self.session = credentials.sessionHandler + self.title = title + + logfile = logger.logCur + launcherWindow = ihm.launcherWindowCur + + logfile.printdbg('Requesting download of {}'.format(urlFile)) + + self.handler = self.session.get(self.urlFile, stream=True, headers={'Connection' : 'close', "Cache-Control": "no-cache", "Pragma": "no-cache"}) + self.handler.raise_for_status() + + self.filesize = int(self.handler.headers['Content-length']) + self.chunksize = int(self.filesize / 7) + self.count = 0 + + def download(self): + logfile = logger.logCur + launcherWindow = ihm.launcherWindowCur + url = self.urlFile + filename = self.destinationFile + title = self.title + + reducedFilename = filename.split("\\")[-1] + + launcherWindow.printmsg('{} {}'.format(lang.all[globs.CNIRlang]["Downloading"], title)) + logfile.printdbg('{} {}'.format("Downloading", reducedFilename)) + + try: + os.remove(filename) + except: + pass + + with open(filename, 'wb') as fh: + for chunk in self.handler.iter_content(chunk_size=self.chunksize): + fh.write(chunk) + + self.count = os.path.getsize(self.destinationFile) + Percent = self.count / self.filesize * 100 + + launcherWindow.progressBar.stop() + launcherWindow.progressBar.configure(mode='determinate', value=(int(Percent)), maximum=100) + launcherWindow.printmsg('{} {}'.format(lang.all[globs.CNIRlang]["Downloading"], title) + ' : {:4.2f} %'.format(Percent)) + + launcherWindow.progressBar.configure(mode='indeterminate', value=0, maximum=20) + launcherWindow.progressBar.start() + + logfile.printdbg('{} {}'.format(lang.all[globs.CNIRlang]["Successful retrieved"], filename)) + + return filename + diff --git a/globs.py b/globs.py new file mode 100644 index 0000000..dfbf838 --- /dev/null +++ b/globs.py @@ -0,0 +1,71 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application launcher global variables * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" +import os + +# CNIRevelator version +verType = "final release" +version = [3, 1, 2] +verstring_full = "{}.{}.{} {}".format(version[0], version[1], version[2], verType) +verstring = "{}.{}".format(version[0], version[1]) + +CNIRTesserHash = '5b58db27f7bc08c58a2cb33d01533b034b067cf8' +CNIRFolder = os.getcwd() +CNIRLColor = "#006699" +CNIRName = "CNIRevelator {}".format(verstring) +CNIRCryptoKey = '82Xh!efX3#@P~2eG' +CNIRNewVersion = False + +CNIRConfig = CNIRFolder + '\\config\\conf.ig' +CNIRTesser = CNIRFolder + '\\Tesseract-OCR5\\' +CNIRErrLog = CNIRFolder + '\\logs\\error.log' +CNIRMainLog = CNIRFolder + '\\logs\\main.log' +CNIRUrlConfig = CNIRFolder + '\\config\\urlconf.ig' +CNIRVerStock = CNIRFolder + '\\downloads\\versions.lst' +CNIREnv = CNIRFolder + '\\Data\\' + + +CNIRTesserHash = '947224361ffab8c01f05c9394b44b1bd7c8c3d4d' +CNIRFolder = os.path.dirname(os.path.realpath(__file__)) +CNIRLColor = "#006699" +CNIRName = "CNIRevelator {}".format(verstring) +CNIRCryptoKey = '82Xh!efX3#@P~2eG' + +CNIRLangFile = CNIRFolder + '\\config\\lang.ig' +CNIRlang = "fr" + +CNIRConfig = CNIRFolder + '\\config\\conf.ig' +CNIRTesser = CNIRFolder + '\\Tesseract-OCR5\\' +CNIRErrLog = CNIRFolder + '\\logs\\error.log' +CNIRMainLog = CNIRFolder + '\\logs\\main.log' +CNIRUrlConfig = CNIRFolder + '\\config\\urlconf.ig' +CNIRVerStock = CNIRFolder + '\\downloads\\versions.lst' +CNIREnv = CNIRFolder + '\\Data\\' + +CNIRBetaURL = "https://raw.githubusercontent.com/neox95/CNIRevelator/v3.1/VERSIONS.LST" +CNIRDefaultURL = "https://raw.githubusercontent.com/neox95/CNIRevelator/master/VERSIONS.LST" + +CNIRNewVersion = False +CNIROpenFile = False +debug = True \ No newline at end of file diff --git a/id-card.ico b/id-card.ico new file mode 100644 index 0000000000000000000000000000000000000000..611b4c9fd0ee4e0616b52303f5663450786e2b27 GIT binary patch literal 16121 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 literal 0 HcmV?d00001 diff --git a/id-card.ico.png b/id-card.ico.png new file mode 100644 index 0000000000000000000000000000000000000000..85b4bc7ce7eb7a59ab1ab8b197084b487f022c7d GIT binary patch literal 71145 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 W|Mzaf+3$n@%nx7%KRPLTKB;K literal 0 HcmV?d00001 diff --git a/id-card.svg b/id-card.svg new file mode 100644 index 0000000..3b51ad0 --- /dev/null +++ b/id-card.svg @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ihm.py b/ihm.py new file mode 100644 index 0000000..cd2784f --- /dev/null +++ b/ihm.py @@ -0,0 +1,375 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application launcher graphical interface * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" + +from tkinter.messagebox import * +from tkinter import * +from tkinter import filedialog +from tkinter import ttk +import PIL.Image, PIL.ImageTk +import traceback +import webbrowser +import cv2 + +import critical # critical.py +import logger # logger.py +import globs # globs.py +import lang # lang.py +import updater # updater.py +import critical # critical.py + +controlKeys = ["Escape", "Right", "Left", "Up", "Down", "Home", "End", "BackSpace", "Delete", "Inser", "Shift_L", "Shift_R", "Control_R", "Control_L"] + +class DocumentAsk(Toplevel): + + def __init__(self, parent, choices): + self.choice = 0 + vals = [0, 1] + super().__init__(parent) + self.title("{} :".format(lang.all[globs.CNIRlang]["Choose the identity document"])) + + ttk.Radiobutton(self, text=choices[0], command=self.register0, value=vals[0]).pack() + ttk.Radiobutton(self, text=choices[1], command=self.register1, value=vals[1]).pack() + + self.button = Button(self, text='OK', command=(self.ok)).pack() + self.resizable(width=False, height=False) + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + w = hs / 3 + h = ws / 20 + self.update() + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + + def register0(self): + self.choice = 0 + def register1(self): + self.choice = 1 + def ok(self): + self.destroy() + + +class OpenScanDialog(Toplevel): + + def __init__(self, parent, text): + super().__init__(parent) + self.parent = parent + self.title(lang.all[globs.CNIRlang]["OCR Detection Validation"]) + self.resizable(width=False, height=False) + self.termtext = Text(self, state='normal', width=45, height=2, wrap='none', font='Terminal 17', fg='#121f38') + self.termtext.grid(column=0, row=0, sticky='NEW', padx=5, pady=5) + self.termtext.insert('end', text + '\n') + self.button = Button(self, text=lang.all[globs.CNIRlang]["Validate"], command=(self.valid)) + self.button.grid(column=0, row=1, sticky='S', padx=5, pady=5) + self.update() + hs = self.winfo_screenheight() + w = int(self.winfo_width()) + h = int(self.winfo_height()) + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + + def valid(self): + self.parent.validatedtext = self.termtext.get('1.0', 'end') + texting = self.parent.validatedtext.replace(' ', '').replace('\r', '').split('\n') + for i in range(len(texting)): + for char in texting[i]: + if char not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<': + showerror(lang.all[globs.CNIRlang]["Validation Error"], lang.all[globs.CNIRlang]["The submitted MRZ contains invalid characters"], parent=self) + self.parent.validatedtext = '' + + self.destroy() + +class LoginDialog(Toplevel): + + def __init__(self, parent): + self.key = '' + self.login = '' + super().__init__(parent) + self.title(lang.all[globs.CNIRlang]["Connection"]) + self["background"] = "white" + Label(self, text='IPN : ', bg="white").pack(fill=Y) + self.entry_login = ttk.Entry(self) + self.entry_login.insert(0, '') + self.entry_login.pack() + Label(self, text='{} : '.format(lang.all[globs.CNIRlang]["Password"]), bg="white").pack(fill=Y) + self.entry_pass = ttk.Entry(self, show='*') + self.entry_pass.insert(0, '') + self.entry_pass.pack(fill=Y) + ttk.Button(self, text=lang.all[globs.CNIRlang]["Connection"], command=(self.connecti)).pack(fill=Y, pady=10) + self.update() + self.resizable(width=False, height=False) + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + w = self.winfo_reqwidth() + 5 + h = self.winfo_reqheight() + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + self.bind("", self.connecti) + + def connecti(self, event=None): + self.login = self.entry_login.get().strip() + self.key = self.entry_pass.get().strip() + self.destroy() + +class ChangelogDialog(Toplevel): + + def __init__(self, parent, text): + super().__init__(parent) + + self.title(lang.all[globs.CNIRlang]["Show Changelog"]) + self["background"] = "white" + + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(0, weight=1) + + self.text = Text(self, wrap='word', width=55, height=15, borderwidth=0, font="TkDefaultFont", bg="white") + self.text.grid(column=0, row=0, sticky='EWNS', padx=5, pady=5) + + ttk.Button(self, text="OK", command=(self.oki)).grid(column=0, row=1, pady=5) + + self.scrollb = ttk.Scrollbar(self, command=(self.text.yview)) + self.scrollb.grid(column=1, row=0, sticky='EWNS', padx=5, pady=5) + self.text['yscrollcommand'] = self.scrollb.set + + self.text.insert('end', text) + self.text['state'] = 'disabled' + + self.update() + self.resizable(width=False, height=False) + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + w = self.winfo_reqwidth() + 5 + h = self.winfo_reqheight() + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + self.bind("", self.oki) + + def oki(self, event=None): + self.destroy() + +class langDialog(Toplevel): + + def __init__(self, parent): + super().__init__(parent) + + self.title(lang.all[globs.CNIRlang]["Language"]) + + Label(self, text=lang.all[globs.CNIRlang]["Please choose your language : "]).pack(fill=Y) + + vals = [i for i in lang.all] + for i in range(len(lang.all)): + ttk.Radiobutton(self, text=vals[i], command=self.createRegister(i), value=vals[i]).pack(fill=Y) + + ttk.Button(self, text="OK", command=(self.oki)).pack(fill=Y, pady=5) + + self.update() + self.resizable(width=False, height=False) + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + w = self.winfo_reqwidth() + 5 + h = self.winfo_reqheight() + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + self.bind("", self.oki) + + def oki(self, event=None): + self.destroy() + + def createRegister(self, i): + def register(): + lang.updateLang([j for j in lang.all][i]) + return register + +class updateSetDialog(Toplevel): + + def __init__(self, parent): + super().__init__(parent) + + self.title(lang.all[globs.CNIRlang]["Update options"]) + + Label(self, text=lang.all[globs.CNIRlang]["Please choose your update channel : "]).pack(fill=Y) + + self.vals = ["Stable", "Beta"] + vals = self.vals + for i in range(len(vals)): + ttk.Radiobutton(self, text=vals[i], command=self.createRegister(i), value=vals[i]).pack(fill=Y) + + ttk.Button(self, text="OK", command=(self.oki)).pack(fill=Y, pady=5) + + self.update() + self.resizable(width=False, height=False) + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + w = self.winfo_reqwidth() + 5 + h = self.winfo_reqheight() + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + self.bind("", self.oki) + + def oki(self, event=None): + self.destroy() + + def createRegister(self, i): + def register(): + updater.updateChannel(self.vals[i]) + return register + +class LauncherWindow(Tk): + + def __init__(self): + # Initialize the tkinter main class + Tk.__init__(self) + self.resizable(width=False, height=False) + + # icon + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + + # Setting up the geometry + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + wheight = 274 + wwidth = 480 + # Centering + x = ws / 2 - wwidth / 2 + y = hs / 2 - wheight / 2 + self.geometry('%dx%d+%d+%d' % (wwidth, wheight, x, y)) + + # Creating objects + # Load an image using OpenCV + # if getattr(sys, 'frozen', False): + # cv_img = cv2.imread(sys._MEIPASS + r"\background.png\background.png") + # else: + cv_img = cv2.imread("background.png") + + cv_img = cv2.imread("background.png") + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) + cv_img = cv2.blur(cv_img, (15, 15)) + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, no_channels = cv_img.shape + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, no_channels = cv_img.shape + # Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage + self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img)) + self.mainCanvas = Canvas(self, width=wwidth, height=wheight, bg=globs.CNIRLColor, highlightthickness=0) + self.mainCanvas.create_image(wwidth /2, wheight /2, image=self.photo) + + # Column + self.mainCanvas.grid_rowconfigure(0, weight=1, minsize=(wheight / 10 * 9)) + self.mainCanvas.grid_rowconfigure(1, weight=1, minsize=(wheight / 10 * 1)) + + self.mainCanvas.create_text((wwidth / 2), (wheight / 3), text=(globs.CNIRName), font='Helvetica 30', fill='white') + self.mainCanvas.create_text((wwidth / 2), (wheight / 2), text="version " + (globs.verstring_full), font='Helvetica 8', fill='white') + self.msg = self.mainCanvas.create_text((wwidth / 2), (wheight / 1.20), text=lang.all[globs.CNIRlang]["Booting up..."], font='Helvetica 9', fill='white') + + #self.pBarZone = Frame(self.mainCanvas, width=wwidth, height=wheight/10) + self.update() + + self.progressBar = ttk.Progressbar(self.mainCanvas, orient=HORIZONTAL, length=wwidth, mode='determinate') + + self.wm_title(globs.CNIRName) + + self.mainCanvas.grid(row=0) + self.update() + self.progressBar.grid(row=1, sticky='S') + + logfile = logger.logCur + logfile.printdbg('Launcher IHM successful') + self.protocol('WM_DELETE_WINDOW', lambda : 0) + self.update() + + def printmsg(self, msg): + self.mainCanvas.itemconfigure(self.msg, text=(msg)) + + def exit(self): + self.after(1000, self.destroy) + +class ResizeableCanvas(Canvas): + def __init__(self,parent,**kwargs): + Canvas.__init__(self,parent,**kwargs) + self.bind("", self.on_resize) + self.height = self.winfo_reqheight() + self.width = self.winfo_reqwidth() + + def on_resize(self,event): + # determine the ratio of old width/height to new width/height + wscale = float(event.width)/self.width + hscale = float(event.height)/self.height + self.width = event.width + self.height = event.height + # rescale all the objects tagged with the "all" tag + self.scale("all",0,0,wscale,hscale) + +class StatusBar(Frame): + + def __init__(self, master): + Frame.__init__(self, master) + self.label = Label(self, bd=1, relief=SUNKEN, anchor=W) + self.label.pack(fill=X) + + def set(self, text): + self.label.config(text="Document : " + text.lower()) + self.label.update_idletasks() + + def clear(self): + self.label.config(text="") + self.label.update_idletasks() + +## Global Handler +launcherWindowCur = LauncherWindow() + diff --git a/lang.py b/lang.py new file mode 100644 index 0000000..f9367b0 --- /dev/null +++ b/lang.py @@ -0,0 +1,1473 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application langage file * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" +import os + +import globs # globs.py +import critical # critical.py + +## FRENCH LANGUAGE +french = \ +{ +"Please type a MRZ or open a scan" : "Veuillez taper une MRZ ou ouvrir un scan svp", +"Changelog : update summary" : "Changelog : résumé de mise à jour", +"Program version" : "Version du logiciel", +"CNIRevelator Fatal Error" : "Erreur fatale de CNIRevelator", +"An error has occured" : "Une erreur s'est produite", +"Downloading" : "Téléchargement de", +"Successful retrieved" : "Réussite du téléchargement de", +"Choose the identity document" : "Choisir le document d'identité", +"OCR Detection Validation" : "Validation de la MRZ détectée par OCR", +"Validate" : "Valider", +"Validation Error" : "Erreur de validation", +"The submitted MRZ contains invalid " + "characters" : "La MRZ soumise contient des caractères invalides", +"Connection" : "Connexion", +"Password" : "Mot de passe", +"Booting up..." : "Démarrage", +"CNIRevelator Fatal Eror" : "Erreur Fatale de CNIRevelator", +"CNIRevelator crashed because a " +"fatal error occured. View log for " +"more infos and please open " +"an issue on Github" : "CNIRevelator s'est arrêté car une erreur fatale s'est produite. Consultez le journal pour plus d'informations et ouvrez s'il vous plaît un ticket sur Github.", +"Would you like to open the " +"log file ?" : "Souhaitez-vous ouvrir le fichier de log ?", +"Would you like to open an issue " +"on Github to report this bug ?" : "Souhaitez-vous ouvrir un ticket sur Github pour signaler ce bogue?", +"Starting..." : "Lancement...", +"Informations about the current " +"document" : "Informations sur la pièce d'identité", +"IDLE" : "EN ATTENTE", +"Status" : "Statut", +"Name" : "Nom", +"Birth date" : "Date de naissance", +"Issue date" : "Date de délivrance", +"Expiration date" : "Date d'expiration", +"Sex" : "Sexe", +"Issuing country" : "Pays émetteur", +"Nationality" : "Nationalité", +"Registration" : "Immatriculation", +"Document number" : "N° de document", +"Unknown" : "Inconnu(e)", +"Display and processing of " +"documents" : "Affichage et traitement de documents", +"Complete MRZ capture terminal" : "Terminal de saisie de MRZ complète", +"Quick entry terminal (731)" : "Terminal de saisie rapide (731)", +"Monitor" : "Moniteur", +"New" : "Nouveau", +"Open scan..." : "Ouvrir scan...", +"Quit" : "Quitter", +"File" : "Fichier", +"Settings" : "Paramètres", +"Keyboard commands" : "Commandes au clavier", +"Report a bug" : "Signaler un problème", +"About CNIRevelator" : "A propos de CNIRevelator", +"Help" : "Aide", +"OCR module error" : "Erreur du module OCR", +"The OCR module located at {} " +"can not be found or corrupted. " +"It will be reinstalled at " +"the next run" : "Le module OCR localisé en {} est introuvable ou corrompu. Il sera réinstallé à la prochaine exécution", +"The Tesseract module " +"encountered a problem: {}" : "Le module Tesseract a rencontré un problème : {}", +"Tesseract error : {}. " +"Will be reinstallated" : "Erreur de Tesseract : {}. Le module sera réinstallé", +"Document detected: {}\n" : "Document detecté : {}\n", +"Document detected again: {}\n" : "Document re-detecté : {}\n", +"Character not accepted !\n" : "Caractère non accepté !\n", +"Open a scan of document..." : "Ouvrir un scan de document...", +"OpenCV error (image processing)" : "Erreur OpenCV (traitement d'images)", +"A critical error has occurred in " +"the OpenCV image processing " +"manager used by CNIRevelator, the " +"application will reset itself" : "Une erreur critique s'est produite dans le gestionnaire de traitement d'images OpenCV utilisé par CNIRevelator. L'application va se réinitialiser", +"ABOUT" : 'Version du logiciel : CNIRevelator ' + globs.verstring_full + '\n\n' + "Copyright © 2018-2019 Adrien Bourmault (neox95)" + "\n\n" + "CNIRevelator est un logiciel libre : vous avez le droit de le modifier et/ou le distribuer " + "dans les termes de la GNU General Public License telle que publiée par " + "la Free Software Foundation, dans sa version 3 ou " + "ultérieure. " + "\n\n" + "CNIRevelator est distribué dans l'espoir d'être utile, sans toutefois " + "impliquer une quelconque garantie de " + "QUALITÉ MARCHANDE ou APTITUDE À UN USAGE PARTICULIER. Référez vous à la " + "GNU General Public License pour plus de détails à ce sujet. " + "\n\n" + "Vous devriez avoir reçu une copie de la GNU General Public License " + "avec CNIRevelator. Si cela n'est pas le cas, jetez un oeil à . " + "\n\n" + "Le module d'OCR Tesseract 4.0 est soumis à l'Apache License 2004." + "\n\n" + "Les bibliothèques python et l'environnement Anaconda 3 sont soumis à la licence BSD 2018-2019." + "\n\n" + "Le code source de ce programme est disponible sur Github à l'adresse .\n" + "Son fonctionnement est conforme aux normes et directives du document 9303 de l'OACI régissant les documents de voyages et d'identité." + '\n\n' + " En cas de problèmes ou demande particulière, ouvrez-y une issue ou bien envoyez un mail à neox@os-k.eu !\n\n", + +"KEYBHELP" : "Terminal de saisie rapide (731) : \n\n" + "Caractères autorisés : Alphanumériques en majuscule et le caractère '<'. Pas de minuscules ni caractères spéciaux, autrement la somme est mise à zéro \n\n" + "Calculer résultat :\t\t\tTouche Ctrl droite \n" + "Copier :\t\t\t\tCtrl-C \n" + "Coller :\t\t\t\tCtrl-V \n" + "\n\n" + "Terminal de saisie MRZ complète : \n\n" + "Caractères autorisés : Alphanumériques en majuscule et le caractère '<'. Pas de minuscules ni caractères spéciaux, autrement la somme est mise à zéro \n\n" + "Calculer résultat :\t\t\tTouche Ctrl droite \n" + "Compléter champ :\t\t\tTouche Tab \n" + "Copier :\t\t\t\tCtrl-C \n" + "Coller :\t\t\t\tCtrl-V \n" + "Forcer une nouvelle détection du document :\tEchap\n", + +"CHANGELOG" : "Version 3.1.2 \nMise-à-jour mineure avec les progressions suivantes :\n- Montée de version de Tesseract OCR : 5.0\n\n" + \ +"Version 3.1.1 \nMise-à-jour mineure avec les progressions suivantes :\n- Correction d'un bug sévère du système de mise à jour\n\n" + \ +"Version 3.1.0 \nMise-à-jour majeure avec les progressions suivantes :\n- Modifications cosmétiques de l'interface utilisateur\n- Stabilisation des changements effectués sur la version mineure 3.0 : interface utilisateur, OCR, VISA A et B, logging\n- Rationalisation du système de langues\n- Ajout des canaux de mise-à-jour\n\n" + \ +"Version 3.0.8 finale\nCorrectif : bug du système de mise-à-jour'\n\n" + \ +"Version 3.0.6 \nMise-à-jour mineure avec les corrections suivantes :\n- Changement de l'apparence du launcher de l'application\n- Améliorations de l'interface, notamment de la stabilité\n- Ajout de la signature numérique de l'exécutable\n\n" + \ +"Version 3.0.7 finale\nMise-à-jour majeure avec les corrections suivantes :\n- Refonte de l'interface utilisateur\n- Fonction OCR intégrée à l'application avec support des TIFF et JPEG\n- Corrections d'erreurs sur le traitement des VISA de type A et B, ainsi que les titres de séjour\n\n" + \ +"Version 3.0.6 \nMise-à-jour mineure avec les corrections suivantes :\n- Changement de l'apparence du launcher de l'application\n- Améliorations de l'interface, notamment de la stabilité\n- Ajout de la signature numérique de l'exécutable\n\n" + \ +"Version 3.0.5 \nMise-à-jour mineure avec les corrections suivantes :\n- Changement de l'icône de l'exécutable afin de refléter le changement de version majeur accompli en 3.0\n\n" + \ +"Version 3.0.4 \nMise-à-jour mineure avec les corrections suivantes :\n- Correction d'un bug affectant le système de mise-à-jour\n\n" + \ +"Version 3.0.3 \nMise-à-jour mineure avec les corrections suivantes :\n- Correction d'un bug affectant le changelog\n- Correction d'une erreur avec la touche Suppr Arrière et Suppr causant une perte de données\n\n" + \ +"Version 3.0.2 \nMise-à-jour mineure avec les corrections suivantes :\n- Changement d'icône de l'exécutable\n- Correction d'un bug affectant le logging\n- Correction d'un bug affectant la détection de documents\n- Et autres modifications mineures\n\n" + \ +"Version 3.0.1 \nMise-à-jour majeure avec les corrections suivantes :\n- Renouvellement de la signature numérique de l'exécutable\n- Amélioration de présentation du log en cas d'erreur\n- Refonte totale du code source et désobfuscation\n- Téléchargements en HTTPS fiables avec somme de contrôle\n- Nouveaux terminaux d'entrées : un rapide (731) et un complet\n- Détection des documents améliorée, possibilité de choix plus fin\nEt les regressions suivantes :\n- Suppression temporaire de la fonction de lecture OCR. Retour planifié pour une prochaine version", + +"Document Review: {}\n\n" : "Examen du document : {}\n\n", +"Calculated {} [facultative]\n" : "Checksum position {}: Lu {} VS Calculé {} [facultatif]\n", +"Checksum position {}: Lu {} VS " +"Calculated {}\n" : "Checksum position {}: Lu {} VS Calculé {}\n", +"COMPLIANT" : "CONFORME", +"IMPROPER" : "NON CONFORME", +"Installing the updates" : "Installation des mises-à-jour", +"Verifying download..." : "Vérification du téléchargement ...", +"Preparing installation..." : "Préparation de l'installation", +"Success !" : "Installation terminée !", +"Launching the new version..." : "Lancement de la nouvelle version...", +"Credentials Error. No effective " +"update !" : "Identifiants incorrects. Pas de mise-à-jour !", +"Deleting old version" : "Suppression de l'ancienne version", +'Software is up-to-date !' : "Logiciel à jour !", +"An error occured. " +"No effective update !" : "Une erreur s'est produite. Pas de mise-à-jour !", +"Shortcut creation" : "Création de raccourci", +"Would you like to create/update " +"the shortcut for CNIRevelator " +"on your desktop ?" : "Souhaitez vous créer/mettre à jour le raccourci pour CNIRevelator sur votre bureau ?", +"The file you provided is " +"not found : {}" : "Fichier transmis non trouvé : {}", +"Update options" : "Options de mise-à-jour", +"Language" : "Langue", +"Show Changelog" : "Résumé de mise-à-jour", +"Please choose your language : " : "Merci de choisir votre langue : ", +"Please choose your update " +"channel : " : "Merci de choisir votre canal de mise-à-jour : ", +"Passeport lisible à la machine" : "Passeport lisible à la machine", +"Carte-passeport" : "Carte-passeport", +"Titre d'identité/de voyage" : "Titre d'identité/de voyage", +"Carte d’identité européenne" : "Carte d’identité européenne", +"Certificat de membre d'équipage" : "Certificat de membre d'équipage", +"Visa de type A" : "Visa de type A", +"Visa de type B" : "Visa de type B", +"Carte de séjour FR" : "Carte de séjour français", +"Pièce d'identité/de voyage" : "Pièce d'identité/de voyage", +"Pièce d'identité FR" : "Pièce d'identité française", +"Permis de conduire" : "Permis de conduire", +"The file you provided is not " +"valid : {}" : "Le fichier transmis n'est pas valide : {}", + +"LANDCODE2" : { + 'AW': 'Aruba', + 'AF': 'Afghanistan', + 'AO': 'Angola', + 'AI': 'Anguilla', + 'AL': 'Albanie', + 'AD': 'Andorre', + 'AE': 'Emirats arabes unis', + 'AR': 'Argentine', + 'AM': 'Arménie', + 'AS': 'Samoa américaines', + 'AQ': 'Antarctique', + 'TF': 'Terres australes et antarctiques françaises', + 'AG': 'Antigua-et-Barbuda', + 'AU': 'Australie', + 'AT': 'Autriche', + 'AZ': 'Azerbaidjan', + 'BI': 'Burundi', + 'BE': 'Belgique', + 'BJ': 'Benin', + 'BQ': 'Pays-Bas caribéens', + 'BF': 'Burkina Faso', + 'BD': 'Bangladesh', + 'BG': 'Bulgarie', + 'BH': 'Bahrein', + 'BS': 'Bahamas', + 'BA': 'Bosnie-Herzegovine', + 'BL': 'Saint-Barthélemy', + 'BY': 'Bielorussie', + 'BZ': 'Belize', + 'BM': 'Bermudes', + 'BO': 'Bolivie', + 'BR': 'Brésil', + 'BB': 'Barbade', + 'BN': 'Brunei', + 'BT': 'Bhoutan', + 'BW': 'Botswana', + 'CF': 'République Centrafricaine', + 'CA': 'Canada', + 'CC': 'Îles Cocos', + 'CH': 'Suisse', + 'CL': 'Chili', + 'CN': 'Chine', + 'CI': "Côte d'Ivoire", + 'CM': 'Cameroun', + 'CD': 'Congo (République démocratique)', + 'CG': 'Congo (République)', + 'CK': 'Îles Cook', + 'CO': 'Colombie', + 'KM': 'Comores', + 'CV': 'Cap-Vert', + 'CR': 'Costa Rica', + 'CU': 'Cuba', + 'CW': 'Curaçao', + 'CX': 'Île Christmas', + 'KY': 'Caimans', + 'CY': 'Chypre', + 'CZ': 'Tchéquie', + 'DE': 'Allemagne', + 'DJ': 'Djibouti', + 'DM': 'Dominique', + 'DK': 'Danemark', + 'DO': 'République dominicaine', + 'DZ': 'Algérie', + 'EC': 'Equateur', + 'EG': 'Egypte', + 'ER': 'Erythrée', + 'EH': 'Sahara occidental', + 'ES': 'Espagne', + 'EE': 'Estonie', + 'ET': 'Ethiopie', + 'FI': 'Finlande', + 'FJ': 'Fidji', + 'FK': 'Îles Malouines', + 'FR': 'France', + 'FO': 'Féroé', + 'FM': 'Micronésie', + 'GA': 'Gabon', + 'GB': 'Royaume-Uni', + 'GE': 'Géorgie', + 'GG': 'Guernesey', + 'GH': 'Ghana', + 'GI': 'Gibraltar', + 'GN': 'Guinée', + 'GP': 'Guadeloupe', + 'GM': 'Gambie', + 'GW': 'Guinée-Bissau', + 'GQ': 'Guinée équatoriale', + 'GR': 'Grèce', + 'GD': 'Grenade', + 'GL': 'Groenland', + 'GT': 'Guatemala', + 'GF': 'Guyane', + 'GU': 'Guam', + 'GY': 'Guyana', + 'HK': 'Hong Kong', + 'HN': 'Honduras', + 'HR': 'Croatie', + 'HT': 'Haïti', + 'HU': 'Hongrie', + 'ID': 'Indonésie', + 'IM': 'Île de Man', + 'IN': 'Inde', + 'IO': "Territoire britannique de l'océan Indien", + 'IE': 'Irlande', + 'IR': 'Irak', + 'IQ': 'Iran', + 'IS': 'Islande', + 'IL': 'Israël', + 'IT': 'Italie', + 'JM': 'Jamaïque', + 'JE': 'Jersey', + 'JO': 'Jordanie', + 'JP': 'Japon', + 'KZ': 'Kazakhstan', + 'KE': 'Kenya', + 'KG': 'Kirghizistan', + 'KH': 'Cambodge', + 'KI': 'Kiribati', + 'KN': 'Saint-Christophe-et-Niévès', + 'KR': 'Corée du Sud', + 'KW': 'Koweït', + 'LA': 'Laos', + 'LB': 'Liban', + 'LR': 'Liberia', + 'LY': 'Libye', + 'LC': 'Sainte-Lucie', + 'LI': 'Liechtenstein', + 'LK': 'Sri Lanka', + 'LS': 'Lesotho', + 'LT': 'Lituanie', + 'LU': 'Luxembourg', + 'LV': 'Lettonie', + 'MO': 'Macao', + 'MF': 'Sint-Maarten', + 'MA': 'Maroc', + 'MC': 'Monaco', + 'MD': 'Moldavie', + 'MG': 'Madagascar', + 'MV': 'Maldives', + 'MX': 'Mexique', + 'MH': 'Marshall', + 'MK': 'Macedoine', + 'ML': 'Mali', + 'MT': 'Malte', + 'MM': 'Birmanie', + 'ME': 'Monténégro', + 'MN': 'Mongolie', + 'MP': 'Îles Mariannes du Nord', + 'MZ': 'Mozambique', + 'MR': 'Mauritanie', + 'MS': 'Montserrat', + 'MQ': 'Martinique', + 'MU': 'Maurice', + 'MW': 'Malawi', + 'MY': 'Malaisie', + 'YT': 'Mayotte', + 'NA': 'Namibie', + 'NC': 'Nouvelle-Calédonie', + 'NE': 'Niger', + 'NF': 'Île Norfolk', + 'NG': 'Nigeria', + 'NI': 'Nicaragua', + 'NU': 'Niue', + 'NL': 'Pays-Bas', + 'NO': 'Norvège', + 'NP': 'Nepal', + 'NR': 'Nauru', + 'NZ': 'Nouvelle-Zélande', + 'OM': 'Oman', + 'PK': 'Pakistan', + 'PA': 'Panama', + 'PN': 'Îles Pitcairn', + 'PE': 'Pérou', + 'PH': 'Philippines', + 'PW': 'Palaos', + 'PG': 'Papouasie-Nouvelle-Guinée', + 'PL': 'Pologne', + 'PR': 'Porto Rico', + 'KP': 'Corée du Nord', + 'PT': 'Portugal', + 'PY': 'Paraguay', + 'PS': 'Palestine', + 'PF': 'Polynésie française', + 'QA': 'Qatar', + 'RE': 'Réunion', + 'RO': 'Roumanie', + 'RU': 'Russie', + 'RW': 'Rwanda', + 'SA': 'Arabie saoudite', + 'SD': 'Soudan', + 'SN': 'Sénégal', + 'SG': 'Singapour', + 'GS': 'Georgie du Sud-et-les iles Sandwich du Sud', + 'SH': 'Sainte-Hélène, Ascension et Tristan da Cunha', + 'SJ': 'Svalbard et île Jan Mayen', + 'SB': 'Salomon', + 'SL': 'Sierra Leone', + 'SV': 'Salvador', + 'SM': 'Saint-Marin', + 'SO': 'Somalie', + 'PM': 'Saint-Pierre-et-Miquelon', + 'RS': 'Serbie', + 'SS': 'Soudan du Sud', + 'ST': 'Sao Tomé-et-Principe', + 'SR': 'Suriname', + 'SK': 'Slovaquie', + 'SI': 'Slovénie', + 'SE': 'Suède', + 'SZ': 'eSwatani', + 'SX': 'Saint-Martin ', + 'SC': 'Seychelles', + 'SY': 'Syrie', + 'TC': 'Îles Turques-et-Caïques', + 'TD': 'Tchad', + 'TG': 'Togo', + 'TH': 'Thaïlande', + 'TJ': 'Tadjikistan', + 'TK': 'Tokelau', + 'TM': 'Turkmenistan', + 'TL': 'Timor oriental', + 'TO': 'Tonga', + 'TT': 'Trinité-et-Tobago', + 'TN': 'Tunisie', + 'TR': 'Turquie', + 'TV': 'Tuvalu', + 'TW': 'Taiwan', + 'TZ': 'Tanzanie', + 'UG': 'Ouganda', + 'UA': 'Ukraine', + 'UY': 'Uruguay', + 'US': 'Etats-Unis', + 'UZ': 'Ouzbékistan', + 'VA': 'Saint-Siège (État de la Cité du Vatican)', + 'VC': 'Saint-Vincent-et-les-Grenadines', + 'VE': 'Venezuela', + 'VG': 'Îles Vierges britanniques', + 'VI': 'Îles Vierges des États-Unis', + 'VN': 'Viêt Nam', + 'VU': 'Vanuatu', + 'WF': 'Wallis-et-Futuna', + 'WS': 'Samoa', + 'XK': 'Kosovo', + 'YE': 'Yémen', + 'ZA': 'Afrique du Sud', + 'ZM': 'Zambie', + 'ZW': 'Zimbabwe' + }, + +"LANDCODE3" : { + 'ABW': 'Aruba', + 'AFG': 'Afghanistan', + 'AGO': 'Angola', + 'AIA': 'Anguilla', + 'ALB': 'Albanie', + 'AND': 'Andorre', + 'ARE': 'Emirats arabes unis', + 'ARG': 'Argentine', + 'ARM': 'Arménie', + 'ASM': 'Samoa américaines', + 'ATA': 'Antarctique', + 'ATF': 'Terres australes et antarctiques françaises', + 'ATG': 'Antigua-et-Barbuda', + 'AUS': 'Australie', + 'AUT': 'Autriche', + 'AZE': 'Azerbaidjan', + 'BDI': 'Burundi', + 'BEL': 'Belgique', + 'BEN': 'Benin', + 'BES': 'Pays-Bas caribéens', + 'BFA': 'Burkina Faso', + 'BGD': 'Bangladesh', + 'BGR': 'Bulgarie', + 'BHR': 'Bahrein', + 'BHS': 'Bahamas', + 'BIH': 'Bosnie-Herzegovine', + 'BLM': 'Saint-Barthélemy', + 'BLR': 'Bielorussie', + 'BLZ': 'Belize', + 'BMU': 'Bermudes', + 'BOL': 'Bolivie', + 'BRA': 'Brésil', + 'BRB': 'Barbade', + 'BRN': 'Brunei', + 'BTN': 'Bhoutan', + 'BWA': 'Botswana', + 'CAF': 'République Centrafricaine', + 'CAN': 'Canada', + 'CCK': 'Îles Cocos', + 'CHE': 'Suisse', + 'CHL': 'Chili', + 'CHN': 'Chine', + 'CIV': "Côte d'Ivoire", + 'CMR': 'Cameroun', + 'COD': 'Congo (République démocratique)', + 'COG': 'Congo (République)', + 'COK': 'Îles Cook', + 'COL': 'Colombie', + 'COM': 'Comores', + 'CPV': 'Cap-Vert', + 'CRI': 'Costa Rica', + 'CUB': 'Cuba', + 'CUW': 'Curaçao', + 'CXR': 'Île Christmas', + 'CYM': 'Caimans', + 'CYP': 'Chypre', + 'CZE': 'Tchéquie', + 'DEU': 'Allemagne', + 'DJI': 'Djibouti', + 'DMA': 'Dominique', + 'DNK': 'Danemark', + 'DOM': 'République dominicaine', + 'DZA': 'Algérie', + 'ECU': 'Equateur', + 'EGY': 'Egypte', + 'ERI': 'Erythrée', + 'ESH': 'Sahara occidental', + 'ESP': 'Espagne', + 'EST': 'Estonie', + 'ETH': 'Ethiopie', + 'FIN': 'Finlande', + 'FJI': 'Fidji', + 'FLK': 'Îles Malouines', + 'FRA': 'France', + 'FRO': 'Féroé', + 'FSM': 'Micronésie', + 'GAB': 'Gabon', + 'GBR': 'Royaume-Uni', + 'GEO': 'Géorgie', + 'GGY': 'Guernesey', + 'GHA': 'Ghana', + 'GIB': 'Gibraltar', + 'GIN': 'Guinée', + 'GLP': 'Guadeloupe', + 'GMB': 'Gambie', + 'GNB': 'Guinée-Bissau', + 'GNQ': 'Guinée équatoriale', + 'GRC': 'Grèce', + 'GRD': 'Grenade', + 'GRL': 'Groenland', + 'GTM': 'Guatemala', + 'GUF': 'Guyane', + 'GUM': 'Guam', + 'GUY': 'Guyana', + 'HKG': 'Hong Kong', + 'HND': 'Honduras', + 'HRV': 'Croatie', + 'HTI': 'Haïti', + 'HUN': 'Hongrie', + 'IDN': 'Indonésie', + 'IMN': 'Île de Man', + 'IND': 'Inde', + 'IOT': "Territoire britannique de l'océan Indien", + 'IRL': 'Irlande', + 'IRN': 'Irak', + 'IRQ': 'Iran', + 'ISL': 'Islande', + 'ISR': 'Israël', + 'ITA': 'Italie', + 'JAM': 'Jamaïque', + 'JEY': 'Jersey', + 'JOR': 'Jordanie', + 'JPN': 'Japon', + 'KAZ': 'Kazakhstan', + 'KEN': 'Kenya', + 'KGZ': 'Kirghizistan', + 'KHM': 'Cambodge', + 'KIR': 'Kiribati', + 'KNA': 'Saint-Christophe-et-Niévès', + 'KOR': 'Corée du Sud', + 'KWT': 'Koweït', + 'LAO': 'Laos', + 'LBN': 'Liban', + 'LBR': 'Liberia', + 'LBY': 'Libye', + 'LCA': 'Sainte-Lucie', + 'LIE': 'Liechtenstein', + 'LKA': 'Sri Lanka', + 'LSO': 'Lesotho', + 'LTU': 'Lituanie', + 'LUX': 'Luxembourg', + 'LVA': 'Lettonie', + 'MAC': 'Macao', + 'MAF': 'Sint-Maarten', + 'MAR': 'Maroc', + 'MCO': 'Monaco', + 'MDA': 'Moldavie', + 'MDG': 'Madagascar', + 'MDV': 'Maldives', + 'MEX': 'Mexique', + 'MHL': 'Marshall', + 'MKD': 'Macedoine', + 'MLI': 'Mali', + 'MLT': 'Malte', + 'MMR': 'Birmanie', + 'MNE': 'Monténégro', + 'MNG': 'Mongolie', + 'MNP': 'Îles Mariannes du Nord', + 'MOZ': 'Mozambique', + 'MRT': 'Mauritanie', + 'MSR': 'Montserrat', + 'MTQ': 'Martinique', + 'MUS': 'Maurice', + 'MWI': 'Malawi', + 'MYS': 'Malaisie', + 'MYT': 'Mayotte', + 'NAM': 'Namibie', + 'NCL': 'Nouvelle-Calédonie', + 'NER': 'Niger', + 'NFK': 'Île Norfolk', + 'NGA': 'Nigeria', + 'NIC': 'Nicaragua', + 'NIU': 'Niue', + 'NLD': 'Pays-Bas', + 'NOR': 'Norvège', + 'NPL': 'Nepal', + 'NRU': 'Nauru', + 'NZL': 'Nouvelle-Zélande', + 'OMN': 'Oman', + 'PAK': 'Pakistan', + 'PAN': 'Panama', + 'PCN': 'Îles Pitcairn', + 'PER': 'Pérou', + 'PHL': 'Philippines', + 'PLW': 'Palaos', + 'PNG': 'Papouasie-Nouvelle-Guinée', + 'POL': 'Pologne', + 'PRI': 'Porto Rico', + 'PRK': 'Corée du Nord', + 'PRT': 'Portugal', + 'PRY': 'Paraguay', + 'PSE': 'Palestine', + 'PYF': 'Polynésie française', + 'QAT': 'Qatar', + 'REU': 'Réunion', + 'ROU': 'Roumanie', + 'RUS': 'Russie', + 'RWA': 'Rwanda', + 'SAU': 'Arabie saoudite', + 'SDN': 'Soudan', + 'SEN': 'Sénégal', + 'SGP': 'Singapour', + 'SGS': 'Georgie du Sud-et-les iles Sandwich du Sud', + 'SHN': 'Sainte-Hélène, Ascension et Tristan da Cunha', + 'SJM': 'Svalbard et île Jan Mayen', + 'SLB': 'Salomon', + 'SLE': 'Sierra Leone', + 'SLV': 'Salvador', + 'SMR': 'Saint-Marin', + 'SOM': 'Somalie', + 'SPM': 'Saint-Pierre-et-Miquelon', + 'SRB': 'Serbie', + 'SSD': 'Soudan du Sud', + 'STP': 'Sao Tomé-et-Principe', + 'SUR': 'Suriname', + 'SVK': 'Slovaquie', + 'SVN': 'Slovénie', + 'SWE': 'Suède', + 'SWZ': 'eSwatani', + 'SXM': 'Saint-Martin ', + 'SYC': 'Seychelles', + 'SYR': 'Syrie', + 'TCA': 'Îles Turques-et-Caïques', + 'TCD': 'Tchad', + 'TGO': 'Togo', + 'THA': 'Thaïlande', + 'TJK': 'Tadjikistan', + 'TKL': 'Tokelau', + 'TKM': 'Turkmenistan', + 'TLS': 'Timor oriental', + 'TON': 'Tonga', + 'TTO': 'Trinité-et-Tobago', + 'TUN': 'Tunisie', + 'TUR': 'Turquie', + 'TUV': 'Tuvalu', + 'TWN': 'Taiwan', + 'TZA': 'Tanzanie', + 'UGA': 'Ouganda', + 'UKR': 'Ukraine', + 'URY': 'Uruguay', + 'USA': 'Etats-Unis', + 'UZB': 'Ouzbékistan', + 'VAT': 'Saint-Siège (État de la Cité du Vatican)', + 'VCT': 'Saint-Vincent-et-les-Grenadines', + 'VEN': 'Venezuela', + 'VGB': 'Îles Vierges britanniques', + 'VIR': 'Îles Vierges des États-Unis', + 'VNM': 'Viêt Nam', + 'VUT': 'Vanuatu', + 'WLF': 'Wallis-et-Futuna', + 'WSM': 'Samoa', + 'XKX': 'Kosovo', + 'YEM': 'Yémen', + 'ZAF': 'Afrique du Sud', + 'ZMB': 'Zambie', + 'ZWE': 'Zimbabwe', + 'NTZ': 'Zone neutre', + 'UNO': 'Fonctionnaire des Nations Unies', + 'UNA': "Fonctionnaire d'une organisation affiliée aux Nations Unies", + 'UNK': 'Représentant des Nations Unies au Kosovo', + 'XXA': 'Apatride Convention 1954', + 'XXB': 'Réfugié Convention 1954', + 'XXC': 'Réfugié autre', + 'XXX': 'Résident Légal de Nationalité Inconnue', + 'D': 'Allemagne', + 'EUE': 'Union Européenne', + 'GBD': "Citoyen Britannique d'Outre-mer (BOTC)", + 'GBN': 'British National (Overseas)', + 'GBO': 'British Overseas Citizen', + 'GBP': 'British Protected Person', + 'GBS': 'British Subject', + 'XBA': 'Banque Africaine de Développement', + 'XIM': "Banque Africaine d'Export–Import", + 'XCC': 'Caribbean Community or one of its emissaries', + 'XCO': 'Common Market for Eastern and Southern Africa', + 'XEC': 'Economic Community of West African States', + 'XPO': 'International Criminal Police Organization', + 'XOM': 'Sovereign Military Order of Malta', + 'RKS': 'Kosovo', + 'WSA': 'World Service Authority World Passport' + } +} + +## ENGLISH LANGUAGE + +english = \ +{ +"Please type a MRZ or open a scan" : "Please type a MRZ or open a scan", +"Changelog : update summary" : "Changelog : update summary", +"Program version" : "Program version", +"CNIRevelator Fatal Error" : "CNIRevelator Fatal Error" , +"An error has occured" : "An error has occured", +"Downloading" : "Downloading", +"Successful retrieved" : "Successful retrieved", +"Choose the identity document" : "Choose the identity document" , +"OCR Detection Validation" : "OCR Detection Validation", +"Validate" : "Validate", +"Validation Error" : "Validation Error", +"The submitted MRZ contains invalid " + "characters" : "The submitted MRZ contains invalid characters", +"Connection" : "Connection", +"Password" : "Password", +"Booting up..." : "Booting up...", +"CNIRevelator Fatal Eror" : "CNIRevelator Fatal Eror", +"CNIRevelator crashed because a " +"fatal error occured. View log for " +"more infos and please open " +"an issue on Github" : "CNIRevelator crashed because a fatal error occured. View log for more infos and please open an issue on Github", +"Would you like to open an issue " +"on Github to report this bug ?" : "Would you like to open an issue on Github to report this bug ?", +"Would you like to open the " +"log file ?" : "Would you like to open the log file ?", +"Starting..." : "Starting...", +"Informations about the current " +"document" : "Informations about the current document", +"IDLE" : "IDLE", +"Status" : "Status", +"Name" : "Name", +"Birth date" : "Birth date", +"Issue date" : "Issue date", +"Expiration date" : "Issue date", +"Sex" : "Sex", +"Issuing country" : "Issuing country", +"Nationality" : "Nationality", +"Registration" : "Registration", +"Document number" : "Document number", +"Unknown" : "Unknown", +"Display and processing of " +"documents" : "Display and processing of documents", +"Complete MRZ capture terminal" : "Complete MRZ capture terminal", +"Quick entry terminal (731)" : "Quick entry terminal (731)", +"Monitor" : "Monitor", +"New" : "New", +"Open scan..." : "Open scan...", +"Quit" : "Quit", +"File" : "File", +"Settings" : "Settings", +"Keyboard commands" : "Keyboard commands", +"Report a bug" : "Report a bug", +"About CNIRevelator" : "About CNIRevelator", +"Help" : "Help", +"OCR module error" : "OCR module error", +"The OCR module located at {} " +"can not be found or corrupted. " +"It will be reinstalled at " +"the next run" : "The OCR module located at {} can not be found or corrupted. It will be reinstalled at the next run", +"The Tesseract module " +"encountered a problem: {}" : "The Tesseract module encountered a problem: {}", +"Tesseract error : {}. " +"Will be reinstallated" : "Tesseract error : {}. Will be reinstallated", +"Document detected: {}\n" : "Document detected : {}\n", +"Document detected again: {}\n" : "Document detected again : {}\n", +"Character not accepted !\n" : "Character not accepted !\n", +"Open a scan of document..." : "Open a scan of document...", +"OpenCV error (image processing)" : "OpenCV error (image processing)", +"A critical error has occurred in " +"the OpenCV image processing " +"manager used by CNIRevelator, the " +"application will reset itself" : "A critical error has occurred in the OpenCV image processing manager used by CNIRevelator, the application will reset itself", + +"ABOUT" : 'Software Version: CNIRevelator' + globs.verstring_full + '\n\n' + "Copyright © 2018-2019 Adrien Bourmault (neox95)" + "\n\n" + "CNIRevelator is free software: you have the right to modify and / or distribute it" + "in the terms of the GNU General Public License as published by" + "Free Software Foundation, version 3 or" + "later." + "\n\n" + "CNIRevelator is distributed in the hope of being useful, without however" + "imply any guarantee of" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE Refer to the" + "GNU General Public License for more details about this." + "\n\n" + "You should have received a copy of the GNU General Public License" + "with CNIRevelator, if this is not the case, take a look at ." + "\n\n" + "The Tesseract 4.0 OCR module is subject to the 2004 Apache License." + "\n\n" + "Python libraries and the Anaconda 3 environment are subject to the BSD 2018-2019 license." + "\n\n" + "The source code for this program is available on Github at . \n" + "Its operation is in accordance with the standards and guidelines of ICAO document 9303 governing travel and identity documents." + '\n\n' + "In case of problems or special request, open an issue or send an email to neox@os-k.eu! \n\n", + + +"KEYBHELP" : "Fast entry terminal (731): \n\n" + "Allowed characters: Alphanumeric uppercase and the character '<' No lowercase or special characters, otherwise the sum is set to zero \n\n" + "Calculate Result:\t\t\tControl Right\n" + "Copy:\t\t\t\tCtrl-C\n" + "Paste:\t\t\t\tCtrl-V\n" + "\n\n" + "MRZ input terminal complete: \n\n" + "Allowed characters: Alphanumeric uppercase and the character '<' No lowercase or special characters, otherwise the sum is set to zero \n\n" + "Calculate Result:\t\t\tControl Right\n" + "Complete field:\t\t\tTab button\n" + "Copy:\t\t\t\tCtrl-C\n" + "Paste:\t\t\t\tCtrl-V\n" + "Force a new document detection:\tEchap\n", + +"CHANGELOG" : "Version 3.1.2 \nMinor update with the following progressions:\n- Upgrade Tesseract OCR to 5.0\n\n" + \ +"Version 3.1.1 \nMinor update with the following progressions: \n- Fixed a severe bug in the update system\n\n" + \ +"Version 3.1.0 \nMajor update with the following progressions: \n- Cosmetic modifications of the user interface \n- Stabilization of the changes made on the minor version 3.0 : user interface, OCR, VISA A and B, logging\n- Rationalization of the language system\n- Added update channels\n\n" + \ +"Version 3.0.8 final\nCorrection: bug in the update system'\n\n" + \ +"Version 3.0.6 \nMinor update with the following fixes:\n- Change in the appearance of the application launcher\n- Improvements to the interface, including stability\n- Added digital signature of the executable\n" + \ +"Version 3.0.7 final\nMajor update with the following corrections: \n- Redesign of the user interface\n- OCR function integrated into the application with TIFF and JPEG support\n- Corrections of errors on the processing of VISA type A and B, as well as residence permits\n\n" + \ +"Version 3.0.6 \nMinor update with the following fixes:\n- Change in the appearance of the application launcher\n- Improvements to the interface, including stability\n- Added digital signature of the executable\n" + \ +"Version 3.0.5 \n Minor update with the following corrections: \n- Change of executable icon to reflect the major version change accomplished in 3.0\n\n" + \ +"Version 3.0.4 \nMinor update with the following fixes:\n- Fixed a bug affecting the update system" + \ +"Version 3.0.3 \nMinor update with the following corrections: \n- Fixed a bug affecting the changelog\n- Fixed an error with the Delete Back key and Deleted causing a data loss" + \ +"Version 3.0.2 \nMinor update with the following corrections: \n- Change of executable icon\n- Fix of a bug affecting logging\n- Fix of a bug affecting document detection- And other minor modifications\n\n" + \ +"Version 3.0.1 \nMajor update with the following corrections: \n- Renewal of the executable's digital signature- Improvement of the log presentation in case of error\n- Total overhaul of the source code and disobfuscation\n- Reliable HTTPS downloads with checksum\n- New input terminals : a fast (731) and a complete one\n- Improved document detection, possibility of finer choice and the following regressions:\n- Temporary deletion of the OCR reading function. Planned return for a next version", + +"Document Review: {}\n\n" : "Document Review: {}\n\n", +"Checksum position {}: Lu {} VS " +"Calculated {} [facultative]\n" : "Checksum position {}: Read {} VS Calculated {} [facultative]\n", +"Checksum position {}: Lu {} VS " +"Calculated {}\n" : "Checksum position {}: Read {} VS Calculated {}\n", +"COMPLIANT" : "COMPLIANT", +"IMPROPER" : "IMPROPER", +"Installing the updates" : "Installing the updates", +"Verifying download..." : "Verifying download...", +"Preparing installation..." : "Preparing installation...", +"Success !" : "Success !", +"Launching the new version..." : "Launching the new version...", +"Credentials Error. No effective " +"update !" : "Credentials Error. No effective update !", +"Deleting old version" : "Deleting old version", +'Software is up-to-date !' : "Software is up-to-date !", +"An error occured. " +"No effective update !" : "An error occured. No effective update !", +"Shortcut creation" : "Shortcut creation", +"Would you like to create/update " +"the shortcut for CNIRevelator on " +"your desktop ?" : "Would you like to create/update the shortcut for CNIRevelator on your desktop ?", +"The file you provided is not " +"found : {}" : "The file you provided is not found : {}", +"Update options" : "Update options", +"Language" : "Language", +"Show Changelog" : "Show the changelog", +"Please choose your language : " : "Please choose your language : ", +"Please choose your update " +"channel : " : "Please choose your update channel : ", +"Passeport lisible à la machine" : "Machine Readable Passport", +"Carte-passeport" : "Passport card", +"Carte d’identité européenne" : "European identity document", +"Titre d'identité/de voyage" : "Identity/travel document", +"Certificat de membre d'équipage" : "Crew member certificate", +"Visa de type A" : "Type A visa", +"Visa de type B" : "Type B visa", +"Carte de séjour FR" : "French Residence permit", +"Pièce d'identité/de voyage" : "Identity/travel document", +"Pièce d'identité FR" : "French Identity card", +"Permis de conduire" : "Driver License", +"The file you provided is not " +"valid : {}" : "The file you provided is not valid : {}", + +"LANDCODE2" : { + "AW": "Aruba", + "AF": "Afghanistan", + "AO": "Angola", + "AI": "Anguilla", + "AL": "Albania", + "AD": "Andorra", + "AE": "United Arab Emirates", + "AR": "Argentina", + "AM": "Armenia", + "AS": "American Samoa", + "QA": "Antarctic", + "TF": 'French Southern and Antarctic Territories', + "AG": 'Antigua and Barbuda', + "TO": "Australia", + "AT": "Austria", + "AZ": "Azerbaidjan", + "BI": "Burundi", + "BE": "Belgium", + "BJ": "Benin", + "BQ": "Caribbean Netherlands", + "BF": "Burkina Faso", + "BD": "Bangladesh", + "BG": "Bulgaria", + "BH": "Bahrain", + "BS": "Bahamas", + "BA": "Bosnia and Herzegovina", + "BL": "Saint-Barthélemy", + "BY": "Belarus", + "BZ": "Belize", + "BM": "Bermuda", + "BO": "Bolivia", + "BR": 'Brazil', + "BB": "Barbados", + "BN": "Brunei", + "BT": "Bhutan", + "BW": "Botswana", + "CF": "Central African Republic", + "CA": "Canada", + "CC": "Cocos Islands", + "CH": "Switzerland", + "CL": "Chile", + "CN": "China", + "CI": "Côte d'Ivoire", + "CM": "Cameroon", + "CD": 'Congo (Democratic Republic)', + "CG": "Congo (Republic)", + "CK": "Cook Islands", + "CO": "Colombia", + "KM": "Comoros", + "CV": "Cape Verde", + "CR": "Costa Rica", + "CU": "Cuba", + "CW": "Curaçao", + "CX": "Christmas Island", + "KY": "Caimans", + "CY": "Cyprus", + "CZ": "Czech Republic", + "DE": "Germany", + "DJ": "Djibouti", + "DM": "Dominique", + "DK": "Denmark", + "DO": 'Dominican Republic', + "DZ": "Algeria", + "EC": "Ecuador", + "EG": "Egypt", + "ER": "Eritrea", + "EH": "Western Sahara", + "ES": "Spain", + "EE": "Estonia", + "AND": "Ethiopia", + "FI": "Finland", + "FJ": "Fiji", + "FK": "Falkland Islands", + "FR": "France", + "FO": "Faroese", + "FM": "Micronesia", + "GA": "Gabon", + "GB": "United Kingdom", + "GE": "Georgia", + "GG": "Guernsey", + "GH": "Ghana", + "GI": "Gibraltar", + "GN": "Guinea", + "GP": "Guadeloupe", + "GM": "Gambia", + "GW": "Guinea-Bissau", + "GQ": "Equatorial Guinea", + "GR": "Greece", + "GD": "Granada", + "GL": "Greenland", + "GT": "Guatemala", + "GF": "Guyana", + "GU": "Guam", + "GY": "Guyana", + "HK": "Hong Kong", + "HN": "Honduras", + "HR": "Croatia", + "HT": "Haiti", + "HU": "Hungary", + "ID": "Indonesia", + "IM": "Isle of Man", + "IN": "India", + "IO": 'British Indian Ocean Territory', + "IE": "Ireland", + "IR": "Iraq", + "IQ": "Iran", + "IS": "Iceland", + "IL": "Israel", + "IT": "Italy", + "JM": "Jamaica", + "I": "Jersey", + "JO": "Jordan", + "JP": "Japan", + "KZ": "Kazakhstan", + "KE": "Kenya", + "KG": "Kyrgyzstan", + "KH": "Cambodia", + "KI": "Kiribati", + "KN": "Saint Kitts and Nevis", + "KR": "South Korea", + "KW": "Kuwait", + "LA": "Laos", + "LB": "Lebanon", + "LR": "Liberia", + "LY": "Libya", + "LC": "Saint Lucia", + "LI": "Liechtenstein", + "LK": "Sri Lanka", + "LS": "Lesotho", + "LT": "Lithuania", + "LU": "Luxembourg", + "LV": "Latvia", + "MO": "Macao", + "MF": "Sint-Maarten", + "MA": "Morocco", + "MC": "Monaco", + "MD": 'Moldavia', + "MG": "Madagascar", + "MV": "Maldives", + "MX": "Mexico", + "MH": "Marshall", + "MK": "Macedonia", + "ML": "Mali", + "MT": "Malta", + "MM": "Burma", + "ME": "Montenegro", + "MN": "Mongolia", + "MP": "Northern Mariana Islands", + "MZ": "Mozambique", + "MR": "Mauritania", + "MS": "Montserrat", + "MQ": "Martinique", + "MU": "Mauritius", + "MW": "Malawi", + "MY": "Malaysia", + "YT": "Mayotte", + "NA": "Namibia", + "NC": "New Caledonia", + "NE": "Niger", + "NF": "Norfolk Island", + "NG": "Nigeria", + "NI": "Nicaragua", + "NU": "Niue", + "NL": 'Netherlands', + "NO": "Norway", + "NP": "Nepal", + "NR": "Nauru", + "NZ": "New Zealand", + "OM": "Oman", + "PK": "Pakistan", + "PA": "Panama", + "NP": "Pitcairn Islands", + "PE": "Peru", + "PH": "Philippines", + "PW": "Palau", + "PG": "Papua New Guinea", + "PL": "Poland", + "PR": "Puerto Rico", + "KP": "North Korea", + "PT": "Portugal", + "PY": "Paraguay", + "PS": "Palestine", + "FP": "French Polynesia", + "QA": "Qatar", + "RE": "Reunion", + "RO": "Romania", + "RU": "Russia", + "RW": "Rwanda", + "SA": "Saudi Arabia", + "SD": "Sudan", + "SN": "Senegal", + "SG": "Singapore", + "GS": "South Georgia and the South Sandwich Islands", + 'SH': 'St Helena, Ascension and Tristan da Cunha', + "SJ": "Svalbard and Jan Mayen Island", + "SB": "Solomon", + "SL": "Sierra Leone", + "SV": "El Salvador", + "SM": "San Marino", + "SO": "Somalia", + "PM": "Saint-Pierre-et-Miquelon", + "RS": "Serbia", + "SS": "South Sudan", + "ST": "Sao Tome and Principe", + "SR": "Suriname", + "SK": "Slovakia", + "SI": "Slovenia", + "SE": "Sweden", + "SZ": "eSwatani", + "SX": "Saint-Martin", + "SC": "Seychelles", + "SY": "Syria", + "TC": "Turks and Caicos Islands", + "TD": "Chad", + "TG": "Togo", + "TH": "Thailand", + "TJ": "Tajikistan", + "TK": "Tokelau", + "TM": "Turkmenistan", + "TL": "East Timor", + "TO": "Tonga", + "TT": "Trinidad and Tobago", + "TN": "Tunisia", + "TR": "Turkey", + "TV": "Tuvalu", + "TW": "Taiwan", + "TZ": "Tanzania", + "UG": "Uganda", + "UA": "Ukraine", + "UY": "Uruguay", + "US": "United States", + "UZ": "Uzbekistan", + 'VA': 'Holy See (Vatican City State)', + 'VC': 'Saint Vincent and the Grenadines', + "EV": "Venezuela", + "VG": "British Virgin Islands", + 'VI': 'Virgin Islands of the United States', + "VN": "Vietnam", + "VU": "Vanuatu", + "WF": "Wallis and Futuna", + 'WS': 'Samoa', + "XK": "Kosovo", + "YE": "Yemen", + "ZA": "South Africa", + "ZM": "Zambia", + "ZW": "Zimbabwe" + }, + +"LANDCODE3" : { + "ABW": "Aruba", + "AFG": "Afghanistan", + "OGM": "Angola", + "AIA": "Anguilla", + 'ALB': 'Albania', + "AND": "Andorra", + "AER": "United Arab Emirates", + "ARG": "Argentina", + "ARM": "Armenia", + "ASM": "American Samoa", + "ATA": "Antarctica", + 'ATF': 'French Southern and Antarctic Lands', + "ATG": "Antigua and Barbuda", + "AUS": "Australia", + "AUT": "Austria", + "AZE": "Azerbaidjan", + "BDI": "Burundi", + "BEL": "Belgium", + "BEN": "Benin", + 'BES': 'Caribbean Netherlands', + "BFA": "Burkina Faso", + "BGD": "Bangladesh", + "BGR": "Bulgaria", + "BHR": "Bahrain", + "BHS": "Bahamas", + "BIH": "Bosnia and Herzegovina", + "BLM": "Saint-Barthélemy", + "BLR": "Belarus", + "BLZ": "Belize", + "BMU": "Bermuda", + "BOL": "Bolivia", + "BRA": "Brazil", + 'BRB': 'Barbados', + "BRN": "Brunei", + "BTN": "Bhutan", + "BWA": "Botswana", + 'CAF': 'Central African Republic', + 'CAN': 'Canada', + "CCK": "Cocos Islands", + "CHE": "Switzerland", + "CHL": "Chile", + "CHN": "China", + 'CIV': "Côte d'Ivoire", + "CMR": "Cameroon", + 'COD': 'Congo (Democratic Republic)', + "COG": "Congo (Republic)", + "COK": "Cook Islands", + "COL": "Colombia", + "COM": "Comoros", + 'CPV': 'Cape Verde', + "CRI": "Costa Rica", + "CUB": "Cuba", + "CUW": "Curaçao", + "CXR": "Christmas Island", + "CYM": "Caimans", + "CYP": "Cyprus", + "CZE": "Czech Republic", + "DEU": "Germany", + "DJI": "Djibouti", + "DMA": "Dominique", + "DNK": "Denmark", + "DOM": "Dominican Republic", + "DZA": "Algeria", + 'ECU': 'Ecuador', + "EGY": "Egypt", + "ERI": "Eritrea", + 'ESH': 'Western Sahara', + "ESP": "Spain", + "EST": "Estonia", + "ETH": "Ethiopia", + "FIN": "Finland", + "FJI": "Fiji", + "FLK": "Falkland Islands", + "FRA": "France", + "FRO": "Faroese", + "WSF": "Micronesia", + "ATM": "Gabon", + "GBR": "United Kingdom", + "GEO": "Georgia", + "GGY": "Guernsey", + "GHA": "Ghana", + "GIB": "Gibraltar", + "GIN": "Guinea", + "GLP": "Guadeloupe", + "GMB": "Gambia", + "GNB": "Guinea-Bissau", + "GNQ": "Equatorial Guinea", + "GRC": "Greece", + "DSO": "Granada", + "GRL": "Greenland", + "GTM": "Guatemala", + 'GUF': 'Guyana', + "GUM": "Guam", + "GUY": "Guyana", + "HKG": "Hong Kong", + "HND": "Honduras", + "HRV": "Croatia", + "HTI": "Haiti", + "HUN": "Hungary", + "IDN": "Indonesia", + "IMN": "Isle of Man", + "IND": "India", + 'IOT': 'British Indian Ocean Territory', + "IRL": "Ireland", + "IRN": "Iraq", + "IRQ": "Iran", + "ISL": "Iceland", + "SRI": "Israel", + "ITA": "Italy", + "JAM": "Jamaica", + "JEY": "Jersey", + "JOR": "Jordan", + 'JPN': 'Japan', + "KAZ": "Kazakhstan", + "KEN": "Kenya", + "KGZ": "Kyrgyzstan", + "KHM": "Cambodia", + "KIR": "Kiribati", + "KNA": "Saint Kitts and Nevis", + "KOR": "South Korea", + "KWT": "Kuwait", + "LAO": "Laos", + "LBN": "Lebanon", + "LBR": "Liberia", + "LBY": "Libya", + "LCA": "Saint Lucia", + "LEL": "Liechtenstein", + "LKA": "Sri Lanka", + "LSO": "Lesotho", + "LTU": "Lithuania", + "LUX": "Luxembourg", + "LVA": "Latvia", + "MAC": "Macao", + "MAF": "Sint-Maarten", + "MAR": "Morocco", + "MCO": "Monaco", + "MDA": "Moldavia", + "MDG": "Madagascar", + "MDV": "Maldives", + "MEX": "Mexico", + "MHL": "Marshall", + "MKD": "Macedonia", + "MLI": "Mali", + "MLT": "Malta", + "MMR": "Burma", + "MNE": "Montenegro", + "MNG": "Mongolia", + 'MNP': 'Northern Mariana Islands', + "MOZ": "Mozambique", + "MRT": "Mauritania", + "MSR": "Montserrat", + "MTQ": "Martinique", + "MUS": "Mauritius", + "MWI": "Malawi", + "MYS": "Malaysia", + "MYT": "Mayotte", + "NAM": "Namibia", + "NCL": "New Caledonia", + "NER": "Niger", + "NFK": "Norfolk Island", + "NGA": "Nigeria", + "NIC": "Nicaragua", + "NIU": "Niue", + 'NLD': 'Netherlands', + "NOR": "Norway", + "NPL": "Nepal", + "NRU": "Nauru", + "NZL": "New Zealand", + "OMN": "Oman", + "PAK": "Pakistan", + "PAN": "Panama", + "PCN": "Pitcairn Islands", + "PER": "Peru", + "PHL": "Philippines", + "PLW": "Palau", + "PNG": "Papua New Guinea", + "POL": "Poland", + "PRI": "Puerto Rico", + "PRK": "North Korea", + "PRT": "Portugal", + "PRY": "Paraguay", + "PES": "Palestine", + "PYF": "French Polynesia", + "QAT": "Qatar", + "REU": "Reunion", + "ROU": "Romania", + "RUS": "Russia", + "RWA": "Rwanda", + "UAA": "Saudi Arabia", + "SDN": "Sudan", + "SEN": "Senegal", + "SGP": "Singapore", + 'SGS': 'South Georgia and the South Sandwich Islands', + "SHN": "St Helena, Ascension and Tristan da Cunha", + "SJM": "Svalbard and Jan Mayen Island", + "SLB": "Solomon", + "SLE": "Sierra Leone", + "SLV": "El Salvador", + "SMR": "San Marino", + "SOM": "Somalia", + "SPM": "Saint-Pierre-et-Miquelon", + "SRB": "Serbia", + "SSD": "South Sudan", + "PTS": "Sao Tome and Principe", + "SUR": "Suriname", + "SVK": "Slovakia", + "SVN": "Slovenia", + "SWE": "Sweden", + "SWZ": "eSwatani", + "SXM": "Saint-Martin", + "SYC": "Seychelles", + "SYR": "Syria", + "TCA": "Turks and Caicos Islands", + "TCD": "Chad", + "TGO": "Togo", + "THA": "Thailand", + "TJK": "Tajikistan", + "TKL": "Tokelau", + "TKM": "Turkmenistan", + "TLS": "East Timor", + "TON": "Tonga", + 'TTO': 'Trinidad and Tobago', + "TUN": "Tunisia", + "TUR": "Turkey", + "TUV": "Tuvalu", + "TWN": "Taiwan", + "TZA": "Tanzania", + "UGA": "Uganda", + "UKR": "Ukraine", + "URY": "Uruguay", + "USA": "United States", + "UZB": "Uzbekistan", + 'VAT': 'Holy See (Vatican City State)', + 'VCT': 'Saint Vincent and the Grenadines', + "VEN": "Venezuela", + 'VGB': 'British Virgin Islands', + 'VIR': 'Virgin Islands of the United States', + "VNM": "Vietnam", + "STV": "Vanuatu", + "WLF": "Wallis and Futuna", + "WSM": "Samoa", + "XKX": "Kosovo", + "YEM": "Yemen", + "ZAF": "South Africa", + "ZMB": "Zambia", + "ZWE": "Zimbabwe", + 'NTZ': 'Neutral Zone', + 'UNO': 'United Nations official', + 'UNA': "Staff member of a United Nations-affiliated organization", + 'UNK': 'United Nations Representative in Kosovo', + 'XXA': 'Stateless Person 1954 Convention', + "XXB": "Refugee Convention 1954", + "XXC": "Other refugee", + 'XXX': 'Legal Resident of Unknown Nationality', + 'D' : 'Germany', + "EUE": "European Union", + "GBD": "British Overseas Citizen (BOTC)", + "GBN": "British National (Overseas)", + "GBO": "British Overseas Citizen", + 'GBP': 'British Protected Person', + "GBS": "British Subject", + 'XBA': 'African Development Bank', + 'XIM': "African Export Import Bank", + 'XCC':' Caribbean Community or one of its emissaries', + 'XCO': 'Common Market for Eastern and Southern Africa', + 'XEC': 'Economic Community of West African States', + 'XPO': 'International Criminal Police Organization', + 'XOM': 'Sovereign Military Order of Malta', + "RKS": "Kosovo", + "WSA": "World Service Authority World Passport" + } +} + +## MAIN DICT +all = \ +{ +"fr" : french, +"en" : english +} + + +## Functions + +def readLang(): + + if os.path.isfile(globs.CNIRLangFile): + with open(globs.CNIRLangFile, 'r') as (configFile): + try: + # Reading it + globs.CNIRlang = configFile.read() + except Exception as e: + critical.crashCNIR() + raise IOError(str(e)) + else: + # Recreating the url file + try: + os.mkdir(globs.CNIRFolder + '\\config') + except: + pass + + with open(globs.CNIRLangFile, 'w') as (configFile): + try: + # Writing it + configFile.write(globs.CNIRlang) + except Exception as e: + critical.crashCNIR() + raise IOError(str(e)) + +def updateLang(choice): + + if os.path.isfile(globs.CNIRLangFile): + with open(globs.CNIRLangFile, 'w') as (configFile): + try: + # Writing it + globs.CNIRlang = choice + configFile.write(choice) + except Exception as e: + critical.crashCNIR() + raise IOError(str(e)) + else: + # Recreating the url file + try: + os.mkdir(globs.CNIRFolder + '\\config') + except: + pass + + with open(globs.CNIRLangFile, 'w') as (configFile): + try: + # Writing it + configFile.write(globs.CNIRlang) + except Exception as e: + critical.crashCNIR() + raise IOError(str(e)) \ No newline at end of file diff --git a/launcher.py b/launcher.py new file mode 100644 index 0000000..116c642 --- /dev/null +++ b/launcher.py @@ -0,0 +1,57 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application launcher & updater main file * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" + +import sys +import os +import threading + +import critical # critical.py +import updater # updater.py +import ihm # ihm.py +import globs # globs.py +import logger # logger.py +import lang # lang.py + +## Main function +def lmain(mainThread): + logfile = logger.logCur + launcherWindow = ihm.launcherWindowCur + + # Hello world + logfile.printdbg('*** CNIRLauncher LOGFILE. Hello World ! ***') + #logfile.printdbg('Files in directory : ' + str(os.listdir(globs.CNIRFolder))) + + # Hello user + launcherWindow.progressBar.configure(mode='indeterminate', value=0, maximum=20) + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Starting..."]) + launcherWindow.progressBar.start() + + # Starting the main update thread + mainThread.start() + + launcherWindow.mainloop() + + logfile.printdbg('*** CNIRLauncher LOGFILE. Goodbye World ! ***') + return diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..9fea829 --- /dev/null +++ b/logger.py @@ -0,0 +1,81 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application launcher logging stuff * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" + +import logging +import os + +import critical # critical.py +import globs # globs.py + +## The logging class +class NewLoggingSystem: + + def __init__(self): + + # Deleting the error log + try: + os.mkdir(globs.CNIRFolder + '\\logs') + os.remove(globs.CNIRErrLog) + except Exception as e: + pass + + # Create new logging handle + logger = logging.getLogger() + logger.setLevel(logging.INFO) # To make sure we can have a debug channel + + # Create channels + formatter = logging.Formatter("\n[ %(module)s/%(funcName)s ] %(asctime)s :: %(levelname)s :: %(message)s") + error_handler = logging.FileHandler((globs.CNIRErrLog), mode='w', encoding='utf-8', delay=True) + info_handler = logging.FileHandler((globs.CNIRMainLog), mode='w', encoding='utf-8') + + error_handler.setLevel(logging.ERROR) + error_handler.setFormatter(formatter) + logger.addHandler(error_handler) + + info_handler.setLevel(logging.DEBUG) + info_handler.setFormatter(formatter) + logger.addHandler(info_handler) + + self.logger = logger + self.printerr = logger.error + + if globs.debug: + self.printdbg = self.logger.info + else: + self.printdbg = self.doNothing + + def doNothing(self, text): + pass + + def close(self): + logging.shutdown() + + handlers = self.logger.handlers[:] + for handler in handlers: + handler.close() + self.logger.removeHandler(handler) + +## Global Handler +logCur = NewLoggingSystem() diff --git a/main.py b/main.py new file mode 100644 index 0000000..d261318 --- /dev/null +++ b/main.py @@ -0,0 +1,1076 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application IHM & work main class * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" + +from PIL import Image, ImageFont, ImageDraw, ImageTk, ImageEnhance, ImageFilter +import math, warnings, string +from tkinter import * +from tkinter.messagebox import * +from tkinter import filedialog +from tkinter import ttk +import threading +from datetime import datetime +from importlib import reload +import re +import cv2 +import PIL.Image, PIL.ImageTk +import os, shutil +import webbrowser +import sys, os + +import critical # critical.py +import ihm # ihm.py +import logger # logger.py +import mrz # mrz.py +import globs # globs.py +import pytesseract # pytesseract.py +import lang # lang.py + +# Global handler +logfile = logger.logCur + +class mainWindow(Tk): + + ## App Pattern + + def __init__(self): + Tk.__init__(self) + self.initialize() + + def initialize(self): + """ + Initializes the main window + """ + self.mrzChar = "" + self.mrzDecided = False + self.Tags = [] + self.compliance = True + self.corners = [] + self.validatedtext = "" + + # The icon + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + + # Hide during construction + self.withdraw() + + # Get the screen size and center + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + logfile.printdbg('Launching main window with resolution' + str(ws) + 'x' + str(hs)) + + # Configuring the size of each part of the window + self.grid_columnconfigure(0, minsize=(ws / 2 * 0.3333333333333333)) + self.grid_columnconfigure(1, minsize=(ws / 2 * 0.3333333333333333)) + self.grid_columnconfigure(2, weight=1, minsize=(ws / 2 * 0.3333333333333333)) + self.grid_rowconfigure(0, minsize=(hs / 2 * 0.5)) + self.grid_rowconfigure(1, minsize=(hs / 2 * 0.10)) + self.grid_rowconfigure(2, minsize=(hs / 2 * 0.35)) + self.grid_rowconfigure(3, minsize=10) + + # Prepare the data sections + self.lecteur_ci = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Informations about the current document"]) + self.lecteur_ci.grid_columnconfigure(0, weight=1) + self.lecteur_ci.grid_columnconfigure(1, weight=1) + self.lecteur_ci.grid_columnconfigure(2, weight=1) + self.lecteur_ci.grid_columnconfigure(3, weight=1) + self.lecteur_ci.grid_columnconfigure(4, weight=1) + self.lecteur_ci.grid_columnconfigure(5, weight=1) + self.lecteur_ci.grid_rowconfigure(1, weight=1) + self.lecteur_ci.grid_rowconfigure(2, weight=1) + self.lecteur_ci.grid_rowconfigure(3, weight=1) + self.lecteur_ci.grid_rowconfigure(4, weight=1) + self.lecteur_ci.grid_rowconfigure(5, weight=1) + + # And what about the status bar ? + self.statusbar = ihm.StatusBar(self) + self.statusbar.grid(row=3, columnspan=3, sticky="NSEW") + + # Fill the data sections + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Status"])).grid(column=0, row=0, padx=5, pady=5) + self.statusbar.set(lang.all[globs.CNIRlang]["IDLE"]) + self.STATUStxt = ttk.Label((self.lecteur_ci), text=lang.all[globs.CNIRlang]["IDLE"], font=("TkDefaultFont", 13, "bold"), foreground="orange", anchor=CENTER) + self.STATUStxt.grid(column=1, row=0, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Name"])).grid(column=0, row=1, padx=5, pady=5) + self.nom = ttk.Label((self.lecteur_ci), text=' ') + self.nom.grid(column=1, row=1, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} (2) : '.format(lang.all[globs.CNIRlang]["Name"])).grid(column=0, row=2, padx=5, pady=5) + self.prenom = ttk.Label((self.lecteur_ci), text=' ') + self.prenom.grid(column=1, row=2, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Birth date"])).grid(column=0, row=3, padx=5, pady=5) + self.bdate = ttk.Label((self.lecteur_ci), text=' ') + self.bdate.grid(column=1, row=3, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Issue date"])).grid(column=0, row=4, padx=5, pady=5) + self.ddate = ttk.Label((self.lecteur_ci), text=' ') + self.ddate.grid(column=1, row=4, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text="{} : ".format(lang.all[globs.CNIRlang]["Expiration date"])).grid(column=0, row=5, padx=5, pady=5) + self.edate = ttk.Label((self.lecteur_ci), text=' ') + self.edate.grid(column=1, row=5, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Sex"])).grid(column=4, row=1, padx=5, pady=5) + self.sex = ttk.Label((self.lecteur_ci), text=' ') + self.sex.grid(column=5, row=1, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Issuing country"])).grid(column=4, row=2, padx=5, pady=5) + self.pays = ttk.Label((self.lecteur_ci), text=' ') + self.pays.grid(column=5, row=2, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Nationality"])).grid(column=4, row=3, padx=5, pady=5) + self.nat = ttk.Label((self.lecteur_ci), text=' ') + self.nat.grid(column=5, row=3, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Registration"])).grid(column=4, row=4, padx=5, pady=5) + self.indic = ttk.Label((self.lecteur_ci), text=' ') + self.indic.grid(column=5, row=4, padx=5, pady=5) + ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Document number"])).grid(column=4, row=5, padx=5, pady=5) + self.no = ttk.Label((self.lecteur_ci), text=' ') + self.no.grid(column=5, row=5, padx=5, pady=5) + + self.nom['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.prenom['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.bdate['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.ddate['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.edate['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.no['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.sex['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.nat['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.pays['text'] = lang.all[globs.CNIRlang]["Unknown"] + self.indic['text'] = lang.all[globs.CNIRlang]["Unknown"] + + + self.infoList = \ + { + "NOM" : self.nom, + "PRENOM" : self.prenom, + "BDATE" : self.bdate, + "DDATE" : self.ddate, + "EDATE" : self.edate, + "NO" : self.no, + "SEX" : self.sex, + "NAT" : self.nat, + "PAYS" : self.pays, + "INDIC" : self.indic, + } + + # The the image viewer + self.imageViewer = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Display and processing of documents"]) + self.imageViewer.grid_columnconfigure(0, weight=1) + self.imageViewer.grid_columnconfigure(1, weight=0) + self.imageViewer.grid_rowconfigure(0, weight=1) + self.imageViewer.grid_rowconfigure(1, weight=1) + self.imageViewer.grid_rowconfigure(2, weight=1) + self.imageViewer.frame = Frame(self.imageViewer) + self.imageViewer.frame.grid(column=0, row=0, sticky='NSEW') + self.imageViewer.frame.grid_columnconfigure(0, weight=1) + self.imageViewer.frame.grid_rowconfigure(0, weight=1) + # + toolbar + self.toolbar = ttk.Frame(self.imageViewer) + self.toolbar.grid_columnconfigure(0, weight=1) + self.toolbar.grid_columnconfigure(1, weight=1) + self.toolbar.grid_columnconfigure(2, weight=1) + self.toolbar.grid_columnconfigure(3, weight=1) + self.toolbar.grid_columnconfigure(4, weight=1) + self.toolbar.grid_columnconfigure(5, weight=1) + self.toolbar.grid_columnconfigure(6, weight=1, minsize=10) + self.toolbar.grid_columnconfigure(7, weight=1) + self.toolbar.grid_columnconfigure(8, weight=1, minsize=10) + self.toolbar.grid_columnconfigure(9, weight=1) + self.toolbar.grid_columnconfigure(10, weight=1) + self.toolbar.grid_columnconfigure(11, weight=1) + self.toolbar.grid_columnconfigure(12, weight=1) + self.toolbar.grid_columnconfigure(13, weight=1, minsize=10) + self.toolbar.grid_columnconfigure(14, weight=1) + self.toolbar.grid_columnconfigure(15, weight=1, minsize=10) + self.toolbar.grid_columnconfigure(16, weight=1) + self.toolbar.grid_rowconfigure(0, weight=1) + + self.toolbar.zoomIn50Img = ImageTk.PhotoImage(PIL.Image.open("zoomIn50.png")) + self.toolbar.zoomIn50 = ttk.Button(self.toolbar, image=self.toolbar.zoomIn50Img, command=self.zoomInScan50) + self.toolbar.zoomIn50.grid(column=0, row=0) + + self.toolbar.zoomIn20Img = ImageTk.PhotoImage(PIL.Image.open("zoomIn20.png")) + self.toolbar.zoomIn20 = ttk.Button(self.toolbar, image=self.toolbar.zoomIn20Img, command=self.zoomInScan20) + self.toolbar.zoomIn20.grid(column=1, row=0) + + self.toolbar.zoomInImg = ImageTk.PhotoImage(PIL.Image.open("zoomIn.png")) + self.toolbar.zoomIn = ttk.Button(self.toolbar, image=self.toolbar.zoomInImg, command=self.zoomInScan) + self.toolbar.zoomIn.grid(column=2, row=0) + + self.toolbar.zoomOutImg = ImageTk.PhotoImage(PIL.Image.open("zoomOut.png")) + self.toolbar.zoomOut = ttk.Button(self.toolbar, image=self.toolbar.zoomOutImg, command=self.zoomOutScan) + self.toolbar.zoomOut.grid(column=3, row=0) + + self.toolbar.zoomOut20Img = ImageTk.PhotoImage(PIL.Image.open("zoomOut20.png")) + self.toolbar.zoomOut20 = ttk.Button(self.toolbar, image=self.toolbar.zoomOut20Img, command=self.zoomOutScan20) + self.toolbar.zoomOut20.grid(column=4, row=0) + + self.toolbar.zoomOut50Img = ImageTk.PhotoImage(PIL.Image.open("zoomOut50.png")) + self.toolbar.zoomOut50 = ttk.Button(self.toolbar, image=self.toolbar.zoomOut50Img, command=self.zoomOutScan50) + self.toolbar.zoomOut50.grid(column=5, row=0) + + self.toolbar.invertImg = ImageTk.PhotoImage(PIL.Image.open("invert.png")) + self.toolbar.invert = ttk.Button(self.toolbar, image=self.toolbar.invertImg, command=self.negativeScan) + self.toolbar.invert.grid(column=7, row=0) + + self.toolbar.rotateLeftImg = ImageTk.PhotoImage(PIL.Image.open("rotateLeft.png")) + self.toolbar.rotateLeft = ttk.Button(self.toolbar, image=self.toolbar.rotateLeftImg, command=self.rotateLeft) + self.toolbar.rotateLeft.grid(column=9, row=0) + + self.toolbar.rotateLeft1Img = ImageTk.PhotoImage(PIL.Image.open("rotateLeft1.png")) + self.toolbar.rotateLeft1 = ttk.Button(self.toolbar, image=self.toolbar.rotateLeft1Img, command=self.rotateLeft1) + self.toolbar.rotateLeft1.grid(column=10, row=0) + + self.toolbar.rotateRight1Img = ImageTk.PhotoImage(PIL.Image.open("rotateRight1.png")) + self.toolbar.rotateRight1 = ttk.Button(self.toolbar, image=self.toolbar.rotateRight1Img, command=self.rotateRight1) + self.toolbar.rotateRight1.grid(column=11, row=0) + + self.toolbar.rotateRightImg = ImageTk.PhotoImage(PIL.Image.open("rotateRight.png")) + self.toolbar.rotateRight = ttk.Button(self.toolbar, image=self.toolbar.rotateRightImg, command=self.rotateRight) + self.toolbar.rotateRight.grid(column=12, row=0) + + self.toolbar.goOCRImg = ImageTk.PhotoImage(PIL.Image.open("OCR.png")) + self.toolbar.goOCR = ttk.Button(self.toolbar, image=self.toolbar.goOCRImg, command=self.goOCRDetection) + self.toolbar.goOCR.grid(column=14, row=0) + + self.toolbar.pagenumber = StringVar() + self.toolbar.pageChooser = ttk.Combobox(self.toolbar, textvariable=self.toolbar.pagenumber) + self.toolbar.pageChooser.bind("<>", self.goPageChoice) + self.toolbar.pageChooser['values'] = ('1') + self.toolbar.pageChooser.current(0) + self.toolbar.pageChooser.grid(column=16, row=0) + + self.toolbar.grid(column=0, row=2, padx=0, pady=0) + + # + image with scrollbars + self.imageViewer.hbar = ttk.Scrollbar(self.imageViewer, orient='horizontal') + self.imageViewer.vbar = ttk.Scrollbar(self.imageViewer, orient='vertical') + self.imageViewer.hbar.grid(row=1, column=0, sticky="NSEW") + self.imageViewer.vbar.grid(row=0, column=1, sticky="NSEW") + + self.imageViewer.ZONE = ihm.ResizeableCanvas(self.imageViewer.frame, bg=self["background"], xscrollcommand=(self.imageViewer.hbar.set), + yscrollcommand=(self.imageViewer.vbar.set)) + self.imageViewer.ZONE.grid(sticky="NSEW") + + self.imageViewer.hbar.config(command=self.imageViewer.ZONE.xview) + self.imageViewer.vbar.config(command=self.imageViewer.ZONE.yview) + + self.STATUSimg = self.imageViewer.ZONE.create_image(0,0, image=None, anchor="nw") + + + # The terminal to enter the MRZ + self.terminal = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Complete MRZ capture terminal"]) + self.terminal.grid_columnconfigure(0, weight=1) + self.terminal.grid_rowconfigure(0, weight=1) + self.termframe = Frame(self.terminal) + self.termframe.grid(column=0, row=0, sticky='EW') + self.termframe.grid_columnconfigure(0, weight=1) + self.termframe.grid_rowconfigure(0, weight=1) + self.termguide = Label((self.termframe), text='', font='Terminal 17', fg='#006699') + self.termguide.grid(column=0, row=0, padx=5, pady=0, sticky='NW') + self.termguide['text'] = '0 |5 |10 |15 |20 |25 |30 |35 |40 |45' + self.termtext = Text((self.termframe), state='normal', width=60, height=4, wrap='none', font='Terminal 17', fg='#121f38') + self.termtext.grid(column=0, row=0, sticky='SW', padx=5, pady=25) + + # Speed Entry Zone for 731 + self.terminal2 = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Quick entry terminal (731)"]) + self.terminal2.grid_columnconfigure(0, weight=1) + self.terminal2.grid_rowconfigure(0, weight=1) + self.speed731 = Frame(self.terminal2) + self.speed731.grid(column=0, row=0, sticky='EW') + self.speed731.grid_columnconfigure(0, weight=1) + self.speed731.grid_columnconfigure(1, weight=1) + self.speed731.grid_columnconfigure(2, weight=1) + self.speed731.grid_columnconfigure(3, weight=1) + self.speed731.grid_columnconfigure(4, weight=1) + self.speed731.grid_columnconfigure(5, weight=1) + self.speed731.grid_columnconfigure(6, weight=1) + self.speed731.grid_columnconfigure(7, weight=1) + self.speed731.grid_columnconfigure(8, weight=1) + self.speed731.grid_columnconfigure(9, weight=1) + self.speed731.grid_rowconfigure(0, weight=1) + self.speed731text = Entry(self.speed731, font='Terminal 14') + self.speed731text.grid(column=0, row=0, sticky='NEW', padx=5, pady=5) + self.speedResult = Text((self.speed731), state='disabled', width=1, height=1, wrap='none', font='Terminal 14') + self.speedResult.grid(column=2, row=0, sticky='NEW', padx=5, pady=5) + + # The monitor that indicates some useful infos + self.monitor = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Monitor"]) + self.monlog = Text((self.monitor), state='disabled', width=60, height=10, wrap='word') + self.monlog.grid(column=0, row=0, sticky='EWNS', padx=5, pady=5) + self.scrollb = ttk.Scrollbar((self.monitor), command=(self.monlog.yview)) + self.scrollb.grid(column=1, row=0, sticky='EWNS', padx=5, pady=5) + self.monlog['yscrollcommand'] = self.scrollb.set + self.monitor.grid_columnconfigure(0, weight=1) + self.monitor.grid_rowconfigure(0, weight=1) + + # All the items griding + self.lecteur_ci.grid(column=2, row=0, sticky='EWNS', columnspan=1, padx=5, pady=5) + self.imageViewer.grid(column=0, row=0, sticky='EWNS', columnspan=2, padx=5, pady=5) + self.terminal.grid(column=0, row=2, sticky='EWNS', columnspan=2, padx=5, pady=5) + self.terminal2.grid(column=0, row=1, sticky='EWNS', columnspan=2, padx=5, pady=5) + self.monitor.grid(column=2, row=1, sticky='EWNS', columnspan=1, rowspan=2, padx=5, pady=5) + + # What is a window without a menu bar ? + menubar = Menu(self) + menu1 = Menu(menubar, tearoff=0) + menu1.add_command(label=lang.all[globs.CNIRlang]["New"], command=(self.newEntry)) + menu1.add_command(label=lang.all[globs.CNIRlang]["Open scan..."], command=(self.openingScan)) + menu1.add_separator() + menu1.add_command(label=lang.all[globs.CNIRlang]["Quit"], command=(self.destroy)) + menubar.add_cascade(label=lang.all[globs.CNIRlang]["File"], menu=menu1) + menu2 = Menu(menubar, tearoff=0) + menu2.add_command(label=lang.all[globs.CNIRlang]["Update options"], command=(self.updateSet)) + menu2.add_command(label=lang.all[globs.CNIRlang]["Show Changelog"], command=(self.showChangeLog)) + menu2.add_separator() + menu2.add_command(label=lang.all[globs.CNIRlang]["Language"], command=(self.languageSet)) + menubar.add_cascade(label=lang.all[globs.CNIRlang]["Settings"], menu=menu2) + menu3 = Menu(menubar, tearoff=0) + menu3.add_command(label=lang.all[globs.CNIRlang]["Keyboard commands"], command=(self.helpbox)) + menu3.add_command(label=lang.all[globs.CNIRlang]["Report a bug"], command=(self.openIssuePage)) + menu3.add_separator() + menu3.add_command(label=lang.all[globs.CNIRlang]["About CNIRevelator"], command=(self.infobox)) + menubar.add_cascade(label=lang.all[globs.CNIRlang]["Help"], menu=menu3) + menubar.add_command(label="<|>", command=(self.panelResize)) + self.config(menu=menubar) + + # The title + self.wm_title(globs.CNIRName) + + # Make this window resizable and set her size + self.resizable(0, 0) + self.update() + self.w = int(self.winfo_reqwidth()) + self.h = int(self.winfo_reqheight()) + self.ws = self.winfo_screenwidth() + self.hs = self.winfo_screenheight() + self.x = (self.ws - self.w)/2 + self.y = (self.hs - self.h)/2 + self.geometry('%dx%d+%d+%d' % (self.w, self.h, self.x, self.y)) + self.update() + self.deiconify() + self.attributes("-topmost", 1) + self.maxsize(self.w, self.h) + self.minsize(int(2.15 * (self.ws / 2 * 0.3333333333333333)), self.h) + self.currentw = self.w + + # Set image + self.imageViewer.image = None + self.imageViewer.imagePath = None + self.imageViewer.imgZoom = 1 + self.imageViewer.rotateCount = 0 + self.imageViewer.blackhat = False + self.imageViewer.pagenumber = 0 + + # Some bindings + self.termtext.bind('', self.entryValidation) + self.termtext.bind('<>', self.pasteValidation) + self.speed731text.bind('', self.speedValidation) + self.imageViewer.ZONE.bind("", self.rectangleSelectScan) + + logfile.printdbg('Initialization successful') + + if globs.CNIROpenFile: + self.after_idle(lambda : self.openScanFile(sys.argv[1])) + + + ## OCR related functions + + def rectangleSelectScan(self, event): + """ + Realises the selection of the MRZ to make OCR possible + """ + if self.imageViewer.image: + canvas = event.widget + #print("Get coordinates : [{}, {}], for [{}, {}]".format(canvas.canvasx(event.x), canvas.canvasy(event.y), event.x, event.y)) + + self.corners.append([canvas.canvasx(event.x), canvas.canvasy(event.y)]) + if len(self.corners) == 2: + self.select = self.imageViewer.ZONE.create_rectangle(self.corners[0][0], self.corners[0][1], self.corners[1][0], self.corners[1][1], outline ='cyan', width = 2) + #print("Get rectangle : [{}, {}], for [{}, {}]".format(self.corners[0][0], self.corners[0][1], self.corners[1][0], self.corners[1][1])) + if len(self.corners) > 2: + self.corners = [] + self.imageViewer.ZONE.delete(self.select) + + def goOCRDetection(self): + """ + Realises the OCR detection and get the text in self.mrzChar (and prints it) + """ + if self.imageViewer.image: + cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber] + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) + if self.imageViewer.blackhat: + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) + cv_img = cv2.GaussianBlur(cv_img, (3, 3), 0) + cv_img = cv2.bitwise_not(cv_img) + try: + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, channels_no = cv_img.shape + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, channels_no = cv_img.shape + except ValueError: + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width = cv_img.shape + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width = cv_img.shape + # Rotate + rotationMatrix=cv2.getRotationMatrix2D((width/2, height/2),int(self.imageViewer.rotateCount*90),1) + cv_img=cv2.warpAffine(cv_img,rotationMatrix,(width,height)) + # Resize + dim = (int(width * (self.imageViewer.imgZoom + 100) / 100), int(height * (self.imageViewer.imgZoom + 100) / 100)) + cv_img = cv2.resize(cv_img, dim, interpolation = cv2.INTER_AREA) + + x0 = int(self.corners[0][0]) + y0 = int(self.corners[0][1]) + x1 = int(self.corners[1][0]) + y1 = int(self.corners[1][1]) + + crop_img = cv_img[y0:y1, x0:x1] + + # Get the text by OCR + try: + os.environ['PATH'] = globs.CNIRTesser + os.environ['TESSDATA_PREFIX'] = globs.CNIRTesser + '\\tessdata' + + text = pytesseract.image_to_string(crop_img, lang='ocrb', config='--psm 6 --oem 1 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<') + + # manual validation + # the regex + regex = re.compile("[^A-Z0-9<\n]") + text = re.sub(regex, '', text) + self.validatedtext = '' + invite = ihm.OpenScanDialog(self, text) + invite.transient(self) + invite.grab_set() + invite.focus_force() + self.wait_window(invite) + + #print("text : {}".format(self.validatedtext)) + + self.mrzChar = "" + + # Get that + for char in self.validatedtext: + self.termtext.delete("1.0","end") + self.termtext.insert("1.0", self.mrzChar) + self.mrzChar = self.mrzChar + char + + self.stringValidation("") + #print(self.mrzChar) + + # Reinstall tesseract + except pytesseract.TesseractNotFoundError as e: + try: + shutil.rmtree(globs.CNIRTesser) + except Exception: + pass + showerror(lang.all[globs.CNIRlang]["OCR module error"], (lang.all[globs.CNIRlang]["The OCR module located at {} can not be found or corrupted. It will be reinstalled at the next run"].format(os.environ['PATH'])), parent=self) + logfile.printerr(lang.all[globs.CNIRlang]["Tesseract error : {}. Will be reinstallated"].format(e)) + + # Tesseract error + except pytesseract.TesseractError as e: + logfile.printerr("Tesseract error : {}".format(e)) + showerror(lang.all[globs.CNIRlang]["OCR module error"], (lang.all[globs.CNIRlang]["The Tesseract module encountered a problem: {}"].format(e)), parent=self) + + ## Regex and document detection + control related functions + + def stringValidation(self, keysym): + """ + Analysis of the already typed document + """ + # analysis + # If we must decide the type of the document + if not self.mrzDecided: + # Get the candidates + candidates = mrz.allDocMatch(self.mrzChar.split("\n")) + + if len(candidates) == 2 and len(self.mrzChar) >= 8: + # Parameters for the choice invite + invite = ihm.DocumentAsk(self, [candidates[0][2], candidates[1][2]]) + invite.transient(self) + invite.grab_set() + invite.focus_force() + + self.wait_window(invite) + + self.logOnTerm(lang.all[globs.CNIRlang]["Document detected: {}\n"].format(candidates[invite.choice][2])) + self.statusbar.set(candidates[invite.choice][2]) + self.mrzDecided = candidates[invite.choice] + + elif len(candidates) == 1: + self.logOnTerm(lang.all[globs.CNIRlang]["Document detected: {}\n"].format(candidates[0][2])) + self.statusbar.set(candidates[0][2]) + self.mrzDecided = candidates[0] + else: + # corrects some problems + if keysym in ["BackSpace", "Delete"]: + return + # get the cursor position + curPos = self.termtext.index(INSERT) + # break the line + if (len(self.mrzChar) - 2 >= len(self.mrzDecided[0][0])) and ("\n" not in self.mrzChar[:-1]): + # In case of there is no second line + if len(self.mrzDecided[0][1]) == 0: + self.mrzChar = self.termtext.get("1.0", "end")[:-1] + self.termtext.delete("1.0","end") + self.termtext.insert("1.0", self.mrzChar[:-1]) + self.termtext.mark_set(INSERT, curPos) + else: + # In case of there is a second line + self.mrzChar = self.termtext.get("1.0", "end")[:-1] + '\n' + self.termtext.delete("1.0","end") + self.termtext.insert("1.0", self.mrzChar) + # stop when limit reached + elif (len(self.mrzChar) - 3 >= 2 * len(self.mrzDecided[0][0])): + self.mrzChar = self.termtext.get("1.0", "end")[:-1] + self.termtext.delete("1.0","end") + self.termtext.insert("1.0", self.mrzChar[:-1]) + self.termtext.mark_set(INSERT, curPos) + # compute the control sum if needed + self.computeSigma() + + def entryValidation(self, event): + """ + On the fly validation with regex + """ + controlled = False + + # get the cursor + if self.mrzDecided: + curPosition = self.termtext.index(INSERT) + position = curPosition.split(".") + pos = (int(position[0]) - 1) * len(self.mrzDecided[0][0]) + (int(position[1]) - 1) + else: + curPosition = self.termtext.index(INSERT) + position = curPosition.split(".") + pos = (int(position[1]) - 1) + + # verifying that there is no Ctrl-C/Ctrl-V and others + if event.state & 0x0004 and ( event.keysym == "c" or + event.keysym == "v" or + event.keysym == "a" or + event.keysym == "z" or + event.keysym == "y" ): + controlled = True + + if event.keysym == "Tab": + if self.mrzDecided: + controlled = True + self.mrzChar = self.termtext.get("1.0", "end")[:-1] + # the regex + regex = re.compile("[^A-Z0-9<]") + code = re.sub(regex, '', self.mrzChar) + + number = mrz.completeDocField(self.mrzDecided, code, pos) - 1 + + if number == 0: + return "break" + + mrzChar = self.termtext.get(curPosition, "end")[:-1] + self.termtext.delete(curPosition,"end") + self.termtext.insert(curPosition, "<"*number + mrzChar) + self.termtext.mark_set("insert", "%d.%d" % (int(position[0]), int(position[1]) + number)) + return "break" + + if event.keysym == "Escape": + if self.mrzDecided: + # Get the candidates + candidates = mrz.allDocMatch(self.mrzChar.split("\n")) + + if len(candidates) == 2 and len(self.mrzChar) >= 8: + # Parameters for the choice invite + invite = ihm.DocumentAsk(self, [candidates[0][2], candidates[1][2]]) + invite.transient(self) + invite.grab_set() + invite.focus_force() + + self.wait_window(invite) + + self.logOnTerm(lang.all[globs.CNIRlang]["Document detected again: {}\n"].format(candidates[invite.choice][2])) + self.statusbar.set(candidates[invite.choice][2]) + self.mrzDecided = candidates[invite.choice] + + elif len(candidates) == 1: + self.logOnTerm(lang.all[globs.CNIRlang]["Document detected again: {}\n"].format(candidates[0][2])) + self.statusbar.set(candidates[0][2]) + self.mrzDecided = candidates[0] + return "break" + + # If not a control char + if not controlled and not event.keysym in ihm.controlKeys: + # the regex + regex = re.compile("[A-Z]|<|[0-9]") + # match ! + if not regex.fullmatch(event.char): + self.logOnTerm(lang.all[globs.CNIRlang]["Character not accepted !\n"]) + return "break" + # Adds the entry + tempChar = self.termtext.get("1.0", "end")[:-1] + self.mrzChar = tempChar[:pos+1] + event.char + tempChar[pos+1:] + '\n' + + # validation of the mrz string + self.stringValidation(event.keysym) + + def pasteValidation(self, event): + """ + On the fly validation of pasted text + """ + # cleanup + self.termtext.delete("1.0","end") + + # get the clipboard content + lines = self.clipboard_get() + self.mrzChar = "" + + # the regex + regex = re.compile("[^A-Z0-9<]") + + lines = re.sub(regex, '', lines) + + # Get that + for char in lines: + self.termtext.delete("1.0","end") + self.termtext.insert("1.0", self.mrzChar) + self.mrzChar = self.mrzChar + char + self.stringValidation("") + self.termtext.insert("1.0", self.mrzChar) + + return "break" + + def speedValidation(self, event): + """ + Computation of the speed entry + """ + char = self.speed731text.get() + self.speedResultPrint(str(mrz.computeControlSum(char))) + return "break" + + def computeSigma(self): + """ + Launch the checksum computation, infos validation and print/display the results + """ + # the regex + regex = re.compile("[^A-Z0-9<]") + code = re.sub(regex, '', self.mrzChar) + self.compliance = True + + allSums = mrz.computeAllControlSum(self.mrzDecided, code)["ctrlSumList"] + #print("Code : _{}_ | Sums : {}".format(code, allSums)) + + self.termtext.tag_remove("conforme", "1.0", "end") + self.termtext.tag_remove("nonconforme", "1.0", "end") + + self.clearTerm() + self.logOnTerm(lang.all[globs.CNIRlang]["Document Review: {}\n\n"].format(self.mrzDecided[2])) + + for sum in allSums: + x = sum[1] // len(self.mrzDecided[0][0]) +1 + y = sum[1] % len(self.mrzDecided[0][0]) + #print("index : {}.{}".format(x,y)) + #print("{} == {}".format(code[sum[1]], sum[2])) + + if sum[3]: + self.logOnTerm(lang.all[globs.CNIRlang]["Checksum position {}: Lu {} VS Calculated {} [facultative]\n"].format(sum[1], code[sum[1]], sum[2])) + else: + self.logOnTerm(lang.all[globs.CNIRlang]["Checksum position {}: Lu {} VS Calculated {}\n"].format(sum[1], code[sum[1]], sum[2])) + + # if sum is facultative or if sum is ok + try: + if sum[3] or int(code[sum[1]]) == int(sum[2]): + self.termtext.tag_add("conforme", "{}.{}".format(x,y), "{}.{}".format(x,y+1)) + self.termtext.tag_configure("conforme", background="green", foreground="white") + else: + self.termtext.tag_add("nonconforme", "{}.{}".format(x,y), "{}.{}".format(x,y+1)) + self.termtext.tag_configure("nonconforme", background="red", relief='raised', foreground="white") + self.compliance = False + except ValueError: + self.termtext.tag_add("nonconforme", "{}.{}".format(x,y), "{}.{}".format(x,y+1)) + self.termtext.tag_configure("nonconforme", background="red", relief='raised', foreground="white") + self.compliance = False + + # get the infos + docInfos = mrz.getDocInfos(self.mrzDecided, code) + #print(docInfos) + # display the infos + for key in [ e for e in docInfos ]: + #print(docInfos[key]) + if key in ["CODE", "CTRL", "CTRLF"]: + continue + if not docInfos[key] == False: + self.infoList[key]['text'] = docInfos[key] + self.infoList[key]['background'] = self['background'] + self.infoList[key]['foreground'] = "black" + else: + self.infoList[key]['background'] = "red" + self.infoList[key]['foreground'] = "white" + self.infoList[key]['text'] = "NC" + self.compliance = False + + if self.compliance == True: + self.STATUStxt["text"] = lang.all[globs.CNIRlang]["COMPLIANT"] + self.STATUStxt["foreground"] = "green" + self.statusbar.set(lang.all[globs.CNIRlang]["COMPLIANT"]) + else: + self.STATUStxt["text"] = lang.all[globs.CNIRlang]["IMPROPER"] + self.STATUStxt["foreground"] = "red" + self.statusbar.set(lang.all[globs.CNIRlang]["IMPROPER"]) + + ## Print functions + + def logOnTerm(self, text): + """ + Writes on the monitor + """ + self.monlog['state'] = 'normal' + self.monlog.insert('end', text) + self.monlog['state'] = 'disabled' + self.monlog.yview(END) + + def clearTerm(self): + """ + Clears the monitor + """ + self.monlog['state'] = 'normal' + self.monlog.delete('1.0', 'end') + self.monlog['state'] = 'disabled' + self.monlog.yview(END) + + def speedResultPrint(self, text): + """ + Prints a result in the quick entry terminal + """ + self.speedResult['state'] = 'normal' + self.speedResult.delete("1.0", 'end') + self.speedResult.insert('end', text) + self.speedResult['state'] = 'disabled' + + ## Document display related functions + + def DisplayUpdate(self, image=None, setplace=False): + """ + Reload the displayer to display the image or not + """ + if image: + self.imageViewer.image = image + self.imageViewer.ZONE.itemconfigure(self.STATUSimg, image=(self.imageViewer.image)) + self.imageViewer.ZONE.configure(scrollregion=self.imageViewer.ZONE.bbox("all")) + + def goPageChoice(self, event): + """ + Change the current viewed page of the multipage tiff if needed + """ + self.imageViewer.pagenumber = int(self.toolbar.pageChooser.get()) - 1 + self.resizeScan() + + def openingScan(self): + """ + Open the scan, ask its path and displays it + """ + path = '' + path = filedialog.askopenfilename(parent=self, title=lang.all[globs.CNIRlang]["Open a scan of document..."], filetypes=(('TIF files', '*.tif'), + ('TIF files', '*.tiff'), + ('JPEG files', '*.jpg'), + ('JPEG files', '*.jpeg'))) + self.openScanFile(path) + + def openScanFile(self, path): + """ + Open an image file at path to display it on the displayer + """ + # Check if the file is valid + if ( path[-3:] != 'jpg' + and path[-3:] != 'tif' + and path[-4:] != 'jpeg' + and path[-4:] != 'tiff' ) or not os.path.isfile(path): + showerror(lang.all[globs.CNIRlang]["Open a scan of document..."], lang.all[globs.CNIRlang]["The file you provided is not valid : {}"].format(path)) + return + + # Load an image using OpenCV + self.imageViewer.imagePath = path + self.imageViewer.imgZoom = 1 + self.imageViewer.blackhat = False + self.imageViewer.rotateCount = 0 + self.imageViewer.pagenumber = 0 + + # Determine how many pages + self.toolbar.pageChooser['values'] = ('1') + total = len(cv2.imreadmulti(self.imageViewer.imagePath)[1]) + + for i in range(2, total + 1): + self.toolbar.pageChooser['values'] += tuple(str(i)) + + # Open the first page + cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber] + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) + + try: + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, channels_no = cv_img.shape + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, channels_no = cv_img.shape + except ValueError: + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width = cv_img.shape + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width = cv_img.shape + + # Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage + photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img)) + self.DisplayUpdate(photo) + + def zoomInScan50(self, quantity = 50): + if self.imageViewer.image: + self.imageViewer.imgZoom += quantity + self.resizeScan() + + def zoomOutScan50(self, quantity = 50): + if self.imageViewer.image: + self.imageViewer.imgZoom -= quantity + self.resizeScan() + + def zoomInScan20(self, quantity = 20): + if self.imageViewer.image: + self.imageViewer.imgZoom += quantity + self.resizeScan() + + def zoomOutScan20(self, quantity = 20): + if self.imageViewer.image: + self.imageViewer.imgZoom -= quantity + self.resizeScan() + + def zoomInScan(self, quantity = 1): + if self.imageViewer.image: + self.imageViewer.imgZoom += quantity + self.resizeScan() + + def zoomOutScan(self, quantity = 1): + if self.imageViewer.image: + self.imageViewer.imgZoom -= quantity + self.resizeScan() + + def rotateRight(self): + if self.imageViewer.image: + self.imageViewer.rotateCount -= 1 + if self.imageViewer.rotateCount < 0: + self.imageViewer.rotateCount = 4 + self.resizeScan() + + def rotateLeft(self): + if self.imageViewer.image: + self.imageViewer.rotateCount += 1 + if self.imageViewer.rotateCount > 4: + self.imageViewer.rotateCount = 0 + self.resizeScan() + + def rotateLeft1(self): + if self.imageViewer.image: + self.imageViewer.rotateCount += 0.01 + if self.imageViewer.rotateCount > 4: + self.imageViewer.rotateCount = 0 + self.resizeScan() + + def rotateRight1(self): + if self.imageViewer.image: + self.imageViewer.rotateCount -= 0.01 + if self.imageViewer.rotateCount < 0: + self.imageViewer.rotateCount = 4 + self.resizeScan() + + def negativeScan(self): + """ + Invert the bits to make a negative of the scan (and highlight the contrasts) + """ + if self.imageViewer.image: + # Load an image using OpenCV + cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber] + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) + if not self.imageViewer.blackhat: + self.imageViewer.blackhat = True + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) + cv_img = cv2.GaussianBlur(cv_img, (3, 3), 0) + cv_img = cv2.bitwise_not(cv_img) + else: + self.imageViewer.blackhat = False + self.resizeScan(cv_img) + + def resizeScan(self, cv_img = None): + """ + Reloads the image according to settings + """ + if self.imageViewer.image: + try: + if not hasattr(cv_img, 'shape'): + # Load an image using OpenCV + cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber] + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) + if self.imageViewer.blackhat: + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) + cv_img = cv2.GaussianBlur(cv_img, (3, 3), 0) + cv_img = cv2.bitwise_not(cv_img) + + try: + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, channels_no = cv_img.shape + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, channels_no = cv_img.shape + except ValueError: + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width = cv_img.shape + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width = cv_img.shape + # Rotate + rotationMatrix=cv2.getRotationMatrix2D((width/2, height/2),int(self.imageViewer.rotateCount*90),1) + cv_img=cv2.warpAffine(cv_img,rotationMatrix,(width,height)) + # Resize + dim = (int(width * (self.imageViewer.imgZoom + 100) / 100), int(height * (self.imageViewer.imgZoom + 100) / 100)) + cv_img = cv2.resize(cv_img, dim, interpolation = cv2.INTER_AREA) + # Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage + photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img)) + self.DisplayUpdate( photo) + except Exception as e: + logfile.printerr("Error with opencv : {}".format(e)) + critical.crashCNIR() + try: + # Reload an image using OpenCV + path = self.imageViewer.imagePath + self.imageViewer.imgZoom = 1 + self.imageViewer.blackhat = False + self.imageViewer.rotateCount = 0 + cv_img = cv2.imreadmulti(path) + cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, channels_no = cv_img.shape + # Get the image dimensions (OpenCV stores image data as NumPy ndarray) + height, width, channels_no = cv_img.shape + # Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage + photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img)) + self.DisplayUpdate(photo) + except Exception as e: + logfile.printerr("Critical error with opencv : ".format(e)) + critical.crashCNIR() + showerror(lang.all[globs.CNIRlang]["OpenCV error (image processing)"], lang.all[globs.CNIRlang]["A critical error has occurred in the OpenCV image processing manager used by CNIRevelator, the application will reset itself"]) + self.initialize() + + ## IHM and user interface related functions + + def newEntry(self): + """ + Reinits the IHM and invite + """ + self.initialize() + self.logOnTerm('\n\n{}\n'.format(lang.all[globs.CNIRlang]["Please type a MRZ or open a scan"])) + + def infobox(self): + """ + Shows the About dialog + """ + Tk().withdraw() + + showinfo( lang.all[globs.CNIRlang]["About CNIRevelator"], + ( + lang.all[globs.CNIRlang]["ABOUT"] + ), + parent=self) + + def helpbox(self): + """ + Shows the keyboard help summary + """ + Tk().withdraw() + + showinfo( lang.all[globs.CNIRlang]["Keyboard commands"], + ( + lang.all[globs.CNIRlang]["KEYBHELP"] + ), + + parent=self) + + def openIssuePage(self): + """ + Opens the Github Issue Repository page + """ + webbrowser.open_new("https://github.com/neox95/CNIRevelator/issues") + + def showChangeLog(self): + changelogWin = ihm.ChangelogDialog(self, ('{} : CNIRevelator {}\n\n{}'.format(lang.all[globs.CNIRlang]["Program version"], globs.verstring_full, lang.all[globs.CNIRlang]["CHANGELOG"]))) + changelogWin.transient(self) + changelogWin.grab_set() + changelogWin.focus_force() + self.wait_window(changelogWin) + + def updateSet(self): + """ + Update Settings + """ + changeupdateWin = ihm.updateSetDialog(self) + changeupdateWin.transient(self) + changeupdateWin.grab_set() + changeupdateWin.focus_force() + self.wait_window(changeupdateWin) + + def languageSet(self): + """ + Lang Settings + """ + changelangWin = ihm.langDialog(self) + changelangWin.transient(self) + changelangWin.grab_set() + changelangWin.focus_force() + self.wait_window(changelangWin) + + global mrz + mrz = reload(mrz) + + self.initialize() + + def panelResize(self): + """ + Shows or hides the panel + """ + if self.currentw > int(2.15 * (self.ws / 2 * 0.3333333333333333)): + self.currentw = int(2.15 * (self.ws / 2 * 0.3333333333333333)) + self.geometry('%dx%d+%d+%d' % (self.currentw, self.h, self.x, self.y)) + self.update() + else: + self.currentw = self.w + self.geometry('%dx%d+%d+%d' % (self.currentw, self.h, self.x, self.y)) + self.update() + + + + + + + + + + + + + + + + + + + + diff --git a/mrz.py b/mrz.py new file mode 100644 index 0000000..cce6c83 --- /dev/null +++ b/mrz.py @@ -0,0 +1,562 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: MRZ data dictionnary for CNIRevelator analyzer and * +* functions to analyze these data * +* * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" + +import re +import datetime + +import logger # logger.py +import globs # globs.py +import lang # lang.py +import critical # critical.py + +## SEX CODES +sexcode = {'M':'Homme', 'F':'Femme', 'X':'Non spécifié'} + +## COUNTRY CODES + +landcode2 = lang.all[globs.CNIRlang]["LANDCODE2"] + +landcode3 = lang.all[globs.CNIRlang]["LANDCODE3"] + +## DOCUMENTS TYPES + +P = [ + ["11222333333333333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCCCCCCCCCCDE"], + { + "1": ["2", "CODE", "P."], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["39", "NOM", "([A-Z]|<)+"], + "4": ["9", "NO", ".+"], + "5": ["1", "CTRL", "[0-9]", "4"], + "6": ["3", "NAT", "[A-Z]+"], + "7": ["6", "BDATE", "[0-9]+"], + "8": ["1", "CTRL", "[0-9]", "7"], + "9": ["1", "SEX", "[A-Z]"], + "A": ["6", "EDATE", "[0-9]+"], + "B": ["1", "CTRL", "[0-9]", "A"], + "C": ["14", "FACULT", ".+"], + "D": ["1", "CTRLF", "[0-9]", "C"], + "E": ["1", "CTRL", "[0-9]", "4578ABCD"] + }, + lang.all[globs.CNIRlang]["Passeport lisible à la machine"] +] + +IP = [ + ["112223333333334555555555555555", "66666678999999ABBBCCCCCCCCCCCD"], + { + "1": ["2", "CODE", "IP"], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["9", "NO", ".+"], + "4": ["1", "CTRL", "[0-9]", "3"], + "5": ["15", "FACULT", ".+"], + "6": ["6", "BDATE", "[0-9]+"], + "7": ["1", "CTRL", "[0-9]", "6"], + "8": ["1", "SEX", "[A-Z]"], + "9": ["6", "EDATE", "[0-9]+"], + "A": ["1", "CTRL", "[0-9]", "9"], + "B": ["3", "NAT", "[A-Z]+"], + "C": ["11", "FACULT", ".+"], + "D": ["1", "CTRL", "[0-9]", "345679AC"] + }, + lang.all[globs.CNIRlang]["Carte-passeport"] +] + +IDEUR = [ + ["112223333333334555555555555555", "66666678999999ABBBCCCCCCCCCCCD"], + { + "1": ["2", "CODE", "I."], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["9", "NO", ".+"], + "4": ["1", "CTRL", "[0-9]", "3"], + "5": ["15", "FACULT", ".+"], + "6": ["6", "BDATE", "[0-9]+"], + "7": ["1", "CTRL", "[0-9]", "6"], + "8": ["1", "SEX", "[A-Z]"], + "9": ["6", "EDATE", "[0-9]+"], + "A": ["1", "CTRL", "[0-9]", "9"], + "B": ["3", "NAT", "[A-Z]+"], + "C": ["11", "FACULT", ".+"], + "D": ["1", "CTRL", "[0-9]", "345679AC"] + }, + lang.all[globs.CNIRlang]["Carte d’identité européenne"] +] + +TSEUR = [ + ["112223333333334555555555555555", "66666678999999ABBBCCCCCCCCCCCD"], + { + "1": ["2", "CODE", "IR"], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["9", "NO", ".+"], + "4": ["1", "CTRL", "[0-9]", "3"], + "5": ["15", "FACULT", ".+"], + "6": ["6", "BDATE", "[0-9]+"], + "7": ["1", "CTRL", "[0-9]", "6"], + "8": ["1", "SEX", "[A-Z]"], + "9": ["6", "EDATE", "[0-9]+"], + "A": ["1", "CTRL", "[0-9]", "9"], + "B": ["3", "NAT", "[A-Z]+"], + "C": ["11", "FACULT", ".+"], + "D": ["1", "CTRL", "[0-9]", "345679AC"] + }, + lang.all[globs.CNIRlang]["Carte d’identité européenne"] +] + +AC = [ + ["112223333333334EEE555555555555", "66666678999999ABBBCCCCCCCCCCCD"], + { + "1": ["2", "CODE", "AC"], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["9", "NO", ".+"], + "4": ["1", "CTRL", "[0-9]", "3"], + "E": ["3", "INDIC", "[A-Z]{1,2}."], + "5": ["12", "FACULT", ".+"], + "6": ["6", "BDATE", "[0-9]+ "], + "7": ["1", "CTRL", "[0-9]", "6"], + "8": ["1", "SEX", "[A-Z]"], + "9": ["6", "EDATE", "[0-9]+"], + "A": ["1", "CTRL", "[0-9]", "9"], + "B": ["3", "NAT", "[A-Z]+"], + "C": ["11", "FACULT", ".+"], + "D": ["1", "CTRL", "[0-9]","345679AC"] + }, + lang.all[globs.CNIRlang]["Certificat de membre d'équipage"] +] + +VA = [ + ["11222333333333333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCCCCCCCCCCCCC"], + { + "1": ["2", "CODE", "V."], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["39", "NOM", "[A-Z]+"], + "4": ["9", "NO", ".+"], + "5": ["1", "CTRL", "[0-9]","4"], + "6": ["3", "NAT", "[A-Z]+"], + "7": ["6", "BDATE", "[0-9]+"], + "8": ["1", "CTRL", "[0-9]", "7"], + "9": ["1", "SEX", "[A-Z]"], + "A": ["6", "EDATE", "[0-9]+"], + "B": ["1", "CTRL", "[0-9]", "A"], + "C": ["16", "FACULT", ".+"] + }, + lang.all[globs.CNIRlang]["Visa de type A"] +] + +VB = [ + ["112223333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCC"], + { + "1": ["2", "CODE", "V."], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["31", "NOM", "([A-Z]|<)+"], + "4": ["9", "NO", ".+"], + "5": ["1", "CTRL", "[0-9]","4"], + "6": ["3", "NAT", "[A-Z]+"], + "7": ["6", "BDATE", "[0-9]+"], + "8": ["1", "CTRL", "[0-9]", "7"], + "9": ["1", "SEX", "[A-Z]"], + "A": ["6", "EDATE", "[0-9]+"], + "B": ["1", "CTRL", "[0-9]", "A"], + "C": ["8", "FACULT", ".+"] + }, + lang.all[globs.CNIRlang]["Visa de type B"] +] + +TSF = [ + ["112223333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCC"], + { + "1": ["2", "CODE", "TS"], + "2": ["3", "PAYS", "FRA"], + "3": ["31", "NOM", "([A-Z]|<)+"], + "4": ["9", "NO", ".+"], + "5": ["1", "CTRL", "[0-9]","4"], + "6": ["3", "NAT", "[A-Z]+"], + "7": ["6", "BDATE", "[0-9]+"], + "8": ["1", "CTRL", "[0-9]", "7"], + "9": ["1", "SEX", "[A-Z]"], + "A": ["6", "EDATE", "[0-9]+"], + "B": ["1", "CTRL", "[0-9]", "A"], + "C": ["8", "FACULT", ".+"] + }, + lang.all[globs.CNIRlang]["Carte de séjour FR"] +] + +TDV = [ + ["112223333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCCCD"], + { + "1": ["2", "CODE", "I."], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["31", "NOM", "([A-Z]|<)+"], + "4": ["9", "NO", ".+"], + "5": ["1", "CTRL", "[0-9]", "4"], + "6": ["3", "NAT", "[A-Z]+"], + "7": ["6", "BDATE", "[0-9]+"], + "8": ["1", "CTRL", "[0-9]", "7"], + "9": ["1", "SEX", "[A-Z]"], + "A": ["6", "EDATE", "[0-9]+"], + "B": ["1", "CTRL", "[0-9]", "A"], + "C": ["7", "FACULT", ".+"], + "D": ["1", "CTRL", "[0-9]", "4578ABC"] + }, + lang.all[globs.CNIRlang]["Titre d'identité/de voyage"] +] + +IDFR = [ + ["112223333333333333333333333333444444", "555566677777899999999999999AAAAAABCD"], + { + "1": ["2", "CODE", "ID"], + "2": ["3", "PAYS", "FRA"], + "3": ["25", "NOM", "([A-Z]|<)+"], + "4": ["6", "NOINT", ".+"], + "5": ["4", "DDATE", "[0-9]+"], + "6": ["3", "NOINT2", "[0-9]+"], + "7": ["5", "NOINT3", "[0-9]+"], + "8": ["1", "CTRL", "[0-9]", "567"], + "9": ["14", "PRENOM", "[A-Z]"], + "A": ["6", "BDATE", "[0-9]+"], + "B": ["1", "CTRL", "[0-9]", "A"], + "C": ["1", "SEX", "[A-Z]"], + "D": ["1", "CTRL", "[0-9]", "123456789ABCE"] + }, + lang.all[globs.CNIRlang]["Pièce d'identité FR"] +] + +DL = [ + ["112223333333334555555666666667", ""], + { + "1": ["2", "CODE", "D1"], + "2": ["3", "PAYS", "[A-Z]+"], + "3": ["9", "NO", "[0-9]{2}[A-Z]{2}[0-9]{5}"], + "4": ["1", "CTRL", "[0-9]", "123"], + "5": ["6", "EDATE", "[0-9]+"], + "6": ["8", "NOM", "([A-Z]|<)+"], + "7": ["1", "CTRL", "[0-9]", "123456"] + }, + lang.all[globs.CNIRlang]["Permis de conduire"] +] + +TYPES = [IDFR, TDV, VB, VA, AC, IDEUR, IP, P, DL, TSF, TSEUR] + +# longest document MRZ line +longest = max([len(x[0][0]) for x in TYPES]) + +## THE ROOT OF THIS PROJECT ! + +def getDocString(doc): + return doc[0][0] + doc[0][1] + +def getFieldLimits(doc, fieldtype): + """ + This function returns the limit of a given field string id for a given document structure + """ + L1 = limits(doc[0][0], fieldtype) + L2 = limits(doc[0][1], fieldtype) + + if -1 in L1: + return 1, L2 + else: + return 0, L1 + return + +def limits(line, fieldtype): + """ + Returns the limit of a given field structure + """ + a = line.find(fieldtype) + b = line.rfind(fieldtype) + return (a,b+1) + +def completeDocField(doc, code, position): + """ + Completes with '<' the document the field that is located at given position + """ + field = getDocString(doc)[position] + limit = limits(getDocString(doc), field) + res = limit[1] - position + #print("field : {}, limit : {}, number of char to complete : {}".format(field, limit, res)) + return res + + +def docMatch(doc, strs): + """ + This function calculates a regex match score for a given document and a string couple + """ + # Global handler + logfile = logger.logCur + + level = 0 + nchar = 0 + bonus = 0 + + for i in range(0,2): + cursor = 0 + #print("Line : {}".format(i)) + + while True: + if cursor > len(doc[0][i]) - 1: + break + # Getting the type of field on the cursor position + fieldtype = doc[0][i][cursor] + lim = limits(doc[0][i], fieldtype) + # ready for next field + cursor = lim[1] + # get the current field and isolates it + field = doc[0][i][ lim[0]:lim[1] ] + fstr = strs[i][ lim[0]:lim[1] ] + # Prepare regex compilation + regex = re.compile(doc[1][fieldtype][2]) + # Test the match + matching = regex.match(fstr) + # Retrieve the mathing level + if matching: + level += matching.end() + if fieldtype == "1": + bonus += 100 + nchar += int(doc[1][fieldtype][0]) + + # Print for debug + + # print("Field : {}, type = {}, on str : {}".format(field, fieldtype, fstr)) + # logfile.printdbg(" REGEX : {}, match : {}".format(regex, matching)) + # exit the loop + + #logfile.printdbg("{} level : {}/{} (+{})".format(doc[2], level, nchar, bonus)) + return (level, nchar, bonus) + +def allDocMatch(strs, final=False): + """ + This functions test all documents types on the lines provided and returns a score for each + """ + # Global handler + logfile = logger.logCur + + #print(strs) + + SCORES = [] + for doc in TYPES: + # Get the score of the document on the strings + level, nchar, bonus = docMatch(doc, strs) + # Number of characters compatibles + bonus with the doc indication + SCORES += [ level + bonus ] + # if the len of strings is the same than document, add a bonus + # but only if we are in a final situation + if final: + if len(strs[0] + strs[1]) == nchar: + SCORES[-1] += 100 + candidate = SCORES.index(max(SCORES)) + candidates = [] + canditxt = [] + # Search the candidates + for i in range(len(SCORES)): + if SCORES[i] == SCORES[candidate]: + candidates += [TYPES[i]] + canditxt += [TYPES[i][2]] + # Return the candidates + #logfile.printdbg("Scores : {}".format(SCORES)) + #logfile.printdbg("Candidates : {}".format(canditxt)) + return candidates + +def computeControlSum(code): + """ + This function computes a control sum for the given characters + """ + resultat = 0 + i = -1 + facteur = [7, 3, 1] + for car in code: + if car == '<' or car == '\n': + valeur = 0 + i += 1 + else: + if car in '0123456789': + valeur = int(car) + i += 1 + else: + if car in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': + valeur = ord(car) - 55 + i += 1 + else: + break + resultat += valeur * facteur[(i % 3)] + + return resultat % 10 + +def computeAllControlSum(doc, code): + """ + This function computes all the ctrl sums on a MRZ string and returns all the results + it returns the misc infos about the document too + """ + ctrlSumList = [] + facult = False + + # iteration on each char of the given MRZ + for charPos in range(len(code)): + field = getDocString(doc)[charPos] + + if doc[1][field][1] == "CTRL": + #print("{} is CTRL field {}".format(code[charPos], field)) + + codeChain = "" + # iteration on the fields to control + for pos in range(len(code)): + target = getDocString(doc)[pos] + if target in doc[1][field][3]: + #print("__field : {} {} {} {}".format(target, pos, field, doc[1][field][3])) + codeChain += code[pos] + + #print("chain to control : _{}_".format(codeChain)) + + ctrlSum = computeControlSum(codeChain) + #print("SUM : {} vs {}".format(code[charPos], ctrlSum)) + + ctrlSumList += [ (field, charPos, ctrlSum, facult) ] + + if doc[1][field][1] == "CTRLF": + #print("{} is CTRL field {}".format(code[charPos], field)) + + codeChain = "" + # iteration on the fields to control + for pos in range(len(code)): + target = getDocString(doc)[pos] + if target in doc[1][field][3]: + #print("__field : {} {} {} {}".format(target, pos, field, doc[1][field][3])) + codeChain += code[pos] + + #print("chain to control : _{}_".format(codeChain)) + + ctrlSum = computeControlSum(codeChain) + #print("SUM : {} vs {}".format(code[charPos], ctrlSum)) + + if code[charPos] == "<": + facult = True + + ctrlSumList += [ (field, charPos, ctrlSum, facult) ] + + return { + "ctrlSumList" : ctrlSumList + } + + +def getDocInfos(doc, code): + # get all the types of infos that are in the document doc + infoTypes = [ (doc[1][field][1], limits(doc[0][0] + doc[0][1], field)) for field in doc[1] ] + + res = {} + + for field in infoTypes: + + value = code[ field[1][0] : field[1][1] ].replace("<", " ").strip() + + # State code + if field[0] == 'PAYS' or field[0] == 'NAT': + try: + if len(value) == 3 and value[-1] != "<": + res[field[0]] = landcode3[value] + elif len(value) == 3 and value[-1] == "<": + res[field[0]] = landcode2[value[:-1]] + else: + res[field[0]] = landcode2[value] + except KeyError: + res[field[0]] = False + + # Dates + elif field[0][1:] == 'DATE': + # size adaptation + if len(value) == 6: + value = "{}/{}/{}".format(value[4:6], value[2:4], value[0:2]) + elif len(value) == 4: + value = "{}/{}/{}".format("01", value[2:4], value[0:2]) + + # date validation + try: + datetime.datetime.strptime(value,"%d/%m/%y") + except ValueError: + #print(value) + if value != "": + res[field[0]] = False + else: + res[field[0]] = value + + # Numbers + elif field[0][:-1] == 'NOINT': + try: + res["NO"] += value + except KeyError: + res["NO"] = value + elif field[0] == 'NOINT': + try: + res["NO"] += value + except KeyError: + res["NO"] = value + + elif field[0] == 'FACULT': + try: + res["INDIC"] += value + except KeyError: + res["INDIC"] = value + + # Sex + elif field[0] == 'SEX': + if not value in "MF": + res[field[0]] = False + else: + res[field[0]] = value + + # All other cases + else: + if value != "": + res[field[0]] = value + + return res + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pytesseract.py b/pytesseract.py new file mode 100644 index 0000000..1f0c04e --- /dev/null +++ b/pytesseract.py @@ -0,0 +1,326 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Pytesseract modification to comply with Pyinstaller * +* * +* Copyright © 2017-2018 Matthias A. Lee (madmaze) * +* Copyright © 2018-2019 Adrien Bourmault (neox95) * +* * +* This file is part of CNIRevelator. * +* * +* CNIRevelator is free software: you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation, either version 3 of the License, or * +* any later version. * +* * +* CNIRevelator is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY*without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with CNIRevelator. If not, see . * +******************************************************************************** +""" + + +try: + import Image +except ImportError: + from PIL import Image + +import os, sys, subprocess, tempfile, shlex, string +from glob import iglob +from pkgutil import find_loader +from distutils.version import LooseVersion +from os.path import realpath, normpath, normcase +numpy_installed = find_loader('numpy') is not None +if numpy_installed: + from numpy import ndarray +tesseract_cmd = 'tesseract' +RGB_MODE = 'RGB' +OSD_KEYS = {'Page number':( + 'page_num', int), + 'Orientation in degrees':( + 'orientation', int), + 'Rotate':( + 'rotate', int), + 'Orientation confidence':( + 'orientation_conf', float), + 'Script':( + 'script', str), + 'Script confidence':( + 'script_conf', float)} + +class Output: + STRING = 'string' + BYTES = 'bytes' + DICT = 'dict' + + +class TesseractError(RuntimeError): + + def __init__(self, status, message): + self.status = status + self.message = message + self.args = (status, message) + + +class TesseractNotFoundError(EnvironmentError): + + def __init__(self): + super(TesseractNotFoundError, self).__init__(tesseract_cmd + " is not installed or it's not in your path") + + +class TSVNotSupported(EnvironmentError): + + def __init__(self): + super(TSVNotSupported, self).__init__('TSV output not supported. Tesseract >= 3.05 required') + + +def run_once(func): + + def wrapper(*args, **kwargs): + if wrapper._result is wrapper: + wrapper._result = func(*args, **kwargs) + return wrapper._result + + wrapper._result = wrapper + return wrapper + + +def get_errors(error_string): + return ' '.join(line for line in error_string.decode('utf-8').splitlines()).strip() + + +def cleanup(temp_name): + """ Tries to remove files by filename wildcard path. """ + for filename in iglob(temp_name + '*' if temp_name else temp_name): + try: + os.remove(filename) + except OSError: + pass + + +def prepare(image): + if isinstance(image, Image.Image): + return image + if numpy_installed: + if isinstance(image, ndarray): + pass + return Image.fromarray(image) + raise TypeError('Unsupported image object') + + +def save_image(image): + temp_name = tempfile.mktemp(prefix='tess_') + if isinstance(image, str): + return (temp_name, realpath(normpath(normcase(image)))) + else: + image = prepare(image) + img_extension = image.format + if image.format not in frozenset({'BMP', 'JPEG', 'GIF', 'TIFF', 'PNG'}): + img_extension = 'PNG' + if not image.mode.startswith(RGB_MODE): + image = image.convert(RGB_MODE) + if 'A' in image.getbands(): + background = Image.new(RGB_MODE, image.size, (255, 255, 255)) + background.paste(image, (0, 0), image) + image = background + input_file_name = temp_name + os.extsep + img_extension + (image.save)(input_file_name, format=img_extension, **image.info) + return ( + temp_name, input_file_name) + + +def subprocess_args(include_stdout=True): + if hasattr(subprocess, 'STARTUPINFO'): + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + env = os.environ + else: + si = None + env = None + if include_stdout: + ret = {'stdout': subprocess.PIPE} + else: + ret = {} + ret.update({'stdin':subprocess.PIPE, 'stderr':subprocess.PIPE, + 'startupinfo':si, + 'env':env}) + return ret + + +def run_tesseract(input_filename, output_filename_base, extension, lang, config='', nice=0): + cmd_args = [] + if not sys.platform.startswith('win32'): + if nice != 0: + cmd_args += ('nice', '-n', str(nice)) + cmd_args += (tesseract_cmd, input_filename, output_filename_base) + if lang is not None: + cmd_args += ('-l', lang) + cmd_args += shlex.split(config) + if extension not in ('box', 'osd', 'tsv'): + cmd_args.append(extension) + try: + proc = (subprocess.Popen)(cmd_args, **subprocess_args()) + except OSError: + raise TesseractNotFoundError() + + status_code, error_string = proc.wait(), proc.stderr.read() + proc.stderr.close() + if status_code: + raise TesseractError(status_code, get_errors(error_string)) + return True + + +def run_and_get_output(image, extension, lang=None, config='', nice=0, return_bytes=False): + temp_name, input_filename = ('', '') + try: + temp_name, input_filename = save_image(image) + kwargs = {'input_filename':input_filename, + 'output_filename_base':temp_name + '_out', + 'extension':extension, + 'lang':lang, + 'config':config, + 'nice':nice} + run_tesseract(**kwargs) + filename = kwargs['output_filename_base'] + os.extsep + extension + with open(filename, 'rb') as (output_file): + if return_bytes: + return output_file.read() + return output_file.read().decode('utf-8').strip() + finally: + cleanup(temp_name) + + +def file_to_dict(tsv, cell_delimiter, str_col_idx): + result = {} + rows = [row.split(cell_delimiter) for row in tsv.split('\n')] + if not rows: + return result + else: + header = rows.pop(0) + if len(rows[(-1)]) < len(header): + rows[(-1)].append('') + if str_col_idx < 0: + str_col_idx += len(header) + for i, head in enumerate(header): + result[head] = [int(row[i]) if i != str_col_idx else row[i] for row in rows] + + return result + + +def is_valid(val, _type): + if _type is int: + return val.isdigit() + else: + if _type is float: + pass + try: + float(val) + return True + except ValueError: + return False + + return True + + +def osd_to_dict(osd): + return {OSD_KEYS[kv[0]][0]:OSD_KEYS[kv[0]][1](kv[1]) for kv in (line.split(': ') for line in osd.split('\n')) if len(kv) == 2 if is_valid(kv[1], OSD_KEYS[kv[0]][1])} + + +@run_once +def get_tesseract_version(): + """ + Returns LooseVersion object of the Tesseract version + """ + try: + return LooseVersion((subprocess.check_output)([tesseract_cmd, '--version'], **subprocess_args(False)).decode('utf-8').split()[1].lstrip(string.printable[10:])) + except OSError: + raise TesseractNotFoundError() + + +def image_to_string(image, lang=None, config='', nice=0, boxes=False, output_type=Output.STRING): + """ + Returns the result of a Tesseract OCR run on the provided image to string + """ + if boxes: + print("\nWarning: Argument 'boxes' is deprecated and will be removed in future versions. Use function image_to_boxes instead.\n") + return image_to_boxes(image, lang, config, nice, output_type) + else: + args = [ + image, 'txt', lang, config, nice] + if output_type == Output.DICT: + return {'text': run_and_get_output(*args)} + if output_type == Output.BYTES: + args.append(True) + return run_and_get_output(*args) + + +def image_to_boxes(image, lang=None, config='', nice=0, output_type=Output.STRING): + """ + Returns string containing recognized characters and their box boundaries + """ + config += ' batch.nochop makebox' + args = [image, 'box', lang, config, nice] + if output_type == Output.DICT: + box_header = 'char left bottom right top page\n' + return file_to_dict(box_header + run_and_get_output(*args), ' ', 0) + else: + if output_type == Output.BYTES: + args.append(True) + return run_and_get_output(*args) + + +def image_to_data(image, lang=None, config='', nice=0, output_type=Output.STRING): + """ + Returns string containing box boundaries, confidences, + and other information. Requires Tesseract 3.05+ + """ + if get_tesseract_version() < '3.05': + raise TSVNotSupported() + config = '{} {}'.format('-c tessedit_create_tsv=1', config.strip()).strip() + args = [image, 'tsv', lang, config, nice] + if output_type == Output.DICT: + return file_to_dict(run_and_get_output(*args), '\t', -1) + else: + if output_type == Output.BYTES: + args.append(True) + return run_and_get_output(*args) + + +def image_to_osd(image, lang='osd', config='', nice=0, output_type=Output.STRING): + """ + Returns string containing the orientation and script detection (OSD) + """ + config = '{}-psm 0 {}'.format('' if get_tesseract_version() < '3.05' else '-', config.strip()).strip() + args = [ + image, 'osd', lang, config, nice] + if output_type == Output.DICT: + return osd_to_dict(run_and_get_output(*args)) + else: + if output_type == Output.BYTES: + args.append(True) + return run_and_get_output(*args) + + +def main(): + if len(sys.argv) == 2: + filename, lang = sys.argv[1], None + else: + if len(sys.argv) == 4: + if sys.argv[1] == '-l': + filename, lang = sys.argv[3], sys.argv[2] + sys.stderr.write('Usage: python pytesseract.py [-l lang] input_file\n') + exit(2) + try: + print(image_to_string((Image.open(filename)), lang=lang)) + except IOError: + sys.stderr.write('ERROR: Could not open file "%s"\n' % filename) + exit(1) + + +if __name__ == '__main__': + main() diff --git a/rotateLeft.png b/rotateLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..7653faa8a5bc8290de02bb48c62838824d1e9e81 GIT binary patch literal 750 zcmVG*q7fe?mPL;JMTC%I?MwHW-jNP z=iYPAd+&2I%3~v1(23JHhITX|gKwC?D?GtCrm$E+?o@2Re%!!2%woBW{lze@Mib4f zh4=zKV!i^xq|IRr=c^&yhTAF0mhcOoh0?x{Y$6)_8Tlk*dZV1HB8NLd$>Irq;1Mq3 zFq+X2Su+ly2M;iWquwxCRd9+02@tot>mv>kezPWf~3`MhEFSaTy`y8&J zk-`}*LjA0-mB`j($#VcMA8m_fC$C}ZNnHa}S!nvAe z144t;0tYb}BYxJ1b0wS?h4Z==_#~Wqd9>FkZ&@^8RIIyewW6v~FXEhVujescFXCO; z6#G9>FX96VPr{e=AkN~Xl@-t8X&rcrj?59Xtrk#=YUJ=dMmmq{qDvwTEx3f8>mY3r zlt=Pf>?{?q8-s!pc`QhqYLSab;}U8KT}mj=WWRYG}ev#(#2lC3Y6w%L69#d5-bT4c2d}@(a6T5u^H*|wyzS> zIL->Xc`lKx5}S`E)Kv{(9vO7tvLFnSvbWR|W<>a8R(--$^0sLZ)JO-;;J9#~Zdd$h g8x*9@lrr+^AH8D$g%Bu!ZU6uP07*qoM6N<$f-&Pq{{R30 literal 0 HcmV?d00001 diff --git a/rotateLeft1.png b/rotateLeft1.png new file mode 100644 index 0000000000000000000000000000000000000000..922496343c9b7348fed9764e206cac015af02308 GIT binary patch literal 450 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3EX7WqAsj$Z!;#Vf2?- zqae&Edv@0fAVadmHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_nlZPZ!4! zkIuJOt-V+r1&)23zetU19lyvyQ6BMw+!7jTYE{hdAB%?6%yqosEFrm~vyorGCG5I` z!9>#+ax$su=~r&%{4+RX5Lj#6-1ceKhh5JkC68M@KeqPJ-O_8;2?{GN%~or&RUw%lyDHi<##n#T3-3 zIa@lVM|9nvJLg8r>L2rdoj>-zDcDR*^`twhSW@Yo3w^L|dtK{C^(uJvdkE4u?UOUY+{}>UlUEuPKgxLRgQZ>F`8w-?xlF+DX7F_Nb6Mw<&;$T%>9xiH literal 0 HcmV?d00001 diff --git a/rotateRight.png b/rotateRight.png new file mode 100644 index 0000000000000000000000000000000000000000..3e008dc9f94ab9e85d4e7ed9d892663dd6fc697b GIT binary patch literal 738 zcmV<80v-K{P)IR6!I5@ZTnDBhi?c2tlw=G%Df?BBEA;*jt*y zh}QlIY9oRe8#_%3ZTtspjM_y+BZwd|G1`bC1mgo^-VckJC3kN&yPM4=9}IhEcIJF% z=AJp{yE&DSM+^4iEKXt%w#NU*@Ch&R1RpSp*(z+8L=O9K4X-hU#R_d2Z*c<$QCAD` zc?@Bu3c_@o#b;bz4Pg_yOBh?gIKE*7gRzaov43$+dfnY`PXRuSa0SM7Mlp^j;dR7Crp(kZ?2+yJu^1_u#MsL5`cA7>bo+}Q}`S`3& zxTncL>d1h!fCKohc;}0CXcsQKIQUq|K3QQ-$YjfKGvUcNx-ukf6#{8aYR7pY_ZIL6 z?=m875dw6V@cN377ZQE}6Zn!5aXoIu-;$WMbOeLtCT5A6sz+ADKN9^7D&*8GiANIs zjo7hX#4i&4MjTo%;yxjn6Lx%Bb(Lt%Xv2V|$R5M7^&qZSx=3u!z;?x3Q>rZXSX)`# zjw_XH~?ldOV=IPnb!(!c{@pT%c4+ZMugPcCmj8#aLg;>KU?1V UT6}_D=Kufz07*qoM6N<$f&y7XIsgCw literal 0 HcmV?d00001 diff --git a/rotateRight1.png b/rotateRight1.png new file mode 100644 index 0000000000000000000000000000000000000000..a7de8622c26497031a13c5d22bed69dd6fb922cf GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3EX7WqAsj$Z!;#Vf2?- zqae&Edv@0fAVadmHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_nk=PZ!4! zkIuJOz4cfEMUH)x50p)OaM!6t!e}Fd(6W}J5pAj+3U3aau=DZp2uQmm$E+;K_Fc_t z%d6G9l0yDJSvF}`{JEd+Udv=w2Z zmTQaTx5Z}1tMohDTaC|_#=R_GcA@K}c~{HdSf;bdE*6)c>)qn1&fqZrV|(z$`Zb*I z%7wSgOej(8JE-*0L4V!8k7|3KtL(}x)vlZFbzAdfyngu7C3ef%#8&&*^hO_LP2o|m zIFRLa!ARBk`{tB9uBor2taFQ}8t6_r>gW52vwemSZ. * +******************************************************************************** +""" + +from win32com.client import Dispatch +from tkinter.messagebox import * +from tkinter import * +import pythoncom +import sys +import time +import os +import shutil +import zipfile +import hashlib +import subprocess +import psutil + +import critical # critical.py +import ihm # ihm.py +import logger # logger.py +import globs # globs.py +import downloader # downloader.py +import lang # lang.py + +UPDATE_IS_MADE = False +UPATH = ' ' + +launcherWindow = ihm.launcherWindowCur + +def createShortcut(path, target='', wDir='', icon=''): + """ + Creates a shortcut for a program or an internet link + """ + ext = path[-3:] + if ext == 'url': + shortcut = file(path, 'w') + shortcut.write('[InternetShortcut]\n') + shortcut.write('URL=%s' % target) + shortcut.close() + else: + shell = Dispatch('WScript.Shell') + shortcut = shell.CreateShortCut(shell.SpecialFolders("Desktop") + r"\{}".format(path)) + shortcut.Targetpath = target + shortcut.WorkingDirectory = wDir + if icon == '': + pass + else: + shortcut.IconLocation = icon + shortcut.save() + +def spawnProcess(args, cd): + """ + Creates a new independant process. Used to launch a new version after update + """ + subprocess.Popen(args, close_fds=True, cwd=cd, creationflags=subprocess.DETACHED_PROCESS) + +def exitProcess(arg): + """ + Forcefully quits a process. Used to help deletion of an old version or to quit properly + """ + # Quit totally without remain in memory + for process in psutil.process_iter(): + if process.pid == os.getpid(): + process.terminate() + sys.exit(arg) + +def updateChannel(choice): + if choice == "Beta": + with open(globs.CNIRUrlConfig, 'w') as (configFile): + configFile.write("{}\n0\n0".format(globs.CNIRBetaURL)) + else: + with open(globs.CNIRUrlConfig, 'w') as (configFile): + configFile.write("{}\n0\n0".format(globs.CNIRDefaultURL)) + +def getLatestVersion(credentials): + """ + Returns the latest version of the software + """ + + finalver, finalurl, finalchecksum = [None]*3 + + # Global Handlers + logfile = logger.logCur + + + # First retrieving the urls ! + while True: + try: + # Open the config file + logfile.printdbg('Reading urlconf.ig') + with open(globs.CNIRUrlConfig, 'r') as (configFile): + try: + # Reading it + reading = configFile.read() + # Parsing it + urlparsed = reading.split("\n") + break + + except Exception as e: + raise IOError(str(e)) + + except FileNotFoundError: + logfile.printdbg('Recreate urlconf.ig') + # Recreating the url file + try: + os.mkdir(globs.CNIRFolder + '\\config') + except: + pass + with open(globs.CNIRUrlConfig, 'w') as (configFile): + configFile.write("{}\n0\n0".format(globs.CNIRDefaultURL)) + + # Getting the list of versions of the software + logfile.printdbg('Retrieving the software versions') + try: + os.mkdir(globs.CNIRFolder + '\\downloads') + except: + pass + getTheVersions = downloader.newdownload(credentials, urlparsed[0], globs.CNIRVerStock, "the version repository").download() + + logfile.printdbg('Parsing the software versions') + with open(globs.CNIRVerStock) as versionsFile: + versionsTab = versionsFile.read().split("\n")[1].split("||") + logfile.printdbg('Versions retrieved : {}'.format(versionsTab)) + # Choose the newer + finalver = globs.version.copy() + for entry in versionsTab: + if not entry: + continue + verstr, url, checksum = entry.split("|") + # Calculating sum considering we can have 99 sub versions + ver = verstr.split(".") + ver = [int(i) for i in ver] + finalsum = finalver[2] + finalver[1]*100 + finalver[0]*100*100 + sum = ver[2] + ver[1]*100 + ver[0]*100*100 + # Make a statement + if sum >= finalsum: + finalver = ver.copy() + finalurl = url + finalchecksum = checksum + else: + finalurl = url + finalchecksum = None + + return (finalver, finalurl, finalchecksum) + + +def tessInstall(PATH, credentials): + # Global Handlers + logfile = logger.logCur + + + # Verifying that Tesseract is installed + if not os.path.exists(PATH + '\\Tesseract-OCR5\\'): + finalver, finalurl, finalchecksum = getLatestVersion(credentials) + + if finalurl == None: + logfile.printerr('Unable to get the Tesseract url') + return False + + tesseracturl = finalurl.replace("CNIRevelator.zip", "tesseract_5.zip") + + # WE ASSUME THAT THE MAIN FILE IS CNIRevelator.zip AND THAT THE TESSERACT PACKAGE IS tesseract_5.zip + logfile.printdbg('Preparing download of Tesseract OCR 4...') + getTesseract = downloader.newdownload(credentials, tesseracturl, PATH + '\\downloads\\TsrtPackage.zip', "Tesseract 5 OCR Module").download() + + try: + # CHECKSUM + BUF_SIZE = 65536 # lets read stuff in 64kb chunks! + + sha1 = hashlib.sha1() + + with open(globs.CNIRFolder + '\\downloads\\TsrtPackage.zip', 'rb') as f: + while True: + data = f.read(BUF_SIZE) + if not data: + break + sha1.update(data) + + check = sha1.hexdigest() + logfile.printdbg("SHA1: {0}".format(check)) + + if not check == globs.CNIRTesserHash: + logfile.printerr("Checksum error") + return False + + # Unzip Tesseract + logfile.printdbg("Unzipping the package") + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Installing the updates"]) + zip_ref = zipfile.ZipFile(PATH + '\\downloads\\TsrtPackage.zip', 'r') + zip_ref.extractall(PATH) + zip_ref.close() + # Cleanup + try: + os.remove(UPATH + '\\downloads\\TsrtPackage.zip') + except: + pass + return True + + except: + return False + else: + return True + +## Main Batch Function +def batch(credentials): + # Global Handlers + logfile = logger.logCur + + + # Get the latest version of CNIRevelator + finalver, finalurl, finalchecksum = getLatestVersion(credentials) + + if finalver == globs.version: + logfile.printdbg('The software is already the newer version') + return True + + logfile.printdbg('Preparing download for the new version') + + getTheUpdate = downloader.newdownload(credentials, finalurl, globs.CNIRFolder + '\\downloads\\CNIPackage.zip', "CNIRevelator {}.{}.{}".format(finalver[0], finalver[1], finalver[2])).download() + + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Verifying download..."]) + + # CHECKSUM + BUF_SIZE = 65536 # lets read stuff in 64kb chunks! + + sha1 = hashlib.sha1() + + with open(globs.CNIRFolder + '\\downloads\\CNIPackage.zip', 'rb') as f: + while True: + data = f.read(BUF_SIZE) + if not data: + break + sha1.update(data) + + check = sha1.hexdigest() + logfile.printdbg("SHA1: {0}".format(check)) + + if not check == finalchecksum: + logfile.printerr("Checksum error") + return False + + # And now prepare install + global UPATH + UPATH = globs.CNIRFolder + '\\..\\CNIRevelator' + "{}.{}.{}".format(finalver[0], finalver[1], finalver[2]) + logfile.printdbg("Make place") + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Preparing installation..."]) + # Cleanup + try: + shutil.rmtree(UPATH + 'temp') + except Exception as e: + logfile.printdbg('Unable to cleanup : ' +str(e)) + try: + shutil.rmtree(UPATH) + except Exception as e: + logfile.printdbg('Unable to cleanup : ' +str(e)) + # Unzip + logfile.printdbg("Unzipping the package") + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Installing the updates"]) + zip_ref = zipfile.ZipFile(globs.CNIRFolder + '\\downloads\\CNIPackage.zip', 'r') + zip_ref.extractall(UPATH + "temp") + zip_ref.close() + + # Move to the right place + shutil.copytree(UPATH + 'temp\\CNIRevelator', UPATH) + shutil.rmtree(UPATH + 'temp') + logfile.printdbg('Extracted :' + UPATH + '\\CNIRevelator.exe') + + # Make a shortcut + # hide main window + pythoncom.CoInitialize() + root = Tk() + root.withdraw() + res = askquestion(lang.all[globs.CNIRlang]["Shortcut creation"], lang.all[globs.CNIRlang]["Would you like to create/update the shortcut for CNIRevelator on your desktop ?"]) + if res == "yes": + createShortcut("CNIRevelator.lnk", UPATH + '\\CNIRevelator.exe', UPATH) + root.destroy() + + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Success !"]) + + # Cleanup + try: + os.remove(globs.CNIRFolder + '\\downloads\\CNIPackage.zip') + except: + pass + # Time to quit + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Launching the new version..."]) + global UPDATE_IS_MADE + UPDATE_IS_MADE = True + return True + +## Main Function +def umain(): + + # Global Handlers + logfile = logger.logCur + + + credentials = downloader.newcredentials() + + if not credentials.valid: + logfile.printerr("Credentials Error. No effective update !") + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Credentials Error. No effective update !"]) + time.sleep(2) + launcherWindow.exit() + return 0 + + # Cleaner for the old version if detected + if len(sys.argv) > 2 and str(sys.argv[1]) == "DELETE": + globs.CNIRNewVersion = True + launcherWindow.printmsg(lang.all[globs.CNIRlang]["Deleting old version"]) + logfile.printdbg("Old install detected : {}".format(sys.argv[1])) + while os.path.exists(str(sys.argv[2])): + try: + shutil.rmtree(str(sys.argv[2])) + except Exception as e: + logfile.printerr(str(e)) + logfile.printdbg('Trying stop the process !') + launcherWindow.printmsg('Fail :{}'.format(e)) + try: + for process in psutil.process_iter(): + if process.name() == 'CNIRevelator.exe': + logfile.printdbg('Process found. Command line: {}'.format(process.cmdline())) + if process.pid == os.getpid(): + logfile.printdbg("Don't touch us ! {} = {}".format(process.pid, os.getpid())) + else: + logfile.printdbg('Terminating process !') + process.terminate() + shutil.rmtree(str(sys.argv[2])) + break + except Exception as e: + logfile.printerr(str(e)) + launcherWindow.printmsg('Fail :{}'.format(e)) + launcherWindow.printmsg(lang.all[globs.CNIRlang]['Starting...']) + + # check we want open a file + elif len(sys.argv) > 1 and str(sys.argv[1]) != "DELETE": + globs.CNIROpenFile = True + print(sys.argv) + + try: + try: + # EXECUTING THE UPDATE BATCH + success = batch(credentials) + except Exception as e: + critical.crashCNIR() + launcherWindow.printmsg('ERROR : ' + str(e)) + time.sleep(3) + launcherWindow.exit() + return 1 + + if success: + logfile.printdbg("Software is up-to-date !") + launcherWindow.printmsg('Software is up-to-date !') + else: + logfile.printerr("An error occured. No effective update !") + launcherWindow.printmsg(lang.all[globs.CNIRlang]['An error occured. No effective update !']) + time.sleep(2) + launcherWindow.exit() + return 0 + + if UPDATE_IS_MADE: + launcherWindow.exit() + return 0 + except: + critical.crashCNIR() + launcherWindow.exit() + sys.exit(2) + return 2 + + try: + try: + # INSTALLING TESSERACT OCR + success = tessInstall(globs.CNIRFolder, credentials) + except Exception as e: + critical.crashCNIR() + launcherWindow.printmsg('ERROR : ' + str(e)) + time.sleep(3) + launcherWindow.exit() + return 1 + + if success: + logfile.printdbg("Software is up-to-date !") + launcherWindow.printmsg(lang.all[globs.CNIRlang]['Software is up-to-date !']) + else: + logfile.printerr("An error occured. No effective update !") + launcherWindow.printmsg(lang.all[globs.CNIRlang]['An error occured. No effective update !']) + time.sleep(2) + launcherWindow.exit() + return 0 + + except: + critical.crashCNIR() + launcherWindow.exit() + sys.exit(2) + return 2 + + time.sleep(2) + launcherWindow.exit() + return 0 diff --git a/zoomIn.png b/zoomIn.png new file mode 100644 index 0000000000000000000000000000000000000000..959aa411bfbedc6d86c2b090ed0864572058fbb5 GIT binary patch literal 772 zcmV+f1N;1mP)quFCq~#gd|DGkReHujFDs>Gvs0IYr1Ejy^qWNzMR^>?_TS-*YI8I z6r`hwt{90~7=s4X;2j>~JPzOno>j2b1cj96wOEKb3Fjhi;t4*XExKb^a-mPyg*|v# z%>{nNARNVY%tJSnQn; zU_`mYlBmL0_*~|^H`a)B5OP_E&3KzUyMoo4D%R`7X4#%D;hXf(y%7c1C?>VZ7hJ~E z)NkfxWWZq>Y$F?K60t#hhN4!7~bXFgJ^s6Z3HrJ@b&4i(x;s znNEj@k60{y(fAEDVnXd#LEI~#AhwZnSSLFjzo8D>M4wKm((x2gOdqARP!>93J?;rb z8jKE_7G5hq1!b-1@735MQZ`nFP^TRfF&zs;&h9I}>Y+J)9P4c`US`5$&-O(=a>Xwir5KCoah+PX830un7l+gtjQUVynpi z-)^sejc(#k!U-uG$^S$z>=xBu->L;2zrh*Iz@zVW8F{HLVEN1d0000EuGYds#dEX z2s%P7MMNngQVEfuL@q*tSW*c>D$ztq$C99;_R`u~w6vC5hGL?+Os6fkt(p7!aBkbt zx!1in{F66t-t+#?^PKbSL1`pt#7Im=3!2dnf8ZB0URe81ETg+)hrE0-#V}s1IF(eYgT>$)Spl~h z-xuWfL4pqPCJhw`s|JUNu3276aVP%75fupQ5zX3Nh8M(Qv_fCs??eW-ij5&DMJk{k z7hqr+-eA03;QlK{ivBpa9I-;2kGECWC~g#c>WKfSbOhQlO=X2fz1R|86o-Lo5inXz zwFgugtc{p0M&j5i6;HxSY!$m**;DS0fw&!K4IJc|Gp1R%M}q^fAtaVOmbJRT%ba^aD! z4QthPtv6Gm=n2Mry3lK*Pw?vU&ph?@5(|G@t>f@xjNO)VWJkNYmhp~l8Y;JVfyqnXGOuN# zd`*p$$4&O?hYKvmFkD}_M4!ns6KDwCW?h)RzrEi%#Vzj66qeLl28Ws&)NZ0}6X?6VvH=OclrDon}uqc6mJwmSni4p`R;j zSlh`ny-#=Q4E1{im!qRIXFjBa;x9wcV`?LXeZ}ijkgfAtm4Q&u-L(nc|S~q+TOaf>57x zlvEOeG|d- z?D4|%tmeSI^5*0dX~K-O+x5xszO%opaE0KZH1!4K6S>*9S~mSQlajnyJvFAM!MfTW zGgH3lT(TH4?g|yKi$*|Tg172EPnkPEulws`S8h~00000NkvXX Hu0mjf*Sb`T literal 0 HcmV?d00001 diff --git a/zoomIn50.png b/zoomIn50.png new file mode 100644 index 0000000000000000000000000000000000000000..21acbdcda6dadcb34bbb5ce0b248e3e60029af9c GIT binary patch literal 1217 zcmV;y1U~zTP)Ud6i1A+Fbp?h1gcSmzp(?~Vi`7JPdl5Pw)ZG?BLmI z)M6F-bmU+kEXRxJ*+Hc(7%aH^L%|az5n~~~5nTIU&>L$oNAV{*j&azEk!1)jDGb*X zln*IJh*60etSpe*LsvYEUL|-NgP7c9tP5 zKIPjUHVTnGu$Z8;u^0c8A*>_Vj>EX5t<9_uh5K==jr~D*Qg{wRx$KQaI8<2MfXAF( zu?`euwwcpeB#OK4pb&5^#iU2Ug7w&4Slc3$%u~@)j*}v?jTH(FpHMYvpOH2aE?vdH0mrPHKR=tSL0 z@Xi$a^u!W8m*H1TC`Bw2x1bIK${{}@H2VwxqtXimBc>`oXq<#9p+kMBo#NgOB4HX? zjpvkpj+4+EON2bVs)LHh3c++lkrZl;wp`I2kE5nrPjB8ZQEk^=C#qVW125-nLZ)V_ z!DsYvb;CvzHm`TxfkrPR&H7-p5Lop{+fv+CZ{3gkvHaYN+#hAl{JCCb6v=0@4mBOt zfY*BU)vrqz6LxJ=RXs%Auu*+7t%{&x9_P6n-7YSwQJc5Q3WxZ@YiX=32;YYp!=Sku?FZmj>z(ksSGGg;$?47YOA zYF`UM`XE*mj5)>PRChddN6vIK#>`6-Ymin{ITn-LHp7N2Yx7zAUdq`E7mxCm$hO_v z{Ze>?uVmD8uF?g63AJGW-otIeU~nAq-u+gjsSOp($tI?uvH6JCg~!>At(mM@Y1T6_ zi>EwlI7E@&qE?n<9E<1_SE?wi9KbzThi8QndP4C#3mk4fYJS9}aqQM+<}ZHQ!NSSa z4!f^{85!vyeN{! zse7jJmTLSB&j7J@xIuCyB>9ACgaaMcSfw+dSXCmja`^56qfmT zAkIW4rHT4RR_#lDoJ79Bbcpi?B;+D$qr~%KAM8J9F>H=~8nG~qEzDD!(%i(Nx?IuD fwG%Ut!DasfMX5d&IBhQ<00000NkvXXu0mjff^j%1 literal 0 HcmV?d00001 diff --git a/zoomOut.png b/zoomOut.png new file mode 100644 index 0000000000000000000000000000000000000000..0eca4be3d8530c66b778c182f961b882b90949ed GIT binary patch literal 735 zcmV<50wDc~P)6|e z>}*+SHkOnP!a^bo7E1?FNb`r!*+;TBHeA>Nl5s{^@|`-8C#E0dVpXu>;uM}LgMjO2m7 z;|Px9W0nW_lgT)bMy$q2RHVfA#^~fBn{ZIfQf4OMB2Hjv#=c{578g*JrDGM&U>AmD z6fIDLV>l^hqEqtNjT>TWyTb@!y3Nv;=r?tEj@oXD7Fd*sPfQEQqp#R5=Y)I9K?9y( zd8wdLxQh8@5-qSEH`5yP!bmxh5Xwn|aC&u`a1B17t<1vB_==gu8Y_egKjBA__Ho#z zw9^|;4;SiOYjyv#`JEAPQz1dkX~r~ML%&uofPMCR&t^mxrrSZkfrko?8jX!$SAy6 zIQUy~otZ({DUQi{;r4+#8n2hwhBL(*HYf3W@d)#jnCi5KJeFduShJU6{o5CN)06K; z92TcsZ)_FQz6tlT2p6andAb%0Fm$t_Th@ISFc@Wr1+C?NlHcXKdHu1vF=s9dQiAk)a}dg`WJL&ail7f RrCtC4002ovPDHLkV1h|nq)gvBe2eWET8?8ie#Cpu z|D9#=71kpjsMD+1oOiwu3>LEX{>eEsiOp7zT);g4hi2Z!yLM?93Zg5*;`sh|M|3=67_cfOA%8uR%o!+A+Vt zTAKnV&Z0}<7V5$`imOq3k+q)q{i0D2{Qm=gV`8Dj%W+yzu1ca_sGFkN{TuGCF!96K zRLUq3^tDomunSG43@^dIxT8YE58wc*24eh>F!=i||1xle(8JTparyw?3mG^cjKVK? zrCi4>*5GGBO3T3%A!Da;Rk_53!dQw^cpaCOupcL=+X+01XM}s$m8D1uVSK|pYisI^ z&SZ3U9JVjF+KPYv(uSHTcbPqAtchutsFmh;1rb{%QA?Nb}2!|HWz%b7O!F}2w$dpCaW*|zKjKiQWcDB8ycKxt1k9%}0#VNZN;`zk2D$9N{Zuygs=l~x_>}p9YIukB(FiMR$`_~8@?1z{z7ViJmY!ddh~HXlCt6OjsWyBBY;0L# jZ4qLEF5H>l#Pt6FC))G-mKAi`00000NkvXXu0mjfM8)CJ literal 0 HcmV?d00001 diff --git a/zoomOut50.png b/zoomOut50.png new file mode 100644 index 0000000000000000000000000000000000000000..f753234c5699ad9fb3ebab0a2c670a871bb21173 GIT binary patch literal 1147 zcmV->1cdvEP))KwJ6@z4K1Q%VtJsj^84E~p6jLO>#>q8LqB zL?1K|eIN=vxIL)Rs7*RPxL{O_k!Tvlia{PwBko%i2t?5!vL&dXL@g*7N{h5~n9kpa zJ8X5PQ0VVvGXH!2_j~TS_nve9ks>6&HZt=%TkpbS~E6F{QWYsII|Ij6Xx@ z5lzDPxUiH#jB?Dtrv>Bg&?fwhnVlI8#wuLVP0%B{QwVuc#pqx7{j;0F8*!d++KYmR zVINw$8T>n1F{apHS~!KxI9z1B3eV{LYW#={_IG%GK*(qVOM%;kl^0X9aK$P+0skVT z(T<7=^R|})?-usC9RGG0Z^Db6Ks(a_^gu(g!5F*H7iXO&pp?!>PvL461@9Ni+@;+J zjfASTRXDgM!E10AQr!d|fa|d2WQJq$D{k#3@GPvv&{HvfSV(iD&k%YUe#0F~HyS6X z9Ipti%*fLKMnaKUhlS|fm6y-KYeKras*B($!eMH|wI?&~c$ix7HAbMOXNAEdM%z8= z3S|?gDw|j>yj5lP%5qOj;$WNY`lh%qt%VOMyf6&ha8?nE$VTz zaAQsc&#(1_M9UwIo~^3X2buPE%VcedB1uJWjgzL^*4X6ZN?bCB!bSKHhg zuf%(7Pv{r3{m3LqUPBa{&|y&ra5omiZ>irWl_26 zB=$=}9yheJIz-l{IB(vZ8Xu<8$|J7EBPG|(9-#+YD1`mKIF&Ld;j8^yt&{aaM4x=S zab