diff --git a/CNIPackage.zip b/CNIPackage.zip
new file mode 100644
index 0000000..ffc5bfa
Binary files /dev/null and b/CNIPackage.zip differ
diff --git a/VERSIONS.LST b/VERSIONS.LST
index 08662e4..e67bbbf 100644
--- a/VERSIONS.LST
+++ b/VERSIONS.LST
@@ -1,2 +1,2 @@
# ver|url|checksum, and | as separator, one version per ||
-0.0.0|https://neoxgroup.eu/ftpaccess/Applicatifs/CNIRevelator/CNIRevelator_2.2.5|1234||3.0.1|https://neoxgroup.eu/ftpaccess/Applicatifs/CNIRevelator/CNIRevelator_2.2.5|1234||0.0.1|https://www.os-k.eu|1234||3.0.2|https://neoxgroup.eu/ftpaccess/Applicatifs/CNIRevelator/CNIRevelator_2.2.5|1234
+3.0.0|https://neoxgroup.eu/ftpaccess/Applicatifs/CNIRevelator/CNIRevelator_2.2.5|1234||
diff --git a/src/CNIRevelator.py b/src/CNIRevelator.py
index 816bd17..1bba07c 100644
--- a/src/CNIRevelator.py
+++ b/src/CNIRevelator.py
@@ -23,42 +23,24 @@
********************************************************************************
"""
-from CNI_GLOBALVAR import *
-try:
- os.remove('error.log')
- os.remove('conf.ig')
-except:
- print("pass log deletion")
- pass
+import sys
+import os
+import subprocess
+import threading
+import traceback
-if not os.path.exists(CST_FOLDER):
- try:
- os.makedirs(CST_FOLDER)
- except IOError:
- print("pass IO ERROR")
- pass
+import launcher # launcher.py
+import ihm # ihm.py
+import logger # logger.py
+import updater # updater.py
-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 *
+## Global Handlers
+logfile = logger.logCur
+launcherWindow = ihm.launcherWindowCur
+## MAIN FUNCTION OF CNIREVELATOR
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\\'
@@ -70,6 +52,7 @@ def main(logger):
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() ****')
@@ -77,34 +60,14 @@ def main(logger):
logger.info('main() : **** Ending App_main() ****')
-logger.info('launcher : ' + CST_NAME + ' ' + CST_VER)
-logger.info('launcher : *****Hello World*****')
-logger.info('launcher : *****Launching SoftUpdate()*****')
-
-logger.info('launcher : *****Launching main()*****')
-State = main(logger)
-
-
-logger.info('launcher : *****Ending main()*****')
-logger.info('launcher : *****Goodbye!*****')
-handlers = logger.handlers[:]
-for handler in handlers:
- handler.close()
- logger.removeHandler(handler)
+## BOOTSTRAP OF CNIREVELATOR
try:
- with open(CST_FOLDER + '\\error.log') as (echo):
- try:
- os.remove('error.log')
- except OSError:
- pass
+ launcherThread = threading.Thread(target=updater.umain, daemon=False)
+ launcher.lmain(launcherThread)
+except Exception:
+ logfile.printerr("A FATAL ERROR OCCURED : " + str(traceback.format_exc()))
+ sys.exit(1)
- from shutil import copyfile
- temptwo = str(echo.read())
- if len(temptwo) != 1:
- copyfile(CST_FOLDER + '\\cnirevelator.log', 'error.log')
-except IOError:
- pass
-
-print("exit")
+main()
sys.exit(0)
diff --git a/src/CNI_classes.py b/src/CNI_classes.py
deleted file mode 100644
index 882c8a1..0000000
--- a/src/CNI_classes.py
+++ /dev/null
@@ -1,1332 +0,0 @@
-"""
-********************************************************************************
-* 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()
diff --git a/src/ihm.py b/src/ihm.py
index 5fc6e7c..76fdf06 100644
--- a/src/ihm.py
+++ b/src/ihm.py
@@ -110,6 +110,61 @@ class LauncherWindow(Tk):
self.protocol('WM_DELETE_WINDOW', lambda : self.destroy())
self.update()
+
+class AutoScrollbar(ttk.Scrollbar):
+
+ 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 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()
+
+
+class OpenScanWin(Toplevel):
+
+ def __init__(self, parent, file, type, nframe=1):
+ super().__init__(parent)
+ self.parent = parent
+ app = OpenScan(self, file, type, nframe)
## Global Handler
launcherWindowCur = LauncherWindow()
diff --git a/src/image.py b/src/image.py
new file mode 100644
index 0000000..d51f369
--- /dev/null
+++ b/src/image.py
@@ -0,0 +1,275 @@
+"""
+********************************************************************************
+* CNIRevelator *
+* *
+* Desc: Image calculation for CNI printing *
+* *
+* 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 CanvasImage:
+
+ 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()
\ No newline at end of file
diff --git a/src/launcher.py b/src/launcher.py
index ec82b7a..7a74584 100644
--- a/src/launcher.py
+++ b/src/launcher.py
@@ -39,8 +39,7 @@ logfile = logger.logCur
launcherWindow = ihm.launcherWindowCur
## Main function
-def main():
-
+def lmain(mainThread):
# Hello world
logfile.printdbg('*** CNIRLauncher LOGFILE. Hello World ! ***')
#logfile.printdbg('Files in directory : ' + str(os.listdir(globs.CNIRFolder)))
@@ -57,13 +56,3 @@ def main():
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/main.py b/src/main.py
new file mode 100644
index 0000000..e50a224
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,681 @@
+"""
+********************************************************************************
+* CNIRevelator *
+* *
+* Desc: Application IHM & work main class *
+* *
+* Copyright © 2018-2019 Adrien Bourmault (neox95) *
+* *
+* This file is part of CNIRevelator. *
+* *
+* CNIRevelator is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation, either version 3 of the License, or *
+* any later version. *
+* *
+* CNIRevelator is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY*without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+* GNU General Public License for more details. *
+* *
+* You should have received a copy of the GNU General Public License *
+* along with CNIRevelator. If not, see . *
+********************************************************************************
+"""
+
+from PIL import Image, ImageFont, ImageDraw, ImageTk, ImageEnhance, ImageFilter
+import math, warnings, string
+
+import mrz # mrz.py
+from image import CanvasImage # image.py
+
+class mainWindow(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 mrz.TYPES:
+ self.Score += [0]
+
+ ws = self.winfo_screenwidth()
+ hs = self.winfo_screenheight()
+ self.logger.info('mainWindow() : 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.entryValidation), '%S', '%P', '%d')
+ self.termentry = Entry((self.termframe), font='Terminal 17', entryValidation='all', entryValidationcommand=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.newEntry))
+ 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.preentryValidation)
+ self.termtext.bind('', self.preentryValidation)
+ self.termentry.bind('', self.onTabPressed)
+ self.update()
+ self.logger.info('mainWindow() : Initialization successful')
+
+ def preentryValidation(self, event):
+ self.logger.debug('preentryValidation() : Entering Validation')
+ if self.PILE_ETAT != [] and len(self.PILE_ETAT) == 1:
+ thetext = self.termentry.get()
+ self.logger.debug('preentryValidation() : PILE_ETAT Satisfy the requisites : ' + str(self.PILE_ETAT))
+ n = len(thetext)
+ champ = mrz.TYPES[self.PILE_ETAT[0]][0]
+ if not n % champ.find('|') == 0:
+ self.logger.debug('preentryValidation() : 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.logOnTerm('Entrez la seconde ligne de la MRZ ou \nappuyez sur Entrée pour terminer.\n')
+ self.logger.debug('preentryValidation() : First line accepted')
+ self.MRZCHAR += '|'
+ else:
+ if len(self.MRZCHAR) == champ.find('|') * 2 + 1 or len(champ) == champ.find('|') + 1:
+ self.logOnTerm('\nCalcul des sommes ...\n')
+ self.logger.info('preentryValidation() : 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('preentryValidation() : PILE_ETAT Satisfy the requisites : ' + str(self.PILE_ETAT))
+ n = len(self.MRZCHAR)
+ champ = mrz.TYPES[self.PILE_ETAT[1]][0]
+ for char in self.MRZCHAR:
+ if char not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<|':
+ self.logOnTerm('\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('preentryValidation() : : ' + str(self.PILE_ETAT))
+ self.logOnTerm('\nCalcul des sommes ...\n')
+ self.logger.info('preentryValidation() : Launching calculSigma() thread')
+ threading.Thread(target=(self.calculSigma), args=[self.MRZCHAR, self.PILE_ETAT[1]]).start()
+ return 'break'
+
+ def onTabPressed(self, event):
+ if self.PILE_ETAT != [] and len(self.PILE_ETAT) == 1:
+ thetext = self.termentry.get()
+ n = len(thetext)
+ champ = mrz.TYPES[self.PILE_ETAT[0]][0]
+ if len(self.MRZCHAR) <= champ.find('|'):
+ champ_type = mrz.TYPES[self.PILE_ETAT[0]][1][champ[(n - 1)]].split('|')
+ self.logger.debug('onTabPressed() : First line detected')
+ self.logger.debug('onTabPressed() : champ_type[0] : ' + str(champ_type[0]))
+ self.logger.debug('onTabPressed() : champ : ' + str(champ))
+ self.logger.debug('onTabPressed() : champ[n-1] : ' + str(champ[(n - 1)]))
+ self.logger.debug('onTabPressed() : 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('onTabPressed() : Second line detected')
+ champ = champ[champ.find('|') + 1:]
+ champ_type = mrz.TYPES[self.PILE_ETAT[0]][1][champ[(n - 1)]].split('|')
+ self.logger.debug('onTabPressed() : champ_type[0] : ' + str(champ_type[0]))
+ self.logger.debug('onTabPressed() : champ : ' + str(champ))
+ self.logger.debug('onTabPressed() : champ[n-1] : ' + str(champ[(n - 1)]))
+ self.logger.debug('onTabPressed() : champ.find(champ[n-1]) : ' + str(champ.find(champ[(n - 1)])))
+ self.logger.debug('onTabPressed() : n : ' + str(n))
+ nb = int(champ_type[0]) - (n - champ.find(champ[(n - 1)]))
+ self.termentry.insert('end', '<' * nb)
+ self.logger.debug('onTabPressed() : Completing entry with ' + str(nb) + ' characters <')
+ return 'break'
+
+ def entryValidation(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(mrz.TYPES, range(len(mrz.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('entryValidation() : type : ' + str(t))
+ self.logger.debug('entryValidation() : Too short to be ok')
+ self.Score[t] += -5
+ break
+ else:
+ self.logger.debug('entryValidation() : 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('entryValidation() : champ_type[2][pos] : ' + str(champ_type[2][pos]))
+ self.logger.debug('entryValidation() : 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('entryValidation() : +1')
+ else:
+ self.Score[t] += -50
+ elif champ_type[2][pos] == '*':
+ self.Score[t] += 1
+ self.logger.debug('entryValidation() : +1')
+ else:
+ if champ_type[2][pos] == 'A':
+ if entry_value[e].isalpha():
+ self.Score[t] += 1
+ self.logger.debug('entryValidation() : +1')
+ if champ_type[2][pos] == '0':
+ if entry_value[e].isnumeric():
+ self.Score[t] += 1
+ self.logger.debug('entryValidation() : +1')
+ if champ_type[1] == 'CTRL':
+ if entry_value[e].isnumeric():
+ self.Score[t] += 1
+ self.logger.debug('entryValidation() : +1')
+ if champ_type[2][pos] == '&':
+ if entry_value[e].isalpha() or entry_value[e] == '<':
+ self.Score[t] += 1
+ self.logger.debug('entryValidation() : +1')
+ self.Score[t] += -1
+ continue
+
+ self.logger.debug('entryValidation() : 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 = mrz.TYPES[self.PILE_ETAT[0]][2]
+ self.logOnTerm(TOPOS + " détectée !\nAppuyez sur Echap pour compléter les champs avec des '<'\nAppuyez sur Entrée pour terminer.\n")
+ self.logger.debug('entryValidation() : Detection : ' + str(TOPOS))
+ else:
+ if len(self.PILE_ETAT) == 1:
+ if len(entry_value) > len(mrz.TYPES[self.PILE_ETAT[0]][0].split('|')[0]):
+ isValid = False
+ return isValid
+
+ def logOnTerm(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.preentryValidation('')
+
+ def newEntry(self):
+ self.initialize(self.logger)
+ self.logOnTerm('\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 mrz.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.logOnTerm('\n')
+ for i in CTRList:
+ sumtxt = mrz.TYPES[numtype][1][i]
+ length = mrz.TYPES[numtype][0].find('|')
+ index = mrz.TYPES[numtype][0].find(i)
+ sum_read = MRZtxt[index]
+ if len(sumtxt.split('|')[2]) == 1:
+ debut = mrz.TYPES[numtype][0].find(sumtxt.split('|')[2][0])
+ sum_calc = mrz.MRZ(MRZtxt[int(debut):index])
+ else:
+ transm_chain = ''
+ for y in sumtxt.split('|')[2]:
+ debut = mrz.TYPES[numtype][0].find(y)
+ fin = debut + int(mrz.TYPES[numtype][1][y].split('|')[0])
+ transm_chain += MRZtxt[int(debut):int(fin)]
+
+ sum_calc = mrz.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.logOnTerm('Somme : Lu ' + str(sum_read) + ' VS calculé ' + str(sum_calc) + '\n')
+
+ NameList = [c for c, v in mrz.TYPES[numtype][1].items() if '|NOM' in v]
+ SurnameList = [c for c, v in mrz.TYPES[numtype][1].items() if 'PRENOM' in v]
+ DDateList = [c for c, v in mrz.TYPES[numtype][1].items() if 'DDATE' in v]
+ BDateList = [c for c, v in mrz.TYPES[numtype][1].items() if 'BDATE' in v]
+ EDateList = [c for c, v in mrz.TYPES[numtype][1].items() if 'EDATE' in v]
+ PAYSList = [c for c, v in mrz.TYPES[numtype][1].items() if 'PAYS' in v]
+ NATList = [c for c, v in mrz.TYPES[numtype][1].items() if 'NAT' in v]
+ SEXList = [c for c, v in mrz.TYPES[numtype][1].items() if 'SEX' in v]
+ NOINTList = [c for c, v in mrz.TYPES[numtype][1].items() if 'NOINT' in v]
+ NOList = [c for c, v in mrz.TYPES[numtype][1].items() if 'NO|' in v]
+ FACULTList = [c for c, v in mrz.TYPES[numtype][1].items() if 'FACULT' in v]
+ INDICList = [c for c, v in mrz.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 = mrz.TYPES[numtype][0].find(champ)
+ fin = debut + int(mrz.TYPES[numtype][1][champ].split('|')[0])
+ if BIGObj[i] == self.pays or BIGObj[i] == self.nat:
+ try:
+ BIGObj[i]['text'] = mrz.landcode[MRZtxt[int(debut):int(fin)].replace('<', '')]
+ except KeyError:
+ self.Falsitude += 1
+ self.logOnTerm('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'] = mrz.sexcode[MRZtxt[int(debut):int(fin)]]
+ except KeyError:
+ self.Falsitude += 1
+ self.logOnTerm('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.logOnTerm('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.logOnTerm('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.logOnTerm('** Score de non conformité : ' + str(self.Falsitude) + '**\n')
+ self.termtext['state'] = 'normal'
+ self.PILE_ETAT = [self.Falsitude, numtype]
+
+
+class OpenScan(ttk.Frame):
+
+ 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()
+
diff --git a/src/mrz.py b/src/mrz.py
new file mode 100644
index 0000000..8efc915
--- /dev/null
+++ b/src/mrz.py
@@ -0,0 +1,756 @@
+"""
+********************************************************************************
+* CNIRevelator *
+* *
+* Desc: MRZ data dictionnary for CNIRevelator analyzer and *
+* functions to analyze these data *
+* *
+* Copyright © 2018-2019 Adrien Bourmault (neox95) *
+* *
+* This file is part of CNIRevelator. *
+* *
+* CNIRevelator is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation, either version 3 of the License, or *
+* any later version. *
+* *
+* CNIRevelator is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY*without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+* GNU General Public License for more details. *
+* *
+* You should have received a copy of the GNU General Public License *
+* along with CNIRevelator. If not, see . *
+********************************************************************************
+"""
+
+## SEX CODES
+sexcode = {'M':'Homme', 'F':'Femme', 'X':'Non spécifié'}
+
+## COUNTRY CODES
+landcode2 =
+ {
+ AW: "Aruba",
+ AF: "Afghanistan",
+ AO: "Angola",
+ AI: "Anguilla",
+ AL: "Albanie",
+ AD: "Andorre",
+ AE: "Emirats arabes unis",
+ AR: "Argentine",
+ AM: "Arménie",
+ AS: "Samoa américaines",
+ AQ: "Antarctique",
+ TF: "Terres australes et antarctiques françaises",
+ AG: "Antigua-et-Barbuda",
+ AU: "Australie",
+ AT: "Autriche",
+ AZ: "Azerbaidjan",
+ BI: "Burundi",
+ BE: "Belgique",
+ BJ: "Benin",
+ BQ: "Pays-Bas caribéens",
+ BF: "Burkina Faso",
+ BD: "Bangladesh",
+ BG: "Bulgarie",
+ BH: "Bahrein",
+ BS: "Bahamas",
+ BA: "Bosnie-Herzegovine",
+ BL: "Saint-Barthélemy",
+ BY: "Bielorussie",
+ BZ: "Belize",
+ BM: "Bermudes",
+ BO: "Bolivie",
+ BR: "Brésil",
+ BB: "Barbade",
+ BN: "Brunei",
+ BT: "Bhoutan",
+ BW: "Botswana",
+ CF: "République Centrafricaine",
+ CA: "Canada",
+ CC: "Îles Cocos",
+ CH: "Suisse",
+ CL: "Chili",
+ CN: "Chine",
+ CI: "Côte d'Ivoire",
+ CM: "Cameroun",
+ CD: "Congo (République démocratique)",
+ CG: "Congo (République)",
+ CK: "Îles Cook",
+ CO: "Colombie",
+ KM: "Comores",
+ CV: "Cap-Vert",
+ CR: "Costa Rica",
+ CU: "Cuba",
+ CW: "Curaçao",
+ CX: "Île Christmas",
+ KY: "Caimans",
+ CY: "Chypre",
+ CZ: "Tchéquie",
+ DE: "Allemagne",
+ DJ: "Djibouti",
+ DM: "Dominique",
+ DK: "Danemark",
+ DO: "République dominicaine",
+ DZ: "Algérie",
+ EC: "Equateur",
+ EG: "Egypte",
+ ER: "Erythrée",
+ EH: "Sahara occidental",
+ ES: "Espagne",
+ EE: "Estonie",
+ ET: "Ethiopie",
+ FI: "Finlande",
+ FJ: "Fidji",
+ FK: "Îles Malouines",
+ FR: "France",
+ FO: "Féroé",
+ FM: "Micronésie",
+ GA: "Gabon",
+ GB: "Royaume-Uni",
+ GE: "Géorgie",
+ GG: "Guernesey",
+ GH: "Ghana",
+ GI: "Gibraltar",
+ GN: "Guinée",
+ GP: "Guadeloupe",
+ GM: "Gambie",
+ GW: "Guinée-Bissau",
+ GQ: "Guinée équatoriale",
+ GR: "Grèce",
+ GD: "Grenade",
+ GL: "Groenland",
+ GT: "Guatemala",
+ GF: "Guyane",
+ GU: "Guam",
+ GY: "Guyana",
+ HK: "Hong Kong",
+ HN: "Honduras",
+ HR: "Croatie",
+ HT: "Haïti",
+ HU: "Hongrie",
+ ID: "Indonésie",
+ IM: "Île de Man",
+ IN: "Inde",
+ IO: "Territoire britannique de l'océan Indien",
+ IE: "Irlande",
+ IR: "Irak",
+ IQ: "Iran",
+ IS: "Islande",
+ IL: "Israël",
+ IT: "Italie",
+ JM: "Jamaïque",
+ JE: "Jersey",
+ JO: "Jordanie",
+ JP: "Japon",
+ KZ: "Kazakhstan",
+ KE: "Kenya",
+ KG: "Kirghizistan",
+ KH: "Cambodge",
+ KI: "Kiribati",
+ KN: "Saint-Christophe-et-Niévès",
+ KR: "Corée du Sud",
+ KW: "Koweït",
+ LA: "Laos",
+ LB: "Liban",
+ LR: "Liberia",
+ LY: "Libye",
+ LC: "Sainte-Lucie",
+ LI: "Liechtenstein",
+ LK: "Sri Lanka",
+ LS: "Lesotho",
+ LT: "Lituanie",
+ LU: "Luxembourg",
+ LV: "Lettonie",
+ MO: "Macao",
+ MF: "Sint-Maarten",
+ MA: "Maroc",
+ MC: "Monaco",
+ MD: "Moldavie",
+ MG: "Madagascar",
+ MV: "Maldives",
+ MX: "Mexique",
+ MH: "Marshall",
+ MK: "Macedoine",
+ ML: "Mali",
+ MT: "Malte",
+ MM: "Birmanie",
+ ME: "Monténégro",
+ MN: "Mongolie",
+ MP: "Îles Mariannes du Nord",
+ MZ: "Mozambique",
+ MR: "Mauritanie",
+ MS: "Montserrat",
+ MQ: "Martinique",
+ MU: "Maurice",
+ MW: "Malawi",
+ MY: "Malaisie",
+ YT: "Mayotte",
+ NA: "Namibie",
+ NC: "Nouvelle-Calédonie",
+ NE: "Niger",
+ NF: "Île Norfolk",
+ NG: "Nigeria",
+ NI: "Nicaragua",
+ NU: "Niue",
+ NL: "Pays-Bas",
+ NO: "Norvège",
+ NP: "Nepal",
+ NR: "Nauru",
+ NZ: "Nouvelle-Zélande",
+ OM: "Oman",
+ PK: "Pakistan",
+ PA: "Panama",
+ PN: "Îles Pitcairn",
+ PE: "Pérou",
+ PH: "Philippines",
+ PW: "Palaos",
+ PG: "Papouasie-Nouvelle-Guinée",
+ PL: "Pologne",
+ PR: "Porto Rico",
+ KP: "Corée du Nord",
+ PT: "Portugal",
+ PY: "Paraguay",
+ PS: "Palestine",
+ PF: "Polynésie française",
+ QA: "Qatar",
+ RE: "Réunion",
+ RO: "Roumanie",
+ RU: "Russie",
+ RW: "Rwanda",
+ SA: "Arabie saoudite",
+ SD: "Soudan",
+ SN: "Sénégal",
+ SG: "Singapour",
+ GS: "Georgie du Sud-et-les iles Sandwich du Sud",
+ SH: "Sainte-Hélène, Ascension et Tristan da Cunha",
+ SJ: "Svalbard et île Jan Mayen",
+ SB: "Salomon",
+ SL: "Sierra Leone",
+ SV: "Salvador",
+ SM: "Saint-Marin",
+ SO: "Somalie",
+ PM: "Saint-Pierre-et-Miquelon",
+ RS: "Serbie",
+ SS: "Soudan du Sud",
+ ST: "Sao Tomé-et-Principe",
+ SR: "Suriname",
+ SK: "Slovaquie",
+ SI: "Slovénie",
+ SE: "Suède",
+ SZ: "eSwatani",
+ SX: "Saint-Martin ",
+ SC: "Seychelles",
+ SY: "Syrie",
+ TC: "Îles Turques-et-Caïques",
+ TD: "Tchad",
+ TG: "Togo",
+ TH: "Thaïlande",
+ TJ: "Tadjikistan",
+ TK: "Tokelau",
+ TM: "Turkmenistan",
+ TL: "Timor oriental",
+ TO: "Tonga",
+ TT: "Trinité-et-Tobago",
+ TN: "Tunisie",
+ TR: "Turquie",
+ TV: "Tuvalu",
+ TW: "Taiwan",
+ TZ: "Tanzanie",
+ UG: "Ouganda",
+ UA: "Ukraine",
+ UY: "Uruguay",
+ US: "Etats-Unis",
+ UZ: "Ouzbékistan",
+ VA: "Saint-Siège (État de la Cité du Vatican)",
+ VC: "Saint-Vincent-et-les-Grenadines",
+ VE: "Venezuela",
+ VG: "Îles Vierges britanniques",
+ VI: "Îles Vierges des États-Unis",
+ VN: "Viêt Nam",
+ VU: "Vanuatu",
+ WF: "Wallis-et-Futuna",
+ WS: "Samoa",
+ XK: "Kosovo",
+ YE: "Yémen",
+ ZA: "Afrique du Sud",
+ ZM: "Zambie",
+ ZW: "Zimbabwe"
+ }
+
+landcode3 =
+ {
+ ABW: "Aruba",
+ AFG: "Afghanistan",
+ AGO: "Angola",
+ AIA: "Anguilla",
+ ALB: "Albanie",
+ AND: "Andorre",
+ ARE: "Emirats arabes unis",
+ ARG: "Argentine",
+ ARM: "Arménie",
+ ASM: "Samoa américaines",
+ ATA: "Antarctique",
+ ATF: "Terres australes et antarctiques françaises",
+ ATG: "Antigua-et-Barbuda",
+ AUS: "Australie",
+ AUT: "Autriche",
+ AZE: "Azerbaidjan",
+ BDI: "Burundi",
+ BEL: "Belgique",
+ BEN: "Benin",
+ BES: "Pays-Bas caribéens",
+ BFA: "Burkina Faso",
+ BGD: "Bangladesh",
+ BGR: "Bulgarie",
+ BHR: "Bahrein",
+ BHS: "Bahamas",
+ BIH: "Bosnie-Herzegovine",
+ BLM: "Saint-Barthélemy",
+ BLR: "Bielorussie",
+ BLZ: "Belize",
+ BMU: "Bermudes",
+ BOL: "Bolivie",
+ BRA: "Brésil",
+ BRB: "Barbade",
+ BRN: "Brunei",
+ BTN: "Bhoutan",
+ BWA: "Botswana",
+ CAF: "République Centrafricaine",
+ CAN: "Canada",
+ CCK: "Îles Cocos",
+ CHE: "Suisse",
+ CHL: "Chili",
+ CHN: "Chine",
+ CIV: "Côte d'Ivoire",
+ CMR: "Cameroun",
+ COD: "Congo (République démocratique)",
+ COG: "Congo (République)",
+ COK: "Îles Cook",
+ COL: "Colombie",
+ COM: "Comores",
+ CPV: "Cap-Vert",
+ CRI: "Costa Rica",
+ CUB: "Cuba",
+ CUW: "Curaçao",
+ CXR: "Île Christmas",
+ CYM: "Caimans",
+ CYP: "Chypre",
+ CZE: "Tchéquie",
+ DEU: "Allemagne",
+ DJI: "Djibouti",
+ DMA: "Dominique",
+ DNK: "Danemark",
+ DOM: "République dominicaine",
+ DZA: "Algérie",
+ ECU: "Equateur",
+ EGY: "Egypte",
+ ERI: "Erythrée",
+ ESH: "Sahara occidental",
+ ESP: "Espagne",
+ EST: "Estonie",
+ ETH: "Ethiopie",
+ FIN: "Finlande",
+ FJI: "Fidji",
+ FLK: "Îles Malouines",
+ FRA: "France",
+ FRO: "Féroé",
+ FSM: "Micronésie",
+ GAB: "Gabon",
+ GBR: "Royaume-Uni",
+ GEO: "Géorgie",
+ GGY: "Guernesey",
+ GHA: "Ghana",
+ GIB: "Gibraltar",
+ GIN: "Guinée",
+ GLP: "Guadeloupe",
+ GMB: "Gambie",
+ GNB: "Guinée-Bissau",
+ GNQ: "Guinée équatoriale",
+ GRC: "Grèce",
+ GRD: "Grenade",
+ GRL: "Groenland",
+ GTM: "Guatemala",
+ GUF: "Guyane",
+ GUM: "Guam",
+ GUY: "Guyana",
+ HKG: "Hong Kong",
+ HND: "Honduras",
+ HRV: "Croatie",
+ HTI: "Haïti",
+ HUN: "Hongrie",
+ IDN: "Indonésie",
+ IMN: "Île de Man",
+ IND: "Inde",
+ IOT: "Territoire britannique de l'océan Indien",
+ IRL: "Irlande",
+ IRN: "Irak",
+ IRQ: "Iran",
+ ISL: "Islande",
+ ISR: "Israël",
+ ITA: "Italie",
+ JAM: "Jamaïque",
+ JEY: "Jersey",
+ JOR: "Jordanie",
+ JPN: "Japon",
+ KAZ: "Kazakhstan",
+ KEN: "Kenya",
+ KGZ: "Kirghizistan",
+ KHM: "Cambodge",
+ KIR: "Kiribati",
+ KNA: "Saint-Christophe-et-Niévès",
+ KOR: "Corée du Sud",
+ KWT: "Koweït",
+ LAO: "Laos",
+ LBN: "Liban",
+ LBR: "Liberia",
+ LBY: "Libye",
+ LCA: "Sainte-Lucie",
+ LIE: "Liechtenstein",
+ LKA: "Sri Lanka",
+ LSO: "Lesotho",
+ LTU: "Lituanie",
+ LUX: "Luxembourg",
+ LVA: "Lettonie",
+ MAC: "Macao",
+ MAF: "Sint-Maarten",
+ MAR: "Maroc",
+ MCO: "Monaco",
+ MDA: "Moldavie",
+ MDG: "Madagascar",
+ MDV: "Maldives",
+ MEX: "Mexique",
+ MHL: "Marshall",
+ MKD: "Macedoine",
+ MLI: "Mali",
+ MLT: "Malte",
+ MMR: "Birmanie",
+ MNE: "Monténégro",
+ MNG: "Mongolie",
+ MNP: "Îles Mariannes du Nord",
+ MOZ: "Mozambique",
+ MRT: "Mauritanie",
+ MSR: "Montserrat",
+ MTQ: "Martinique",
+ MUS: "Maurice",
+ MWI: "Malawi",
+ MYS: "Malaisie",
+ MYT: "Mayotte",
+ NAM: "Namibie",
+ NCL: "Nouvelle-Calédonie",
+ NER: "Niger",
+ NFK: "Île Norfolk",
+ NGA: "Nigeria",
+ NIC: "Nicaragua",
+ NIU: "Niue",
+ NLD: "Pays-Bas",
+ NOR: "Norvège",
+ NPL: "Nepal",
+ NRU: "Nauru",
+ NZL: "Nouvelle-Zélande",
+ OMN: "Oman",
+ PAK: "Pakistan",
+ PAN: "Panama",
+ PCN: "Îles Pitcairn",
+ PER: "Pérou",
+ PHL: "Philippines",
+ PLW: "Palaos",
+ PNG: "Papouasie-Nouvelle-Guinée",
+ POL: "Pologne",
+ PRI: "Porto Rico",
+ PRK: "Corée du Nord",
+ PRT: "Portugal",
+ PRY: "Paraguay",
+ PSE: "Palestine",
+ PYF: "Polynésie française",
+ QAT: "Qatar",
+ REU: "Réunion",
+ ROU: "Roumanie",
+ RUS: "Russie",
+ RWA: "Rwanda",
+ SAU: "Arabie saoudite",
+ SDN: "Soudan",
+ SEN: "Sénégal",
+ SGP: "Singapour",
+ SGS: "Georgie du Sud-et-les iles Sandwich du Sud",
+ SHN: "Sainte-Hélène, Ascension et Tristan da Cunha",
+ SJM: "Svalbard et île Jan Mayen",
+ SLB: "Salomon",
+ SLE: "Sierra Leone",
+ SLV: "Salvador",
+ SMR: "Saint-Marin",
+ SOM: "Somalie",
+ SPM: "Saint-Pierre-et-Miquelon",
+ SRB: "Serbie",
+ SSD: "Soudan du Sud",
+ STP: "Sao Tomé-et-Principe",
+ SUR: "Suriname",
+ SVK: "Slovaquie",
+ SVN: "Slovénie",
+ SWE: "Suède",
+ SWZ: "eSwatani",
+ SXM: "Saint-Martin ",
+ SYC: "Seychelles",
+ SYR: "Syrie",
+ TCA: "Îles Turques-et-Caïques",
+ TCD: "Tchad",
+ TGO: "Togo",
+ THA: "Thaïlande",
+ TJK: "Tadjikistan",
+ TKL: "Tokelau",
+ TKM: "Turkmenistan",
+ TLS: "Timor oriental",
+ TON: "Tonga",
+ TTO: "Trinité-et-Tobago",
+ TUN: "Tunisie",
+ TUR: "Turquie",
+ TUV: "Tuvalu",
+ TWN: "Taiwan",
+ TZA: "Tanzanie",
+ UGA: "Ouganda",
+ UKR: "Ukraine",
+ URY: "Uruguay",
+ USA: "Etats-Unis",
+ UZB: "Ouzbékistan",
+ VAT: "Saint-Siège (État de la Cité du Vatican)",
+ VCT: "Saint-Vincent-et-les-Grenadines",
+ VEN: "Venezuela",
+ VGB: "Îles Vierges britanniques",
+ VIR: "Îles Vierges des États-Unis",
+ VNM: "Viêt Nam",
+ VUT: "Vanuatu",
+ WLF: "Wallis-et-Futuna",
+ WSM: "Samoa",
+ XKX: "Kosovo",
+ YEM: "Yémen",
+ ZAF: "Afrique du Sud",
+ ZMB: "Zambie",
+ ZWE: "Zimbabwe",
+ NTZ: "Zone neutre",
+ UNO: "Fonctionnaire des Nations Unies",
+ UNA: "Fonctionnaire d'une organisation affiliée aux Nations Unies",
+ UNK: "Représentant des Nations Unies au Kosovo",
+ XXA: "Apatride Convention 1954",
+ XXB: "Réfugié Convention 1954",
+ XXC: "Réfugié autre",
+ XXX: "Résident Légal de Nationalité Inconnue",
+ D: "Allemagne",
+ EUE: "Union Européenne",
+ GBD: "Citoyen Britannique d'Outre-mer (BOTC)",
+ GBN: "British National (Overseas)",
+ GBO: "British Overseas Citizen",
+ GBP: "British Protected Person",
+ GBS: "British Subject",
+ XBA: "Banque Africaine de Développement",
+ XIM: "Banque Africaine d'Export–Import",
+ XCC: "Caribbean Community or one of its emissaries",
+ XCO: "Common Market for Eastern and Southern Africa",
+ XEC: "Economic Community of West African States",
+ XPO: "International Criminal Police Organization",
+ XOM: "Sovereign Military Order of Malta",
+ RKS: "Kosovo",
+ WSA: "World Service Authority World Passport"
+ }
+
+## DOCUMENTS TYPES
+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]
+
+## THE ROOT OF THIS PROJECT !
+def MRZ(code):
+ """
+ This function computes a control sum for a range of characters
+ """
+ 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
\ No newline at end of file