diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b329234 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.pyc +*.log +*.spec + +build/* +dist/* diff --git a/CNI_GLOBALVAR.py b/CNI_GLOBALVAR.py deleted file mode 100644 index 67c331a..0000000 --- a/CNI_GLOBALVAR.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -******************************************************************************** - *** Projet CNI_Revelator *** - - GNU GPL * 07/2018 - - Adrien Bourmault - - VARIABLES - -******************************************************************************** -""" -CST_REV = "8" -CST_VERTITLE = "2.2" -CST_TAB_VER = ["2","2","1"] -CST_VER = "{0}.{1}.{2}".format(CST_TAB_VER[0], CST_TAB_VER[1], CST_TAB_VER[2]) -CST_TYPE = "Final Release" -CST_NAME = "CNIRevelator" -CST_TITLE = CST_NAME + " " + CST_VER + " - GNU/GPL Licensing 2018" -CST_MARK = CST_NAME + " " + CST_TYPE + " " + CST_VER + " - by NeoX, GNU/GPL Licensing 2018" -CST_SUM_VER = int(CST_TAB_VER[0])*100 + int(CST_TAB_VER[1])*10 + int(CST_TAB_VER[2]) -CST_LINK = -CST_COLOR = "#003380" - - -import base64 -import hashlib -from Crypto import Random -from Crypto.Cipher import AES -from tkinter import * -from tkinter.messagebox import * -from tkinter import filedialog -from tkinter import ttk as ttk -import os -import time -import threading -import sys -import urllib.request as urllib2 -import urllib.error as URLExcept -import random -from pypac import PACSession -from requests.auth import HTTPProxyAuth -import subprocess -from datetime import datetime -from PIL import Image, ImageFont, ImageDraw, ImageTk, ImageEnhance, ImageFilter -import math -import warnings -import string - -CST_FOLDER = os.getenv('APPDATA') + "/CNIRevelator/" \ No newline at end of file diff --git a/CNI_Revelator.py b/CNI_Revelator.py deleted file mode 100644 index 3a79a24..0000000 --- a/CNI_Revelator.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -******************************************************************************** - *** Projet CNI_Revelator *** - - GNU GPL * 07/2018 - - Adrien Bourmault - - main - -******************************************************************************** -""" - -###IMPORTS GLOBAUX -from CNI_GLOBALVAR import * - - -##LOGGING - -try: - os.remove("error.log") - os.remove("conf.ig") -except: - pass -import logging - -CST_NIVEAU_LOG = logging.ERROR - -from logging.handlers import RotatingFileHandler - -# création de l'objet logger qui va nous servir à écrire dans les logs -logger = logging.getLogger() -# on met le niveau du logger à DEBUG, comme ça il écrit tout -logger.setLevel(CST_NIVEAU_LOG) - -# création d'un formateur qui va ajouter le temps, le niveau -# de chaque message quand on écrira un message dans le log -formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(message)s') -# création d'un handler qui va rediriger une écriture du log vers -# un fichier en mode 'append', avec 1 backup et une taille max de 1Mo -file_handler = RotatingFileHandler('error.log', 'a', 1000000, 1) -# on lui met le niveau sur DEBUG, on lui dit qu'il doit utiliser le formateur -# créé précédement et on ajoute ce handler au logger -file_handler.setLevel(CST_NIVEAU_LOG) -file_handler.setFormatter(formatter) -logger.addHandler(file_handler) - -##History - - - -### IMPORTS LOCAUX -from CNI_classes import * -from CNI_Update import * - -### FONCTION PRINCIPALE - - -def main(logger): - - logger.info("main() : " +"**** Creating App_main() ****") - main_w = App_main(logger) - - main_w.montext("* " + CST_NAME + " " + CST_VER + " " + CST_TYPE + " Revision " + CST_REV + " *\n") - - import CNI_pytesseract as pytesseract - - try: - - os.environ["PATH"] = CST_FOLDER + "Tesseract-OCR4\\" - os.environ["TESSDATA_PREFIX"] = CST_FOLDER + "Tesseract-OCR4\\tessdata" - tesser_version = pytesseract.get_tesseract_version() - - except Exception as e: - logger.error("main() : " +"**** ERROR WITH TESSERACT MODULE " + str(e) + " ****") - else: - text = "Tesseract version " + str(tesser_version) +" Licensed Apache 2004 successfully initiated\n" - main_w.montext(text) - - - main_w.montext("\n\nEntrez la première ligne de MRZ svp \n") - - - - logger.info("main() : " +"**** Launching App_main() ****") - main_w.mainloop() - logger.info("main() : " +"**** Ending App_main() ****") - - - -##Launcher - -logger.info("launcher : " + CST_NAME +" "+ CST_VER) -logger.info("launcher : " +"*****Hello World*****") - -logger.info("launcher : " +"*****Launching SoftUpdate()*****") -try: - Answer = SoftUpdate(logger) -except Exception as e: - logger.info("launcher : " +"*****FATAL ERROR*****" + str(e)) - os.abort() -logger.info("launcher : " +"*****Ending SoftUpdate()*****") - - -try: - if Answer == True: - logger.info("launcher : " +"*****Launching main()*****") - State = main(logger) -except Exception as e: - logger.info("launcher : " +"*****FATAL ERROR*****" + str(e)) - os.abort() - -logger.info("launcher : " +"*****Ending main()*****") -logger.info("launcher : " +"*****Goodbye!*****") - -handlers = logger.handlers[:] -for handler in handlers: - handler.close() - logger.removeHandler(handler) - - -if os.path.getsize("error.log") == 0: - try: - os.remove("error.log") - except: - raise(OSError) - os.abort() - -sys.exit(0) - - \ No newline at end of file diff --git a/CNI_Update.py b/CNI_Update.py deleted file mode 100644 index 416edab..0000000 --- a/CNI_Update.py +++ /dev/null @@ -1,568 +0,0 @@ -""" -******************************************************************************** - *** Projet CNI_Revelator *** - - GNU GPL * 07/2018 - - Adrien Bourmault - - UPDATE - -******************************************************************************** -""" - - -###IMPORTS GLOBAUX -from CNI_GLOBALVAR import * - - -###IMPORTS LOCAUX -from CNI_classes import * - - -###BEGIN - -def SoftUpdate(logger): #Fonction de mise à jour de l'application - - import zipfile - # zip_ref = zipfile.ZipFile(path_to_zip_file, 'r') - # zip_ref.extractall(directory_to_extract_to) - # zip_ref.close() - - logger.info("SoftUpdate() : " +"Verifying local data dir...") - if not os.path.exists(CST_FOLDER): - os.makedirs(CST_FOLDER) - logger.info("SoftUpdate() : " +"Data dir created !") - - logger.info("SoftUpdate() : " +"Looking for older version in dir...") - list = os.listdir('.') - - for file in list: - - if file.startswith("CNIRevelator_"): - - temp = ["0","0","0"] - ver = file[13:].split(".") - - - for i in range(len(ver)): - - - - if ver[i] != "exe" : - try: - temp[i] = ver[i] - except: - None - - - ver = temp.copy() - - try : - sum_ver = int(ver[0])*100 + int(ver[1])*10 + int(ver[2]) - - if sum_ver < CST_SUM_VER: - - os.remove(file) - logger.info("SoftUpdate() : " + "Removed old version : " + str(file) ) - - except: - - logger.error("SoftUpdate() : " +"Failing to remove old version : " + str(file) ) - - None - - - def updating(): #Fonction de lancement du thread de maj - - def updator(): #Fonction d'exécution de la maj - - logger.info("[updator() thread] : " + "Welcome !") - global ret - ret = 11 - canvas.itemconfigure(message, text="Recherche de mises-à-jour...") - p.configure(mode = "indeterminate", value = 0, maximum = 20) - p.start() - upwin.update() - logger.info("[updator() thread] : " + "Looking for updates...") - - try:#Essai par proxy - - def download(url, filename): - - try: # if identifiants disponibles dans un fichier - - logger.info("[download() thread] : " + "Trying getting credentials in the config file") - - with open(CST_FOLDER +'conf.ig', "rb") as config: - - AESObj = AESCipher("") - IPN, IPN_PASS = AESObj.decrypt(config.read()).split("||")[0:2] - logger.info("[download() thread] : " + "Got credentials !") - - session = PACSession(proxy_auth=HTTPProxyAuth(IPN, IPN_PASS)) - logger.info("[download() thread] : " + "Authenticated to proxy successfully") - - - except IOError as e: #Else : on vérifie les identifiants de proxy - - logger.error("[download() thread] : " + "False or absent credentials in the config file : " + str(e)) - - NoConnect = True - - while NoConnect: - - class LoginDialog(Toplevel): - global login - global key - - def __init__(self, parent): - super().__init__(parent) - self.title("Connexion") - # --------------------------------------- - Label(self, text="IPN : ").pack() - self.entry_login = Entry(self) - self.entry_login.insert(0, "") - self.entry_login.pack() - # --------------------------------------- - Label(self, text="Mot de passe : ").pack() - self.entry_pass = Entry(self, show='*') - self.entry_pass.insert(0, "") - self.entry_pass.pack() - # --------------------------------------- - Button(self, text="Connexion", command=self.connecti).pack() - self.resizable(width=False, height=False) - #taille souhaite de la fenetre - w = 150 - h = 110 - #pour centrer la fenetre - #taille de l'ecran - self.update() - ws = self.winfo_screenwidth() - hs = self.winfo_screenheight() - - if getattr( sys, 'frozen', False ) : - self.iconbitmap(sys._MEIPASS + "\id-card.ico\id-card.ico") - else: - self.iconbitmap("id-card.ico") - - upwin.update() - #calcul la position de la fenetre - x = (ws/2) - (w/2) - y = (hs/2) - (h/2) - #applique la taille et la position - self.geometry('%dx%d+%d+%d' % (w, h, x, y)) - - def connecti(self): - global login - global key - login = self.entry_login.get().strip() - key = self.entry_pass.get().strip() - self.destroy() - - session = PACSession() - - if session.get_pac() == None: - IPN = "" - IPN_PASS = "" - NoConnect = False - - break - - canvas.itemconfigure(message, text="En attente de connexion au serveur proxy...") - global login - global key - login = "" - key = "" - - result = LoginDialog(upwin) - result.transient(upwin) - result.grab_set() - upwin.wait_window(result) - - IPN = login - IPN_PASS = key - - session = PACSession(proxy_auth=HTTPProxyAuth(IPN, IPN_PASS)) - Ans = session.get("http://www.google.com") - - if str(Ans) == "": - canvas.itemconfigure(message, text="Identifiants erronés, accès refusé") - logger.info("[download() thread] : " + "407 Error") - time.sleep(1) - elif str(Ans) == "": - logger.info("[download() thread] : " + "Connection ok !") - NoConnect = False - else: - raise(IOError()) - - AESObj = AESCipher("") - - with open(CST_FOLDER +"conf.ig","wb+") as f: - logger.info("[download() thread] : " + "Saving credentials in encrypted config file") - f.write(AESObj.encrypt(IPN + "||" + IPN_PASS)) - - - - if IPN == "i005316": - canvas.itemconfigure(message, text="Bienvenue Thierry !") - elif IPN == "i020251": - canvas.itemconfigure(message, text="Bienvenue Samia !") - elif IPN == "i018410": - canvas.itemconfigure(message, text="Bienvenue Adrien !") - elif IPN == "i003067": - canvas.itemconfigure(message, text="Bienvenue Remy !") - elif IPN == "i018422": - canvas.itemconfigure(message, text="Bienvenue Eloise !") - time.sleep(1) - - try: - Prox_us = session.get_pac().find_proxy_for_url(CST_LINK,"neoxgroup.eu") - PROXY_USABLE = Prox_us[6:-1].split(";")[0] - proxy_server_url = IPN +":"+IPN_PASS+"@" + PROXY_USABLE - ph = urllib2.ProxyHandler( { 'http' : proxy_server_url } ) - auth = urllib2.ProxyBasicAuthHandler() - server= urllib2.build_opener( ph, auth, urllib2.HTTPHandler ) - urllib2.install_opener(server) - logger.info("[download() thread] : " + "Proxy connection initiated successfully") - - except: - - logger.info("[download() thread] : " + "Proxy connection not initiated") - - try: - urllib2.urlretrieve(url, filename) - return True - except Exception as e: - logger.error("[download() thread] : " + "HTTP ERROR " ) - return e - - ##updator() - logger.info("[updator() thread] : " + "Prepare downloading the version recap file...") - tempfile = CST_FOLDER + 'temp' + str(random.randint(11111,99999)) + ".cniu" - isOk = download(CST_LINK+"Version.txt", tempfile) - - if not isOk: - raise(isOk) - - urllib2.urlcleanup() - logger.info("[updator() thread] : " + "Opening version recap file...") - file_ver = open(tempfile, "r") - logger.info("[updator() thread] : " + "Reading version recap file...") - version = file_ver.read() - logger.info("[updator() thread] : " + "Closing version recap file...") - repert = version.split("|") - file_ver.close() - logger.info("[updator() thread] : " + "Deleting version recap file...") - os.remove(tempfile) - logger.info("[updator() thread] : " + "Parsing informations about version...") - final_f = "CNI_file" - final_ver = ['0','0','0'] - - for file in repert: - - if str.startswith(file,CST_NAME): #On prend les fichiers d'interêt du répertoire - - ver = file.replace(CST_NAME+"_","").split(".") #On ne garde que le numéro de version - - temp = ["0","0","0"] - - for i in range(len(ver)): - - temp[i] = ver[i] - - ver = temp.copy() - - sum_fver = int(final_ver[0])*100 + int(final_ver[1])*10 + int(final_ver[2]) - - sum_ver = int(ver[0])*100 + int(ver[1])*10 + int(ver[2]) - - - if sum_ver > sum_fver: - - final_ver = ver.copy() - final_f = file - - sum_ver = int(final_ver[0])*100 + int(final_ver[1])*10 + int(final_ver[2]) - - if final_f != "CNI_file" and (sum_ver > CST_SUM_VER): - #On a une maj - logger.info("[updator() thread] : " + "New version of CNIRevelator found !") - canvas.itemconfigure(message, text="Mise à jour disponible ! Préparation du téléchargement...") - logger.info("[updator() thread] : " + "Preparing download") - - with open(CST_FOLDER +'conf.ig', "rb") as config: - - logger.info("[updator() thread] : " + "Reading credentials for proxy in config file...") - - AESObj = AESCipher("") - IPN, IPN_PASS = AESObj.decrypt(config.read()).split("||")[0:2] - - session = PACSession(proxy_auth=HTTPProxyAuth(IPN, IPN_PASS)) - - try: - - Prox_us = session.get_pac().find_proxy_for_url(CST_LINK,"neoxgroup.eu") - PROXY_USABLE = Prox_us[6:-1].split(";")[0] - proxy_server_url = IPN +":"+IPN_PASS+"@" + PROXY_USABLE - ph = urllib2.ProxyHandler( { 'http' : proxy_server_url } ) - auth = urllib2.ProxyBasicAuthHandler() - server= urllib2.build_opener( ph, auth, urllib2.HTTPHandler ) - logger.info("[updator() thread] : " + "Connection to the proxy initiated successfully !") - - - except: - - canvas.itemconfigure(message, text="Téléchargement en connexion directe...") - server= urllib2.build_opener() - logger.info("[updator() thread] : " + "Direct connection initiated successfully") - - logger.info("[updator() thread] : " + "Launching download of " + final_f) - Statut = Download( CST_LINK + final_f, final_f, final_f, server, p, canvas, message, logger ) - - if Statut.success: - - try: - os.rename(final_f, final_f + ".exe") - except IOError: - logger.error("[updator() thread] : " + "Unable to rename the file ! Wait 3 sec and retry") - time.sleep(3) - try: - os.rename(final_f, final_f + ".exe") - except IOError: - logger.critical("[updator() thread] : " + "Unable to rename the file !") - - else: - canvas.itemconfigure(message, text="Téléchargement terminé ! Préparation du lancement...") - logger.info("[updator() thread] : " + "Download of " + final_f + "finished successfully") - p.configure(mode = "indeterminate", value = 0, maximum = 20) - p.start() - time.sleep(1) - logger.info("[updator() thread] : " + "Launching " + final_f) - try: - proc = subprocess.Popen(final_f + ".exe", shell=False,stdin=None, stdout=None, stderr=None, close_fds=True) - except: - logger.error("[updator() thread] : " + "Unable to start the new version ! Wait 3 sec and retry") - time.sleep(3) - try: - proc = subprocess.Popen(final_f + ".exe", shell=False,stdin=None, stdout=None, stderr=None, close_fds=True) - except Exception as e: - logger.critical("[updator() thread] : " + "Unable to start the new version ! Stopping : " + str(e)) - showerror("Erreur d'appel de procédure distante", "Le lancement du nouveau programme a échoué, vous devez le lancer manuellement une fois cette fenêtre fermée") - ret = 12 - else: - canvas.itemconfigure(message, text="Echec de la mise à jour : Erreur HTTP. Préparation du lancement...") - logger.error("[updator() thread] : " + "Update has failed with HTTP error") - time.sleep(1) - - - - else: - canvas.itemconfigure(message, text="Logiciel déjà à jour. Préparation du lancement...") - logger.info("[updator() thread] : " + "CNIRevelator is up to date !") - time.sleep(1) - ret = 11 - - - #Tesseract - if os.path.exists(CST_FOLDER + "Tesseract-OCR4\\tesseract.exe"): - os.environ["PATH"] = CST_FOLDER + "Tesseract-OCR4\\" - os.environ["TESSDATA_PREFIX"] = CST_FOLDER + "Tesseract-OCR4\\tessdata" - - - else: - - final_f = "tesseract_4" - - #On a une maj - logger.info("[updator() thread] : " + "Downloading tesseract 4 !") - canvas.itemconfigure(message, text="Mise à jour du module OCR ! Préparation du téléchargement...") - logger.info("[updator() thread] : " + "Preparing download") - - with open(CST_FOLDER +'conf.ig', "rb") as config: - - logger.info("[updator() thread] : " + "Reading credentials for proxy in config file...") - - AESObj = AESCipher("") - IPN, IPN_PASS = AESObj.decrypt(config.read()).split("||")[0:2] - - session = PACSession(proxy_auth=HTTPProxyAuth(IPN, IPN_PASS)) - - try: - - Prox_us = session.get_pac().find_proxy_for_url(CST_LINK,"neoxgroup.eu") - PROXY_USABLE = Prox_us[6:-1].split(";")[0] - proxy_server_url = IPN +":"+IPN_PASS+"@" + PROXY_USABLE - ph = urllib2.ProxyHandler( { 'http' : proxy_server_url } ) - auth = urllib2.ProxyBasicAuthHandler() - server= urllib2.build_opener( ph, auth, urllib2.HTTPHandler ) - logger.info("[updator() thread] : " + "Connection to the proxy initiated successfully !") - - - except: - - canvas.itemconfigure(message, text="Téléchargement en connexion directe...") - server= urllib2.build_opener() - logger.info("[updator() thread] : " + "Direct connection initiated successfully") - - logger.info("[updator() thread] : " + "Launching download of " + final_f) - - Statut = Download( CST_LINK + final_f, CST_FOLDER + final_f, final_f, server, p, canvas, message, logger ) - - if Statut.success: - - canvas.itemconfigure(message, text="Téléchargement terminé ! Installation...") - logger.info("[updator() thread] : " + "Download of " + final_f + "finished successfully") - p.configure(mode = "indeterminate", value = 0, maximum = 20) - p.start() - - try: - zip_ref = zipfile.ZipFile(CST_FOLDER + final_f, 'r') - zip_ref.extractall(CST_FOLDER) - zip_ref.close() - os.environ["PATH"] = CST_FOLDER + "Tesseract-OCR4\\" - os.environ["TESSDATA_PREFIX"] = CST_FOLDER + "Tesseract-OCR4\\tessdata" - canvas.itemconfigure(message, text="Installation terminée !") - - - - except: - logger.error("[updator() thread] : " + "Unable to install the module. Wait and retry") - time.sleep(3) - try: - zip_ref = zipfile.ZipFile(CST_FOLDER + final_f, 'r') - zip_ref.extractall(CST_FOLDER) - zip_ref.close() - os.environ["PATH"] = CST_FOLDER + "Tesseract-OCR4\\" - os.environ["TESSDATA_PREFIX"] = CST_FOLDER + "Tesseract-OCR4\\tessdata" - canvas.itemconfigure(message, text="Installation terminée !") - - - - except Exception as e: - logger.critical("[updator() thread] : " + "Unable to install the module ! Stopping : " + str(e)) - showerror("Erreur d'appel de procédure distante", "L'installation du module OCR a échoué, contactez le développeur.") - ret = 11 - - else: - - logger.critical("[updator() thread] : " + "Unable to download the module ! ") - showerror("Erreur de téléchargement", "L'installation du module OCR a échoué, merci d'indiquer le chemin d'accès au fichier tesseract_4 dans la fenêtre suivante") - - path = filedialog.askopenfilename(title = "Indiquez le chemin d'accès à tesseract_4...",filetypes = (("Tesseract_4","*.cni4"), ("Tesseract_4","*.cni4"))) - - if path != "": - - try: - canvas.itemconfigure(message, text="Installation...") - zip_ref = zipfile.ZipFile(path, 'r') - zip_ref.extractall(CST_FOLDER) - zip_ref.close() - logger.error("[updator() thread] : " + "Manual installation successed") - canvas.itemconfigure(message, text="Installation terminée !") - os.environ["PATH"] = CST_FOLDER + "Tesseract-OCR4\\" - os.environ["TESSDATA_PREFIX"] = CST_FOLDER + "Tesseract-OCR4\\tessdata" - - - except Exception as e: - - logger.error("[updator() thread] : " + "Manual installation has failed" + str(e) ) - showerror("Erreur de lecture", "Le module OCR n'a pas pu être installé, la saisie automatique de scans ne pourra donc fonctionner") - - - else: - - showerror("Opération annulée", "Le module OCR n'a été installé, la saisie automatique de scans ne pourra donc fonctionner") - - - except URLExcept.HTTPError as e: - - canvas.itemconfigure(message, text="Echec de la mise à jour : Erreur HTTP " + str(e.code) + " . Préparation du lancement...") - logger.error("[updator() thread] : " + "Update has failed with HTTP error" + str(e.code) ) - - if int(e.code) == 407: - showerror("Erreur 407", "Attention : le système de mise à jour automatique a fait face à une erreur 407, signifiant que la connexion au serveur proxy a été refusée. Vos identifiants vous seront redemandés au prochain démarrage. La mise à jour a échoué.") - logger.info("[updator() thread] : " + "Credential error. Deleting the config file...") - os.remove(CST_FOLDER +"conf.ig") - p.configure(mode = "indeterminate", value = 0, maximum = 20) - p.start() - time.sleep(3) - - except Exception as e: - - canvas.itemconfigure(message, text="Echec de la mise à jour. Préparation du lancement...") - logger.error("[updator() thread] : " + "Error from the updating system : " + str(e)) - p.configure(mode = "indeterminate", value = 0, maximum = 20) - p.start() - time.sleep(2) - - - p.stop() - upwin.destroy() - root.destroy() - return ret - - ##updating() - global ret - logger.info("updating() : " + "Launching updator() thread...") - threading.Thread(target=updator, daemon=True).start() - logger.info("updating() [Thread] : " + "Ending updator() thread") - return None - - ##SoftUpdate() - - global ret - ret = 11 - global upwin - root = Tk() - root.attributes('-alpha', 0.0) #For icon - #root.lower() - root.iconify() - - upwin = Toplevel(root) - upwin.overrideredirect(1) - upwin.configure(bg = CST_COLOR) - - upwin.resizable(width=False, height=False) - #taille souhaite de la fenetre - w = 600 - h = 300 - #pour centrer la fenetre - #taille de l'ecran - upwin.update() - canvas = Canvas(upwin, width=600, height=270, bg=CST_COLOR, highlightthickness=0) - pbar = Canvas(upwin, width=600, height=30, bg=CST_COLOR) - p = ttk.Progressbar(pbar, orient=HORIZONTAL, length=590, mode='determinate') - upwin.update() - ws = upwin.winfo_screenwidth() - hs = upwin.winfo_screenheight() - canvas.create_text(w/2, h/3, text=CST_NAME + " " + CST_VERTITLE, font="Calibri 30 bold", fill="white") - message = canvas.create_text(w/2, h/1.150, text=" ", font="Calibri 9", fill="white") - upwin.update() - #calcul la position de la fenetre - x = (ws/2) - (w/2) - y = (hs/2) - (h/2) - #applique la taille et la position - upwin.geometry('%dx%d+%d+%d' % (w, h, x, y)) - canvas.grid() - pbar.grid() - p.grid() - upwin.after(2000, updating) - - if getattr( sys, 'frozen', False ) : - root.iconbitmap(sys._MEIPASS + "\id-card.ico\id-card.ico") - else: - root.iconbitmap("id-card.ico") - - # On démarre la boucle Tkinter qui s'interrompt quand on ferme la fenêtre - logger.info("SoftUpdate() : " + "Entering upwin mainloop()") - upwin.protocol('WM_DELETE_WINDOW', lambda: root.destroy()) - upwin.mainloop() - logger.info("SoftUpdate() : " + "Exiting upwin mainloop()") - - if ret == 11: - logger.info("SoftUpdate() : " + "OK to start to main() normally !") - return True - else: - logger.info("SoftUpdate() : " + "Program will stop !") - return False \ No newline at end of file diff --git a/CNI_classes.py b/CNI_classes.py deleted file mode 100644 index fb48695..0000000 --- a/CNI_classes.py +++ /dev/null @@ -1,1551 +0,0 @@ -""" -******************************************************************************** - *** Projet CNI_Revelator *** - - GNU GPL * 07/2018 - - Adrien Bourmault - - CLASSES - -******************************************************************************** -""" - -###IMPORTS GLOBAUX -from CNI_GLOBALVAR import * - - - -###BEGIN - -class App_main(Tk): - - def __init__(self, logger): - - Tk.__init__(self) - self.initialize(logger) - - - def initialize(self, logger): - - self.logger = logger - - #variable mrz - self.PILE_ETAT = [] - self.MRZCHAR = "" - self.varnum = 10 - self.Score = [] - for type in MRZCODE.TYPES: - self.Score += [0] - - #taille de l'écran - - ws = self.winfo_screenwidth() - hs = self.winfo_screenheight() - - self.logger.info("App_main() : " + "Launching main window with resolution" + str(ws) +"x" + str(hs)) - - self.grid() #init de la grille - self.grid_columnconfigure(0,weight=1, minsize=(ws/2)* (1/3) ) - self.grid_columnconfigure(1,weight=1, minsize=(ws/2)* (1/3) ) - self.grid_columnconfigure(2,weight=1, minsize=(ws/2)* (1/3) ) - - self.grid_rowconfigure(0,weight=1, minsize=(hs/2)* (1/2) ) - self.grid_rowconfigure(1,weight=1, minsize=(hs/2)* (1/2) ) - - # [Zones de travail] - - - - self.lecteur_ci = ttk.Labelframe(self, text = "Informations sur la pièce d'identité") - - 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) - - - ttk.Label(self.lecteur_ci, text='Nom : ').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='Nom (2) : ').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='Date de naissance : ').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='Date de délivrance : ').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="Date d'expiration : ").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='Sexe du porteur : ').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='Pays de délivrance : ').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='Nationalité du porteur : ').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='Immatriculation : ').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='Numéro de document : ').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'] = "Inconnu(e)" - self.prenom['text'] = "Inconnu(e)" - self.bdate['text'] = "Inconnu(e)" - self.ddate['text'] = "Inconnu(e)" - self.edate['text'] = "Inconnu(e)" - self.no['text'] = "Inconnu(e)" - self.sex['text'] = "Inconnu(e)" - self.nat['text'] = "Inconnu(e)" - self.pays['text'] = "Inconnu(e)" - self.indic['text'] = "Inconnu(e)" - - self.STATUT = ttk.Labelframe(self, text = "Statut") - self.STATUT.grid_columnconfigure(0, weight=1) - self.STATUT.grid_rowconfigure(0, weight=1) - self.STATUStxt = Label(self.STATUT, text = "", font = "Times 24", fg = "#FFBF00") - self.STATUStxt.grid(column = 0, row = 0, padx = 0, pady = 0, sticky='EWNS') - self.STATUStxt['text'] = "EN ATTENTE" - - - self.terminal = ttk.Labelframe(self, text = "Terminal de saisie") - 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.termtext = Text(self.termframe, state='disabled', width=60, height=4, wrap='none', font = "Terminal 17", fg = "#121f38") - self.termtext.grid(column=0,row=0,sticky='NEW', padx=5) - vcmd = (self.register(self.validate), '%S', '%P', '%d' ) - self.termentry = Entry(self.termframe, font = "Terminal 17", validate = 'all', validatecommand = vcmd, fg = "#121f38", width = 44) - self.termentry.grid(column=0,row=0,sticky='SEW', padx=5) - - self.monitor = ttk.Labelframe(self, text = "Moniteur") - 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.monitor.grid_columnconfigure(0, weight=1) - self.monitor.grid_rowconfigure(0, weight=1) - - # création du canvas - self.lecteur_ci.grid(column=0,row=0,sticky='EWNS',columnspan=2, padx=5, pady=5) - self.STATUT.grid(column=2,row=0,sticky='EWNS',columnspan=1, padx=5, pady=5) - self.terminal.grid(column=0,row=1,sticky='EWNS',columnspan=2, padx=5, pady=5) - self.monitor.grid(column=2,row=1,sticky='EWNS',columnspan=1, padx=5, pady=5) - - # [barres de menu] - menubar = Menu(self) - menu1 = Menu(menubar, tearoff=0) - menu1.add_command(label="Nouveau", command=self.newbie) - menu1.add_command(label="Ouvrir scan...", command=self.openingscan) - menu1.add_separator() - menu1.add_command(label="Quitter", command=self.destroy) - menubar.add_cascade(label="Fichier", menu=menu1) - menu3 = Menu(menubar, tearoff=0) - menu3.add_command(label="A propos", command=self.infobox) - menubar.add_cascade(label="Aide", menu=menu3) - - self.config(menu=menubar) - - # Options de la fenêtre principale - self.wm_title(CST_TITLE) - - if getattr( sys, 'frozen', False ) : - self.iconbitmap(sys._MEIPASS + "\id-card.ico\id-card.ico") - else: - self.iconbitmap("id-card.ico") - - - - self.resizable(width=True, height=True) - self.update() - - self.minsize(self.winfo_width(), self.winfo_height()) - - w = int(self.winfo_width()) # width for the Tk root - h = int(self.winfo_height()) # height for the Tk root - - ws = self.winfo_screenwidth() # width of the screen - hs = self.winfo_screenheight() # height of the screen - - x = (ws/2) - (w/2) - y = (hs/2) - (h/2) - self.geometry('%dx%d+%d+%d' % (w, h, x, y)) - - self.bind('', self.returnaj) - self.bind('', self.tabaj) - - self.logger.info("App_main() : " + "Initialization successful") - - def returnaj(self, event): - self.logger.debug("returnaj() : " + "Entering Validation") - if self.PILE_ETAT != [] and len(self.PILE_ETAT)==1 : - thetext = self.termentry.get() - self.logger.debug("returnaj() : " + "PILE_ETAT Satisfy the requisites") - - n = len(thetext) - champ = MRZCODE.TYPES[self.PILE_ETAT[0]][0] - - if not n%champ.find("|") == 0 : - self.logger.debug("returnaj() : " + "Line not complete, operation aborted") - return None - - self.MRZCHAR += thetext - self.termtext['state'] = "normal" - self.termtext.insert("end", thetext + "\n") - self.termtext['state'] = "disabled" - self.termentry.delete(0, "end") - - if len(self.MRZCHAR) == champ.find("|"): - self.montext("Entrez la seconde ligne de la MRZ ou \nappuyez sur Entrée pour terminer.\n") - self.logger.debug("returnaj() : " + "First line accepted") - self.MRZCHAR += "|" - elif len(self.MRZCHAR) == champ.find("|")*2+1 or len(champ)==champ.find("|") +1: - self.montext("\nCalcul des sommes ...\n") - self.logger.info("returnaj() : " + "Launching calculsigma() thread") - threading.Thread(target=self.calculsigma, args=[self.MRZCHAR,self.PILE_ETAT[0]]).start() - - - - def tabaj(self, event): #fonction de complétion de champ par touche Escape - - if self.PILE_ETAT != [] and len(self.PILE_ETAT)==1 : - thetext = self.termentry.get() - - n = len(thetext) - - champ = MRZCODE.TYPES[self.PILE_ETAT[0]][0] - - - if len(self.MRZCHAR) <= champ.find("|"): - - champ_type = MRZCODE.TYPES[self.PILE_ETAT[0]][1][ champ[n-1] ].split("|") - self.logger.debug("tabaj() : " + " First line detected") - self.logger.debug("tabaj() : " + " champ_type[0] : " + str(champ_type[0])) - self.logger.debug("tabaj() : " + " champ : " + str(champ)) - self.logger.debug("tabaj() : " + " champ[n-1] : " + str(champ[n-1])) - self.logger.debug("tabaj() : " + " champ.find(champ[n-1]) : " + str(champ.find(champ[n-1]))) - nb = int(champ_type[0]) - (n - champ.find(champ[n-1])) - - else: - - self.logger.debug("tabaj() : " + " Second line detected") - champ = champ[champ.find("|")+1:] - champ_type = MRZCODE.TYPES[self.PILE_ETAT[0]][1][ champ[n-1] ].split("|") - self.logger.debug("tabaj() : " + " champ_type[0] : " + str(champ_type[0])) - self.logger.debug("tabaj() : " + " champ : " + str(champ)) - self.logger.debug("tabaj() : " + " champ[n-1] : " + str(champ[n-1])) - self.logger.debug("tabaj() : " + " champ.find(champ[n-1]) : " + str(champ.find(champ[n-1]))) - self.logger.debug("tabaj() : " + " n : " + str(n)) - nb = int(champ_type[0]) - (n - champ.find(champ[n-1])) - - - self.termentry.insert("end", "<"*nb) - - self.logger.debug("tabaj() : " + "Completing entry with " + str(nb) + " characters <") - - - - - def validate(self, char, entry_value, typemod): - set = False - isValid = True - - if typemod == "1": - - SUM = 0 - - for ch in char: - if ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<": - - None - - else: - - self.bell() - isValid = False - - elif typemod == "0": - - for ch in char: - if ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<" : - None - else: - - self.bell() - isValid = False - - - #PARSEUR INTERACTIF - n = len(entry_value) - - if (n%self.varnum == 0 or len(char)>3) and n<45 and self.PILE_ETAT == []: - - for i in range(len(self.Score)): #Reinitialisation des scores - - self.Score[i] = 0 - - - for type, t in zip(MRZCODE.TYPES, range(len(MRZCODE.TYPES))): #Test de chaque type - - for e in range(len(entry_value)): #Parcours des caractères de la chaine évaluee - - - try: - champchartest = type[0][:type[0].find("|")][e] - champchar = type[0][e] - except IndexError: - self.logger.debug("validate() : " + "type : " + str(t)) - self.logger.debug("validate() : " + "Too short to be ok") - self.Score[t] += -5 - break - else: - - - self.logger.debug("validate() : " + "type : " + str(t)) - - if len(entry_value) <= type[0].find("|"): #test ligne 1 mrz - champ_type = type[1][str(champchar)].split("|") - - pos = e - type[0].find(champchar) - - self.logger.debug("validate() : " + "champ_type[2][pos] : " + str(champ_type[2][pos])) - self.logger.debug("validate() : " + "champ_type[1] : " + str(champ_type[1])) - - if champ_type[1] == "CODE": - - if champ_type[2][pos] == "*": - self.Score[t] += 0 - elif champ_type[2][pos] == entry_value[e]: - self.Score[t] += 1 - self.logger.debug("validate() : " + "+1") - else: - self.Score[t] += -50 - - else: - - if champ_type[2][pos] == "*": - self.Score[t] += 1 - self.logger.debug("validate() : " + "+1") - - elif champ_type[2][pos] == "A" and entry_value[e].isalpha(): - self.Score[t] += 1 - self.logger.debug("validate() : " + "+1") - elif champ_type[2][pos] == "0" and entry_value[e].isnumeric(): - self.Score[t] += 1 - self.logger.debug("validate() : " + "+1") - elif champ_type[1] == "CTRL" and entry_value[e].isnumeric(): - self.Score[t] += 1 - self.logger.debug("validate() : " + "+1") - elif champ_type[2][pos] == "&" and (entry_value[e].isalpha() or entry_value[e]=="<"): - self.Score[t] += 1 - self.logger.debug("validate() : " + "+1") - - else: - self.Score[t] += -1 - - - else: - None #bah on fait rien parcequ'on a atteint le bout de la ligne dans le type donné. - - - self.logger.debug("validate() : " + "self.Score : " + str(self.Score)) - - m = max(self.Score) - typem = [i for i, j in enumerate(self.Score) if j == m and m > 5] - - for h in typem: - self.PILE_ETAT += [ h ] - - if len(self.PILE_ETAT) > 1: - self.varnum += 3 - self.PILE_ETAT = [] - elif len(self.PILE_ETAT) == 1: - TOPOS = MRZCODE.TYPES[ self.PILE_ETAT[0] ][2] - - self.montext(TOPOS + " détectée !\nAppuyez sur Echap pour compléter les champs avec des '<'\nAppuyez sur Entrée pour terminer.\n") - self.logger.debug("validate() : " + "Detection : " + str(TOPOS)) - - - - elif len(self.PILE_ETAT) == 1 and len(entry_value) > len(MRZCODE.TYPES[self.PILE_ETAT[0]][0].split("|")[0]): - isValid = False - - return isValid - - def montext(self,text): - - self.monlog['state'] = "normal" - self.monlog.insert("end",text) - self.monlog['state'] = "disabled" - - def openingscan(self): - - self.initialize(self.logger) - self.update() - - path = "" - path = filedialog.askopenfilename(parent=self, title = "Ouvrir un scan de CNI...",filetypes = (("TIF files","*.tif"),("TIF files","*.tiff"), ("JPEG files","*.jpg"), ("JPEG files","*.jpeg"))) #dialog - - - if path != "": - self.logger.info("openingscan() : " + "Opening file with path " + str(path)) - - im = Image.open(path) - - try: - nframe = im.n_frames - except AttributeError: - nframe = 1 - - if nframe == 1: - - self.mrzdetected = "" - self.mrzdict = {} - opening = OpenScanWin(self, path, 0) - opening.transient(self) - opening.grab_set() - self.wait_window(opening) - - self.logger.debug("openingscan() : " + str(self.mrzdetected)) - - elif nframe > 1: #If multi framed tiff - - # self.page = 1 - - # opening = OpenPageDialog(self, nframe) - # opening.transient(self) - # opening.grab_set() - # self.wait_window(opening) - # - # im.seek(self.page) - # im.save(CST_FOLDER + '\\temp.tif') - # - # - # self.mrzdetected = "" - # self.mrzdict = {} - # opening = OpenScanWin(self, CST_FOLDER + '\\temp.tif', 1) - # opening.transient(self) - # opening.grab_set() - # self.wait_window(opening) - - self.mrzdetected = "" - self.mrzdict = {} - opening = OpenScanWin(self, path, 1, nframe) - opening.transient(self) - opening.grab_set() - self.wait_window(opening) - - self.logger.debug("openingscan() : " + str(self.mrzdetected)) - - - - try: - os.remove(CST_FOLDER + '\\temp.tif') - except IOError: - pass - - else: - raise(Exception) - - mrzsoumisetab = self.mrzdetected.replace(" ", "").split("\n") - - #Sending the mrz to the core system - - for chain in mrzsoumisetab: - - self.termentry.insert("end", chain) - - if len(chain)>=5 : self.returnaj("") - - - - - - return None - - - def newbie(self): - self.initialize(self.logger) - self.montext("\n\nEntrez la première ligne de MRZ svp \n") - - def infobox(self): - - Tk().withdraw() - showinfo("A propos du logiciel", "Version du logiciel : \n" +CST_NAME + " " + CST_VER + " " + CST_TYPE + " Revision " + CST_REV +"\nLicence GNU/GPL 2018\n\nAuteur : NeoX_ ; devadmin@neoxgroup.eu\n\nTesseract 4.0 est soumis à l'Apache License 2004\n\n N'hésitez pas à faire part de vos commentaires !") - - def calculsigma(self, MRZtxt, numtype): #Calcul des sommes - - #récupération des index des CTRL - CTRList = [c for c,v in MRZCODE.TYPES[numtype][1].items() if "CTRL" in v] - self.logger.info("[calculsigma() thread] : " + "Sigma calculation launched!") - self.logger.debug("[calculsigma() thread] : " + "CTRList = " + str(CTRList)) - self.Falsitude = 0 - self.montext("\n") - - for i in CTRList: - - - sumtxt = MRZCODE.TYPES[numtype][1][i] #Récupération du descriptif de la somme - length = MRZCODE.TYPES[numtype][0].find("|") # Récupération de la longueur de ligne du code MRZ - index = MRZCODE.TYPES[numtype][0].find(i) # Récupération de l'index de la somme - - - sum_read = MRZtxt[index] #lecture de la somme donnée par la carte - - if len(sumtxt.split("|")[2])==1: #Cas d'une somme de champ - - debut = MRZCODE.TYPES[numtype][0].find(sumtxt.split("|")[2][0]) - sum_calc = MRZCODE.MRZ(MRZtxt[ int(debut) : index ]) - - else: #Cas d'une somme composite - - transm_chain = "" - for y in sumtxt.split("|")[2]: - - debut = MRZCODE.TYPES[numtype][0].find(y) - fin = debut + int(MRZCODE.TYPES[numtype][1][y].split("|")[0]) - transm_chain += MRZtxt[ int(debut) : int(fin) ] - - sum_calc = MRZCODE.MRZ(transm_chain) - - - - - if str(sum_read)[0] != str(sum_calc)[0] and not(sumtxt.split("|")[1] == "CTRLF" and str(sum_read)[0]=="<"): - - self.Falsitude += 1 - self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, sum errored : " + str(i)) - self.termtext.tag_add('highLOW', '1.0+'+ str(index) + 'c', '1.0+'+ str(index+1) + 'c') - self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white') - - else: - self.termtext.tag_add('highLOWB', '1.0+'+ str(index) + 'c', '1.0+'+ str(index+1) + 'c') - self.termtext.tag_configure('highLOWB', background='#04B404', relief='raised', foreground = 'white') - - self.montext("Somme : Lu " + str(sum_read) + " VS calculé " + str(sum_calc) + "\n") - - - #Retriving infos - NameList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "|NOM" in v ] - SurnameList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "PRENOM" in v ] - DDateList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "DDATE" in v ] - BDateList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "BDATE" in v ] - EDateList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "EDATE" in v ] - PAYSList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "PAYS" in v ] - NATList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "NAT" in v ] - SEXList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "SEX" in v ] - NOINTList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "NOINT" in v ] - NOList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "NO|" in v ] - FACULTList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "FACULT" in v ] - INDICList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "INDIC" in v ] - - BIGList = [ NameList, SurnameList, DDateList, BDateList, EDateList, PAYSList, NATList, SEXList, NOList, INDICList, NOINTList] - - BIGObj = [ self.nom, self.prenom, self.ddate, self.bdate, self.edate, self.pays, self.nat, self.sex, self.no, self.indic, self.no] - - for i in range(len(BIGList)): - - for (champ, champnum) in zip(BIGList[i], range(len(BIGList[i]))): - - debut = MRZCODE.TYPES[numtype][0].find(champ) - fin = debut + int(MRZCODE.TYPES[numtype][1][champ].split("|")[0]) #Récupération des coordonnées de champs utiles - - - if BIGObj[i] == self.pays or BIGObj[i] == self.nat: - - try: - - BIGObj[i]["text"] = MRZCODE.landcode[ MRZtxt[ int(debut) : int(fin) ] ] #Si c'est un pays on traduit - - except KeyError: - - self.Falsitude += 1 - self.montext("Code pays : " + str(MRZtxt[ int(debut) : int(fin) ]) + " est inconnu \n") - self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, unknown state") - self.termtext.tag_add('highLOW', '1.0+'+ str(debut) + 'c', '1.0+'+ str(fin) + 'c') - self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white') - BIGObj[i]["background"] = "#760808" - BIGObj[i]["foreground"] = "white" - - elif BIGObj[i] == self.sex: - - try: - - BIGObj[i]["text"] = MRZCODE.sexcode[ MRZtxt[ int(debut) : int(fin) ] ] - - except KeyError: - - self.Falsitude += 1 - self.montext("Sexe : " + str(MRZtxt[ int(debut) : int(fin) ]) + " est inconnu \n") - self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, unknown state") - self.termtext.tag_add('highLOW', '1.0+'+ str(debut) + 'c', '1.0+'+ str(fin) + 'c') - self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white') - BIGObj[i]["background"] = "#760808" - BIGObj[i]["foreground"] = "white" - - elif BIGObj[i] == self.edate or BIGObj[i] == self.ddate or BIGObj[i] == self.bdate: - - txtl = MRZtxt[ int(debut) : int(fin) ].replace("<<<","").replace("<"," ") - - if len(txtl) < 6 : txtl += "0" * (6 - len(txtl)) - - BIGObj[i]["text"] = "{0}/{1}/{2}".format(txtl[4:], txtl[2:4], txtl[:2]) - - if BIGObj[i] == self.edate: - present = datetime.now() - - try: - - expiration = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), int(txtl[4:]) ) - - except ValueError: - - BIGObj[i]["background"] = "#760808" - BIGObj[i]["foreground"] = "white" - self.Falsitude += 1 - self.montext("Date : " + str(BIGObj[i]["text"]) + " est invalide \n") - self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, invalid expiration date") - self.termtext.tag_add('highLOW', '1.0+'+ str(debut) + 'c', '1.0+'+ str(fin) + 'c') - self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white') - - else: - - if expiration < present: - - BIGObj[i]["background"] = "#e67300" - BIGObj[i]["foreground"] = "white" - else: - - try: - - if int(txtl[4:]) == 0: - verif = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), 1 ) - else: - verif = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), int(txtl[4:]) ) - - - except ValueError: - - BIGObj[i]["background"] = "#760808" - BIGObj[i]["foreground"] = "white" - self.Falsitude += 1 - self.montext("Date : " + str(BIGObj[i]["text"]) + " est invalide \n") - self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, invalid datetime") - self.termtext.tag_add('highLOW', '1.0+'+ str(debut) + 'c', '1.0+'+ str(fin) + 'c') - self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white') - - else: - - if champnum == 0: - BIGObj[i]["text"] = MRZtxt[ int(debut) : int(fin) ].replace("<<<","").replace("<"," ") - else: - BIGObj[i]["text"] += MRZtxt[ int(debut) : int(fin) ].replace("<<<","").replace("<"," ")#Sinon on indique juste - - if self.Falsitude == 0 : - - self.STATUStxt['text'] = "CONFORME" - self.STATUStxt['fg'] = "#04B404" - self.logger.debug("[calculsigma() thread] : " + "Conforme !") - - else: - - self.STATUStxt['text'] = "NON CONFORME" - self.STATUStxt['fg'] = "#760808" - self.logger.debug("[calculsigma() thread] : " + "Non conforme !") - - self.montext("** Score de non conformité : " + str(self.Falsitude) + "**\n") - - return None - - -class MRZCODE: - - #Fonction de calcul - def MRZ(code): - """ - Calcul sommes de contrôle de la chaîne transmise - """ - resultat = 0 #init de la somme avant calcul - i = -1 #init du pas de somme avant calcul - facteur = [7, 3, 1] #facteurs magiques - - for car in code: #Traitement selon le type de caractère - if car == "<" or car == "|": - valeur = 0 - i += 1 - elif car in "0123456789": - valeur = int(car) - i += 1 - elif car in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": - valeur = ord(car)-55 - i += 1 - else: - self.logger.error("MRZ() : " + "Erreur de calcul : caractère non conforme") - showerror("Erreur du module de calcul",Exception("Caractère non conforme dans la MRZ")) - break - - resultat += valeur * facteur[i%3] #Rajout de la valeur du caractère dans la somme et itération suivante - - return (resultat % 10) #Fin de la fonction, application du mod 10 - - #Dictionnaire des sexes - sexcode = { 'M' : 'Homme' , 'F' : 'Femme', 'X' : 'Non spécifié'} - - #Dictionnaire des pays - 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'} - - landcode = {'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'} - - #Passeport - P = ["11222333333333333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCCCCCCCCDE" , { - "1" : "2|CODE|P*" , - "2" : "3|PAYS|AAA" , - "3" : "39|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", - "4" : "9|NO|*********", - "5" : "1|CTRL|4", - "6" : "3|NAT|AAA", - "7" : "6|BDATE|000000", - "8" : "1|CTRL|7", - "9" : "1|SEX|A", - "A" : "6|EDATE|000000", - "B" : "1|CTRL|A", - "C" : "14|FACULT|**************", - "D" : "1|CTRLF|C", - "E" : "1|CTRL|4578ABCD" - - }, "Passeport"] - #Carte passeport - IP = ["112223333333334555555555555555|66666678999999ABBBCCCCCCCCCCCD" , { - "1" : "2|CODE|IP" , - "2" : "3|PAYS|AAA" , - "3" : "9|NO|*********", - "4" : "1|CTRL|3", - "5" : "15|FACULT|***************", - "6" : "6|BDATE|000000", - "7" : "1|CTRL|6", - "8" : "1|SEX|A", - "9" : "6|EDATE|000000", - "A" : "1|CTRL|9", - "B" : "3|NAT|AAA", - "C" : "11|FACULT|***********", - "D" : "1|CTRL|345679AC" - }, "Carte-passeport"] - #ID dv1 - I_ = ["112223333333334555555555555555|66666678999999ABBBCCCCCCCCCCCD" , { - "1" : "2|CODE|I*" , - "2" : "3|PAYS|AAA" , - "3" : "9|NO|*********", - "4" : "1|CTRL|3", - "5" : "15|FACULT|***************", - "6" : "6|BDATE|000000", - "7" : "1|CTRL|6", - "8" : "1|SEX|A", - "9" : "6|EDATE|000000", - "A" : "1|CTRL|9", - "B" : "3|NAT|AAA", - "C" : "11|FACULT|***********", - "D" : "1|CTRL|345679AC" - }, "Titre d'identité/de voyage"] - #Certificat de membre dequipaj - AC = ["112223333333334EEE555555555555|66666678999999ABBBCCCCCCCCCCCD" , { - "1" : "2|CODE|AC" , - "2" : "3|PAYS|AAA" , - "3" : "9|NO|*********", - "4" : "1|CTRL|3", - "5" : "15|FACULT|***************", - "6" : "6|BDATE|000000", - "7" : "1|CTRL|6", - "8" : "1|SEX|A", - "9" : "6|EDATE|000000", - "A" : "1|CTRL|9", - "B" : "3|NAT|AAA", - "C" : "11|FACULT|***********", - "D" : "1|CTRL|345679AC", - "E" : "3|INDIC|AA&" #ATTENTION : Doc 8585 (3 lettres) + Airline Coding Directory de l’IATA - }, "Certificat de membre d'équipage"] - - #Visa de type A - VA = ["11222333333333333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCCCCCCCCDE" , { - "1" : "2|CODE|V*" , - "2" : "3|PAYS|AAA" , - "3" : "39|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", - "4" : "9|NO|*********", - "5" : "1|CTRL|4", - "6" : "3|NAT|AAA", - "7" : "6|BDATE|000000", - "8" : "1|CTRL|7", - "9" : "1|SEX|A", - "A" : "6|EDATE|000000", - "B" : "1|CTRL|A", - "C" : "14|FACULT|**************", - - }, "Visa de type A"] - #Visa de type B - VB = ["112223333333333333333333333333333333|444444444566677777789AAAAAABCCCCCC" , { - "1" : "2|CODE|V*" , - "2" : "3|PAYS|AAA" , - "3" : "31|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", - "4" : "9|NO|*********", - "5" : "1|CTRL|4", - "6" : "3|NAT|AAA", - "7" : "6|BDATE|000000", - "8" : "1|CTRL|7", - "9" : "1|SEX|A", - "A" : "6|EDATE|000000", - "B" : "1|CTRL|A", - "C" : "8|FACULT|********" - - - }, "Visa de type B"] - #ID dv2 - I__ = ["112223333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCD" , { - "1" : "2|CODE|I*" , - "2" : "3|PAYS|AAA" , - "3" : "31|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", - "4" : "9|NO|*********", - "5" : "1|CTRL|4", - "6" : "3|NAT|AAA", - "7" : "6|BDATE|000000", - "8" : "1|CTRL|7", - "9" : "1|SEX|A", - "A" : "6|EDATE|000000", - "B" : "1|CTRL|A", - "C" : "7|FACULT|*******", - "D" : "1|CTRL|4578ABC" - }, "Pièce d'identité/de voyage"] - #ID2 - ID = ["112223333333333333333333333333444444|555566677777899999999999999AAAAAABCD" , { - "1" : "2|CODE|ID" , - "2" : "3|PAYS|AAA" , - "3" : "25|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&", - "4" : "6|NOINT|000***", #000 pour le département avec prefixe 0 et poste agent *** - "5" : "4|DDATE|0000", #AAMM - "6" : "3|NOINT2|000", #departement et pref - "7" : "5|NOINT3|00000", #No arbitraire - "8" : "1|CTRL|567", - "9" : "14|PRENOM|A", - "A" : "6|BDATE|000000", - "B" : "1|CTRL|A", - "C" : "1|SEX|A", - "D" : "1|CTRL|123456789ABC" - },"Pièce d'identité FR"] - DL = ["112223333333334555555666666667|" , { - "1" : "2|CODE|D1" , - "2" : "3|PAYS|AAA" , - "3" : "9|NO|00AA00000", - "4" : "1|CTRL|123", - "5" : "6|EDATE|000000", - "6" : "8|NOM|&&&&&&&&", - "7" : "1|CTRL|123456", - - },"Permis de conduire"] - - - TYPES = [ ID, I__, VB, VA, AC, I_, IP, P, DL ] - - - - -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 Download: #Gestionnaire de DL - - def __init__(self, url, localfile, namefile, opener, p, canvas, message, logger): #p is the progress bar, canvas and message are using to display text - - from socket import timeout - - self.retry = 6 - self.logger = logger - self.opener = opener - self.url = url - self.localfile = localfile - self.success = False - self.time = 5 - - self.count = 0 # Counts downloaded size. - self.success = False - self.downloading = True - - self.logger.error("[Download() class] : " + "Initialization ok") - - while (not(self.success) and self.downloading): - try: - self.Err = "" - self._netfile = self.opener.open(self.url, timeout=self.time) - self.filesize = float(self._netfile.info()['Content-Length']) - - if (os.path.exists(self.localfile) and os.path.isfile(self.localfile)): - self.count = os.path.getsize(self.localfile) - - if self.count >= self.filesize: - #already downloaded - self.downloading = False - self.success = True - self._netfile.close() - return - - if (os.path.exists(self.localfile) and os.path.isfile(self.localfile)): - #File already exists, start where it left off: - self._netfile.close() - req = urllib2.Request(self.url) - - req.add_header("Range","bytes=%s-" % (self.count)) - self._netfile = self.opener.open(req, timeout=self.time) - - if (self.downloading): #Don't do it if cancelled, downloading=false. - self._outfile = open(self.localfile,"ab") #to append binary - - next = self._netfile.read(1024) - p.stop() - p.configure(mode = "determinate", value = 0, maximum = 100) - while (len(next)>0 and self.downloading): - self._outfile.write(next) - self.count += len(next) - next = self._netfile.read(1024) - - Percent = int(((self.count)/self.filesize*100)) - p.configure(mode = "determinate", value = int(Percent)) - canvas.itemconfigure(message, text="Téléchargement de " + str(namefile) + " de taille " + str(int(self.filesize/1024/1024)) + " Mo : " + str(Percent) + " %") - - self._netfile.close() - self._outfile.close() - self.success = True - - except urllib2.HTTPError as e: - - self.logger.error("[Download() class] : " + "HTTP ERROR " + str(e.code)) - - try: - self._netfile.close() - self._outfile.close() - except Exception as err: - self.logger.critical("[Download() class] : " + "FILE I/O ERROR : " + str(err)) - - self.retry += -1 - canvas.itemconfigure(message, text="Erreur HTTP " + str(e.code) +". Nouvelles tentatives : " +str(self.retry)) - - if self.retry <= 0: - self.downloading = False - - except timeout as e: - - self.logger.error("[Download() class] : " + "TIMEOUT ERROR : " + str(e)) - try: - self._netfile.close() - self._outfile.close() - except Exception as err: - self.logger.critical("[Download() class] : " + "FILE I/O ERROR : " + str(err)) - - self.retry += -1 - self.time *= 2 - canvas.itemconfigure(message, text="Connexion expirée. Nouvelles tentatives : " + str(self.retry) + ", " + str(self.time) + " s") - - if self.retry <= 0: - self.downloading = False - - - except IOError as e: - - self.logger.error("[Download() class] : " + "I/O ERROR :" + str(e)) - try: - self._netfile.close() - self._outfile.close() - except Exception as err: - self.logger.critical("[Download() class] : " + "FILE I/O ERROR : " + str(err)) - - self.retry += -1 - self.time *= 2 - canvas.itemconfigure(message, text="Connexion expirée. Nouvelles tentatives : " + str(self.retry) + ", " + str(self.time) + " s") - - if self.retry <= 0: - self.downloading = False - - except Exception as e: - - self.logger.error("[Download() class] : " + "UNKNOWN ERROR : " + str(e)) - - try: - self._netfile.close() - self._outfile.close() - except Exception as err: - self.logger.critical("[Download() class] : " + "FILE I/O ERROR : " + str(err)) - - self.retry += -1 - canvas.itemconfigure(message, text="Erreur inconnue. Nouvelles tentatives : " + str(self.retry)) - - if self.retry <= 0: - self.downloading = False - - -class AutoScrollbar(ttk.Scrollbar): - """ A scrollbar that hides itself if it's not needed. Works only for grid geometry manager """ - def set(self, lo, hi): - if float(lo) <= 0.0 and float(hi) >= 1.0: - self.grid_remove() - else: - self.grid() - ttk.Scrollbar.set(self, lo, hi) - - def pack(self, **kw): - raise TclError('Cannot use pack with the widget ' + self.__class__.__name__) - - def place(self, **kw): - raise TclError('Cannot use place with the widget ' + self.__class__.__name__) - -class CanvasImage: - """ Display and zoom image """ - def __init__(self, placeholder, file, type): - """ Initialize the ImageFrame """ - self.type = type #1 for multipage, 0 for normal - self.angle = 0 - self.imscale = 1.0 # scale for the canvas image zoom, public for outer classes - self.__delta = 1.3 # zoom magnitude - self.__filter = Image.ANTIALIAS # could be: NEAREST, BILINEAR, BICUBIC and ANTIALIAS - self.__previous_state = 0 # previous state of the keyboard - self.path = file # path to the image, should be public for outer classes - # Create ImageFrame in placeholder widget - self.__imframe = ttk.Frame(placeholder) - self.placeholder = placeholder # placeholder of the ImageFrame object - # Vertical and horizontal scrollbars for canvas - hbar = AutoScrollbar(self.__imframe, orient='horizontal') - vbar = AutoScrollbar(self.__imframe, orient='vertical') - hbar.grid(row=1, column=0, sticky='we') - vbar.grid(row=0, column=1, sticky='ns') - # Create canvas and bind it with scrollbars. Public for outer classes - self.canvas = Canvas(self.__imframe, highlightthickness=0, - xscrollcommand=hbar.set, yscrollcommand=vbar.set) - self.canvas.grid(row=0, column=0, sticky='nswe') - self.canvas.update() # wait till canvas is created - hbar.configure(command=self.__scroll_x) # bind scrollbars to the canvas - vbar.configure(command=self.__scroll_y) - # Bind events to the Canvas - self.canvas.bind('', lambda event: self.__show_image()) # canvas is resized - self.canvas.bind('', self.__move_from) # remember canvas position - self.canvas.bind('', self.__move_to) # move canvas to the new position - self.canvas.bind('', self.__wheel) # zoom - # Decide if this image huge or not - self.__huge = False # huge or not - self.__huge_size = 14000 # define size of the huge image - self.__band_width = 1024 # width of the tile band - Image.MAX_IMAGE_PIXELS = 1000000000 # suppress DecompressionBombError for the big image - with warnings.catch_warnings(): # suppress DecompressionBombWarning - warnings.simplefilter('ignore') - self.__image = Image.open(self.path) # open image, but down't load it - self.imwidth, self.imheight = self.__image.size # public for outer classes - if self.imwidth * self.imheight > self.__huge_size * self.__huge_size and \ - self.__image.tile[0][0] == 'raw': # only raw images could be tiled - self.__huge = True # image is huge - self.__offset = self.__image.tile[0][2] # initial tile offset - self.__tile = [self.__image.tile[0][0], # it have to be 'raw' - [0, 0, self.imwidth, 0], # tile extent (a rectangle) - self.__offset, - self.__image.tile[0][3]] # list of arguments to the decoder - self.__min_side = min(self.imwidth, self.imheight) # get the smaller image side - # Create image pyramid - self.__pyramid = [self.smaller()] if self.__huge else [Image.open(self.path)] - # Set ratio coefficient for image pyramid - self.__ratio = max(self.imwidth, self.imheight) / self.__huge_size if self.__huge else 1.0 - self.__curr_img = 0 # current image from the pyramid - self.__scale = self.imscale * self.__ratio # image pyramide scale - self.__reduction = 2 # reduction degree of image pyramid - w, h = self.__pyramid[-1].size - while w > 512 and h > 512: # top pyramid image is around 512 pixels in size - w /= self.__reduction # divide on reduction degree - h /= self.__reduction # divide on reduction degree - try: - self.__pyramid.append(self.__pyramid[-1].resize((int(w), int(h)), self.__filter)) - except TypeError: - showerror(title="Erreur de fichier", message="Image incompatible. Merci d'utiliser une autre image ou de la convertir", parent = self.placeholder) - # Put image into container rectangle and use it to set proper coordinates to the image - self.container = self.canvas.create_rectangle((0, 0, self.imwidth, self.imheight), width=0) - self.__show_image() # show image on the canvas - self.canvas.focus_set() # set focus on the canvas - - def rotatem(self): - self.angle += 1 - self.__show_image() - - def rotatep(self): - self.angle -= 1 - self.__show_image() - - def rotatemm(self): - self.angle += 90 - self.__show_image() - - def rotatepp(self): - self.angle -= 90 - self.__show_image() - - def smaller(self): - """ Resize image proportionally and return smaller image """ - w1, h1 = float(self.imwidth), float(self.imheight) - w2, h2 = float(self.__huge_size), float(self.__huge_size) - aspect_ratio1 = w1 / h1 - aspect_ratio2 = w2 / h2 # it equals to 1.0 - if aspect_ratio1 == aspect_ratio2: - image = Image.new('RGB', (int(w2), int(h2))) - k = h2 / h1 # compression ratio - w = int(w2) # band length - elif aspect_ratio1 > aspect_ratio2: - image = Image.new('RGB', (int(w2), int(w2 / aspect_ratio1))) - k = h2 / w1 # compression ratio - w = int(w2) # band length - else: # aspect_ratio1 < aspect_ration2 - image = Image.new('RGB', (int(h2 * aspect_ratio1), int(h2))) - k = h2 / h1 # compression ratio - w = int(h2 * aspect_ratio1) # band length - i, j, n = 0, 1, round(0.5 + self.imheight / self.__band_width) - while i < self.imheight: - - band = min(self.__band_width, self.imheight - i) # width of the tile band - self.__tile[1][3] = band # set band width - self.__tile[2] = self.__offset + self.imwidth * i * 3 # tile offset (3 bytes per pixel) - self.__image.close() - self.__image = Image.open(self.path) # reopen / reset image - self.__image.size = (self.imwidth, band) # set size of the tile band - self.__image.tile = [self.__tile] # set tile - cropped = self.__image.crop((0, 0, self.imwidth, band)) # crop tile band - image.paste(cropped.resize((w, int(band * k)+1), self.__filter), (0, int(i * k))) - i += band - j += 1 - - return image - - def redraw_figures(self): - """ Dummy function to redraw figures in the children classes """ - pass - - def grid(self, **kw): - """ Put CanvasImage widget on the parent widget """ - self.__imframe.grid(**kw) # place CanvasImage widget on the grid - self.__imframe.grid(sticky='nswe') # make frame container sticky - self.__imframe.rowconfigure(0, weight=1) # make canvas expandable - self.__imframe.columnconfigure(0, weight=1) - - # noinspection PyUnusedLocal - def __scroll_x(self, *args, **kwargs): - """ Scroll canvas horizontally and redraw the image """ - self.canvas.xview(*args) # scroll horizontally - self.__show_image() # redraw the image - - # noinspection PyUnusedLocal - def __scroll_y(self, *args, **kwargs): - """ Scroll canvas vertically and redraw the image """ - self.canvas.yview(*args) # scroll vertically - self.__show_image() # redraw the image - - def __show_image(self): - """ Show image on the Canvas. Implements correct image zoom almost like in Google Maps """ - box_image = self.canvas.coords(self.container) # get image area - box_canvas = (self.canvas.canvasx(0), # get visible area of the canvas - self.canvas.canvasy(0), - self.canvas.canvasx(self.canvas.winfo_width()), - self.canvas.canvasy(self.canvas.winfo_height())) - box_img_int = tuple(map(int, box_image)) # convert to integer or it will not work properly - # Get scroll region box - box_scroll = [min(box_img_int[0], box_canvas[0]), min(box_img_int[1], box_canvas[1]), - max(box_img_int[2], box_canvas[2]), max(box_img_int[3], box_canvas[3])] - # Horizontal part of the image is in the visible area - if box_scroll[0] == box_canvas[0] and box_scroll[2] == box_canvas[2]: - box_scroll[0] = box_img_int[0] - box_scroll[2] = box_img_int[2] - # Vertical part of the image is in the visible area - if box_scroll[1] == box_canvas[1] and box_scroll[3] == box_canvas[3]: - box_scroll[1] = box_img_int[1] - box_scroll[3] = box_img_int[3] - # Convert scroll region to tuple and to integer - self.canvas.configure(scrollregion=tuple(map(int, box_scroll))) # set scroll region - x1 = max(box_canvas[0] - box_image[0], 0) # get coordinates (x1,y1,x2,y2) of the image tile - y1 = max(box_canvas[1] - box_image[1], 0) - x2 = min(box_canvas[2], box_image[2]) - box_image[0] - y2 = min(box_canvas[3], box_image[3]) - box_image[1] - if int(x2 - x1) > 0 and int(y2 - y1) > 0: # show image if it in the visible area - if self.__huge and self.__curr_img < 0: # show huge image - h = int((y2 - y1) / self.imscale) # height of the tile band - self.__tile[1][3] = h # set the tile band height - self.__tile[2] = self.__offset + self.imwidth * int(y1 / self.imscale) * 3 - self.__image.close() - self.__image = Image.open(self.path) # reopen / reset image - self.__image.size = (self.imwidth, h) # set size of the tile band - self.__image.tile = [self.__tile] - image = self.__image.crop((int(x1 / self.imscale), 0, int(x2 / self.imscale), h)) - else: # show normal image - image = self.__pyramid[max(0, self.__curr_img)].crop( # crop current img from pyramid - (int(x1 / self.__scale), int(y1 / self.__scale), - int(x2 / self.__scale), int(y2 / self.__scale))) - # - - self.resizedim = image.resize((int(x2 - x1), int(y2 - y1)), self.__filter).rotate(self.angle, expand = 1) - imagetk = ImageTk.PhotoImage(self.resizedim, master = self.placeholder) - imageid = self.canvas.create_image(max(box_canvas[0], box_img_int[0]), - max(box_canvas[1], box_img_int[1]), - anchor='nw', image=imagetk) - self.canvas.lower(imageid) # set image into background - self.canvas.imagetk = imagetk # keep an extra reference to prevent garbage-collection - - def __move_from(self, event): - """ Remember previous coordinates for scrolling with the mouse """ - self.canvas.scan_mark(event.x, event.y) - - def __move_to(self, event): - """ Drag (move) canvas to the new position """ - self.canvas.scan_dragto(event.x, event.y, gain=1) - self.__show_image() # zoom tile and show it on the canvas - - def outside(self, x, y): - """ Checks if the point (x,y) is outside the image area """ - bbox = self.canvas.coords(self.container) # get image area - if bbox[0] < x < bbox[2] and bbox[1] < y < bbox[3]: - return False # point (x,y) is inside the image area - else: - return True # point (x,y) is outside the image area - - def __wheel(self, event): - """ Zoom with mouse wheel """ - x = self.canvas.canvasx(event.x) # get coordinates of the event on the canvas - y = self.canvas.canvasy(event.y) - if self.outside(x, y): return # zoom only inside image area - scale = 1.0 - # Respond to Linux (event.num) or Windows (event.delta) wheel event - if event.delta == -120: # scroll down, smaller - if round(self.__min_side * self.imscale) < int(self.placeholder.winfo_screenheight()): return # image is less than the number of window pixels - self.imscale /= self.__delta - scale /= self.__delta - if event.delta == 120: # scroll up, bigger - i = min(self.canvas.winfo_width(), self.canvas.winfo_height()) >> 1 - if i < self.imscale: return # 1 pixel is bigger than the visible area - self.imscale *= self.__delta - scale *= self.__delta - # Take appropriate image from the pyramid - k = self.imscale * self.__ratio # temporary coefficient - self.__curr_img = min((-1) * int(math.log(k, self.__reduction)), len(self.__pyramid) - 1) - self.__scale = k * math.pow(self.__reduction, max(0, self.__curr_img)) - # - self.canvas.scale('all', x, y, scale, scale) # rescale all objects - # Redraw some figures before showing image on the screen - self.redraw_figures() # method for child classes - self.__show_image() - - def crop(self, bbox): - """ Crop rectangle from the image and return it """ - if self.__huge: # image is huge and not totally in RAM - band = bbox[3] - bbox[1] # width of the tile band - self.__tile[1][3] = band # set the tile height - self.__tile[2] = self.__offset + self.imwidth * bbox[1] * 3 # set offset of the band - self.__image.close() - self.__image = Image.open(self.path) # reopen / reset image - self.__image.size = (self.imwidth, band) # set size of the tile band - self.__image.tile = [self.__tile] - return self.__image.crop((bbox[0], 0, bbox[2], band)) - else: # image is totally in RAM - return self.__pyramid[0].crop(bbox) - - def destroy(self): - """ ImageFrame destructor """ - self.__image.close() - map(lambda i: i.close, self.__pyramid) # close all pyramid images - del self.__pyramid[:] # delete pyramid list - del self.__pyramid # delete pyramid variable - self.canvas.destroy() - - -class OpenScan(ttk.Frame): - """ Main window class """ - def __init__(self, mainframe, fileorig, type, nframe = 1, pagenum = 0, file = None): - """ Initialize the main Frame """ - if file == None : file = fileorig - self.file = file - self.fileorig = fileorig - self.nframe = nframe - self.pagenum = pagenum - self.parent = mainframe.parent - ttk.Frame.__init__(self, master=mainframe) - - self.master.title('Ouvrir un scan... (Utilisez la roulette pour zoomer, clic gauche pour déplacer et clic droit pour sélectionner la MRZ)') - - self.master.resizable(width=False, height=False) - hs = self.winfo_screenheight() - - w = int(self.winfo_screenheight()/1.5) # width for the Tk root - h = int(self.winfo_screenheight()/2) # height for the Tk root - - ws = self.winfo_screenwidth() # width of the screen - hs = self.winfo_screenheight() # height of the screen - - x = (ws/2) - (w/2) - y = (hs/2) - (h/2) - self.master.geometry('%dx%d+%d+%d' % (w, h, x, y)) - - if getattr( sys, 'frozen', False ) : - self.master.iconbitmap(sys._MEIPASS + "\id-card.ico\id-card.ico") - else: - self.master.iconbitmap("id-card.ico") - self.master.rowconfigure(0, weight=1) - self.master.columnconfigure(0, weight=1) - self.cadre = CanvasImage(self.master, self.file, type) - self.cadre.grid(row=0, column=0) - - self.master.menubar = Menu(self.master) - if type==1 : self.master.menubar.add_command(label="Page précédente", command= self.pagep) - self.master.menubar.add_command(label="Pivoter -90°", command= self.cadre.rotatemm) - self.master.menubar.add_command(label="Pivoter -1°", command= self.cadre.rotatem) - self.master.menubar.add_command(label="Pivoter +1°", command= self.cadre.rotatep) - self.master.menubar.add_command(label="Pivoter +90°", command= self.cadre.rotatepp) - if type==1 : self.master.menubar.add_command(label="Page suivante", command= self.pages) - - self.master.config(menu=self.master.menubar) - - self.cadre.canvas.bind('', self.motionprep) - self.cadre.canvas.bind('', self.motionize) - self.cadre.canvas.bind('', self.motionend) - - def pages(self): - - if self.pagenum +1 < self.nframe: - # self.destroy() - im = Image.open(self.fileorig) - im.seek(self.pagenum +1) - newpath = CST_FOLDER + '\\temp' + str(random.randint(11111,99999)) +'.tif' - im.save(newpath) - im.close() - self.cadre.destroy() - self.__init__(self.master, self.fileorig, 1, self.nframe, self.pagenum +1, newpath) - - def pagep(self): - - if self.pagenum - 1 >= 0: - # self.destroy() - im = Image.open(self.fileorig) - im.seek(self.pagenum - 1) - newpath = CST_FOLDER + '\\temp' + str(random.randint(11111,99999)) +'.tif' - im.save(newpath) - im.close() - self.cadre.destroy() - self.__init__(self.master, self.fileorig, 1, self.nframe, self.pagenum -1, newpath) - - def motionprep(self,event): - - if hasattr(self,"rect"): - - self.begx = event.x - self.begy = event.y - - - self.ix = self.cadre.canvas.canvasx(event.x) - self.iy = self.cadre.canvas.canvasy(event.y) - - self.cadre.canvas.coords(self.rect, self.cadre.canvas.canvasx(event.x), self.cadre.canvas.canvasy(event.y),self.ix,self.iy) - - else: - self.begx = event.x - self.begy = event.y - - self.ix = self.cadre.canvas.canvasx(event.x) - self.iy = self.cadre.canvas.canvasy(event.y) - self.rect = self.cadre.canvas.create_rectangle(self.cadre.canvas.canvasx(event.x), self.cadre.canvas.canvasy(event.y),self.ix,self.iy, outline='red') - - def motionize(self,event): - - event.x - event.y - - self.cadre.canvas.coords(self.rect, self.ix,self.iy,self.cadre.canvas.canvasx(event.x),self.cadre.canvas.canvasy(event.y)) - - def motionend(self,event): - - self.endx = event.x - self.endy = event.y - - - self.imtotreat = self.cadre.resizedim.crop((min(self.begx,self.endx), min(self.begy, self.endy), max(self.endx, self.begx), max(self.endy, self.begy))) - - im = self.imtotreat - - import CNI_pytesseract as pytesseract - - try: - os.environ["PATH"] = CST_FOLDER + "\\Tesseract-OCR4\\" - os.environ["TESSDATA_PREFIX"] = CST_FOLDER + "\\Tesseract-OCR4\\tessdata" - self.text = pytesseract.image_to_string(im, lang="ocrb",boxes=False,config="--psm 6 --oem 0 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<") - - - except pytesseract.TesseractNotFoundError as e: - - try: - os.remove(CST_FOLDER + "\\Tesseract-OCR4\\*.*") - except Exception: - pass - - showerror("Erreur de module OCR", "Le module OCR localisé en " + str(os.environ["PATH"]) + "est introuvable. Il sera réinstallé à la prochaine exécution", parent = self) - - - except pytesseract.TesseractError as e: - - pass - - self.master.success = False - dialogconf = OpenScanDialog(self.master, self.text) - dialogconf.transient(self) - dialogconf.grab_set() - self.wait_window(dialogconf) - - if self.master.success: - self.master.destroy() - - - -class OpenScanWin(Toplevel): - - def __init__(self, parent, file, type, nframe = 1): - - super().__init__(parent) - self.parent = parent - app = OpenScan(self, file, type, nframe) - -class OpenScanDialog(Toplevel): - - def __init__(self, parent, text): - - super().__init__(parent) - self.parent = parent - - self.title('Validation de la MRZ détectée') - - 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="Valider", 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()) # width for the Tk root - h = int(self.winfo_height()) # height for the Tk root - - ws = self.winfo_screenwidth() # width of the screen - hs = self.winfo_screenheight() # height of the screen - - 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.parent.mrzdetected = self.termtext.get('1.0', 'end') - - texting = self.parent.parent.mrzdetected.replace(" ","").replace("\r","").split("\n") - - for i in range(len(texting)): - for char in texting[i]: - if not char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<": - showerror("Erreur de validation", "La MRZ soumise contient des caractères invalides", parent = self) - self.parent.parent.mrzdetected = "" - return - - self.parent.success = True - - self.destroy() - - -class OpenPageDialog(Toplevel): - - def __init__(self, parent, number): - - super().__init__(parent) - self.parent = parent - - self.title("Choisir la page à afficher de l'image selectionnée") - - self.resizable(width=False, height=False) - self.termtext = Label(self, text = "Merci de selectionner un numéro de page dans la liste ci-dessous.") - self.termtext.grid(column=0,row=0,sticky='N', padx=5, pady = 5) - - self.combotry = ttk.Combobox(self) - self.combotry['values'] = tuple(str(x) for x in range(1,number+1) ) - self.combotry.grid(column=0,row=1,sticky='N', padx=5, pady = 5) - - self.button = Button(self, text="Valider", command=self.valid) - self.button.grid(column=0,row=2,sticky='S', padx=5, pady = 5) - - self.update() - hs = self.winfo_screenheight() - - w = int(self.winfo_width()) # width for the Tk root - h = int(self.winfo_height()) # height for the Tk root - - ws = self.winfo_screenwidth() # width of the screen - hs = self.winfo_screenheight() # height of the screen - - 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.page = self.combotry.current() - - self.destroy() \ No newline at end of file diff --git a/CNI_pytesseract.py b/CNI_pytesseract.py deleted file mode 100644 index 9256662..0000000 --- a/CNI_pytesseract.py +++ /dev/null @@ -1,413 +0,0 @@ -""" -******************************************************************************** - *** Projet CNI_Revelator *** - - GNU GPL * 07/2018 - - Adrien Bourmault - - Pytesseract modification to comply with Pyinstaller - -***** -""" -#!/usr/bin/env python - -''' -Python-tesseract. For more information: https://github.com/madmaze/pytesseract -''' - -try: - import Image -except ImportError: - from PIL import Image - -import os -import sys -import subprocess -import tempfile -import shlex -import 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 - -# CHANGE THIS IF TESSERACT IS NOT IN YOUR PATH, OR IS NAMED DIFFERENTLY -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 u' '.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 and isinstance(image, ndarray): - 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))) - - image = prepare(image) - img_extension = image.format - if image.format not in {'JPEG', 'PNG', 'TIFF', 'BMP', 'GIF'}: - img_extension = 'PNG' - - if not image.mode.startswith(RGB_MODE): - image = image.convert(RGB_MODE) - - if 'A' in image.getbands(): - # discard and replace the alpha channel with white background - 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): - # The following is true only on Windows. - if hasattr(subprocess, 'STARTUPINFO'): - # On Windows, subprocess calls will pop up a command window by default - # when run from Pyinstaller with the ``--noconsole`` option. Avoid this - # distraction. - si = subprocess.STARTUPINFO() - si.dwFlags |= subprocess.STARTF_USESHOWWINDOW - # Windows doesn't search the path by default. Pass it an environment so - # it will. - env = os.environ - else: - si = None - env = None - - # ``subprocess.check_output`` doesn't allow specifying ``stdout``:: - # - # Traceback (most recent call last): - # File "test_subprocess.py", line 58, in - # **subprocess_args(stdout=None)) - # File "C:\Python27\lib\subprocess.py", line 567, in check_output - # raise ValueError('stdout argument not allowed, it will be overridden.') - # ValueError: stdout argument not allowed, it will be overridden. - # - # So, add it only if it's needed. - if include_stdout: - ret = {'stdout': subprocess.PIPE} - else: - ret = {} - - # On Windows, running this from the binary produced by Pyinstaller - # with the ``--noconsole`` option requires redirecting everything - # (stdin, stdout, stderr) to avoid an OSError exception - # "[Error 6] the handle is invalid." - ret.update({'stdin': subprocess.PIPE, - 'stderr': subprocess.PIPE, - 'startupinfo': si, - 'env': env - }) - return ret - - return kwargs - - -def run_tesseract(input_filename, - output_filename_base, - extension, - lang, - config='', - nice=0): - cmd_args = [] - - if not sys.platform.startswith('win32') and 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 - - header = rows.pop(0) - if len(rows[-1]) < len(header): - # Fixes bug that occurs when last text string in TSV is null, and - # last row is missing a final cell in TSV file - 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() - - if _type is float: - 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 and 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: - # Added for backwards compatibility - 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) - - args = [image, 'txt', lang, config, nice] - - if output_type == Output.DICT: - return {'text': run_and_get_output(*args)} - elif 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) - elif 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) - elif 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)) - elif 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 - elif len(sys.argv) == 4 and sys.argv[1] == '-l': - filename, lang = sys.argv[3], sys.argv[2] - else: - 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/README.MD b/README.MD new file mode 100644 index 0000000..c1fece4 --- /dev/null +++ b/README.MD @@ -0,0 +1,8 @@ +# CNIRevelator +### Analyzer for MRZ Codes on identity cards, passports and others for Windows. + +This project is released under GNU General Public License v3 and has dependancies modules released under free licenses including : + +Tesseract 4.0 beta, under Apache license 2004 + +Python Anaconda 3, under Berkeley Software Distribution license 2018-2019 diff --git a/README.md b/README.md deleted file mode 100644 index a80771a..0000000 --- a/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# CNIRevelator -Analyzer for MRZ Codes on identity cards, passports and others - -This project is released under GNU General Public License v3 and contains modules released under free licenses including : - -Tesseract 4.0 beta, under Apache license 2004 - -Python Miniconda3, under Berkeley Software Distribution license 2017 - - -It as been developped by NeoX_ in 2018, August. - diff --git a/VERSIONS.LST b/VERSIONS.LST new file mode 100644 index 0000000..a9bb98e --- /dev/null +++ b/VERSIONS.LST @@ -0,0 +1,2 @@ +# ver|url|checksum, and | as separator, one version per || +0.0.0|https://www.os-k.eu|1234||0.0.1|https://www.os-k.eu|1234||0.0.2|https://www.os-k.eu|1234 diff --git a/id-card.ico b/id-card.ico new file mode 100644 index 0000000..efa86e4 Binary files /dev/null and b/id-card.ico differ diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..bfcdb9f --- /dev/null +++ b/make.bat @@ -0,0 +1,18 @@ +@echo off +title Compilation de CNIRLauncher + +call pyinstaller -w -D --exclude-module PyQt5 --bootloader-ignore-signals --add-data "C:\users\adrie\Anaconda3\Lib\site-packages\tld\res\effective_tld_names.dat.txt";"tld\res" --add-data "id-card.ico";"id-card.ico" -i "id-card.ico" -n CNILauncher src\launcher\CNIRLauncher.py + +title Compilation de CNIRevelator + +call pyinstaller -w -D --exclude-module PyQt5 --bootloader-ignore-signals --add-data "C:\users\adrie\Anaconda3\Lib\site-packages\tld\res\effective_tld_names.dat.txt";"tld\res" --add-data "id-card.ico";"id-card.ico" -i "id-card.ico" -n CNIRevelator src\analyzer\CNI_Revelator.py + +copy LICENSE dist\CNIRevelator\ +copy id-card.ico dist\CNIRevelator\ + +robocopy dist\CNILauncher dist\CNIRevelator /E /MOVE + +rmdir dist\CNILauncher /Q /S + +pause + diff --git a/src/analyzer/CNI_GLOBALVAR.py b/src/analyzer/CNI_GLOBALVAR.py new file mode 100644 index 0000000..ba94884 --- /dev/null +++ b/src/analyzer/CNI_GLOBALVAR.py @@ -0,0 +1,59 @@ +""" +******************************************************************************** +* 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 . * +******************************************************************************** +""" + +class changelog: + + def __init__(self): + self.isOn = False + self.text = "Mise-à-jour de sécurité avec corrections suivantes :\n- ajout de la signature numérique de l'exécutable\n- ajout d'une clé de cryptage plus performante pour le stockage des identifiants\n- passage à la méthode de cryptage AES256\n\nAjout/correction des fonctionnalités suivantes :\n- somme de contrôle des téléchargements pour une meilleure fiabilité\n- amélioration de présentation du log en cas d'erreur\n- correction d'un bug affectant l'analyse des MRZ après la correction manuelle\n\nEt un petit bonjour à tout le monde! ;)" + + +CST_REV = '0' +CST_VERTITLE = '2.2' +CST_TAB_VER = ['2', '2', '5'] +CST_VER = '{0}.{1}.{2}'.format(CST_TAB_VER[0], CST_TAB_VER[1], CST_TAB_VER[2]) +CST_TYPE = 'Final Release' +CST_NAME = 'CNIRevelator' +CST_TITLE = CST_NAME + ' ' + CST_VER + ' - GNU/GPL Licensing 2018' +CST_MARK = CST_NAME + ' ' + CST_TYPE + ' ' + CST_VER + ' - by NeoX, GNU/GPL Licensing 2018' +CST_SUM_VER = int(CST_TAB_VER[0]) * 100 + int(CST_TAB_VER[1]) * 10 + int(CST_TAB_VER[2]) +CST_LINK = 'http://neoxgroup.eu/ftpaccess/Applicatifs/CNIRevelator/' +CST_COLOR = '#003380' +CST_TesserHash = '5b58db27f7bc08c58a2cb33d01533b034b067cf8' + + +import base64, hashlib +from Crypto import Random +from Crypto.Cipher import AES +from tkinter import * +from tkinter.messagebox import * +from tkinter import filedialog +from tkinter import ttk +import os, time, threading, sys, urllib.request as urllib2, urllib.error as URLExcept, random +from datetime import datetime + +CST_FOLDER = os.getenv('APPDATA') + '/CNIRevelator/' +CST_CRYPTOKEY = '82Xh!efX3#@P~2eG' +CST_CHANGELOG = changelog() \ No newline at end of file diff --git a/src/analyzer/CNI_Revelator.py b/src/analyzer/CNI_Revelator.py new file mode 100644 index 0000000..947d93f --- /dev/null +++ b/src/analyzer/CNI_Revelator.py @@ -0,0 +1,120 @@ +""" +******************************************************************************** +* 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 . * +******************************************************************************** +""" + +from CNI_GLOBALVAR import * +try: + os.remove('error.log') + os.remove('conf.ig') +except: + print("pass log deletion") + pass + +if not os.path.exists(CST_FOLDER): + try: + os.makedirs(CST_FOLDER) + except IOError: + print("pass IO ERROR") + pass + +print("debug") +import logging +from logging import FileHandler +logger = logging.getLogger() +logger.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(message)s') +error_handler = FileHandler((CST_FOLDER + '\\error.log'), mode='w', encoding='utf-8', delay=True) +info_handler = FileHandler((CST_FOLDER + '\\cnirevelator.log'), mode='w', encoding='utf-8') +error_handler.setLevel(logging.ERROR) +logger.addHandler(error_handler) +info_handler.setLevel(logging.DEBUG) +info_handler.setFormatter(formatter) +logger.addHandler(info_handler) +from CNI_classes import * +from CNI_Update import * + +def main(logger): + logger.error('') + logger.info('main() : **** Creating App_main() ****') + main_w = App_main(logger) + main_w.montext('* ' + CST_NAME + ' ' + CST_VER + ' ' + CST_TYPE + ' Revision ' + CST_REV + ' *\n') + import CNI_pytesseract as pytesseract + try: + os.environ['PATH'] = CST_FOLDER + 'Tesseract-OCR4\\' + os.environ['TESSDATA_PREFIX'] = CST_FOLDER + 'Tesseract-OCR4\\tessdata' + tesser_version = pytesseract.get_tesseract_version() + except Exception as e: + logger.error('main() : **** ERROR WITH TESSERACT MODULE ' + str(e) + ' ****') + else: + text = 'Tesseract version ' + str(tesser_version) + ' Licensed Apache 2004 successfully initiated\n' + main_w.montext(text) + main_w.montext('\n\nEntrez la première ligne de MRZ svp \n') + if CST_CHANGELOG.isOn: + showinfo('Changelog : résumé de mise à jour', ('Version du logiciel : ' + CST_VER + ' ' + CST_TYPE + ' Revision ' + CST_REV + '\n\n' + CST_CHANGELOG.text), parent=main_w) + logger.info('main() : **** Launching App_main() ****') + main_w.mainloop() + logger.info('main() : **** Ending App_main() ****') + + +logger.info('launcher : ' + CST_NAME + ' ' + CST_VER) +logger.info('launcher : *****Hello World*****') +logger.info('launcher : *****Launching SoftUpdate()*****') +try: + Answer = SoftUpdate(logger) +except Exception as e: + logger.info('launcher : *****FATAL ERROR*****' + str(e)) + os.abort() + +logger.info('launcher : *****Ending SoftUpdate()*****') +try: + if Answer == True: + logger.info('launcher : *****Launching main()*****') + State = main(logger) +except Exception as e: + logger.info('launcher : *****FATAL ERROR*****' + str(e)) + os.abort() + +logger.info('launcher : *****Ending main()*****') +logger.info('launcher : *****Goodbye!*****') +handlers = logger.handlers[:] +for handler in handlers: + handler.close() + logger.removeHandler(handler) + +try: + with open(CST_FOLDER + '\\error.log') as (echo): + try: + os.remove('error.log') + except OSError: + pass + + from shutil import copyfile + temptwo = str(echo.read()) + if len(temptwo) != 1: + copyfile(CST_FOLDER + '\\cnirevelator.log', 'error.log') +except IOError: + pass + +print("exit") +sys.exit(0) \ No newline at end of file diff --git a/src/analyzer/CNI_Update.py b/src/analyzer/CNI_Update.py new file mode 100644 index 0000000..834dd39 --- /dev/null +++ b/src/analyzer/CNI_Update.py @@ -0,0 +1,506 @@ +""" +******************************************************************************** +* 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 . * +******************************************************************************** +""" + +from CNI_GLOBALVAR import * +from CNI_classes import * +import hashlib +from pypac import PACSession +from requests.auth import HTTPProxyAuth +import subprocess + +def SoftUpdate(logger): + global ret + global upwin + import zipfile + for f in os.listdir(CST_FOLDER): + if f[-4:] == '.tif': + try: + os.remove(CST_FOLDER + '\\' + f) + except PermissionError as e: + logger.info('SoftUpdate() : Failing to purge : ' + str(e)) + + logger.info('SoftUpdate() : Looking for older version in dir...') + list = os.listdir('.') + for file in list: + if file.startswith('CNIRevelator_'): + temp = ['0', '0', '0'] + ver = file[13:].split('.') + for i in range(len(ver)): + if ver[i] != 'exe': + try: + temp[i] = ver[i] + except: + pass + + ver = temp.copy() + try: + sum_ver = int(ver[0]) * 100 + int(ver[1]) * 10 + int(ver[2]) + if sum_ver < CST_SUM_VER: + if file[-3:] == 'exe': + os.remove(file) + logger.info('SoftUpdate() : Removed old version : ' + str(file)) + CST_CHANGELOG.isOn = True + except Exception as e: + logger.error('SoftUpdate() : Failing to remove old version ' + str(file) + ' : ' + str(e)) + + def updating(): + + def updator(): + global ret + logger.info('[updator() thread] : Welcome !') + ret = 11 + canvas.itemconfigure(message, text='Recherche de mises-à-jour...') + p.configure(mode='indeterminate', value=0, maximum=20) + p.start() + upwin.update() + logger.info('[updator() thread] : Looking for updates...') + try: + + def download(url, filename): + global key + global login + try: + logger.info('[download() thread] : Trying getting credentials in the config file') + with open(CST_FOLDER + 'conf.ig', 'rb') as (config): + AESObj = AESCipher(CST_CRYPTOKEY) + try: + tempone = AESObj.decrypt(config.read()) + if tempone != '||': + if tempone.find('||') != -1: + IPN, IPN_PASS = tempone.split('||')[0:2] + else: + raise ValueError('Cryptokey is bad !') + else: + IPN = '' + IPN_PASS = '' + except Exception as e: + raise IOError(str(e)) + else: + logger.info('[download() thread] : Got credentials !') + session = PACSession(proxy_auth=(HTTPProxyAuth(IPN, IPN_PASS))) + logger.info('[download() thread] : Authenticated to proxy successfully') + except IOError as e: + logger.error('[download() thread] : False or absent credentials in the config file : ' + str(e)) + NoConnect = True + while NoConnect: + + class LoginDialog(Toplevel): + + def __init__(self, parent): + super().__init__(parent) + self.title('Connexion') + Label(self, text='IPN : ').pack() + self.entry_login = Entry(self) + self.entry_login.insert(0, '') + self.entry_login.pack() + Label(self, text='Mot de passe : ').pack() + self.entry_pass = Entry(self, show='*') + self.entry_pass.insert(0, '') + self.entry_pass.pack() + Button(self, text='Connexion', command=(self.connecti)).pack() + self.resizable(width=False, height=False) + w = 150 + h = 110 + self.update() + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + upwin.update() + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + + def connecti(self): + global key + global login + login = self.entry_login.get().strip() + key = self.entry_pass.get().strip() + self.destroy() + + session = PACSession() + if session.get_pac() == None: + IPN = '' + IPN_PASS = '' + NoConnect = False + break + canvas.itemconfigure(message, text='En attente de connexion au serveur proxy...') + login = '' + key = '' + result = LoginDialog(upwin) + result.transient(upwin) + result.grab_set() + upwin.wait_window(result) + IPN = login + IPN_PASS = key + session = PACSession(proxy_auth=(HTTPProxyAuth(IPN, IPN_PASS))) + Ans = session.get('http://www.google.com') + if str(Ans) == '': + canvas.itemconfigure(message, text='Identifiants erronés, accès refusé') + logger.info('[download() thread] : 407 Error') + time.sleep(1) + else: + if str(Ans) == '': + logger.info('[download() thread] : Connection ok !') + NoConnect = False + else: + raise IOError() + + AESObj = AESCipher(CST_CRYPTOKEY) + with open(CST_FOLDER + 'conf.ig', 'wb+') as (f): + logger.info('[download() thread] : Saving credentials in encrypted config file') + f.write(AESObj.encrypt(IPN + '||' + IPN_PASS)) + + if IPN == 'i005316': + canvas.itemconfigure(message, text='Bienvenue Thierry !') + else: + if IPN == 'i020251': + canvas.itemconfigure(message, text='Bienvenue Samia !') + else: + if IPN == 'i018410': + canvas.itemconfigure(message, text='Bienvenue Adrien !') + else: + if IPN == 'i003067': + canvas.itemconfigure(message, text='Bienvenue Remy !') + else: + if IPN == 'i018422': + canvas.itemconfigure(message, text='Bienvenue Eloise !') + time.sleep(1) + try: + Prox_us = session.get_pac().find_proxy_for_url(CST_LINK, 'neoxgroup.eu') + PROXY_USABLE = Prox_us[6:-1].split(';')[0] + proxy_server_url = IPN + ':' + IPN_PASS + '@' + PROXY_USABLE + ph = urllib2.ProxyHandler({'http': proxy_server_url}) + auth = urllib2.ProxyBasicAuthHandler() + server = urllib2.build_opener(ph, auth, urllib2.HTTPHandler) + urllib2.install_opener(server) + logger.info('[download() thread] : Proxy connection initiated successfully') + except: + logger.info('[download() thread] : Proxy connection not initiated') + + try: + urllib2.urlretrieve(url, filename) + return True + except Exception as e: + logger.error('[download() thread] : HTTP ERROR ') + return e + + logger.info('[updator() thread] : Prepare downloading the version recap file...') + tempfile = CST_FOLDER + 'temp' + str(random.randint(11111, 99999)) + '.cniu' + isOk = download(CST_LINK + 'cnir.ver', tempfile) + if not isOk: + raise isOk + urllib2.urlcleanup() + logger.info('[updator() thread] : Opening version recap file...') + file_ver = open(tempfile, 'r') + logger.info('[updator() thread] : Reading version recap file...') + version = file_ver.read() + logger.info('[updator() thread] : Closing version recap file...') + repert = version.split('|') + file_ver.close() + logger.info('[updator() thread] : Deleting version recap file...') + os.remove(tempfile) + logger.info('[updator() thread] : Parsing informations about version...') + final_f = 'CNI_file' + final_ver = ['0', '0', '0'] + final_hash = '' + for sentence in repert: + try: + file, hashref = sentence.split(':') + except ValueError: + pass + else: + if str.startswith(file, CST_NAME): + ver = file.replace(CST_NAME + '_', '').split('.') + temp = [ + '0', '0', '0'] + for i in range(len(ver)): + temp[i] = ver[i] + + ver = temp.copy() + sum_fver = int(final_ver[0]) * 100 + int(final_ver[1]) * 10 + int(final_ver[2]) + sum_ver = int(ver[0]) * 100 + int(ver[1]) * 10 + int(ver[2]) + if sum_ver > sum_fver: + final_ver = ver.copy() + final_f = file + final_hash = hashref + + sum_ver = int(final_ver[0]) * 100 + int(final_ver[1]) * 10 + int(final_ver[2]) + if final_f != 'CNI_file': + if sum_ver > CST_SUM_VER: + logger.info('[updator() thread] : New version of CNIRevelator found !') + canvas.itemconfigure(message, text='Mise à jour disponible ! Préparation du téléchargement...') + logger.info('[updator() thread] : Preparing download') + with open(CST_FOLDER + 'conf.ig', 'rb') as (config): + logger.info('[updator() thread] : Reading credentials for proxy in config file...') + AESObj = AESCipher(CST_CRYPTOKEY) + IPN, IPN_PASS = AESObj.decrypt(config.read()).split('||')[0:2] + session = PACSession(proxy_auth=(HTTPProxyAuth(IPN, IPN_PASS))) + try: + Prox_us = session.get_pac().find_proxy_for_url(CST_LINK, 'neoxgroup.eu') + PROXY_USABLE = Prox_us[6:-1].split(';')[0] + proxy_server_url = IPN + ':' + IPN_PASS + '@' + PROXY_USABLE + ph = urllib2.ProxyHandler({'http': proxy_server_url}) + auth = urllib2.ProxyBasicAuthHandler() + server = urllib2.build_opener(ph, auth, urllib2.HTTPHandler) + logger.info('[updator() thread] : Connection to the proxy initiated successfully !') + except: + canvas.itemconfigure(message, text='Téléchargement en connexion directe...') + server = urllib2.build_opener() + logger.info('[updator() thread] : Direct connection initiated successfully') + + logger.info('[updator() thread] : Launching download of ' + final_f) + Statut = Download(CST_LINK + final_f, final_f, final_f, server, p, canvas, message, logger) + BLOCKSIZE = 65536 + hasher = hashlib.sha1() + try: + with open(final_f, 'rb') as (afile): + buf = afile.read(BLOCKSIZE) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(BLOCKSIZE) + + hashcod = hasher.hexdigest() + if hashcod != final_hash: + Statut.success = False + logger.error('[updator() thread] : Hashcode Error :' + final_f) + try: + os.remove(final_f) + except IOError: + pass + + else: + logger.info('[updator() thread] : Hashcode pass :' + final_f) + except FileNotFoundError as e: + logger.error('[updator() thread] : File not found ' + final_f) + + if Statut.success: + try: + os.rename(final_f, final_f + '.exe') + except IOError: + logger.error('[updator() thread] : Unable to rename the file ! Wait 3 sec and retry') + time.sleep(3) + try: + os.rename(final_f, final_f + '.exe') + except IOError: + logger.critical('[updator() thread] : Unable to rename the file !') + + else: + canvas.itemconfigure(message, text='Téléchargement terminé ! Préparation du lancement...') + logger.info('[updator() thread] : Download of ' + final_f + 'finished successfully') + p.configure(mode='indeterminate', value=0, maximum=20) + p.start() + time.sleep(1) + logger.info('[updator() thread] : Launching ' + final_f) + try: + proc = subprocess.Popen((final_f + '.exe'), shell=False, stdin=None, stdout=None, stderr=None, close_fds=True) + except: + logger.error('[updator() thread] : Unable to start the new version ! Wait 3 sec and retry') + time.sleep(3) + try: + proc = subprocess.Popen((final_f + '.exe'), shell=False, stdin=None, stdout=None, stderr=None, close_fds=True) + except Exception as e: + logger.critical('[updator() thread] : Unable to start the new version ! Stopping : ' + str(e)) + showerror("Erreur d'appel de procédure distante", 'Le lancement du nouveau programme a échoué, vous devez le lancer manuellement une fois cette fenêtre fermée', parent=upwin) + + ret = 12 + else: + canvas.itemconfigure(message, text='Echec de la mise à jour : Erreur HTTP. Préparation du lancement...') + logger.error('[updator() thread] : Update has failed with HTTP error') + time.sleep(1) + canvas.itemconfigure(message, text='Logiciel déjà à jour. Préparation du lancement...') + logger.info('[updator() thread] : CNIRevelator is up to date !') + time.sleep(1) + ret = 11 + if os.path.exists(CST_FOLDER + 'Tesseract-OCR4\\tesseract.exe'): + os.environ['PATH'] = CST_FOLDER + 'Tesseract-OCR4\\' + os.environ['TESSDATA_PREFIX'] = CST_FOLDER + 'Tesseract-OCR4\\tessdata' + else: + final_f = 'tesseract_4' + logger.info('[updator() thread] : Downloading tesseract 4 !') + canvas.itemconfigure(message, text='Mise à jour du module OCR ! Préparation du téléchargement...') + logger.info('[updator() thread] : Preparing download') + with open(CST_FOLDER + 'conf.ig', 'rb') as (config): + logger.info('[updator() thread] : Reading credentials for proxy in config file...') + AESObj = AESCipher(CST_CRYPTOKEY) + IPN, IPN_PASS = AESObj.decrypt(config.read()).split('||')[0:2] + session = PACSession(proxy_auth=(HTTPProxyAuth(IPN, IPN_PASS))) + try: + Prox_us = session.get_pac().find_proxy_for_url(CST_LINK, 'neoxgroup.eu') + PROXY_USABLE = Prox_us[6:-1].split(';')[0] + proxy_server_url = IPN + ':' + IPN_PASS + '@' + PROXY_USABLE + ph = urllib2.ProxyHandler({'http': proxy_server_url}) + auth = urllib2.ProxyBasicAuthHandler() + server = urllib2.build_opener(ph, auth, urllib2.HTTPHandler) + logger.info('[updator() thread] : Connection to the proxy initiated successfully !') + except: + canvas.itemconfigure(message, text='Téléchargement en connexion directe...') + server = urllib2.build_opener() + logger.info('[updator() thread] : Direct connection initiated successfully') + + logger.info('[updator() thread] : Launching download of ' + final_f) + Statut = Download(CST_LINK + final_f, CST_FOLDER + final_f, final_f, server, p, canvas, message, logger) + hashcod = '' + logger.info('[updator() thread] : Verifying hashcode of ' + final_f) + BLOCKSIZE = 65536 + hasher = hashlib.sha1() + try: + with open(CST_FOLDER + final_f, 'rb') as (afile): + buf = afile.read(BLOCKSIZE) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(BLOCKSIZE) + + hashcod = hasher.hexdigest() + if hashcod != CST_TesserHash: + Statut.success = False + logger.error('[updator() thread] : Hashcode Error : ' + final_f) + try: + os.remove(CST_FOLDER + final_f) + except IOError: + pass + + else: + logger.info('[updator() thread] : Hashcode pass : ' + final_f) + except FileNotFoundError as e: + logger.error('[updator() thread] : File not found ' + final_f) + + if Statut.success: + canvas.itemconfigure(message, text='Téléchargement terminé ! Installation...') + logger.info('[updator() thread] : Download of ' + final_f + 'finished successfully') + p.configure(mode='indeterminate', value=0, maximum=20) + p.start() + try: + zip_ref = zipfile.ZipFile(CST_FOLDER + final_f, 'r') + zip_ref.extractall(CST_FOLDER) + zip_ref.close() + os.environ['PATH'] = CST_FOLDER + 'Tesseract-OCR4\\' + os.environ['TESSDATA_PREFIX'] = CST_FOLDER + 'Tesseract-OCR4\\tessdata' + canvas.itemconfigure(message, text='Installation terminée !') + except: + logger.error('[updator() thread] : Unable to install the module. Wait and retry') + time.sleep(3) + try: + zip_ref = zipfile.ZipFile(CST_FOLDER + final_f, 'r') + zip_ref.extractall(CST_FOLDER) + zip_ref.close() + os.environ['PATH'] = CST_FOLDER + 'Tesseract-OCR4\\' + os.environ['TESSDATA_PREFIX'] = CST_FOLDER + 'Tesseract-OCR4\\tessdata' + canvas.itemconfigure(message, text='Installation terminée !') + except Exception as e: + logger.critical('[updator() thread] : Unable to install the module ! Stopping : ' + str(e)) + showerror("Erreur d'appel de procédure distante", "L'installation du module OCR a échoué, contactez le développeur.") + + ret = 11 + else: + logger.critical('[updator() thread] : Unable to download the module ! ') + showerror('Erreur de téléchargement', "L'installation du module OCR a échoué, merci d'indiquer le chemin d'accès au fichier tesseract_4 dans la fenêtre suivante") + path = filedialog.askopenfilename(title="Indiquez le chemin d'accès à tesseract_4...", filetypes=(('Tesseract_4', '*.cni4'), + ('Tesseract_4', '*.cni4'))) + if path != '': + try: + canvas.itemconfigure(message, text='Installation...') + zip_ref = zipfile.ZipFile(path, 'r') + zip_ref.extractall(CST_FOLDER) + zip_ref.close() + logger.error('[updator() thread] : Manual installation successed') + canvas.itemconfigure(message, text='Installation terminée !') + os.environ['PATH'] = CST_FOLDER + 'Tesseract-OCR4\\' + os.environ['TESSDATA_PREFIX'] = CST_FOLDER + 'Tesseract-OCR4\\tessdata' + except Exception as e: + logger.error('[updator() thread] : Manual installation has failed' + str(e)) + showerror('Erreur de lecture', "Le module OCR n'a pas pu être installé, la saisie automatique de scans ne pourra donc fonctionner") + + else: + showerror('Opération annulée', "Le module OCR n'a été installé, la saisie automatique de scans ne pourra donc fonctionner") + except URLExcept.HTTPError as e: + canvas.itemconfigure(message, text=('Echec de la mise à jour : Erreur HTTP ' + str(e.code) + ' . Préparation du lancement...')) + logger.error('[updator() thread] : Update has failed with HTTP error' + str(e.code)) + if int(e.code) == 407: + showerror('Erreur 407', 'Attention : le système de mise à jour automatique a fait face à une erreur 407, signifiant que la connexion au serveur proxy a été refusée. Vos identifiants vous seront redemandés au prochain démarrage. La mise à jour a échoué.') + logger.info('[updator() thread] : Credential error. Deleting the config file...') + os.remove(CST_FOLDER + 'conf.ig') + p.configure(mode='indeterminate', value=0, maximum=20) + p.start() + time.sleep(3) + except Exception as e: + canvas.itemconfigure(message, text='Echec de la mise à jour. Préparation du lancement...') + logger.error('[updator() thread] : Error from the updating system : ' + str(e)) + p.configure(mode='indeterminate', value=0, maximum=20) + p.start() + time.sleep(2) + + p.stop() + upwin.destroy() + root.destroy() + return ret + + logger.info('updating() : Launching updator() thread...') + threading.Thread(target=updator, daemon=True).start() + logger.info('updating() [Thread] : Ending updator() thread') + + ret = 11 + root = Tk() + root.attributes('-alpha', 0.0) + root.iconify() + upwin = Toplevel(root) + upwin.overrideredirect(1) + upwin.configure(bg=CST_COLOR) + upwin.resizable(width=False, height=False) + w = 600 + h = 300 + upwin.update() + canvas = Canvas(upwin, width=600, height=270, bg=CST_COLOR, highlightthickness=0) + pbar = Canvas(upwin, width=600, height=30, bg=CST_COLOR) + p = ttk.Progressbar(pbar, orient=HORIZONTAL, length=590, mode='determinate') + upwin.update() + ws = upwin.winfo_screenwidth() + hs = upwin.winfo_screenheight() + canvas.create_text((w / 2), (h / 3), text=(CST_NAME + ' ' + CST_VERTITLE), font='Calibri 30 bold', fill='white') + message = canvas.create_text((w / 2), (h / 1.15), text=' ', font='Calibri 9', fill='white') + upwin.update() + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + upwin.geometry('%dx%d+%d+%d' % (w, h, x, y)) + canvas.grid() + pbar.grid() + p.grid() + upwin.after(2000, updating) + if getattr(sys, 'frozen', False): + root.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + root.iconbitmap('id-card.ico') + logger.info('SoftUpdate() : Entering upwin mainloop()') + upwin.protocol('WM_DELETE_WINDOW', lambda : root.destroy()) + upwin.mainloop() + logger.info('SoftUpdate() : Exiting upwin mainloop()') + if ret == 11: + logger.info('SoftUpdate() : OK to start to main() normally !') + return True + else: + logger.info('SoftUpdate() : Program will stop !') + return False \ No newline at end of file diff --git a/src/analyzer/CNI_classes.py b/src/analyzer/CNI_classes.py new file mode 100644 index 0000000..18695a3 --- /dev/null +++ b/src/analyzer/CNI_classes.py @@ -0,0 +1,1332 @@ +""" +******************************************************************************** +* 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 . * +******************************************************************************** +""" + +from CNI_GLOBALVAR import * +from PIL import Image, ImageFont, ImageDraw, ImageTk, ImageEnhance, ImageFilter +import math, warnings, string + +class App_main(Tk): + + def __init__(self, logger): + Tk.__init__(self) + self.initialize(logger) + + def initialize(self, logger): + self.logger = logger + self.PILE_ETAT = [] + self.MRZCHAR = '' + self.varnum = 10 + self.Score = [] + for type in MRZCODE.TYPES: + self.Score += [0] + + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + self.logger.info('App_main() : Launching main window with resolution' + str(ws) + 'x' + str(hs)) + self.grid() + self.grid_columnconfigure(0, weight=1, minsize=(ws / 2 * 0.3333333333333333)) + self.grid_columnconfigure(1, weight=1, minsize=(ws / 2 * 0.3333333333333333)) + self.grid_columnconfigure(2, weight=1, minsize=(ws / 2 * 0.3333333333333333)) + self.grid_rowconfigure(0, weight=1, minsize=(hs / 2 * 0.5)) + self.grid_rowconfigure(1, weight=1, minsize=(hs / 2 * 0.5)) + self.lecteur_ci = ttk.Labelframe(self, text="Informations sur la pièce d'identité") + 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) + ttk.Label((self.lecteur_ci), text='Nom : ').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='Nom (2) : ').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='Date de naissance : ').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='Date de délivrance : ').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="Date d'expiration : ").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='Sexe du porteur : ').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='Pays de délivrance : ').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='Nationalité du porteur : ').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='Immatriculation : ').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='Numéro de document : ').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'] = 'Inconnu(e)' + self.prenom['text'] = 'Inconnu(e)' + self.bdate['text'] = 'Inconnu(e)' + self.ddate['text'] = 'Inconnu(e)' + self.edate['text'] = 'Inconnu(e)' + self.no['text'] = 'Inconnu(e)' + self.sex['text'] = 'Inconnu(e)' + self.nat['text'] = 'Inconnu(e)' + self.pays['text'] = 'Inconnu(e)' + self.indic['text'] = 'Inconnu(e)' + self.STATUT = ttk.Labelframe(self, text='Statut') + self.STATUT.grid_columnconfigure(0, weight=1) + self.STATUT.grid_rowconfigure(0, weight=1) + self.STATUStxt = Label((self.STATUT), text='', font='Times 24', fg='#FFBF00') + self.STATUStxt.grid(column=0, row=0, padx=0, pady=0, sticky='EWNS') + self.STATUStxt['text'] = 'EN ATTENTE' + self.terminal = ttk.Labelframe(self, text='Terminal de saisie') + 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.termtext = Text((self.termframe), state='disabled', width=60, height=4, wrap='none', font='Terminal 17', fg='#121f38') + self.termtext.grid(column=0, row=0, sticky='NEW', padx=5) + vcmd = (self.register(self.validate), '%S', '%P', '%d') + self.termentry = Entry((self.termframe), font='Terminal 17', validate='all', validatecommand=vcmd, fg='#121f38', width=44) + self.termentry.grid(column=0, row=0, sticky='SEW', padx=5) + self.monitor = ttk.Labelframe(self, text='Moniteur') + 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) + self.lecteur_ci.grid(column=0, row=0, sticky='EWNS', columnspan=2, padx=5, pady=5) + self.STATUT.grid(column=2, row=0, sticky='EWNS', columnspan=1, padx=5, pady=5) + self.terminal.grid(column=0, row=1, sticky='EWNS', columnspan=2, padx=5, pady=5) + self.monitor.grid(column=2, row=1, sticky='EWNS', columnspan=1, padx=5, pady=5) + menubar = Menu(self) + menu1 = Menu(menubar, tearoff=0) + menu1.add_command(label='Nouveau', command=(self.newbie)) + menu1.add_command(label='Ouvrir scan...', command=(self.openingscan)) + menu1.add_separator() + menu1.add_command(label='Quitter', command=(self.destroy)) + menubar.add_cascade(label='Fichier', menu=menu1) + menu3 = Menu(menubar, tearoff=0) + menu3.add_command(label='A propos', command=(self.infobox)) + menubar.add_cascade(label='Aide', menu=menu3) + self.config(menu=menubar) + self.wm_title(CST_TITLE) + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + self.resizable(width=True, height=True) + self.update() + self.minsize(self.winfo_width(), self.winfo_height()) + 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)) + self.termentry.bind('', self.returnaj) + self.termtext.bind('', self.returnaj) + self.termentry.bind('', self.tabaj) + self.update() + self.logger.info('App_main() : Initialization successful') + + def returnaj(self, event): + self.logger.debug('returnaj() : Entering Validation') + if self.PILE_ETAT != [] and len(self.PILE_ETAT) == 1: + thetext = self.termentry.get() + self.logger.debug('returnaj() : PILE_ETAT Satisfy the requisites : ' + str(self.PILE_ETAT)) + n = len(thetext) + champ = MRZCODE.TYPES[self.PILE_ETAT[0]][0] + if not n % champ.find('|') == 0: + self.logger.debug('returnaj() : Line not complete, operation aborted') + return 'break' + self.MRZCHAR += thetext + self.termtext['state'] = 'normal' + self.termtext.insert('end', thetext + '\n') + self.termtext['state'] = 'disabled' + self.termentry.delete(0, 'end') + if len(self.MRZCHAR) == champ.find('|'): + self.montext('Entrez la seconde ligne de la MRZ ou \nappuyez sur Entrée pour terminer.\n') + self.logger.debug('returnaj() : First line accepted') + self.MRZCHAR += '|' + else: + if len(self.MRZCHAR) == champ.find('|') * 2 + 1 or len(champ) == champ.find('|') + 1: + self.montext('\nCalcul des sommes ...\n') + self.logger.info('returnaj() : Launching calculsigma() thread') + threading.Thread(target=(self.calculsigma), args=[self.MRZCHAR, self.PILE_ETAT[0]]).start() + else: + if self.PILE_ETAT != []: + if len(self.PILE_ETAT) == 2: + self.MRZCHAR = self.termtext.get('1.0', 'end').replace('\n', '|') + temp = self.termtext.get('1.0', 'end') + self.termtext.delete('1.0', 'end') + self.termtext.insert('1.0', temp) + self.logger.debug('returnaj() : PILE_ETAT Satisfy the requisites : ' + str(self.PILE_ETAT)) + n = len(self.MRZCHAR) + champ = MRZCODE.TYPES[self.PILE_ETAT[1]][0] + for char in self.MRZCHAR: + if char not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<|': + self.montext('\nSyntaxe erronée, caractère incompatible') + return 'break' + if self.MRZCHAR[(-1)] == '|': + self.MRZCHAR = self.MRZCHAR[:-1] + + if len(self.MRZCHAR) == champ.find('|') * 2 + 1 or len(champ) == champ.find('|') + 1: + self.logger.debug('returnaj() : : ' + str(self.PILE_ETAT)) + self.montext('\nCalcul des sommes ...\n') + self.logger.info('returnaj() : Launching calculsigma() thread') + threading.Thread(target=(self.calculsigma), args=[self.MRZCHAR, self.PILE_ETAT[1]]).start() + return 'break' + + def tabaj(self, event): + if self.PILE_ETAT != [] and len(self.PILE_ETAT) == 1: + thetext = self.termentry.get() + n = len(thetext) + champ = MRZCODE.TYPES[self.PILE_ETAT[0]][0] + if len(self.MRZCHAR) <= champ.find('|'): + champ_type = MRZCODE.TYPES[self.PILE_ETAT[0]][1][champ[(n - 1)]].split('|') + self.logger.debug('tabaj() : First line detected') + self.logger.debug('tabaj() : champ_type[0] : ' + str(champ_type[0])) + self.logger.debug('tabaj() : champ : ' + str(champ)) + self.logger.debug('tabaj() : champ[n-1] : ' + str(champ[(n - 1)])) + self.logger.debug('tabaj() : champ.find(champ[n-1]) : ' + str(champ.find(champ[(n - 1)]))) + nb = int(champ_type[0]) - (n - champ.find(champ[(n - 1)])) + else: + self.logger.debug('tabaj() : Second line detected') + champ = champ[champ.find('|') + 1:] + champ_type = MRZCODE.TYPES[self.PILE_ETAT[0]][1][champ[(n - 1)]].split('|') + self.logger.debug('tabaj() : champ_type[0] : ' + str(champ_type[0])) + self.logger.debug('tabaj() : champ : ' + str(champ)) + self.logger.debug('tabaj() : champ[n-1] : ' + str(champ[(n - 1)])) + self.logger.debug('tabaj() : champ.find(champ[n-1]) : ' + str(champ.find(champ[(n - 1)]))) + self.logger.debug('tabaj() : n : ' + str(n)) + nb = int(champ_type[0]) - (n - champ.find(champ[(n - 1)])) + self.termentry.insert('end', '<' * nb) + self.logger.debug('tabaj() : Completing entry with ' + str(nb) + ' characters <') + return 'break' + + def validate(self, char, entry_value, typemod): + set = False + isValid = True + if typemod == '1': + SUM = 0 + for ch in char: + if ch in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<': + continue + self.bell() + isValid = False + + else: + if typemod == '0': + for ch in char: + if ch in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<': + continue + self.bell() + isValid = False + + n = len(entry_value) + if (n % self.varnum == 0 or len(char) > 3) and n < 45 and self.PILE_ETAT == []: + for i in range(len(self.Score)): + self.Score[i] = 0 + + for type, t in zip(MRZCODE.TYPES, range(len(MRZCODE.TYPES))): + for e in range(len(entry_value)): + try: + champchartest = type[0][:type[0].find('|')][e] + champchar = type[0][e] + except IndexError: + self.logger.debug('validate() : type : ' + str(t)) + self.logger.debug('validate() : Too short to be ok') + self.Score[t] += -5 + break + else: + self.logger.debug('validate() : type : ' + str(t)) + if len(entry_value) <= type[0].find('|'): + champ_type = type[1][str(champchar)].split('|') + pos = e - type[0].find(champchar) + self.logger.debug('validate() : champ_type[2][pos] : ' + str(champ_type[2][pos])) + self.logger.debug('validate() : champ_type[1] : ' + str(champ_type[1])) + if champ_type[1] == 'CODE': + if champ_type[2][pos] == '*': + self.Score[t] += 0 + else: + if champ_type[2][pos] == entry_value[e]: + self.Score[t] += 1 + self.logger.debug('validate() : +1') + else: + self.Score[t] += -50 + elif champ_type[2][pos] == '*': + self.Score[t] += 1 + self.logger.debug('validate() : +1') + else: + if champ_type[2][pos] == 'A': + if entry_value[e].isalpha(): + self.Score[t] += 1 + self.logger.debug('validate() : +1') + if champ_type[2][pos] == '0': + if entry_value[e].isnumeric(): + self.Score[t] += 1 + self.logger.debug('validate() : +1') + if champ_type[1] == 'CTRL': + if entry_value[e].isnumeric(): + self.Score[t] += 1 + self.logger.debug('validate() : +1') + if champ_type[2][pos] == '&': + if entry_value[e].isalpha() or entry_value[e] == '<': + self.Score[t] += 1 + self.logger.debug('validate() : +1') + self.Score[t] += -1 + continue + + self.logger.debug('validate() : self.Score : ' + str(self.Score)) + m = max(self.Score) + typem = [i for i, j in enumerate(self.Score) if j == m if m > 5] + for h in typem: + self.PILE_ETAT += [h] + + if len(self.PILE_ETAT) > 1: + self.varnum += 3 + self.PILE_ETAT = [] + else: + if len(self.PILE_ETAT) == 1: + TOPOS = MRZCODE.TYPES[self.PILE_ETAT[0]][2] + self.montext(TOPOS + " détectée !\nAppuyez sur Echap pour compléter les champs avec des '<'\nAppuyez sur Entrée pour terminer.\n") + self.logger.debug('validate() : Detection : ' + str(TOPOS)) + else: + if len(self.PILE_ETAT) == 1: + if len(entry_value) > len(MRZCODE.TYPES[self.PILE_ETAT[0]][0].split('|')[0]): + isValid = False + return isValid + + def montext(self, text): + self.monlog['state'] = 'normal' + self.monlog.insert('end', text) + self.monlog['state'] = 'disabled' + self.monlog.yview(END) + + def openingscan(self): + self.initialize(self.logger) + self.update() + path = '' + path = filedialog.askopenfilename(parent=self, title='Ouvrir un scan de CNI...', filetypes=(('TIF files', '*.tif'), + ('TIF files', '*.tiff'), + ('JPEG files', '*.jpg'), + ('JPEG files', '*.jpeg'))) + self.openerrored = False + if path != '': + self.logger.info('openingscan() : Opening file with path ' + str(path)) + im = Image.open(path) + try: + nframe = im.n_frames + except AttributeError: + nframe = 1 + + if nframe == 1: + self.mrzdetected = '' + self.mrzdict = {} + try: + opening = OpenScanWin(self, path, 0) + opening.transient(self) + opening.grab_set() + self.wait_window(opening) + except TclError: + pass + except Exception as e: + self.logger.critical('openingscan() : ' + str(e)) + + if self.openerrored == True: + self.logger.error('openingscan() : Incompatible file with path ' + str(path)) + return + self.logger.debug('openingscan() : ' + str(self.mrzdetected)) + else: + if nframe > 1: + self.mrzdetected = '' + self.mrzdict = {} + try: + opening = OpenScanWin(self, path, 1, nframe) + opening.transient(self) + opening.grab_set() + self.wait_window(opening) + except TclError: + pass + except Exception as e: + self.logger.critical('openingscan() : ' + str(e)) + + if self.openerrored == True: + self.logger.critical('openingscan() : Incompatible file with path ' + str(path)) + return + self.logger.debug('openingscan() : ' + str(self.mrzdetected)) + try: + os.remove(CST_FOLDER + '\\temp.tif') + except IOError: + pass + + else: + raise Exception + mrzsoumisetab = self.mrzdetected.replace(' ', '').split('\n') + for chain in mrzsoumisetab: + self.termentry.insert('end', chain) + if len(chain) >= 5: + self.returnaj('') + + def newbie(self): + self.initialize(self.logger) + self.montext('\n\nEntrez la première ligne de MRZ svp \n') + + def infobox(self): + Tk().withdraw() + showinfo('A propos du logiciel', ('Version du logiciel : \n' + CST_NAME + ' ' + CST_VER + ' ' + CST_TYPE + ' Revision ' + CST_REV + "\nLicence GNU/GPL 2018\n\nAuteur : NeoX_ ; devadmin@neoxgroup.eu\n\nTesseract 4.0 est soumis à l'Apache License 2004\n\n N'hésitez pas à faire part de vos commentaires !"), parent=self) + + def calculsigma(self, MRZtxt, numtype): + CST_BACKGROUND = self['background'] + CTRList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'CTRL' in v] + self.logger.info('[calculsigma() thread] : Sigma calculation launched!') + self.logger.debug('[calculsigma() thread] : CTRList = ' + str(CTRList)) + self.Falsitude = 0 + self.montext('\n') + for i in CTRList: + sumtxt = MRZCODE.TYPES[numtype][1][i] + length = MRZCODE.TYPES[numtype][0].find('|') + index = MRZCODE.TYPES[numtype][0].find(i) + sum_read = MRZtxt[index] + if len(sumtxt.split('|')[2]) == 1: + debut = MRZCODE.TYPES[numtype][0].find(sumtxt.split('|')[2][0]) + sum_calc = MRZCODE.MRZ(MRZtxt[int(debut):index]) + else: + transm_chain = '' + for y in sumtxt.split('|')[2]: + debut = MRZCODE.TYPES[numtype][0].find(y) + fin = debut + int(MRZCODE.TYPES[numtype][1][y].split('|')[0]) + transm_chain += MRZtxt[int(debut):int(fin)] + + sum_calc = MRZCODE.MRZ(transm_chain) + if str(sum_read)[0] != str(sum_calc)[0]: + if not (sumtxt.split('|')[1] == 'CTRLF' and str(sum_read)[0] == '<'): + self.Falsitude += 1 + self.logger.debug('[calculsigma() thread] : Falsitude +1, sum errored : ' + str(i)) + self.termtext.tag_add('highLOW', '1.0+' + str(index) + 'c', '1.0+' + str(index + 1) + 'c') + self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground='white') + self.termtext.tag_add('highLOWB', '1.0+' + str(index) + 'c', '1.0+' + str(index + 1) + 'c') + self.termtext.tag_configure('highLOWB', background='#04B404', relief='raised', foreground='white') + self.montext('Somme : Lu ' + str(sum_read) + ' VS calculé ' + str(sum_calc) + '\n') + + NameList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if '|NOM' in v] + SurnameList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'PRENOM' in v] + DDateList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'DDATE' in v] + BDateList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'BDATE' in v] + EDateList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'EDATE' in v] + PAYSList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'PAYS' in v] + NATList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'NAT' in v] + SEXList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'SEX' in v] + NOINTList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'NOINT' in v] + NOList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'NO|' in v] + FACULTList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'FACULT' in v] + INDICList = [c for c, v in MRZCODE.TYPES[numtype][1].items() if 'INDIC' in v] + BIGList = [ + NameList, SurnameList, DDateList, BDateList, EDateList, PAYSList, NATList, SEXList, NOList, INDICList, NOINTList] + BIGObj = [ + self.nom, self.prenom, self.ddate, self.bdate, self.edate, self.pays, self.nat, self.sex, self.no, self.indic, self.no] + for i in range(len(BIGList)): + for champ, champnum in zip(BIGList[i], range(len(BIGList[i]))): + debut = MRZCODE.TYPES[numtype][0].find(champ) + fin = debut + int(MRZCODE.TYPES[numtype][1][champ].split('|')[0]) + if BIGObj[i] == self.pays or BIGObj[i] == self.nat: + try: + BIGObj[i]['text'] = MRZCODE.landcode[MRZtxt[int(debut):int(fin)].replace('<', '')] + except KeyError: + self.Falsitude += 1 + self.montext('Code pays : ' + str(MRZtxt[int(debut):int(fin)]) + ' est inconnu \n') + self.logger.debug('[calculsigma() thread] : Falsitude +1, unknown state') + self.termtext.tag_add('highLOW', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground='white') + BIGObj[i]['background'] = '#760808' + BIGObj[i]['foreground'] = 'white' + else: + BIGObj[i]['background'] = CST_BACKGROUND + BIGObj[i]['foreground'] = 'black' + self.termtext.tag_add('highLOWN', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOWN', background='white', relief='raised', foreground='#121f38') + elif BIGObj[i] == self.sex: + try: + BIGObj[i]['text'] = MRZCODE.sexcode[MRZtxt[int(debut):int(fin)]] + except KeyError: + self.Falsitude += 1 + self.montext('Sexe : ' + str(MRZtxt[int(debut):int(fin)]) + ' est inconnu \n') + self.logger.debug('[calculsigma() thread] : Falsitude +1, unknown state') + self.termtext.tag_add('highLOW', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground='white') + BIGObj[i]['background'] = '#760808' + BIGObj[i]['foreground'] = 'white' + else: + BIGObj[i]['background'] = CST_BACKGROUND + BIGObj[i]['foreground'] = 'black' + self.termtext.tag_add('highLOWN', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOWN', background='white', relief='raised', foreground='#121f38') + else: + if BIGObj[i] == self.edate or BIGObj[i] == self.ddate or BIGObj[i] == self.bdate: + txtl = MRZtxt[int(debut):int(fin)].replace('<<<', '').replace('<', ' ') + if len(txtl) < 6: + txtl += '0' * (6 - len(txtl)) + BIGObj[i]['text'] = '{0}/{1}/{2}'.format(txtl[4:], txtl[2:4], txtl[:2]) + if BIGObj[i] == self.edate: + present = datetime.now() + try: + expiration = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), int(txtl[4:])) + except ValueError: + BIGObj[i]['background'] = '#760808' + BIGObj[i]['foreground'] = 'white' + self.Falsitude += 1 + self.montext('Date : ' + str(BIGObj[i]['text']) + ' est invalide \n') + self.logger.debug('[calculsigma() thread] : Falsitude +1, invalid expiration date') + self.termtext.tag_add('highLOW', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground='white') + else: + BIGObj[i]['background'] = CST_BACKGROUND + BIGObj[i]['foreground'] = 'black' + self.termtext.tag_add('highLOWN', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOWN', background='white', relief='raised', foreground='#121f38') + if expiration < present: + BIGObj[i]['background'] = '#e67300' + BIGObj[i]['foreground'] = 'white' + self.termtext.tag_add('highLOWN', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOWN', background='white', relief='raised', foreground='#121f38') + else: + BIGObj[i]['background'] = CST_BACKGROUND + BIGObj[i]['foreground'] = 'black' + else: + try: + if int(txtl[4:]) == 0: + verif = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), 1) + else: + verif = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), int(txtl[4:])) + except ValueError: + BIGObj[i]['background'] = '#760808' + BIGObj[i]['foreground'] = 'white' + self.Falsitude += 1 + self.montext('Date : ' + str(BIGObj[i]['text']) + ' est invalide \n') + self.logger.debug('[calculsigma() thread] : Falsitude +1, invalid datetime') + self.termtext.tag_add('highLOW', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground='white') + else: + BIGObj[i]['background'] = CST_BACKGROUND + BIGObj[i]['foreground'] = 'black' + self.termtext.tag_add('highLOWN', '1.0+' + str(debut) + 'c', '1.0+' + str(fin) + 'c') + self.termtext.tag_configure('highLOWN', background=CST_BACKGROUND, relief='raised', foreground='#121f38') + else: + if champnum == 0: + BIGObj[i]['text'] = MRZtxt[int(debut):int(fin)].replace('<<<', '').replace('<', ' ') + else: + BIGObj[i]['text'] += MRZtxt[int(debut):int(fin)].replace('<<<', '').replace('<', ' ') + + if self.Falsitude == 0: + self.STATUStxt['text'] = 'CONFORME' + self.STATUStxt['fg'] = '#04B404' + self.logger.debug('[calculsigma() thread] : Conforme !') + else: + self.STATUStxt['text'] = 'NON CONFORME' + self.STATUStxt['fg'] = '#760808' + self.logger.debug('[calculsigma() thread] : Non conforme !') + self.montext('** Score de non conformité : ' + str(self.Falsitude) + '**\n') + self.termtext['state'] = 'normal' + self.PILE_ETAT = [self.Falsitude, numtype] + + +class MRZCODE: + + def MRZ(code): + """ + Calcul sommes de contrôle de la chaîne transmise + """ + resultat = 0 + i = -1 + facteur = [7, 3, 1] + for car in code: + if car == '<' or car == '|': + 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 + + sexcode = {'M':'Homme', + 'F':'Femme', 'X':'Non spécifié'} + 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'} + landcode = {'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'} + P = ['11222333333333333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCCCCCCCCDE', {'1':'2|CODE|P*', '2':'3|PAYS|AAA', '3':'39|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&', '4':'9|NO|*********', '5':'1|CTRL|4', '6':'3|NAT|AAA', '7':'6|BDATE|000000', '8':'1|CTRL|7', '9':'1|SEX|A', 'A':'6|EDATE|000000', 'B':'1|CTRL|A', 'C':'14|FACULT|**************', 'D':'1|CTRLF|C', 'E':'1|CTRL|4578ABCD'}, 'Passeport'] + IP = ['112223333333334555555555555555|66666678999999ABBBCCCCCCCCCCCD', {'1':'2|CODE|IP', '2':'3|PAYS|AAA', '3':'9|NO|*********', '4':'1|CTRL|3', '5':'15|FACULT|***************', '6':'6|BDATE|000000', '7':'1|CTRL|6', '8':'1|SEX|A', '9':'6|EDATE|000000', 'A':'1|CTRL|9', 'B':'3|NAT|AAA', 'C':'11|FACULT|***********', 'D':'1|CTRL|345679AC'}, 'Carte-passeport'] + I_ = ['112223333333334555555555555555|66666678999999ABBBCCCCCCCCCCCD', {'1':'2|CODE|I*', '2':'3|PAYS|AAA', '3':'9|NO|*********', '4':'1|CTRL|3', '5':'15|FACULT|***************', '6':'6|BDATE|000000', '7':'1|CTRL|6', '8':'1|SEX|A', '9':'6|EDATE|000000', 'A':'1|CTRL|9', 'B':'3|NAT|AAA', 'C':'11|FACULT|***********', 'D':'1|CTRL|345679AC'}, "Titre d'identité/de voyage"] + AC = ['112223333333334EEE555555555555|66666678999999ABBBCCCCCCCCCCCD', {'1':'2|CODE|AC', '2':'3|PAYS|AAA', '3':'9|NO|*********', '4':'1|CTRL|3', '5':'15|FACULT|***************', '6':'6|BDATE|000000', '7':'1|CTRL|6', '8':'1|SEX|A', '9':'6|EDATE|000000', 'A':'1|CTRL|9', 'B':'3|NAT|AAA', 'C':'11|FACULT|***********', 'D':'1|CTRL|345679AC', 'E':'3|INDIC|AA&'}, "Certificat de membre d'équipage"] + VA = ['11222333333333333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCCCCCCCCDE', {'1':'2|CODE|V*', '2':'3|PAYS|AAA', '3':'39|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&', '4':'9|NO|*********', '5':'1|CTRL|4', '6':'3|NAT|AAA', '7':'6|BDATE|000000', '8':'1|CTRL|7', '9':'1|SEX|A', 'A':'6|EDATE|000000', 'B':'1|CTRL|A', 'C':'14|FACULT|**************'}, 'Visa de type A'] + VB = ['112223333333333333333333333333333333|444444444566677777789AAAAAABCCCCCC', {'1':'2|CODE|V*', '2':'3|PAYS|AAA', '3':'31|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&', '4':'9|NO|*********', '5':'1|CTRL|4', '6':'3|NAT|AAA', '7':'6|BDATE|000000', '8':'1|CTRL|7', '9':'1|SEX|A', 'A':'6|EDATE|000000', 'B':'1|CTRL|A', 'C':'8|FACULT|********'}, 'Visa de type B'] + I__ = ['112223333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCD', {'1':'2|CODE|I*', '2':'3|PAYS|AAA', '3':'31|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&', '4':'9|NO|*********', '5':'1|CTRL|4', '6':'3|NAT|AAA', '7':'6|BDATE|000000', '8':'1|CTRL|7', '9':'1|SEX|A', 'A':'6|EDATE|000000', 'B':'1|CTRL|A', 'C':'7|FACULT|*******', 'D':'1|CTRL|4578ABC'}, "Pièce d'identité/de voyage"] + ID = ['112223333333333333333333333333444444|555566677777899999999999999AAAAAABCD', {'1':'2|CODE|ID', '2':'3|PAYS|AAA', '3':'25|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&', '4':'6|NOINT|000***', '5':'4|DDATE|0000', '6':'3|NOINT2|000', '7':'5|NOINT3|00000', '8':'1|CTRL|567', '9':'14|PRENOM|A', 'A':'6|BDATE|000000', 'B':'1|CTRL|A', 'C':'1|SEX|A', 'D':'1|CTRL|123456789ABC'}, "Pièce d'identité FR"] + DL = ['112223333333334555555666666667|', {'1':'2|CODE|D1', '2':'3|PAYS|AAA', '3':'9|NO|00AA00000', '4':'1|CTRL|123', '5':'6|EDATE|000000', '6':'8|NOM|&&&&&&&&', '7':'1|CTRL|123456'}, 'Permis de conduire'] + TYPES = [ID, I__, VB, VA, AC, I_, IP, P, DL] + + +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 Download: + + def __init__(self, url, localfile, namefile, opener, p, canvas, message, logger): + from socket import timeout + self.retry = 6 + self.logger = logger + self.opener = opener + self.url = url + self.localfile = localfile + self.success = False + self.time = 5 + self.count = 0 + self.success = False + self.downloading = True + self.logger.info('[Download() class] : Initialization ok') + while not self.success and self.downloading: + try: + self.Err = '' + self._netfile = self.opener.open((self.url), timeout=(self.time)) + self.filesize = float(self._netfile.info()['Content-Length']) + if os.path.exists(self.localfile): + if os.path.isfile(self.localfile): + self.count = os.path.getsize(self.localfile) + if self.count >= self.filesize: + self.downloading = False + self.success = True + self._netfile.close() + return + if os.path.exists(self.localfile): + if os.path.isfile(self.localfile): + self._netfile.close() + req = urllib2.Request(self.url) + req.add_header('Range', 'bytes=%s-' % self.count) + self._netfile = self.opener.open(req, timeout=(self.time)) + if self.downloading: + self._outfile = open(self.localfile, 'ab') + next = self._netfile.read(1024) + p.stop() + p.configure(mode='determinate', value=0, maximum=100) + while len(next) > 0: + if self.downloading: + self._outfile.write(next) + self.count += len(next) + next = self._netfile.read(1024) + Percent = int(self.count / self.filesize * 100) + p.configure(mode='determinate', value=(int(Percent))) + canvas.itemconfigure(message, text=('Téléchargement de ' + str(namefile) + ' de taille ' + str(int(self.filesize / 1024 / 1024)) + ' Mo : ' + str(Percent) + ' %')) + + self._netfile.close() + self._outfile.close() + self.success = True + except urllib2.HTTPError as e: + self.logger.error('[Download() class] : HTTP ERROR ' + str(e.code)) + try: + self._netfile.close() + self._outfile.close() + except Exception as err: + self.logger.critical('[Download() class] : FILE I/O ERROR : ' + str(err)) + + self.retry += -1 + canvas.itemconfigure(message, text=('Erreur HTTP ' + str(e.code) + '. Nouvelles tentatives : ' + str(self.retry))) + if self.retry <= 0: + self.downloading = False + except timeout as e: + self.logger.error('[Download() class] : TIMEOUT ERROR : ' + str(e)) + try: + self._netfile.close() + self._outfile.close() + except Exception as err: + self.logger.critical('[Download() class] : FILE I/O ERROR : ' + str(err)) + + self.retry += -1 + self.time *= 2 + canvas.itemconfigure(message, text=('Connexion expirée. Nouvelles tentatives : ' + str(self.retry) + ', ' + str(self.time) + ' s')) + if self.retry <= 0: + self.downloading = False + except IOError as e: + self.logger.error('[Download() class] : I/O ERROR :' + str(e)) + try: + self._netfile.close() + self._outfile.close() + except Exception as err: + self.logger.critical('[Download() class] : FILE I/O ERROR : ' + str(err)) + + self.retry += -1 + self.time *= 2 + canvas.itemconfigure(message, text=('Connexion expirée. Nouvelles tentatives : ' + str(self.retry) + ', ' + str(self.time) + ' s')) + if self.retry <= 0: + self.downloading = False + except Exception as e: + self.logger.error('[Download() class] : UNKNOWN ERROR : ' + str(e)) + try: + self._netfile.close() + self._outfile.close() + except Exception as err: + self.logger.critical('[Download() class] : FILE I/O ERROR : ' + str(err)) + + self.retry += -1 + canvas.itemconfigure(message, text=('Erreur inconnue. Nouvelles tentatives : ' + str(self.retry))) + if self.retry <= 0: + self.downloading = False + + +class AutoScrollbar(ttk.Scrollbar): + """" A scrollbar that hides itself if it's not needed. Works only for grid geometry manager \"""" + + def set(self, lo, hi): + if float(lo) <= 0.0: + if float(hi) >= 1.0: + self.grid_remove() + self.grid() + ttk.Scrollbar.set(self, lo, hi) + + def pack(self, **kw): + raise TclError('Cannot use pack with the widget ' + self.__class__.__name__) + + def place(self, **kw): + raise TclError('Cannot use place with the widget ' + self.__class__.__name__) + + +class CanvasImage: + """' Display and zoom image '""" + + def __init__(self, placeholder, file, type): + """ Initialize the ImageFrame """ + self.type = type + self.angle = 0 + self.imscale = 1.0 + self._CanvasImage__delta = 1.3 + self._CanvasImage__filter = Image.ANTIALIAS + self._CanvasImage__previous_state = 0 + self.path = file + self._CanvasImage__imframe = ttk.Frame(placeholder) + self.placeholder = placeholder + hbar = AutoScrollbar((self._CanvasImage__imframe), orient='horizontal') + vbar = AutoScrollbar((self._CanvasImage__imframe), orient='vertical') + hbar.grid(row=1, column=0, sticky='we') + vbar.grid(row=0, column=1, sticky='ns') + self.canvas = Canvas((self._CanvasImage__imframe), highlightthickness=0, xscrollcommand=(hbar.set), + yscrollcommand=(vbar.set)) + self.canvas.grid(row=0, column=0, sticky='nswe') + self.canvas.update() + hbar.configure(command=(self._CanvasImage__scroll_x)) + vbar.configure(command=(self._CanvasImage__scroll_y)) + self.canvas.bind('', lambda event: self._CanvasImage__show_image()) + self.canvas.bind('', self._CanvasImage__move_from) + self.canvas.bind('', self._CanvasImage__move_to) + self.canvas.bind('', self._CanvasImage__wheel) + self._CanvasImage__huge = False + self._CanvasImage__huge_size = 14000 + self._CanvasImage__band_width = 1024 + Image.MAX_IMAGE_PIXELS = 1000000000 + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + self._CanvasImage__image = Image.open(self.path) + self.imwidth, self.imheight = self._CanvasImage__image.size + if self.imwidth * self.imheight > self._CanvasImage__huge_size * self._CanvasImage__huge_size: + if self._CanvasImage__image.tile[0][0] == 'raw': + self._CanvasImage__huge = True + self._CanvasImage__offset = self._CanvasImage__image.tile[0][2] + self._CanvasImage__tile = [self._CanvasImage__image.tile[0][0], + [ + 0, 0, self.imwidth, 0], + self._CanvasImage__offset, + self._CanvasImage__image.tile[0][3]] + self._CanvasImage__min_side = min(self.imwidth, self.imheight) + self._CanvasImage__pyramid = [self.smaller()] if self._CanvasImage__huge else [Image.open(self.path)] + self._CanvasImage__ratio = max(self.imwidth, self.imheight) / self._CanvasImage__huge_size if self._CanvasImage__huge else 1.0 + self._CanvasImage__curr_img = 0 + self._CanvasImage__scale = self.imscale * self._CanvasImage__ratio + self._CanvasImage__reduction = 2 + w, h = self._CanvasImage__pyramid[(-1)].size + while w > 512 and h > 512: + w /= self._CanvasImage__reduction + h /= self._CanvasImage__reduction + try: + self._CanvasImage__pyramid.append(self._CanvasImage__pyramid[(-1)].resize((int(w), int(h)), self._CanvasImage__filter)) + except TypeError: + showerror(title='Erreur de fichier', message="Image incompatible. Merci d'utiliser une autre image ou de la convertir dans un format standard accepté!", parent=(self.placeholder)) + self.placeholder.parent.openerrored = True + self.placeholder.destroy() + self.destroy() + return + + self.container = self.canvas.create_rectangle((0, 0, self.imwidth, self.imheight), width=0) + self._CanvasImage__show_image() + self.canvas.focus_set() + + def rotatem(self): + self.angle += 1 + self._CanvasImage__show_image() + + def rotatep(self): + self.angle -= 1 + self._CanvasImage__show_image() + + def rotatemm(self): + self.angle += 90 + self._CanvasImage__show_image() + + def rotatepp(self): + self.angle -= 90 + self._CanvasImage__show_image() + + def smaller(self): + """ Resize image proportionally and return smaller image """ + w1, h1 = float(self.imwidth), float(self.imheight) + w2, h2 = float(self._CanvasImage__huge_size), float(self._CanvasImage__huge_size) + aspect_ratio1 = w1 / h1 + aspect_ratio2 = w2 / h2 + if aspect_ratio1 == aspect_ratio2: + image = Image.new('RGB', (int(w2), int(h2))) + k = h2 / h1 + w = int(w2) + else: + if aspect_ratio1 > aspect_ratio2: + image = Image.new('RGB', (int(w2), int(w2 / aspect_ratio1))) + k = h2 / w1 + w = int(w2) + else: + image = Image.new('RGB', (int(h2 * aspect_ratio1), int(h2))) + k = h2 / h1 + w = int(h2 * aspect_ratio1) + i, j, n = 0, 1, round(0.5 + self.imheight / self._CanvasImage__band_width) + while i < self.imheight: + band = min(self._CanvasImage__band_width, self.imheight - i) + self._CanvasImage__tile[1][3] = band + self._CanvasImage__tile[2] = self._CanvasImage__offset + self.imwidth * i * 3 + self._CanvasImage__image.close() + self._CanvasImage__image = Image.open(self.path) + self._CanvasImage__image.size = (self.imwidth, band) + self._CanvasImage__image.tile = [self._CanvasImage__tile] + cropped = self._CanvasImage__image.crop((0, 0, self.imwidth, band)) + image.paste(cropped.resize((w, int(band * k) + 1), self._CanvasImage__filter), (0, int(i * k))) + i += band + j += 1 + + return image + + def redraw_figures(self): + """ Dummy function to redraw figures in the children classes """ + pass + + def grid(self, **kw): + """ Put CanvasImage widget on the parent widget """ + (self._CanvasImage__imframe.grid)(**kw) + self._CanvasImage__imframe.grid(sticky='nswe') + self._CanvasImage__imframe.rowconfigure(0, weight=1) + self._CanvasImage__imframe.columnconfigure(0, weight=1) + + def __scroll_x(self, *args, **kwargs): + """ Scroll canvas horizontally and redraw the image """ + (self.canvas.xview)(*args) + self._CanvasImage__show_image() + + def __scroll_y(self, *args, **kwargs): + """ Scroll canvas vertically and redraw the image """ + (self.canvas.yview)(*args) + self._CanvasImage__show_image() + + def __show_image(self): + """ Show image on the Canvas. Implements correct image zoom almost like in Google Maps """ + box_image = self.canvas.coords(self.container) + box_canvas = (self.canvas.canvasx(0), + self.canvas.canvasy(0), + self.canvas.canvasx(self.canvas.winfo_width()), + self.canvas.canvasy(self.canvas.winfo_height())) + box_img_int = tuple(map(int, box_image)) + box_scroll = [ + min(box_img_int[0], box_canvas[0]), min(box_img_int[1], box_canvas[1]), + max(box_img_int[2], box_canvas[2]), max(box_img_int[3], box_canvas[3])] + if box_scroll[0] == box_canvas[0]: + if box_scroll[2] == box_canvas[2]: + box_scroll[0] = box_img_int[0] + box_scroll[2] = box_img_int[2] + if box_scroll[1] == box_canvas[1] and box_scroll[3] == box_canvas[3]: + box_scroll[1] = box_img_int[1] + box_scroll[3] = box_img_int[3] + self.canvas.configure(scrollregion=(tuple(map(int, box_scroll)))) + x1 = max(box_canvas[0] - box_image[0], 0) + y1 = max(box_canvas[1] - box_image[1], 0) + x2 = min(box_canvas[2], box_image[2]) - box_image[0] + y2 = min(box_canvas[3], box_image[3]) - box_image[1] + if int(x2 - x1) > 0: + if int(y2 - y1) > 0: + if self._CanvasImage__huge: + if self._CanvasImage__curr_img < 0: + h = int((y2 - y1) / self.imscale) + self._CanvasImage__tile[1][3] = h + self._CanvasImage__tile[2] = self._CanvasImage__offset + self.imwidth * int(y1 / self.imscale) * 3 + self._CanvasImage__image.close() + self._CanvasImage__image = Image.open(self.path) + self._CanvasImage__image.size = (self.imwidth, h) + self._CanvasImage__image.tile = [self._CanvasImage__tile] + image = self._CanvasImage__image.crop((int(x1 / self.imscale), 0, int(x2 / self.imscale), h)) + image = self._CanvasImage__pyramid[max(0, self._CanvasImage__curr_img)].crop(( + int(x1 / self._CanvasImage__scale), int(y1 / self._CanvasImage__scale), + int(x2 / self._CanvasImage__scale), int(y2 / self._CanvasImage__scale))) + self.resizedim = image.resize((int(x2 - x1), int(y2 - y1)), self._CanvasImage__filter).rotate((self.angle), resample=(Image.BICUBIC), expand=1) + imagetk = ImageTk.PhotoImage((self.resizedim), master=(self.placeholder)) + imageid = self.canvas.create_image((max(box_canvas[0], box_img_int[0])), (max(box_canvas[1], box_img_int[1])), + anchor='nw', + image=imagetk) + self.canvas.lower(imageid) + self.canvas.imagetk = imagetk + + def __move_from(self, event): + """ Remember previous coordinates for scrolling with the mouse """ + self.canvas.scan_mark(event.x, event.y) + + def __move_to(self, event): + """ Drag (move) canvas to the new position """ + self.canvas.scan_dragto((event.x), (event.y), gain=1) + self._CanvasImage__show_image() + + def outside(self, x, y): + """ Checks if the point (x,y) is outside the image area """ + bbox = self.canvas.coords(self.container) + if bbox[0] < x < bbox[2]: + if bbox[1] < y < bbox[3]: + pass + return False + else: + return True + + def __wheel(self, event): + """ Zoom with mouse wheel """ + x = self.canvas.canvasx(event.x) + y = self.canvas.canvasy(event.y) + if self.outside(x, y): + return + scale = 1.0 + if event.delta == -120: + if round(self._CanvasImage__min_side * self.imscale) < int(self.placeholder.winfo_screenheight()): + return + self.imscale /= self._CanvasImage__delta + scale /= self._CanvasImage__delta + if event.delta == 120: + i = min(self.canvas.winfo_width(), self.canvas.winfo_height()) >> 1 + if i < self.imscale: + return + self.imscale *= self._CanvasImage__delta + scale *= self._CanvasImage__delta + k = self.imscale * self._CanvasImage__ratio + self._CanvasImage__curr_img = min(-1 * int(math.log(k, self._CanvasImage__reduction)), len(self._CanvasImage__pyramid) - 1) + self._CanvasImage__scale = k * math.pow(self._CanvasImage__reduction, max(0, self._CanvasImage__curr_img)) + self.canvas.scale('all', x, y, scale, scale) + self.redraw_figures() + self._CanvasImage__show_image() + + def crop(self, bbox): + """ Crop rectangle from the image and return it """ + if self._CanvasImage__huge: + band = bbox[3] - bbox[1] + self._CanvasImage__tile[1][3] = band + self._CanvasImage__tile[2] = self._CanvasImage__offset + self.imwidth * bbox[1] * 3 + self._CanvasImage__image.close() + self._CanvasImage__image = Image.open(self.path) + self._CanvasImage__image.size = (self.imwidth, band) + self._CanvasImage__image.tile = [self._CanvasImage__tile] + return self._CanvasImage__image.crop((bbox[0], 0, bbox[2], band)) + else: + return self._CanvasImage__pyramid[0].crop(bbox) + + def destroy(self): + """ ImageFrame destructor """ + self._CanvasImage__image.close() + map(lambda i: i.close, self._CanvasImage__pyramid) + del self._CanvasImage__pyramid[:] + del self._CanvasImage__pyramid + self.canvas.destroy() + + +class OpenScan(ttk.Frame): + """' Main window class '""" + + def __init__(self, mainframe, fileorig, type, nframe=1, pagenum=0, file=None): + """ Initialize the main Frame """ + if file == None: + file = fileorig + self.file = file + self.fileorig = fileorig + self.nframe = nframe + self.pagenum = pagenum + self.parent = mainframe.parent + ttk.Frame.__init__(self, master=mainframe) + self.master.title('Ouvrir un scan... (Utilisez la roulette pour zoomer, clic gauche pour déplacer et clic droit pour sélectionner la MRZ)') + self.master.resizable(width=False, height=False) + hs = self.winfo_screenheight() + w = int(self.winfo_screenheight() / 1.5) + h = int(self.winfo_screenheight() / 2) + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.master.geometry('%dx%d+%d+%d' % (w, h, x, y)) + if getattr(sys, 'frozen', False): + self.master.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.master.iconbitmap('id-card.ico') + self.master.rowconfigure(0, weight=1) + self.master.columnconfigure(0, weight=1) + self.cadre = CanvasImage(self.master, self.file, type) + self.cadre.grid(row=0, column=0) + self.master.menubar = Menu(self.master) + if type == 1: + self.master.menubar.add_command(label='Page précédente', command=(self.pagep)) + self.master.menubar.add_command(label='Pivoter -90°', command=(self.cadre.rotatemm)) + self.master.menubar.add_command(label='Pivoter -1°', command=(self.cadre.rotatem)) + self.master.menubar.add_command(label='Pivoter +1°', command=(self.cadre.rotatep)) + self.master.menubar.add_command(label='Pivoter +90°', command=(self.cadre.rotatepp)) + if type == 1: + self.master.menubar.add_command(label='Page suivante', command=(self.pages)) + self.master.config(menu=(self.master.menubar)) + self.cadre.canvas.bind('', self.motionprep) + self.cadre.canvas.bind('', self.motionize) + self.cadre.canvas.bind('', self.motionend) + + def pages(self): + if self.pagenum + 1 < self.nframe: + im = Image.open(self.fileorig) + im.seek(self.pagenum + 1) + newpath = CST_FOLDER + '\\temp' + str(random.randint(11111, 99999)) + '.tif' + im.save(newpath) + im.close() + self.cadre.destroy() + self.__init__(self.master, self.fileorig, 1, self.nframe, self.pagenum + 1, newpath) + + def pagep(self): + if self.pagenum - 1 >= 0: + im = Image.open(self.fileorig) + im.seek(self.pagenum - 1) + newpath = CST_FOLDER + '\\temp' + str(random.randint(11111, 99999)) + '.tif' + im.save(newpath) + im.close() + self.cadre.destroy() + self.__init__(self.master, self.fileorig, 1, self.nframe, self.pagenum - 1, newpath) + + def motionprep(self, event): + if hasattr(self, 'rect'): + self.begx = event.x + self.begy = event.y + self.ix = self.cadre.canvas.canvasx(event.x) + self.iy = self.cadre.canvas.canvasy(event.y) + self.cadre.canvas.coords(self.rect, self.cadre.canvas.canvasx(event.x), self.cadre.canvas.canvasy(event.y), self.ix, self.iy) + else: + self.begx = event.x + self.begy = event.y + self.ix = self.cadre.canvas.canvasx(event.x) + self.iy = self.cadre.canvas.canvasy(event.y) + self.rect = self.cadre.canvas.create_rectangle((self.cadre.canvas.canvasx(event.x)), (self.cadre.canvas.canvasy(event.y)), (self.ix), (self.iy), outline='red') + + def motionize(self, event): + event.x + event.y + self.cadre.canvas.coords(self.rect, self.ix, self.iy, self.cadre.canvas.canvasx(event.x), self.cadre.canvas.canvasy(event.y)) + + def motionend(self, event): + self.endx = event.x + self.endy = event.y + self.imtotreat = self.cadre.resizedim.crop((min(self.begx, self.endx), min(self.begy, self.endy), max(self.endx, self.begx), max(self.endy, self.begy))) + im = self.imtotreat + import CNI_pytesseract as pytesseract + try: + os.environ['PATH'] = CST_FOLDER + '\\Tesseract-OCR4\\' + os.environ['TESSDATA_PREFIX'] = CST_FOLDER + '\\Tesseract-OCR4\\tessdata' + self.text = pytesseract.image_to_string(im, lang='ocrb', boxes=False, config='--psm 6 --oem 0 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<') + except pytesseract.TesseractNotFoundError as e: + try: + os.remove(CST_FOLDER + '\\Tesseract-OCR4\\*.*') + except Exception: + pass + + showerror('Erreur de module OCR', ('Le module OCR localisé en ' + str(os.environ['PATH']) + 'est introuvable. Il sera réinstallé à la prochaine exécution'), parent=self) + except pytesseract.TesseractError as e: + pass + + self.master.success = False + dialogconf = OpenScanDialog(self.master, self.text) + dialogconf.transient(self) + dialogconf.grab_set() + self.wait_window(dialogconf) + if self.master.success: + self.master.destroy() + + +class OpenScanWin(Toplevel): + + def __init__(self, parent, file, type, nframe=1): + super().__init__(parent) + self.parent = parent + app = OpenScan(self, file, type, nframe) + + +class OpenScanDialog(Toplevel): + + def __init__(self, parent, text): + super().__init__(parent) + self.parent = parent + self.title('Validation de la MRZ détectée') + 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='Valider', 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.parent.mrzdetected = self.termtext.get('1.0', 'end') + texting = self.parent.parent.mrzdetected.replace(' ', '').replace('\r', '').split('\n') + for i in range(len(texting)): + for char in texting[i]: + if char not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<': + showerror('Erreur de validation', 'La MRZ soumise contient des caractères invalides', parent=self) + self.parent.parent.mrzdetected = '' + return + + self.parent.success = True + self.destroy() + + +class OpenPageDialog(Toplevel): + + def __init__(self, parent, number): + super().__init__(parent) + self.parent = parent + self.title("Choisir la page à afficher de l'image selectionnée") + self.resizable(width=False, height=False) + self.termtext = Label(self, text='Merci de selectionner un numéro de page dans la liste ci-dessous.') + self.termtext.grid(column=0, row=0, sticky='N', padx=5, pady=5) + self.combotry = ttk.Combobox(self) + self.combotry['values'] = tuple(str(x) for x in range(1, number + 1)) + self.combotry.grid(column=0, row=1, sticky='N', padx=5, pady=5) + self.button = Button(self, text='Valider', command=(self.valid)) + self.button.grid(column=0, row=2, 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.page = self.combotry.current() + self.destroy() \ No newline at end of file diff --git a/src/analyzer/CNI_pytesseract.py b/src/analyzer/CNI_pytesseract.py new file mode 100644 index 0000000..ba023a5 --- /dev/null +++ b/src/analyzer/CNI_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() \ No newline at end of file diff --git a/src/launcher/CNIRLauncher.py b/src/launcher/CNIRLauncher.py new file mode 100644 index 0000000..ce80eb8 --- /dev/null +++ b/src/launcher/CNIRLauncher.py @@ -0,0 +1,69 @@ +""" +******************************************************************************** +* 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 subprocess +import threading +import traceback + +import updater # updater.py +import ihm # ihm.py +import globs # globs.py +import logger # logger.py + +## Global Handlers +logfile = logger.logCur +launcherWindow = ihm.launcherWindowCur + +## Main function +def main(): + + # Starting the main update thread + mainThread.start() + + # 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.mainCanvas.itemconfigure(launcherWindow.msg, text='Starting...') + launcherWindow.progressBar.start() + + launcherWindow.mainloop() + + logfile.printdbg('*** CNIRLauncher LOGFILE. Goodbye World ! ***') + return + +## Bootstrap +try: + mainThread = threading.Thread(target=updater.umain, daemon=False) + main() +except Exception: + logfile.printerr("A FATAL ERROR OCCURED : " + str(traceback.format_exc())) + sys.exit(1) + +sys.exit(0) diff --git a/src/launcher/downloader.py b/src/launcher/downloader.py new file mode 100644 index 0000000..02fd780 --- /dev/null +++ b/src/launcher/downloader.py @@ -0,0 +1,33 @@ +""" +******************************************************************************** +* 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 hashlib +from pypac import PACSession +from requests.auth import HTTPProxyAuth + +class newdownload(): + def __init__(url): + self.url = url + diff --git a/src/launcher/globs.py b/src/launcher/globs.py new file mode 100644 index 0000000..dd26cb9 --- /dev/null +++ b/src/launcher/globs.py @@ -0,0 +1,30 @@ +""" +******************************************************************************** +* 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 + +CNIRTesserHash = '5b58db27f7bc08c58a2cb33d01533b034b067cf8' +CNIRFolder = os.getcwd() +CNIRLColor = "#006699" +CNIRName = "CNIRevelator Launcher 3" diff --git a/src/launcher/id-card.ico b/src/launcher/id-card.ico new file mode 100644 index 0000000..efa86e4 Binary files /dev/null and b/src/launcher/id-card.ico differ diff --git a/src/launcher/ihm.py b/src/launcher/ihm.py new file mode 100644 index 0000000..fa98736 --- /dev/null +++ b/src/launcher/ihm.py @@ -0,0 +1,119 @@ +""" +******************************************************************************** +* 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 import * +from tkinter.messagebox import * +from tkinter import filedialog +from tkinter import ttk + +import logger # logger.py +import globs # globs.py + +class LoginDialog(Toplevel): + + def __init__(self, parent): + super().__init__(parent) + self.title('Connexion') + Label(self, text='IPN : ').pack() + self.entry_login = Entry(self) + self.entry_login.insert(0, '') + self.entry_login.pack() + Label(self, text='Mot de passe : ').pack() + self.entry_pass = Entry(self, show='*') + self.entry_pass.insert(0, '') + self.entry_pass.pack() + Button(self, text='Connexion', command=(self.connecti)).pack() + self.resizable(width=False, height=False) + w = 150 + h = 110 + self.update() + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + upwin.update() + x = ws / 2 - w / 2 + y = hs / 2 - h / 2 + self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + + def logingin(self): + global key + global login + login = self.entry_login.get().strip() + key = self.entry_pass.get().strip() + self.destroy() + +class LauncherWindow(Tk): + + def __init__(self): + # Initialize the tkinter main class + Tk.__init__(self) + self.configure(bg=globs.CNIRLColor) + self.resizable(width=False, height=False) + self.queue = [] + + # Setting up the geometry + ws = self.winfo_screenwidth() + hs = self.winfo_screenheight() + wheight = hs /4 + wwidth = ws /4 + #self.update() + + # Creating objects + self.mainCanvas = Canvas(self, width=wwidth, height=wheight*9/10, bg=globs.CNIRLColor, highlightthickness=0) + self.pBarZone = Canvas(self, width=wwidth, height=wheight/10, bg=globs.CNIRLColor) + + self.progressBar = ttk.Progressbar(self.pBarZone, orient=HORIZONTAL, length=wwidth-10, mode='determinate') + #self.update() + + self.mainCanvas.create_text((wwidth / 2), (wheight / 3), text=(globs.CNIRName), font='Helvetica 30', fill='white') + self.msg = self.mainCanvas.create_text((wwidth / 2.05), (wheight / 1.20), text='', font='Helvetica 9', fill='white') + #self.update() + + self.wm_title(globs.CNIRName) + + # Centering + x = ws / 2 - wwidth / 2 + y = hs / 2 - wheight / 2 + self.geometry('%dx%d+%d+%d' % (wwidth, wheight, x, y)) + self.mainCanvas.grid() + self.pBarZone.grid() + self.progressBar.grid() + + #self.after(2000, updating) + + if getattr(sys, 'frozen', False): + self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico') + else: + self.iconbitmap('id-card.ico') + logfile = logger.logCur + logfile.printdbg('Launcher IHM successful') + self.protocol('WM_DELETE_WINDOW', lambda : self.destroy()) + +## Global Handler +launcherWindowCur = LauncherWindow() + diff --git a/src/launcher/logger.py b/src/launcher/logger.py new file mode 100644 index 0000000..871d836 --- /dev/null +++ b/src/launcher/logger.py @@ -0,0 +1,64 @@ +""" +******************************************************************************** +* 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 globs +import os + +## The logging class +class NewLoggingSystem: + + def __init__(self): + + # Deleting the error log + try: + os.remove(globs.CNIRFolder + '\\error.log') # The deletion does not working + except Exception as e: + #print(str(e) + " : " + str(globs.CNIRFolder + '\\error.log')) + 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.CNIRFolder + '\\error.log'), mode='w', encoding='utf-8', delay=True) + info_handler = logging.FileHandler((globs.CNIRFolder + '\\launcher.log'), 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 + self.printdbg = logger.info + +## Global Handler +logCur = NewLoggingSystem() diff --git a/src/launcher/updater.py b/src/launcher/updater.py new file mode 100644 index 0000000..39a6ee1 --- /dev/null +++ b/src/launcher/updater.py @@ -0,0 +1,91 @@ +""" +******************************************************************************** +* CNIRevelator * +* * +* Desc: Application launcher updating system * +* * +* 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 win32com.client import Dispatch +import traceback +import sys +import time + +import logger # logger.py +import globs # globs.py +import ihm # ihm.py + +def createShortcut(path, target='', wDir='', icon=''): + 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(path) + shortcut.Targetpath = target + shortcut.WorkingDirectory = wDir + if icon == '': + pass + else: + shortcut.IconLocation = icon + shortcut.save() + +## Main Batch Function +def batch(): + + # Global Handlers + logfile = logger.logCur + launcherWindow = ihm.launcherWindowCur + + for i in range(0,10000): + if i % 1000 : launcherWindow.mainCanvas.itemconfigure(launcherWindow.msg, text=('Starting... ' + str(i))) + return + +## Main Function +def umain(): + try: + # Global Handlers + logfile = logger.logCur + launcherWindow = ihm.launcherWindowCur + + try: + batch() + except Exception as e: + logfile.printerr("An error occured on the thread : " + str(traceback.format_exc())) + launcherWindow.mainCanvas.itemconfigure(launcherWindow.msg, text=('ERROR : ' + str(e))) + time.sleep(3) + launcherWindow.destroy() + return 1 + + launcherWindow.mainCanvas.itemconfigure(launcherWindow.msg, text='Software is up-to-date !') + time.sleep(2) + launcherWindow.destroy() + return 0 + + except: + logfile.printerr("A FATAL ERROR OCCURED : " + str(traceback.format_exc())) + launcherWindow.destroy() + sys.exit(2) + return 2 + +