1
0
mirror of https://gitlab.os-k.eu/neox/CNIRevelator.git synced 2023-08-25 14:03:10 +02:00
CNIRevelator/CNI_classes.py

1551 lines
93 KiB
Python
Raw Normal View History

2018-08-29 17:04:00 +02:00
"""
********************************************************************************
*** Projet CNI_Revelator ***
GNU GPL * 07/2018
Adrien Bourmault
CLASSES
********************************************************************************
"""
###IMPORTS GLOBAUX
from CNI_GLOBALVAR import *
###BEGIN
class App_main(Tk):
def __init__(self, logger):
Tk.__init__(self)
self.initialize(logger)
def initialize(self, logger):
self.logger = logger
#variable mrz
self.PILE_ETAT = []
self.MRZCHAR = ""
self.varnum = 10
self.Score = []
for type in MRZCODE.TYPES:
self.Score += [0]
#taille de l'écran
ws = self.winfo_screenwidth()
hs = self.winfo_screenheight()
self.logger.info("App_main() : " + "Launching main window with resolution" + str(ws) +"x" + str(hs))
self.grid() #init de la grille
self.grid_columnconfigure(0,weight=1, minsize=(ws/2)* (1/3) )
self.grid_columnconfigure(1,weight=1, minsize=(ws/2)* (1/3) )
self.grid_columnconfigure(2,weight=1, minsize=(ws/2)* (1/3) )
self.grid_rowconfigure(0,weight=1, minsize=(hs/2)* (1/2) )
self.grid_rowconfigure(1,weight=1, minsize=(hs/2)* (1/2) )
# [Zones de travail]
self.lecteur_ci = ttk.Labelframe(self, text = "Informations sur la pièce d'identité")
self.lecteur_ci.grid_columnconfigure(0, weight=1)
self.lecteur_ci.grid_columnconfigure(1, weight=1)
self.lecteur_ci.grid_columnconfigure(2, weight=1)
self.lecteur_ci.grid_columnconfigure(3, weight=1)
self.lecteur_ci.grid_columnconfigure(4, weight=1)
self.lecteur_ci.grid_columnconfigure(5, weight=1)
self.lecteur_ci.grid_rowconfigure(1, weight=1)
self.lecteur_ci.grid_rowconfigure(2, weight=1)
self.lecteur_ci.grid_rowconfigure(3, weight=1)
self.lecteur_ci.grid_rowconfigure(4, weight=1)
self.lecteur_ci.grid_rowconfigure(5, weight=1)
ttk.Label(self.lecteur_ci, text='Nom : ').grid(column = 0, row = 1, padx = 5, pady = 5)
self.nom = ttk.Label(self.lecteur_ci,text=' ')
self.nom.grid(column = 1, row = 1, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text='Nom (2) : ').grid(column = 0, row = 2, padx = 5, pady = 5)
self.prenom = ttk.Label(self.lecteur_ci,text=' ')
self.prenom.grid(column = 1, row = 2, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text='Date de naissance : ').grid(column = 0, row = 3, padx = 5, pady = 5)
self.bdate = ttk.Label(self.lecteur_ci,text=' ')
self.bdate.grid(column = 1, row = 3, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text='Date de délivrance : ').grid(column = 0, row = 4, padx = 5, pady = 5)
self.ddate = ttk.Label(self.lecteur_ci,text=' ')
self.ddate.grid(column = 1, row = 4, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text="Date d'expiration : ").grid(column = 0, row = 5, padx = 5, pady = 5)
self.edate = ttk.Label(self.lecteur_ci,text=' ')
self.edate.grid(column = 1, row = 5, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text='Sexe du porteur : ').grid(column = 4, row = 1, padx = 5, pady = 5)
self.sex = ttk.Label(self.lecteur_ci,text=' ')
self.sex.grid(column = 5, row = 1, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text='Pays de délivrance : ').grid(column = 4, row = 2, padx = 5, pady = 5)
self.pays = ttk.Label(self.lecteur_ci,text=' ')
self.pays.grid(column = 5, row = 2, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text='Nationalité du porteur : ').grid(column = 4, row = 3, padx = 5, pady = 5)
self.nat = ttk.Label(self.lecteur_ci,text=' ')
self.nat.grid(column = 5, row = 3, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text='Immatriculation : ').grid(column = 4, row = 4, padx = 5, pady = 5)
self.indic = ttk.Label(self.lecteur_ci,text=' ')
self.indic.grid(column = 5, row = 4, padx = 5, pady = 5)
ttk.Label(self.lecteur_ci, text='Numéro de document : ').grid(column = 4, row = 5, padx = 5, pady = 5)
self.no = ttk.Label(self.lecteur_ci,text=' ')
self.no.grid(column = 5, row = 5, padx = 5, pady = 5)
self.nom['text'] = "Inconnu(e)"
self.prenom['text'] = "Inconnu(e)"
self.bdate['text'] = "Inconnu(e)"
self.ddate['text'] = "Inconnu(e)"
self.edate['text'] = "Inconnu(e)"
self.no['text'] = "Inconnu(e)"
self.sex['text'] = "Inconnu(e)"
self.nat['text'] = "Inconnu(e)"
self.pays['text'] = "Inconnu(e)"
self.indic['text'] = "Inconnu(e)"
self.STATUT = ttk.Labelframe(self, text = "Statut")
self.STATUT.grid_columnconfigure(0, weight=1)
self.STATUT.grid_rowconfigure(0, weight=1)
self.STATUStxt = Label(self.STATUT, text = "", font = "Times 24", fg = "#FFBF00")
self.STATUStxt.grid(column = 0, row = 0, padx = 0, pady = 0, sticky='EWNS')
self.STATUStxt['text'] = "EN ATTENTE"
self.terminal = ttk.Labelframe(self, text = "Terminal de saisie")
self.terminal.grid_columnconfigure(0, weight=1)
self.terminal.grid_rowconfigure(0, weight=1)
self.termframe = Frame(self.terminal)
self.termframe.grid(column=0,row=0,sticky='EW')
self.termframe.grid_columnconfigure(0, weight=1)
self.termframe.grid_rowconfigure(0, weight=1)
self.termtext = Text(self.termframe, state='disabled', width=60, height=4, wrap='none', font = "Terminal 17", fg = "#121f38")
self.termtext.grid(column=0,row=0,sticky='NEW', padx=5)
vcmd = (self.register(self.validate), '%S', '%P', '%d' )
self.termentry = Entry(self.termframe, font = "Terminal 17", validate = 'all', validatecommand = vcmd, fg = "#121f38", width = 44)
self.termentry.grid(column=0,row=0,sticky='SEW', padx=5)
self.monitor = ttk.Labelframe(self, text = "Moniteur")
self.monlog = Text(self.monitor, state='disabled', width=60, height=10, wrap='word')
self.monlog.grid(column=0,row=0,sticky='EWNS', padx=5, pady=5)
self.monitor.grid_columnconfigure(0, weight=1)
self.monitor.grid_rowconfigure(0, weight=1)
# création du canvas
self.lecteur_ci.grid(column=0,row=0,sticky='EWNS',columnspan=2, padx=5, pady=5)
self.STATUT.grid(column=2,row=0,sticky='EWNS',columnspan=1, padx=5, pady=5)
self.terminal.grid(column=0,row=1,sticky='EWNS',columnspan=2, padx=5, pady=5)
self.monitor.grid(column=2,row=1,sticky='EWNS',columnspan=1, padx=5, pady=5)
# [barres de menu]
menubar = Menu(self)
menu1 = Menu(menubar, tearoff=0)
menu1.add_command(label="Nouveau", command=self.newbie)
menu1.add_command(label="Ouvrir scan...", command=self.openingscan)
menu1.add_separator()
menu1.add_command(label="Quitter", command=self.destroy)
menubar.add_cascade(label="Fichier", menu=menu1)
menu3 = Menu(menubar, tearoff=0)
menu3.add_command(label="A propos", command=self.infobox)
menubar.add_cascade(label="Aide", menu=menu3)
self.config(menu=menubar)
# Options de la fenêtre principale
self.wm_title(CST_TITLE)
if getattr( sys, 'frozen', False ) :
self.iconbitmap(sys._MEIPASS + "\id-card.ico\id-card.ico")
else:
self.iconbitmap("id-card.ico")
self.resizable(width=True, height=True)
self.update()
self.minsize(self.winfo_width(), self.winfo_height())
w = int(self.winfo_width()) # width for the Tk root
h = int(self.winfo_height()) # height for the Tk root
ws = self.winfo_screenwidth() # width of the screen
hs = self.winfo_screenheight() # height of the screen
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
self.geometry('%dx%d+%d+%d' % (w, h, x, y))
self.bind('<Return>', self.returnaj)
self.bind('<Escape>', self.tabaj)
self.logger.info("App_main() : " + "Initialization successful")
def returnaj(self, event):
self.logger.debug("returnaj() : " + "Entering Validation")
if self.PILE_ETAT != [] and len(self.PILE_ETAT)==1 :
thetext = self.termentry.get()
self.logger.debug("returnaj() : " + "PILE_ETAT Satisfy the requisites")
n = len(thetext)
champ = MRZCODE.TYPES[self.PILE_ETAT[0]][0]
if not n%champ.find("|") == 0 :
self.logger.debug("returnaj() : " + "Line not complete, operation aborted")
return None
self.MRZCHAR += thetext
self.termtext['state'] = "normal"
self.termtext.insert("end", thetext + "\n")
self.termtext['state'] = "disabled"
self.termentry.delete(0, "end")
if len(self.MRZCHAR) == champ.find("|"):
self.montext("Entrez la seconde ligne de la MRZ ou \nappuyez sur Entrée pour terminer.\n")
self.logger.debug("returnaj() : " + "First line accepted")
self.MRZCHAR += "|"
elif len(self.MRZCHAR) == champ.find("|")*2+1 or len(champ)==champ.find("|") +1:
self.montext("\nCalcul des sommes ...\n")
self.logger.info("returnaj() : " + "Launching calculsigma() thread")
threading.Thread(target=self.calculsigma, args=[self.MRZCHAR,self.PILE_ETAT[0]]).start()
def tabaj(self, event): #fonction de complétion de champ par touche Escape
if self.PILE_ETAT != [] and len(self.PILE_ETAT)==1 :
thetext = self.termentry.get()
n = len(thetext)
champ = MRZCODE.TYPES[self.PILE_ETAT[0]][0]
if len(self.MRZCHAR) <= champ.find("|"):
champ_type = MRZCODE.TYPES[self.PILE_ETAT[0]][1][ champ[n-1] ].split("|")
self.logger.debug("tabaj() : " + " First line detected")
self.logger.debug("tabaj() : " + " champ_type[0] : " + str(champ_type[0]))
self.logger.debug("tabaj() : " + " champ : " + str(champ))
self.logger.debug("tabaj() : " + " champ[n-1] : " + str(champ[n-1]))
self.logger.debug("tabaj() : " + " champ.find(champ[n-1]) : " + str(champ.find(champ[n-1])))
nb = int(champ_type[0]) - (n - champ.find(champ[n-1]))
else:
self.logger.debug("tabaj() : " + " Second line detected")
champ = champ[champ.find("|")+1:]
champ_type = MRZCODE.TYPES[self.PILE_ETAT[0]][1][ champ[n-1] ].split("|")
self.logger.debug("tabaj() : " + " champ_type[0] : " + str(champ_type[0]))
self.logger.debug("tabaj() : " + " champ : " + str(champ))
self.logger.debug("tabaj() : " + " champ[n-1] : " + str(champ[n-1]))
self.logger.debug("tabaj() : " + " champ.find(champ[n-1]) : " + str(champ.find(champ[n-1])))
self.logger.debug("tabaj() : " + " n : " + str(n))
nb = int(champ_type[0]) - (n - champ.find(champ[n-1]))
self.termentry.insert("end", "<"*nb)
self.logger.debug("tabaj() : " + "Completing entry with " + str(nb) + " characters <")
def validate(self, char, entry_value, typemod):
set = False
isValid = True
if typemod == "1":
SUM = 0
for ch in char:
if ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<":
None
else:
self.bell()
isValid = False
elif typemod == "0":
for ch in char:
if ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<" :
None
else:
self.bell()
isValid = False
#PARSEUR INTERACTIF
n = len(entry_value)
if (n%self.varnum == 0 or len(char)>3) and n<45 and self.PILE_ETAT == []:
for i in range(len(self.Score)): #Reinitialisation des scores
self.Score[i] = 0
for type, t in zip(MRZCODE.TYPES, range(len(MRZCODE.TYPES))): #Test de chaque type
for e in range(len(entry_value)): #Parcours des caractères de la chaine évaluee
try:
champchartest = type[0][:type[0].find("|")][e]
champchar = type[0][e]
except IndexError:
self.logger.debug("validate() : " + "type : " + str(t))
self.logger.debug("validate() : " + "Too short to be ok")
self.Score[t] += -5
break
else:
self.logger.debug("validate() : " + "type : " + str(t))
if len(entry_value) <= type[0].find("|"): #test ligne 1 mrz
champ_type = type[1][str(champchar)].split("|")
pos = e - type[0].find(champchar)
self.logger.debug("validate() : " + "champ_type[2][pos] : " + str(champ_type[2][pos]))
self.logger.debug("validate() : " + "champ_type[1] : " + str(champ_type[1]))
if champ_type[1] == "CODE":
if champ_type[2][pos] == "*":
self.Score[t] += 0
elif champ_type[2][pos] == entry_value[e]:
self.Score[t] += 1
self.logger.debug("validate() : " + "+1")
else:
self.Score[t] += -50
else:
if champ_type[2][pos] == "*":
self.Score[t] += 1
self.logger.debug("validate() : " + "+1")
elif champ_type[2][pos] == "A" and entry_value[e].isalpha():
self.Score[t] += 1
self.logger.debug("validate() : " + "+1")
elif champ_type[2][pos] == "0" and entry_value[e].isnumeric():
self.Score[t] += 1
self.logger.debug("validate() : " + "+1")
elif champ_type[1] == "CTRL" and entry_value[e].isnumeric():
self.Score[t] += 1
self.logger.debug("validate() : " + "+1")
elif champ_type[2][pos] == "&" and (entry_value[e].isalpha() or entry_value[e]=="<"):
self.Score[t] += 1
self.logger.debug("validate() : " + "+1")
else:
self.Score[t] += -1
else:
None #bah on fait rien parcequ'on a atteint le bout de la ligne dans le type donné.
self.logger.debug("validate() : " + "self.Score : " + str(self.Score))
m = max(self.Score)
typem = [i for i, j in enumerate(self.Score) if j == m and m > 5]
for h in typem:
self.PILE_ETAT += [ h ]
if len(self.PILE_ETAT) > 1:
self.varnum += 3
self.PILE_ETAT = []
elif len(self.PILE_ETAT) == 1:
TOPOS = MRZCODE.TYPES[ self.PILE_ETAT[0] ][2]
self.montext(TOPOS + " détectée !\nAppuyez sur Echap pour compléter les champs avec des '<'\nAppuyez sur Entrée pour terminer.\n")
self.logger.debug("validate() : " + "Detection : " + str(TOPOS))
elif len(self.PILE_ETAT) == 1 and len(entry_value) > len(MRZCODE.TYPES[self.PILE_ETAT[0]][0].split("|")[0]):
isValid = False
return isValid
def montext(self,text):
self.monlog['state'] = "normal"
self.monlog.insert("end",text)
self.monlog['state'] = "disabled"
def openingscan(self):
self.initialize(self.logger)
self.update()
path = ""
path = filedialog.askopenfilename(parent=self, title = "Ouvrir un scan de CNI...",filetypes = (("TIF files","*.tif"),("TIF files","*.tiff"), ("JPEG files","*.jpg"), ("JPEG files","*.jpeg"))) #dialog
if path != "":
self.logger.info("openingscan() : " + "Opening file with path " + str(path))
im = Image.open(path)
try:
nframe = im.n_frames
except AttributeError:
nframe = 1
if nframe == 1:
self.mrzdetected = ""
self.mrzdict = {}
opening = OpenScanWin(self, path, 0)
opening.transient(self)
opening.grab_set()
self.wait_window(opening)
self.logger.debug("openingscan() : " + str(self.mrzdetected))
elif nframe > 1: #If multi framed tiff
# self.page = 1
# opening = OpenPageDialog(self, nframe)
# opening.transient(self)
# opening.grab_set()
# self.wait_window(opening)
#
# im.seek(self.page)
# im.save(CST_FOLDER + '\\temp.tif')
#
#
# self.mrzdetected = ""
# self.mrzdict = {}
# opening = OpenScanWin(self, CST_FOLDER + '\\temp.tif', 1)
# opening.transient(self)
# opening.grab_set()
# self.wait_window(opening)
self.mrzdetected = ""
self.mrzdict = {}
opening = OpenScanWin(self, path, 1, nframe)
opening.transient(self)
opening.grab_set()
self.wait_window(opening)
self.logger.debug("openingscan() : " + str(self.mrzdetected))
try:
os.remove(CST_FOLDER + '\\temp.tif')
except IOError:
pass
else:
raise(Exception)
mrzsoumisetab = self.mrzdetected.replace(" ", "").split("\n")
#Sending the mrz to the core system
for chain in mrzsoumisetab:
self.termentry.insert("end", chain)
if len(chain)>=5 : self.returnaj("<Return>")
return None
def newbie(self):
self.initialize(self.logger)
self.montext("\n\nEntrez la première ligne de MRZ svp \n")
def infobox(self):
Tk().withdraw()
showinfo("A propos du logiciel", "Version du logiciel : \n" +CST_NAME + " " + CST_VER + " " + CST_TYPE + " Revision " + CST_REV +"\nLicence GNU/GPL 2018\n\nAuteur : NeoX_ ; devadmin@neoxgroup.eu\n\nTesseract 4.0 est soumis à l'Apache License 2004\n\n N'hésitez pas à faire part de vos commentaires !")
def calculsigma(self, MRZtxt, numtype): #Calcul des sommes
#récupération des index des CTRL
CTRList = [c for c,v in MRZCODE.TYPES[numtype][1].items() if "CTRL" in v]
self.logger.info("[calculsigma() thread] : " + "Sigma calculation launched!")
self.logger.debug("[calculsigma() thread] : " + "CTRList = " + str(CTRList))
self.Falsitude = 0
self.montext("\n")
for i in CTRList:
sumtxt = MRZCODE.TYPES[numtype][1][i] #Récupération du descriptif de la somme
length = MRZCODE.TYPES[numtype][0].find("|") # Récupération de la longueur de ligne du code MRZ
index = MRZCODE.TYPES[numtype][0].find(i) # Récupération de l'index de la somme
sum_read = MRZtxt[index] #lecture de la somme donnée par la carte
if len(sumtxt.split("|")[2])==1: #Cas d'une somme de champ
debut = MRZCODE.TYPES[numtype][0].find(sumtxt.split("|")[2][0])
sum_calc = MRZCODE.MRZ(MRZtxt[ int(debut) : index ])
else: #Cas d'une somme composite
transm_chain = ""
for y in sumtxt.split("|")[2]:
debut = MRZCODE.TYPES[numtype][0].find(y)
fin = debut + int(MRZCODE.TYPES[numtype][1][y].split("|")[0])
transm_chain += MRZtxt[ int(debut) : int(fin) ]
sum_calc = MRZCODE.MRZ(transm_chain)
if str(sum_read)[0] != str(sum_calc)[0] and not(sumtxt.split("|")[1] == "CTRLF" and str(sum_read)[0]=="<"):
self.Falsitude += 1
self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, sum errored : " + str(i))
self.termtext.tag_add('highLOW', '1.0+'+ str(index) + 'c', '1.0+'+ str(index+1) + 'c')
self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white')
else:
self.termtext.tag_add('highLOWB', '1.0+'+ str(index) + 'c', '1.0+'+ str(index+1) + 'c')
self.termtext.tag_configure('highLOWB', background='#04B404', relief='raised', foreground = 'white')
self.montext("Somme : Lu " + str(sum_read) + " VS calculé " + str(sum_calc) + "\n")
#Retriving infos
NameList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "|NOM" in v ]
SurnameList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "PRENOM" in v ]
DDateList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "DDATE" in v ]
BDateList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "BDATE" in v ]
EDateList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "EDATE" in v ]
PAYSList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "PAYS" in v ]
NATList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "NAT" in v ]
SEXList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "SEX" in v ]
NOINTList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "NOINT" in v ]
NOList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "NO|" in v ]
FACULTList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "FACULT" in v ]
INDICList = [ c for c,v in MRZCODE.TYPES[numtype][1].items() if "INDIC" in v ]
BIGList = [ NameList, SurnameList, DDateList, BDateList, EDateList, PAYSList, NATList, SEXList, NOList, INDICList, NOINTList]
BIGObj = [ self.nom, self.prenom, self.ddate, self.bdate, self.edate, self.pays, self.nat, self.sex, self.no, self.indic, self.no]
for i in range(len(BIGList)):
for (champ, champnum) in zip(BIGList[i], range(len(BIGList[i]))):
debut = MRZCODE.TYPES[numtype][0].find(champ)
fin = debut + int(MRZCODE.TYPES[numtype][1][champ].split("|")[0]) #Récupération des coordonnées de champs utiles
if BIGObj[i] == self.pays or BIGObj[i] == self.nat:
try:
BIGObj[i]["text"] = MRZCODE.landcode[ MRZtxt[ int(debut) : int(fin) ] ] #Si c'est un pays on traduit
except KeyError:
self.Falsitude += 1
self.montext("Code pays : " + str(MRZtxt[ int(debut) : int(fin) ]) + " est inconnu \n")
self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, unknown state")
self.termtext.tag_add('highLOW', '1.0+'+ str(debut) + 'c', '1.0+'+ str(fin) + 'c')
self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white')
BIGObj[i]["background"] = "#760808"
BIGObj[i]["foreground"] = "white"
elif BIGObj[i] == self.sex:
try:
BIGObj[i]["text"] = MRZCODE.sexcode[ MRZtxt[ int(debut) : int(fin) ] ]
except KeyError:
self.Falsitude += 1
self.montext("Sexe : " + str(MRZtxt[ int(debut) : int(fin) ]) + " est inconnu \n")
self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, unknown state")
self.termtext.tag_add('highLOW', '1.0+'+ str(debut) + 'c', '1.0+'+ str(fin) + 'c')
self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white')
BIGObj[i]["background"] = "#760808"
BIGObj[i]["foreground"] = "white"
elif BIGObj[i] == self.edate or BIGObj[i] == self.ddate or BIGObj[i] == self.bdate:
txtl = MRZtxt[ int(debut) : int(fin) ].replace("<<<","").replace("<"," ")
if len(txtl) < 6 : txtl += "0" * (6 - len(txtl))
BIGObj[i]["text"] = "{0}/{1}/{2}".format(txtl[4:], txtl[2:4], txtl[:2])
if BIGObj[i] == self.edate:
present = datetime.now()
try:
expiration = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), int(txtl[4:]) )
except ValueError:
BIGObj[i]["background"] = "#760808"
BIGObj[i]["foreground"] = "white"
self.Falsitude += 1
self.montext("Date : " + str(BIGObj[i]["text"]) + " est invalide \n")
self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, invalid expiration date")
self.termtext.tag_add('highLOW', '1.0+'+ str(debut) + 'c', '1.0+'+ str(fin) + 'c')
self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white')
else:
if expiration < present:
BIGObj[i]["background"] = "#e67300"
BIGObj[i]["foreground"] = "white"
else:
try:
if int(txtl[4:]) == 0:
verif = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), 1 )
else:
verif = datetime(2000 + int(txtl[:2]), int(txtl[2:4]), int(txtl[4:]) )
except ValueError:
BIGObj[i]["background"] = "#760808"
BIGObj[i]["foreground"] = "white"
self.Falsitude += 1
self.montext("Date : " + str(BIGObj[i]["text"]) + " est invalide \n")
self.logger.debug("[calculsigma() thread] : " + "Falsitude +1, invalid datetime")
self.termtext.tag_add('highLOW', '1.0+'+ str(debut) + 'c', '1.0+'+ str(fin) + 'c')
self.termtext.tag_configure('highLOW', background='#760808', relief='raised', foreground = 'white')
else:
if champnum == 0:
BIGObj[i]["text"] = MRZtxt[ int(debut) : int(fin) ].replace("<<<","").replace("<"," ")
else:
BIGObj[i]["text"] += MRZtxt[ int(debut) : int(fin) ].replace("<<<","").replace("<"," ")#Sinon on indique juste
if self.Falsitude == 0 :
self.STATUStxt['text'] = "CONFORME"
self.STATUStxt['fg'] = "#04B404"
self.logger.debug("[calculsigma() thread] : " + "Conforme !")
else:
self.STATUStxt['text'] = "NON CONFORME"
self.STATUStxt['fg'] = "#760808"
self.logger.debug("[calculsigma() thread] : " + "Non conforme !")
self.montext("** Score de non conformité : " + str(self.Falsitude) + "**\n")
return None
class MRZCODE:
#Fonction de calcul
def MRZ(code):
"""
Calcul sommes de contrôle de la chaîne transmise
"""
resultat = 0 #init de la somme avant calcul
i = -1 #init du pas de somme avant calcul
facteur = [7, 3, 1] #facteurs magiques
for car in code: #Traitement selon le type de caractère
if car == "<" or car == "|":
valeur = 0
i += 1
elif car in "0123456789":
valeur = int(car)
i += 1
elif car in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
valeur = ord(car)-55
i += 1
else:
self.logger.error("MRZ() : " + "Erreur de calcul : caractère non conforme")
showerror("Erreur du module de calcul",Exception("Caractère non conforme dans la MRZ"))
break
resultat += valeur * facteur[i%3] #Rajout de la valeur du caractère dans la somme et itération suivante
return (resultat % 10) #Fin de la fonction, application du mod 10
#Dictionnaire des sexes
sexcode = { 'M' : 'Homme' , 'F' : 'Femme', 'X' : 'Non spécifié'}
#Dictionnaire des pays
landcode2 = {'AW': 'Aruba', 'AF': 'Afghanistan', 'AO': 'Angola', 'AI': 'Anguilla', 'AL': 'Albanie', 'AD': 'Andorre', 'AE': 'Emirats arabes unis', 'AR': 'Argentine', 'AM': 'Arménie', 'AS': 'Samoa américaines', 'AQ': 'Antarctique', 'TF': 'Terres australes et antarctiques françaises', 'AG': 'Antigua-et-Barbuda', 'AU': 'Australie', 'AT': 'Autriche', 'AZ': 'Azerbaidjan', 'BI': 'Burundi', 'BE': 'Belgique', 'BJ': 'Benin', 'BQ': 'Pays-Bas caribéens', 'BF': 'Burkina Faso', 'BD': 'Bangladesh', 'BG': 'Bulgarie', 'BH': 'Bahrein', 'BS': 'Bahamas', 'BA': 'Bosnie-Herzegovine', 'BL': 'Saint-Barthélemy', 'BY': 'Bielorussie', 'BZ': 'Belize', 'BM': 'Bermudes', 'BO': 'Bolivie', 'BR': 'Brésil', 'BB': 'Barbade', 'BN': 'Brunei', 'BT': 'Bhoutan', 'BW': 'Botswana', 'CF': 'République Centrafricaine', 'CA': 'Canada', 'CC': 'Îles Cocos', 'CH': 'Suisse', 'CL': 'Chili', 'CN': 'Chine', 'CI': "Côte d'Ivoire", 'CM': 'Cameroun', 'CD': 'Congo (République démocratique)', 'CG': 'Congo (République)', 'CK': 'Îles Cook', 'CO': 'Colombie', 'KM': 'Comores', 'CV': 'Cap-Vert', 'CR': 'Costa Rica', 'CU': 'Cuba', 'CW': 'Curaçao', 'CX': 'Île Christmas', 'KY': 'Caimans', 'CY': 'Chypre', 'CZ': 'Tchéquie', 'DE': 'Allemagne', 'DJ': 'Djibouti', 'DM': 'Dominique', 'DK': 'Danemark', 'DO': 'République dominicaine', 'DZ': 'Algérie', 'EC': 'Equateur', 'EG': 'Egypte', 'ER': 'Erythrée', 'EH': 'Sahara occidental', 'ES': 'Espagne', 'EE': 'Estonie', 'ET': 'Ethiopie', 'FI': 'Finlande', 'FJ': 'Fidji', 'FK': 'Îles Malouines', 'FR': 'France', 'FO': 'Féroé', 'FM': 'Micronésie', 'GA': 'Gabon', 'GB': 'Royaume-Uni', 'GE': 'Géorgie', 'GG': 'Guernesey', 'GH': 'Ghana', 'GI': 'Gibraltar', 'GN': 'Guinée', 'GP': 'Guadeloupe', 'GM': 'Gambie', 'GW': 'Guinée-Bissau', 'GQ': 'Guinée équatoriale', 'GR': 'Grèce', 'GD': 'Grenade', 'GL': 'Groenland', 'GT': 'Guatemala', 'GF': 'Guyane', 'GU': 'Guam', 'GY': 'Guyana', 'HK': 'Hong Kong', 'HN': 'Honduras', 'HR': 'Croatie', 'HT': 'Haïti', 'HU': 'Hongrie', 'ID': 'Indonésie', 'IM': 'Île de Man', 'IN': 'Inde', 'IO': "Territoire britannique de l'océan Indien", 'IE': 'Irlande', 'IR': 'Irak', 'IQ': 'Iran', 'IS': 'Islande', 'IL': 'Israël', 'IT': 'Italie', 'JM': 'Jamaïque', 'JE': 'Jersey', 'JO': 'Jordanie', 'JP': 'Japon', 'KZ': 'Kazakhstan', 'KE': 'Kenya', 'KG': 'Kirghizistan', 'KH': 'Cambodge', 'KI': 'Kiribati', 'KN': 'Saint-Christophe-et-Niévès', 'KR': 'Corée du Sud', 'KW': 'Koweït', 'LA': 'Laos', 'LB': 'Liban', 'LR': 'Liberia', 'LY': 'Libye', 'LC': 'Sainte-Lucie', 'LI': 'Liechtenstein', 'LK': 'Sri Lanka', 'LS': 'Lesotho', 'LT': 'Lituanie', 'LU': 'Luxembourg', 'LV': 'Lettonie', 'MO': 'Macao', 'MF': 'Sint-Maarten', 'MA': 'Maroc', 'MC': 'Monaco', 'MD': 'Moldavie', 'MG': 'Madagascar', 'MV': 'Maldives', 'MX': 'Mexique', 'MH': 'Marshall', 'MK': 'Macedoine', 'ML': 'Mali', 'MT': 'Malte', 'MM': 'Birmanie', 'ME': 'Monténégro', 'MN': 'Mongolie', 'MP': 'Îles Mariannes du Nord', 'MZ': 'Mozambique', 'MR': 'Mauritanie', 'MS': 'Montserrat', 'MQ': 'Martinique', 'MU': 'Maurice', 'MW': 'Malawi', 'MY': 'Malaisie', 'YT': 'Mayotte', 'NA': 'Namibie', 'NC': 'Nouvelle-Calédonie', 'NE': 'Niger', 'NF': 'Île Norfolk', 'NG': 'Nigeria', 'NI': 'Nicaragua', 'NU': 'Niue', 'NL': 'Pays-Bas', 'NO': 'Norvège', 'NP': 'Nepal', 'NR': 'Nauru', 'NZ': 'Nouvelle-Zélande', 'OM': 'Oman', 'PK': 'Pakistan', 'PA': 'Panama', 'PN': 'Îles Pitcairn', 'PE': 'Pérou', 'PH': 'Philippines', 'PW': 'Palaos', 'PG': 'Papouasie-Nouvelle-Guinée', 'PL': 'Pologne', 'PR': 'Porto Rico', 'KP': 'Corée du Nord', 'PT': 'Portugal', 'PY': 'Paraguay', 'PS': 'Palestine', 'PF': 'Polynésie française', 'QA': 'Qatar', 'RE': 'Réunion', 'RO': 'Roumanie', 'RU': 'Russie', 'RW': 'Rwanda', 'SA': 'Arabie saoudite', 'SD': 'Soudan', 'SN': 'Sénégal', 'SG': 'Singapour', 'GS': 'Georgie du Sud-et-les iles Sandwich du Sud', 'SH': 'Sainte-Hélène, Ascension et Tristan da Cunha', 'SJ': 'Svalbard et île Jan Mayen', 'SB': 'Salomon', 'SL': 'Sierra Leone', 'SV': 'Salvador', 'SM': 'Saint-Marin', 'SO': 'Somalie', 'PM': 'Saint-Pierre-et-Miquelon', 'RS': 'Serbie', 'SS': 'Soudan du Sud', 'ST': 'Sao Tomé-et-P
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 May
#Passeport
P = ["11222333333333333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCCCCCCCCDE" , {
"1" : "2|CODE|P*" ,
"2" : "3|PAYS|AAA" ,
"3" : "39|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&",
"4" : "9|NO|*********",
"5" : "1|CTRL|4",
"6" : "3|NAT|AAA",
"7" : "6|BDATE|000000",
"8" : "1|CTRL|7",
"9" : "1|SEX|A",
"A" : "6|EDATE|000000",
"B" : "1|CTRL|A",
"C" : "14|FACULT|**************",
"D" : "1|CTRLF|C",
"E" : "1|CTRL|4578ABCD"
}, "Passeport"]
#Carte passeport
IP = ["112223333333334555555555555555|66666678999999ABBBCCCCCCCCCCCD" , {
"1" : "2|CODE|IP" ,
"2" : "3|PAYS|AAA" ,
"3" : "9|NO|*********",
"4" : "1|CTRL|3",
"5" : "15|FACULT|***************",
"6" : "6|BDATE|000000",
"7" : "1|CTRL|6",
"8" : "1|SEX|A",
"9" : "6|EDATE|000000",
"A" : "1|CTRL|9",
"B" : "3|NAT|AAA",
"C" : "11|FACULT|***********",
"D" : "1|CTRL|345679AC"
}, "Carte-passeport"]
#ID dv1
I_ = ["112223333333334555555555555555|66666678999999ABBBCCCCCCCCCCCD" , {
"1" : "2|CODE|I*" ,
"2" : "3|PAYS|AAA" ,
"3" : "9|NO|*********",
"4" : "1|CTRL|3",
"5" : "15|FACULT|***************",
"6" : "6|BDATE|000000",
"7" : "1|CTRL|6",
"8" : "1|SEX|A",
"9" : "6|EDATE|000000",
"A" : "1|CTRL|9",
"B" : "3|NAT|AAA",
"C" : "11|FACULT|***********",
"D" : "1|CTRL|345679AC"
}, "Titre d'identité/de voyage"]
#Certificat de membre dequipaj
AC = ["112223333333334EEE555555555555|66666678999999ABBBCCCCCCCCCCCD" , {
"1" : "2|CODE|AC" ,
"2" : "3|PAYS|AAA" ,
"3" : "9|NO|*********",
"4" : "1|CTRL|3",
"5" : "15|FACULT|***************",
"6" : "6|BDATE|000000",
"7" : "1|CTRL|6",
"8" : "1|SEX|A",
"9" : "6|EDATE|000000",
"A" : "1|CTRL|9",
"B" : "3|NAT|AAA",
"C" : "11|FACULT|***********",
"D" : "1|CTRL|345679AC",
"E" : "3|INDIC|AA&" #ATTENTION : Doc 8585 (3 lettres) + Airline Coding Directory de lIATA
}, "Certificat de membre d'équipage"]
#Visa de type A
VA = ["11222333333333333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCCCCCCCCDE" , {
"1" : "2|CODE|V*" ,
"2" : "3|PAYS|AAA" ,
"3" : "39|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&",
"4" : "9|NO|*********",
"5" : "1|CTRL|4",
"6" : "3|NAT|AAA",
"7" : "6|BDATE|000000",
"8" : "1|CTRL|7",
"9" : "1|SEX|A",
"A" : "6|EDATE|000000",
"B" : "1|CTRL|A",
"C" : "14|FACULT|**************",
}, "Visa de type A"]
#Visa de type B
VB = ["112223333333333333333333333333333333|444444444566677777789AAAAAABCCCCCC" , {
"1" : "2|CODE|V*" ,
"2" : "3|PAYS|AAA" ,
"3" : "31|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&",
"4" : "9|NO|*********",
"5" : "1|CTRL|4",
"6" : "3|NAT|AAA",
"7" : "6|BDATE|000000",
"8" : "1|CTRL|7",
"9" : "1|SEX|A",
"A" : "6|EDATE|000000",
"B" : "1|CTRL|A",
"C" : "8|FACULT|********"
}, "Visa de type B"]
#ID dv2
I__ = ["112223333333333333333333333333333333|444444444566677777789AAAAAABCCCCCCCD" , {
"1" : "2|CODE|I*" ,
"2" : "3|PAYS|AAA" ,
"3" : "31|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&",
"4" : "9|NO|*********",
"5" : "1|CTRL|4",
"6" : "3|NAT|AAA",
"7" : "6|BDATE|000000",
"8" : "1|CTRL|7",
"9" : "1|SEX|A",
"A" : "6|EDATE|000000",
"B" : "1|CTRL|A",
"C" : "7|FACULT|*******",
"D" : "1|CTRL|4578ABC"
}, "Pièce d'identité/de voyage"]
#ID2
ID = ["112223333333333333333333333333444444|555566677777899999999999999AAAAAABCD" , {
"1" : "2|CODE|ID" ,
"2" : "3|PAYS|AAA" ,
"3" : "25|NOM|&&&&&&&&&&&&&&&&&&&&&&&&&",
"4" : "6|NOINT|000***", #000 pour le département avec prefixe 0 et poste agent ***
"5" : "4|DDATE|0000", #AAMM
"6" : "3|NOINT2|000", #departement et pref
"7" : "5|NOINT3|00000", #No arbitraire
"8" : "1|CTRL|567",
"9" : "14|PRENOM|A",
"A" : "6|BDATE|000000",
"B" : "1|CTRL|A",
"C" : "1|SEX|A",
"D" : "1|CTRL|123456789ABC"
},"Pièce d'identité FR"]
DL = ["112223333333334555555666666667|" , {
"1" : "2|CODE|D1" ,
"2" : "3|PAYS|AAA" ,
"3" : "9|NO|00AA00000",
"4" : "1|CTRL|123",
"5" : "6|EDATE|000000",
"6" : "8|NOM|&&&&&&&&",
"7" : "1|CTRL|123456",
},"Permis de conduire"]
TYPES = [ ID, I__, VB, VA, AC, I_, IP, P, DL ]
class AESCipher(object):
def __init__(self, key):
self.bs = 32
self.key = hashlib.sha256(key.encode()).digest()
def encrypt(self, raw):
raw = self._pad(raw)
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw))
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
@staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]
class Download: #Gestionnaire de DL
def __init__(self, url, localfile, namefile, opener, p, canvas, message, logger): #p is the progress bar, canvas and message are using to display text
from socket import timeout
self.retry = 6
self.logger = logger
self.opener = opener
self.url = url
self.localfile = localfile
self.success = False
self.time = 5
self.count = 0 # Counts downloaded size.
self.success = False
self.downloading = True
self.logger.error("[Download() class] : " + "Initialization ok")
while (not(self.success) and self.downloading):
try:
self.Err = ""
self._netfile = self.opener.open(self.url, timeout=self.time)
self.filesize = float(self._netfile.info()['Content-Length'])
if (os.path.exists(self.localfile) and os.path.isfile(self.localfile)):
self.count = os.path.getsize(self.localfile)
if self.count >= self.filesize:
#already downloaded
self.downloading = False
self.success = True
self._netfile.close()
return
if (os.path.exists(self.localfile) and os.path.isfile(self.localfile)):
#File already exists, start where it left off:
self._netfile.close()
req = urllib2.Request(self.url)
req.add_header("Range","bytes=%s-" % (self.count))
self._netfile = self.opener.open(req, timeout=self.time)
if (self.downloading): #Don't do it if cancelled, downloading=false.
self._outfile = open(self.localfile,"ab") #to append binary
next = self._netfile.read(1024)
p.stop()
p.configure(mode = "determinate", value = 0, maximum = 100)
while (len(next)>0 and self.downloading):
self._outfile.write(next)
self.count += len(next)
next = self._netfile.read(1024)
Percent = int(((self.count)/self.filesize*100))
p.configure(mode = "determinate", value = int(Percent))
canvas.itemconfigure(message, text="Téléchargement de " + str(namefile) + " de taille " + str(int(self.filesize/1024/1024)) + " Mo : " + str(Percent) + " %")
self._netfile.close()
self._outfile.close()
self.success = True
except urllib2.HTTPError as e:
self.logger.error("[Download() class] : " + "HTTP ERROR " + str(e.code))
try:
self._netfile.close()
self._outfile.close()
except Exception as err:
self.logger.critical("[Download() class] : " + "FILE I/O ERROR : " + str(err))
self.retry += -1
canvas.itemconfigure(message, text="Erreur HTTP " + str(e.code) +". Nouvelles tentatives : " +str(self.retry))
if self.retry <= 0:
self.downloading = False
except timeout as e:
self.logger.error("[Download() class] : " + "TIMEOUT ERROR : " + str(e))
try:
self._netfile.close()
self._outfile.close()
except Exception as err:
self.logger.critical("[Download() class] : " + "FILE I/O ERROR : " + str(err))
self.retry += -1
self.time *= 2
canvas.itemconfigure(message, text="Connexion expirée. Nouvelles tentatives : " + str(self.retry) + ", " + str(self.time) + " s")
if self.retry <= 0:
self.downloading = False
except IOError as e:
self.logger.error("[Download() class] : " + "I/O ERROR :" + str(e))
try:
self._netfile.close()
self._outfile.close()
except Exception as err:
self.logger.critical("[Download() class] : " + "FILE I/O ERROR : " + str(err))
self.retry += -1
self.time *= 2
canvas.itemconfigure(message, text="Connexion expirée. Nouvelles tentatives : " + str(self.retry) + ", " + str(self.time) + " s")
if self.retry <= 0:
self.downloading = False
except Exception as e:
self.logger.error("[Download() class] : " + "UNKNOWN ERROR : " + str(e))
try:
self._netfile.close()
self._outfile.close()
except Exception as err:
self.logger.critical("[Download() class] : " + "FILE I/O ERROR : " + str(err))
self.retry += -1
canvas.itemconfigure(message, text="Erreur inconnue. Nouvelles tentatives : " + str(self.retry))
if self.retry <= 0:
self.downloading = False
class AutoScrollbar(ttk.Scrollbar):
""" A scrollbar that hides itself if it's not needed. Works only for grid geometry manager """
def set(self, lo, hi):
if float(lo) <= 0.0 and float(hi) >= 1.0:
self.grid_remove()
else:
self.grid()
ttk.Scrollbar.set(self, lo, hi)
def pack(self, **kw):
raise TclError('Cannot use pack with the widget ' + self.__class__.__name__)
def place(self, **kw):
raise TclError('Cannot use place with the widget ' + self.__class__.__name__)
class CanvasImage:
""" Display and zoom image """
def __init__(self, placeholder, file, type):
""" Initialize the ImageFrame """
self.type = type #1 for multipage, 0 for normal
self.angle = 0
self.imscale = 1.0 # scale for the canvas image zoom, public for outer classes
self.__delta = 1.3 # zoom magnitude
self.__filter = Image.ANTIALIAS # could be: NEAREST, BILINEAR, BICUBIC and ANTIALIAS
self.__previous_state = 0 # previous state of the keyboard
self.path = file # path to the image, should be public for outer classes
# Create ImageFrame in placeholder widget
self.__imframe = ttk.Frame(placeholder)
self.placeholder = placeholder # placeholder of the ImageFrame object
# Vertical and horizontal scrollbars for canvas
hbar = AutoScrollbar(self.__imframe, orient='horizontal')
vbar = AutoScrollbar(self.__imframe, orient='vertical')
hbar.grid(row=1, column=0, sticky='we')
vbar.grid(row=0, column=1, sticky='ns')
# Create canvas and bind it with scrollbars. Public for outer classes
self.canvas = Canvas(self.__imframe, highlightthickness=0,
xscrollcommand=hbar.set, yscrollcommand=vbar.set)
self.canvas.grid(row=0, column=0, sticky='nswe')
self.canvas.update() # wait till canvas is created
hbar.configure(command=self.__scroll_x) # bind scrollbars to the canvas
vbar.configure(command=self.__scroll_y)
# Bind events to the Canvas
self.canvas.bind('<Configure>', lambda event: self.__show_image()) # canvas is resized
self.canvas.bind('<ButtonPress-1>', self.__move_from) # remember canvas position
self.canvas.bind('<B1-Motion>', self.__move_to) # move canvas to the new position
self.canvas.bind('<MouseWheel>', self.__wheel) # zoom
# Decide if this image huge or not
self.__huge = False # huge or not
self.__huge_size = 14000 # define size of the huge image
self.__band_width = 1024 # width of the tile band
Image.MAX_IMAGE_PIXELS = 1000000000 # suppress DecompressionBombError for the big image
with warnings.catch_warnings(): # suppress DecompressionBombWarning
warnings.simplefilter('ignore')
self.__image = Image.open(self.path) # open image, but down't load it
self.imwidth, self.imheight = self.__image.size # public for outer classes
if self.imwidth * self.imheight > self.__huge_size * self.__huge_size and \
self.__image.tile[0][0] == 'raw': # only raw images could be tiled
self.__huge = True # image is huge
self.__offset = self.__image.tile[0][2] # initial tile offset
self.__tile = [self.__image.tile[0][0], # it have to be 'raw'
[0, 0, self.imwidth, 0], # tile extent (a rectangle)
self.__offset,
self.__image.tile[0][3]] # list of arguments to the decoder
self.__min_side = min(self.imwidth, self.imheight) # get the smaller image side
# Create image pyramid
self.__pyramid = [self.smaller()] if self.__huge else [Image.open(self.path)]
# Set ratio coefficient for image pyramid
self.__ratio = max(self.imwidth, self.imheight) / self.__huge_size if self.__huge else 1.0
self.__curr_img = 0 # current image from the pyramid
self.__scale = self.imscale * self.__ratio # image pyramide scale
self.__reduction = 2 # reduction degree of image pyramid
w, h = self.__pyramid[-1].size
while w > 512 and h > 512: # top pyramid image is around 512 pixels in size
w /= self.__reduction # divide on reduction degree
h /= self.__reduction # divide on reduction degree
try:
self.__pyramid.append(self.__pyramid[-1].resize((int(w), int(h)), self.__filter))
except TypeError:
showerror(title="Erreur de fichier", message="Image incompatible. Merci d'utiliser une autre image ou de la convertir", parent = self.placeholder)
# Put image into container rectangle and use it to set proper coordinates to the image
self.container = self.canvas.create_rectangle((0, 0, self.imwidth, self.imheight), width=0)
self.__show_image() # show image on the canvas
self.canvas.focus_set() # set focus on the canvas
def rotatem(self):
self.angle += 1
self.__show_image()
def rotatep(self):
self.angle -= 1
self.__show_image()
def rotatemm(self):
self.angle += 90
self.__show_image()
def rotatepp(self):
self.angle -= 90
self.__show_image()
def smaller(self):
""" Resize image proportionally and return smaller image """
w1, h1 = float(self.imwidth), float(self.imheight)
w2, h2 = float(self.__huge_size), float(self.__huge_size)
aspect_ratio1 = w1 / h1
aspect_ratio2 = w2 / h2 # it equals to 1.0
if aspect_ratio1 == aspect_ratio2:
image = Image.new('RGB', (int(w2), int(h2)))
k = h2 / h1 # compression ratio
w = int(w2) # band length
elif aspect_ratio1 > aspect_ratio2:
image = Image.new('RGB', (int(w2), int(w2 / aspect_ratio1)))
k = h2 / w1 # compression ratio
w = int(w2) # band length
else: # aspect_ratio1 < aspect_ration2
image = Image.new('RGB', (int(h2 * aspect_ratio1), int(h2)))
k = h2 / h1 # compression ratio
w = int(h2 * aspect_ratio1) # band length
i, j, n = 0, 1, round(0.5 + self.imheight / self.__band_width)
while i < self.imheight:
band = min(self.__band_width, self.imheight - i) # width of the tile band
self.__tile[1][3] = band # set band width
self.__tile[2] = self.__offset + self.imwidth * i * 3 # tile offset (3 bytes per pixel)
self.__image.close()
self.__image = Image.open(self.path) # reopen / reset image
self.__image.size = (self.imwidth, band) # set size of the tile band
self.__image.tile = [self.__tile] # set tile
cropped = self.__image.crop((0, 0, self.imwidth, band)) # crop tile band
image.paste(cropped.resize((w, int(band * k)+1), self.__filter), (0, int(i * k)))
i += band
j += 1
return image
def redraw_figures(self):
""" Dummy function to redraw figures in the children classes """
pass
def grid(self, **kw):
""" Put CanvasImage widget on the parent widget """
self.__imframe.grid(**kw) # place CanvasImage widget on the grid
self.__imframe.grid(sticky='nswe') # make frame container sticky
self.__imframe.rowconfigure(0, weight=1) # make canvas expandable
self.__imframe.columnconfigure(0, weight=1)
# noinspection PyUnusedLocal
def __scroll_x(self, *args, **kwargs):
""" Scroll canvas horizontally and redraw the image """
self.canvas.xview(*args) # scroll horizontally
self.__show_image() # redraw the image
# noinspection PyUnusedLocal
def __scroll_y(self, *args, **kwargs):
""" Scroll canvas vertically and redraw the image """
self.canvas.yview(*args) # scroll vertically
self.__show_image() # redraw the image
def __show_image(self):
""" Show image on the Canvas. Implements correct image zoom almost like in Google Maps """
box_image = self.canvas.coords(self.container) # get image area
box_canvas = (self.canvas.canvasx(0), # get visible area of the canvas
self.canvas.canvasy(0),
self.canvas.canvasx(self.canvas.winfo_width()),
self.canvas.canvasy(self.canvas.winfo_height()))
box_img_int = tuple(map(int, box_image)) # convert to integer or it will not work properly
# Get scroll region box
box_scroll = [min(box_img_int[0], box_canvas[0]), min(box_img_int[1], box_canvas[1]),
max(box_img_int[2], box_canvas[2]), max(box_img_int[3], box_canvas[3])]
# Horizontal part of the image is in the visible area
if box_scroll[0] == box_canvas[0] and box_scroll[2] == box_canvas[2]:
box_scroll[0] = box_img_int[0]
box_scroll[2] = box_img_int[2]
# Vertical part of the image is in the visible area
if box_scroll[1] == box_canvas[1] and box_scroll[3] == box_canvas[3]:
box_scroll[1] = box_img_int[1]
box_scroll[3] = box_img_int[3]
# Convert scroll region to tuple and to integer
self.canvas.configure(scrollregion=tuple(map(int, box_scroll))) # set scroll region
x1 = max(box_canvas[0] - box_image[0], 0) # get coordinates (x1,y1,x2,y2) of the image tile
y1 = max(box_canvas[1] - box_image[1], 0)
x2 = min(box_canvas[2], box_image[2]) - box_image[0]
y2 = min(box_canvas[3], box_image[3]) - box_image[1]
if int(x2 - x1) > 0 and int(y2 - y1) > 0: # show image if it in the visible area
if self.__huge and self.__curr_img < 0: # show huge image
h = int((y2 - y1) / self.imscale) # height of the tile band
self.__tile[1][3] = h # set the tile band height
self.__tile[2] = self.__offset + self.imwidth * int(y1 / self.imscale) * 3
self.__image.close()
self.__image = Image.open(self.path) # reopen / reset image
self.__image.size = (self.imwidth, h) # set size of the tile band
self.__image.tile = [self.__tile]
image = self.__image.crop((int(x1 / self.imscale), 0, int(x2 / self.imscale), h))
else: # show normal image
image = self.__pyramid[max(0, self.__curr_img)].crop( # crop current img from pyramid
(int(x1 / self.__scale), int(y1 / self.__scale),
int(x2 / self.__scale), int(y2 / self.__scale)))
#
self.resizedim = image.resize((int(x2 - x1), int(y2 - y1)), self.__filter).rotate(self.angle, expand = 1)
imagetk = ImageTk.PhotoImage(self.resizedim, master = self.placeholder)
imageid = self.canvas.create_image(max(box_canvas[0], box_img_int[0]),
max(box_canvas[1], box_img_int[1]),
anchor='nw', image=imagetk)
self.canvas.lower(imageid) # set image into background
self.canvas.imagetk = imagetk # keep an extra reference to prevent garbage-collection
def __move_from(self, event):
""" Remember previous coordinates for scrolling with the mouse """
self.canvas.scan_mark(event.x, event.y)
def __move_to(self, event):
""" Drag (move) canvas to the new position """
self.canvas.scan_dragto(event.x, event.y, gain=1)
self.__show_image() # zoom tile and show it on the canvas
def outside(self, x, y):
""" Checks if the point (x,y) is outside the image area """
bbox = self.canvas.coords(self.container) # get image area
if bbox[0] < x < bbox[2] and bbox[1] < y < bbox[3]:
return False # point (x,y) is inside the image area
else:
return True # point (x,y) is outside the image area
def __wheel(self, event):
""" Zoom with mouse wheel """
x = self.canvas.canvasx(event.x) # get coordinates of the event on the canvas
y = self.canvas.canvasy(event.y)
if self.outside(x, y): return # zoom only inside image area
scale = 1.0
# Respond to Linux (event.num) or Windows (event.delta) wheel event
if event.delta == -120: # scroll down, smaller
if round(self.__min_side * self.imscale) < int(self.placeholder.winfo_screenheight()): return # image is less than the number of window pixels
self.imscale /= self.__delta
scale /= self.__delta
if event.delta == 120: # scroll up, bigger
i = min(self.canvas.winfo_width(), self.canvas.winfo_height()) >> 1
if i < self.imscale: return # 1 pixel is bigger than the visible area
self.imscale *= self.__delta
scale *= self.__delta
# Take appropriate image from the pyramid
k = self.imscale * self.__ratio # temporary coefficient
self.__curr_img = min((-1) * int(math.log(k, self.__reduction)), len(self.__pyramid) - 1)
self.__scale = k * math.pow(self.__reduction, max(0, self.__curr_img))
#
self.canvas.scale('all', x, y, scale, scale) # rescale all objects
# Redraw some figures before showing image on the screen
self.redraw_figures() # method for child classes
self.__show_image()
def crop(self, bbox):
""" Crop rectangle from the image and return it """
if self.__huge: # image is huge and not totally in RAM
band = bbox[3] - bbox[1] # width of the tile band
self.__tile[1][3] = band # set the tile height
self.__tile[2] = self.__offset + self.imwidth * bbox[1] * 3 # set offset of the band
self.__image.close()
self.__image = Image.open(self.path) # reopen / reset image
self.__image.size = (self.imwidth, band) # set size of the tile band
self.__image.tile = [self.__tile]
return self.__image.crop((bbox[0], 0, bbox[2], band))
else: # image is totally in RAM
return self.__pyramid[0].crop(bbox)
def destroy(self):
""" ImageFrame destructor """
self.__image.close()
map(lambda i: i.close, self.__pyramid) # close all pyramid images
del self.__pyramid[:] # delete pyramid list
del self.__pyramid # delete pyramid variable
self.canvas.destroy()
class OpenScan(ttk.Frame):
""" Main window class """
def __init__(self, mainframe, fileorig, type, nframe = 1, pagenum = 0, file = None):
""" Initialize the main Frame """
if file == None : file = fileorig
self.file = file
self.fileorig = fileorig
self.nframe = nframe
self.pagenum = pagenum
self.parent = mainframe.parent
ttk.Frame.__init__(self, master=mainframe)
self.master.title('Ouvrir un scan... (Utilisez la roulette pour zoomer, clic gauche pour déplacer et clic droit pour sélectionner la MRZ)')
self.master.resizable(width=False, height=False)
hs = self.winfo_screenheight()
w = int(self.winfo_screenheight()/1.5) # width for the Tk root
h = int(self.winfo_screenheight()/2) # height for the Tk root
ws = self.winfo_screenwidth() # width of the screen
hs = self.winfo_screenheight() # height of the screen
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
self.master.geometry('%dx%d+%d+%d' % (w, h, x, y))
if getattr( sys, 'frozen', False ) :
self.master.iconbitmap(sys._MEIPASS + "\id-card.ico\id-card.ico")
else:
self.master.iconbitmap("id-card.ico")
self.master.rowconfigure(0, weight=1)
self.master.columnconfigure(0, weight=1)
self.cadre = CanvasImage(self.master, self.file, type)
self.cadre.grid(row=0, column=0)
self.master.menubar = Menu(self.master)
if type==1 : self.master.menubar.add_command(label="Page précédente", command= self.pagep)
self.master.menubar.add_command(label="Pivoter -90°", command= self.cadre.rotatemm)
self.master.menubar.add_command(label="Pivoter -1°", command= self.cadre.rotatem)
self.master.menubar.add_command(label="Pivoter +1°", command= self.cadre.rotatep)
self.master.menubar.add_command(label="Pivoter +90°", command= self.cadre.rotatepp)
if type==1 : self.master.menubar.add_command(label="Page suivante", command= self.pages)
self.master.config(menu=self.master.menubar)
self.cadre.canvas.bind('<ButtonPress-3>', self.motionprep)
self.cadre.canvas.bind('<B3-Motion>', self.motionize)
self.cadre.canvas.bind('<ButtonRelease-3>', self.motionend)
def pages(self):
if self.pagenum +1 < self.nframe:
# self.destroy()
im = Image.open(self.fileorig)
im.seek(self.pagenum +1)
newpath = CST_FOLDER + '\\temp' + str(random.randint(11111,99999)) +'.tif'
im.save(newpath)
im.close()
self.cadre.destroy()
self.__init__(self.master, self.fileorig, 1, self.nframe, self.pagenum +1, newpath)
def pagep(self):
if self.pagenum - 1 >= 0:
# self.destroy()
im = Image.open(self.fileorig)
im.seek(self.pagenum - 1)
newpath = CST_FOLDER + '\\temp' + str(random.randint(11111,99999)) +'.tif'
im.save(newpath)
im.close()
self.cadre.destroy()
self.__init__(self.master, self.fileorig, 1, self.nframe, self.pagenum -1, newpath)
def motionprep(self,event):
if hasattr(self,"rect"):
self.begx = event.x
self.begy = event.y
self.ix = self.cadre.canvas.canvasx(event.x)
self.iy = self.cadre.canvas.canvasy(event.y)
self.cadre.canvas.coords(self.rect, self.cadre.canvas.canvasx(event.x), self.cadre.canvas.canvasy(event.y),self.ix,self.iy)
else:
self.begx = event.x
self.begy = event.y
self.ix = self.cadre.canvas.canvasx(event.x)
self.iy = self.cadre.canvas.canvasy(event.y)
self.rect = self.cadre.canvas.create_rectangle(self.cadre.canvas.canvasx(event.x), self.cadre.canvas.canvasy(event.y),self.ix,self.iy, outline='red')
def motionize(self,event):
event.x
event.y
self.cadre.canvas.coords(self.rect, self.ix,self.iy,self.cadre.canvas.canvasx(event.x),self.cadre.canvas.canvasy(event.y))
def motionend(self,event):
self.endx = event.x
self.endy = event.y
self.imtotreat = self.cadre.resizedim.crop((min(self.begx,self.endx), min(self.begy, self.endy), max(self.endx, self.begx), max(self.endy, self.begy)))
im = self.imtotreat
import CNI_pytesseract as pytesseract
try:
os.environ["PATH"] = CST_FOLDER + "\\Tesseract-OCR4\\"
os.environ["TESSDATA_PREFIX"] = CST_FOLDER + "\\Tesseract-OCR4\\tessdata"
self.text = pytesseract.image_to_string(im, lang="ocrb",boxes=False,config="--psm 6 --oem 0 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<")
except pytesseract.TesseractNotFoundError as e:
try:
os.remove(CST_FOLDER + "\\Tesseract-OCR4\\*.*")
except Exception:
pass
showerror("Erreur de module OCR", "Le module OCR localisé en " + str(os.environ["PATH"]) + "est introuvable. Il sera réinstallé à la prochaine exécution", parent = self)
except pytesseract.TesseractError as e:
pass
self.master.success = False
dialogconf = OpenScanDialog(self.master, self.text)
dialogconf.transient(self)
dialogconf.grab_set()
self.wait_window(dialogconf)
if self.master.success:
self.master.destroy()
class OpenScanWin(Toplevel):
def __init__(self, parent, file, type, nframe = 1):
super().__init__(parent)
self.parent = parent
app = OpenScan(self, file, type, nframe)
class OpenScanDialog(Toplevel):
def __init__(self, parent, text):
super().__init__(parent)
self.parent = parent
self.title('Validation de la MRZ détectée')
self.resizable(width=False, height=False)
self.termtext = Text(self, state='normal', width=45, height=2, wrap='none', font = "Terminal 17", fg = "#121f38")
self.termtext.grid(column=0,row=0,sticky='NEW', padx=5, pady = 5)
self.termtext.insert("end", text + "\n")
self.button = Button(self, text="Valider", command=self.valid)
self.button.grid(column=0,row=1,sticky='S', padx=5, pady = 5)
self.update()
hs = self.winfo_screenheight()
w = int(self.winfo_width()) # width for the Tk root
h = int(self.winfo_height()) # height for the Tk root
ws = self.winfo_screenwidth() # width of the screen
hs = self.winfo_screenheight() # height of the screen
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
self.geometry('%dx%d+%d+%d' % (w, h, x, y))
if getattr( sys, 'frozen', False ) :
self.iconbitmap(sys._MEIPASS + "\id-card.ico\id-card.ico")
else:
self.iconbitmap("id-card.ico")
def valid(self):
self.parent.parent.mrzdetected = self.termtext.get('1.0', 'end')
texting = self.parent.parent.mrzdetected.replace(" ","").replace("\r","").split("\n")
for i in range(len(texting)):
for char in texting[i]:
if not char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<":
showerror("Erreur de validation", "La MRZ soumise contient des caractères invalides", parent = self)
self.parent.parent.mrzdetected = ""
return
self.parent.success = True
self.destroy()
class OpenPageDialog(Toplevel):
def __init__(self, parent, number):
super().__init__(parent)
self.parent = parent
self.title("Choisir la page à afficher de l'image selectionnée")
self.resizable(width=False, height=False)
self.termtext = Label(self, text = "Merci de selectionner un numéro de page dans la liste ci-dessous.")
self.termtext.grid(column=0,row=0,sticky='N', padx=5, pady = 5)
self.combotry = ttk.Combobox(self)
self.combotry['values'] = tuple(str(x) for x in range(1,number+1) )
self.combotry.grid(column=0,row=1,sticky='N', padx=5, pady = 5)
self.button = Button(self, text="Valider", command=self.valid)
self.button.grid(column=0,row=2,sticky='S', padx=5, pady = 5)
self.update()
hs = self.winfo_screenheight()
w = int(self.winfo_width()) # width for the Tk root
h = int(self.winfo_height()) # height for the Tk root
ws = self.winfo_screenwidth() # width of the screen
hs = self.winfo_screenheight() # height of the screen
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
self.geometry('%dx%d+%d+%d' % (w, h, x, y))
if getattr( sys, 'frozen', False ) :
self.iconbitmap(sys._MEIPASS + "\id-card.ico\id-card.ico")
else:
self.iconbitmap("id-card.ico")
def valid(self):
self.parent.page = self.combotry.current()
self.destroy()