#!python3 # Written by Jordan ERNST Q1 2018. # Contact : pro.ernst@gmail.com import configparser import sys import os import re from datetime import date, timedelta, datetime import csv import code128 import cv2 from pywinauto.findwindows import find_window from pywinauto.win32functions import SetForegroundWindow from PIL import Image, ImageDraw, ImageFont from PyPDF2 import PdfFileReader, PdfFileWriter import fitz # = PyMuPDF : To convert pdf to png import subprocess from shutil import copyfile, move from pyfiglet import Figlet from colorama import init from termcolor import colored version = '3.2' # dev/devnocam configdir = os.path.join(os.getenv('PROGRAMDATA'), 'IFPass') config = os.path.join(configdir, 'IFPass.conf') def initialisation(): conf = configparser.ConfigParser() conf.optionxform = str # For case sensitive config file if not os.path.exists(config): # Check if config file exists print('Fichier de configuration introuvable.') IFPassDBdir = input(r'Quel est le répertoire de la base de données IFPass ? (Ex : \\192.168.1.1\IFPass) : ') printername = input("Quel est le nom de l'imprimante à cartes ? : ") adobex86 = os.path.join(os.getenv('PROGRAMFILES(x86)'), 'Adobe', "Acrobat Reader DC", "Reader", "AcroRd32.exe") adobex64 = os.path.join(os.getenv('PROGRAMFILES'), 'Adobe', "Acrobat Reader DC", "Reader", "AcroRd32.exe") if os.path.exists(adobex86): AcrobatReader = adobex86 elif os.path.exists(adobex64): AcrobatReader = adobex64 else: acrinstalled = yes_or_no("Acrobat Reader est nécessaire pour imprimer les cartes. Est il installé ?") if acrinstalled: while "Wrong path": AcrobatReader = input(r"Quel est le chemin vers Acrobat Reader ? ( Ex : C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe) : ") if os.path.exists(AcrobatReader): break else: print(colored('\nChemin invalide !', 'red')) else: print(colored('\nInstallez Acrobat Reader, puis relancez IFPass.', 'red')) os.system("pause") sys.exit() conf['DEFAULT'] = {'IFPassDBdir': IFPassDBdir, 'printername': printername, 'AcrobatReader': AcrobatReader} os.makedirs(configdir, 0o777) with open(config, 'w') as configfile: conf.write(configfile) else: conf.read(config) IFPassDBdir = conf['DEFAULT']['IFPassDBdir'] printername = conf['DEFAULT']['printername'] AcrobatReader = conf['DEFAULT']['AcrobatReader'] clientsfile = os.path.join(IFPassDBdir, 'Clients_IFPass.csv') clientsbkpfile = os.path.join(IFPassDBdir, 'Clients_IFPass_backup.csv') imgdir = os.path.join(IFPassDBdir, 'Cartes') templatesdir = os.path.join(IFPassDBdir, 'Templates') pdftemplate = os.path.join(templatesdir, 'IFPass_PDF_Template.pdf') pngtemplate = os.path.join(templatesdir, 'IFPass_PNG_Template.png') fonttemplate = os.path.join(templatesdir, 'Roboto-Bold.ttf') if not os.path.exists(imgdir): # Cartes dir creation if it doesn't exist os.makedirs(imgdir, 0o777) if not os.path.exists(clientsfile): # Creation Clients_File if it doesn't exist with open(clientsfile, 'w', newline='', encoding='utf-8') as csvfile: writer = csv.writer(csvfile, delimiter=';') writer.writerow(['Titre', 'Prénom', 'Nom', 'Numéro de client', "Date d'inscription", "Date d'expiration"]) if not os.path.exists(templatesdir): move('Templates', IFPassDBdir) return IFPassDBdir, printername, AcrobatReader, clientsfile, clientsbkpfile, imgdir, templatesdir, pdftemplate, pngtemplate, fonttemplate def get_fullname(**kwargs): # **kwargs => Optionnal arguments if 'firstname' in kwargs: firstname = kwargs['firstname'] newfirstname = input("Prénom (" + firstname + "): ").strip() if len(newfirstname) != 0: firstname = newfirstname else: while "Empty firstname": firstname = input("Prénom : ").strip() if len(firstname) == 0: os.system('cls') print(colored("\nLe Prénom ne peut pas être vide.", 'red')) else: break firstname = firstname[0].upper() + firstname[1:].lower() if '-' in firstname: pos = firstname.find("-") firstname = firstname[:pos + 1] + firstname[pos + 1].upper() + \ firstname[pos + 2:] # Check if compound name if ' ' in firstname: pos = firstname.find(" ") firstname = firstname[:pos + 1] + firstname[pos + 1].upper() + \ firstname[pos + 2:] # Check if compound name if 'surname' in kwargs: surname = kwargs['surname'] newsurname = input("Nom (" + surname + "): ").strip() if len(newsurname) != 0: surname = newsurname else: while "Empty surname": surname = input("Nom : ").strip() if len(surname) == 0: os.system('cls') print(colored("\nLe Nom ne peut pas être vide.", 'red')) else: break surname = surname.upper() docteur = yes_or_no('Est-ce un Docteur ?') if docteur: titre = 'Dr.' titrename = titre + ' ' + surname else: titre = '' titrename = surname return titre, firstname, surname, titrename def yes_or_no(question): while "the answer is invalid": reply = input(question + " O/N : ").lower().strip() if reply[:1] in ['oui', 'ou', 'o']: return True if reply[:1] in ['non', 'no', 'n']: return False def getclientID(): with open(clientsfile, 'r', newline='', encoding='utf-8') as csvfile: lastline = csvfile.readlines()[-1] lastID = lastline.split(';')[3] if lastID == "Numéro de client": clientID = "0000000001" else: clientID = str(int(lastID) + 1).zfill(10) return clientID def barcode_gen(clientID): print("Génération du code barre... ", end='') barcode = code128.image(clientID, thickness=4) # .save(imgdir + clientID + '.png') print(colored('[OK]', 'green')) return barcode def getpic(): while True: print("Prendre la photo... ", end='') sys.stdout.flush() cap = cv2.VideoCapture(0) while True: # Loop stream webcam try: ret, frame = cap.read() cv2.rectangle(frame, (170, 73), (470, 407), (0, 255, 0), 2) cv2.imshow('IFCamera - Touche Espace pour prendre la photo, Echap pour une carte sans photo, Q pour quitter.', frame) except cv2.error: print(colored('\nLa webcam est débranchée. Branchez-la, puis relancez le programme.', 'red')) os.system("pause") sys.exit() SetForegroundWindow(find_window(title='IFCamera - Touche Espace ' 'pour prendre la photo, Echap pour une carte sans photo, Q pour quitter.')) k = cv2.waitKey(1) if k == 27: # Echap cap.release() print(colored('[OK]', 'green')) cv2.destroyAllWindows() defaultpicture = os.path.join(IFPassDBdir, 'Templates', 'default_avatar.jpg') picture = Image.open(defaultpicture) return picture elif k & 0xFF == ord(' '): # Space cap.release() cv2.destroyAllWindows() break elif k & 0xFF == ord('q'): # Q or q to quit cap.release() cv2.destroyAllWindows() sys.exit() cropped = frame[75:405, 172:468] cv2.imshow('IFCamera - Espace pour valider, Echap pour modifier, Q pour quitter', cropped) while True: k = cv2.waitKey(1) if k == 27: # Echap print(colored('[KO]', 'red')) cv2.destroyAllWindows() break elif k & 0xFF == ord(' '): # Space print(colored('[OK]', 'green')) cv2.destroyAllWindows() # Color conversion and cv2 img to Pillow img cropped = cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB) picture = Image.fromarray(cropped) return picture elif k & 0xFF == ord('q'): # Q or q to quit cv2.destroyAllWindows() sys.exit() def writeindb(titre, firstname, surname, clientID, dateinsc, dateexp, new): if new is True: print("Ajout dans la base de données... ", end="") with open(clientsfile, 'a', newline='', encoding='utf-8') as csvfile: writer = csv.writer(csvfile, delimiter=';') writer.writerow([titre, firstname, surname, clientID, dateinsc, dateexp]) print(colored('[OK]', 'green')) elif new is False: print("Modification de la base de données... ", end="") with open(clientsfile, 'r+', newline='', encoding='utf-8') as csvfile: content = csvfile.readlines() for index, member in enumerate(content): member = member.split(';') if member[3] == clientID: tochange = index break content.pop(tochange) changewith = ";".join([titre, firstname, surname, clientID, dateinsc, dateexp]) + '\n' content.insert(tochange, changewith) content = "".join(content) csvfile.seek(0) csvfile.truncate(0) csvfile.write(content) print(colored('[OK]', 'green')) def bkpdb(): copyfile(clientsfile, clientsbkpfile) def fillcard(clientID, titrename, firstname, dateexp, barcode, picture): print("Création du verso de la carte avec les informations...", end='') try: im = Image.open(pngtemplate) except FileNotFoundError: os.system('cls') print(colored('Vous avez besoin de 2 fichiers dans le dossier Templates pour générer des cartes :', 'red')) print(templatesdir + '\\IFPass_PDF_Template.pdf : Modèle de la carte (recto et verso)') print(templatesdir + '\\IFPass_PNG_Template.png : La face avant de la carte (celle à remplir)') os.system("pause") sys.exit() draw = ImageDraw.Draw(im) # Name embedding : font = ImageFont.truetype(fonttemplate, 40) draw.text((401, 296), titrename, fill=(0, 0, 0), font=font) draw.text((401, 334), firstname, fill=(0, 0, 0), font=font) # Date embedding : font = ImageFont.truetype(fonttemplate, 30) draw.text((401, 400), dateexp, fill=(0, 0, 0), font=font) # ID embedding : font = ImageFont.truetype('arial.ttf', 30) draw.text((693, 560), clientID, fill=(0, 0, 0), font=font) # Barcode + picture embedding : im.paste(barcode, (556, 460)) if version != 'devnocam': im.paste(picture, (47, 49)) # Create PDF : im = im.convert("RGB") im.save(imgdir + clientID + '_Front.pdf', 'PDF', resolution=299.0, quality=98) print(colored('[OK]', 'green')) def mergepdf(clientID): print("Fusion du recto et du verso de la carte...", end='') cartefilename = os.path.join(imgdir, clientID + '.pdf') output = PdfFileWriter() pdf1 = PdfFileReader(imgdir + clientID + '_Front.pdf', 'rb') recto = pdf1.getPage(0) output.addPage(recto) pdf2 = PdfFileReader(pdftemplate, 'rb') verso = pdf2.getPage(1) output.addPage(verso) with open(cartefilename, 'wb') as f: output.write(f) os.remove(imgdir + clientID + '_Front.pdf') print(colored('[OK]', 'green')) return cartefilename def printcard(cartefilename): # Working : subprocess.Popen('"C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" /h /n /t ' + cartefilename + ' '+ printername, shell=False) subprocess.Popen('"' + AcrobatReader + '"' + ' /h /n /t ' + cartefilename + ' ' + printername, shell=False) def getdateexp(): while True: dateexp = input("Quelle date d'expiration voulez-vous mettre (Format : JJ/MM/AAAA)? : ") match = re.fullmatch(r'^(0[1-9]|1[0-9]|2[0-9]|3[0-1])/(0[1-9]|1[0-2])/([0-9]){4}$', dateexp) if match: break else: print('Mauvais format ! JJ/MM/AAAA, exemple : 01/08/2042') return dateexp def newmember(): while "the informations are incorrect": # Loop Filling informations os.system('cls') titre, firstname, surname, titrename = get_fullname() dateinsc = date.today() dateexp = dateinsc + timedelta(days=365) dateinsc = dateinsc.strftime('%d/%m/%Y') dateexp = dateexp.strftime('%d/%m/%Y') changeexp = yes_or_no("Voulez-vous choisir la date d'expiration ?") if changeexp: dateexp = getdateexp() os.system('cls') print("Titre : ", colored(titre, 'green')) print("Prénom : ", colored(firstname, 'green')) print("Nom : ", colored(surname, 'green')) print("Date d'inscription :", colored(dateinsc, 'green')) print("Date d'expiration : ", colored(dateexp, 'green')) correct = yes_or_no("Ces informations sont elles correctes ?") if correct: os.system('cls') if version != 'devnocam': picture = getpic() clientID = getclientID() barcode = barcode_gen(clientID) fillcard(clientID, titrename, firstname, dateexp, barcode, picture) cartefilename = mergepdf(clientID) if version not in ('dev', 'devnocam'): bkpdb() printcard(cartefilename) writeindb(titre, firstname, surname, clientID, dateinsc, dateexp, new=True) break def membersearch(): with open(clientsfile, 'r', newline='', encoding='utf-8') as csvfile: reader = csv.reader(csvfile, delimiter=';') csvlist = list(map(list, reader)) del csvlist[0] # We dele the first line (Prénom, Nom...) research = input('Entrez une partie du nom, prénom, ou numéro de carte (ou flashez) : ').lower() os.system('cls') results = [] for member in csvlist: resfirstname = member[1].lower() ressurname = member[2].lower() resnumber = member[3] if any(research in data for data in [resfirstname, ressurname, resnumber]): results.append(member) if results: if len(results) == 1: member = results[0] else: print('-' * 56) print(f'{"Choix":8} {"Prénom":15} {"Nom":15} {"Numéro de carte":15}') print('-' * 56) for index, result in enumerate(results, start=1): print(f'{index:^8} {result[1]:15} {result[2]:15} {result[3]:^15}') del member while 'The choice is not valid': try: memberchoice = input("De quel membre s'agit il ? (Colonne Choix) : ") member = results[int(memberchoice) - 1] break except (IndexError, ValueError): print(colored('Choix invalide ! Veillez bien à sélectionner le numéro de la colonne "Choix"', 'red', attrs=['bold'])) os.system('cls') while 'Choix incorect': print("Titre : ", colored(member[0], 'green')) print("Prénom : ", colored(member[1], 'green')) print("Nom : ", colored(member[2], 'green')) print("Numéro de carte : ", colored(member[3], 'green')) print("Date d'inscription :", member[4]) print("Date d'expiration : ", member[5]) dateexp = datetime.strptime(member[5], '%d/%m/%Y').date() diff = (dateexp - date.today()).days if diff > 0: print(colored(f"L'abonnement est encore valable {diff} jours.", 'green', attrs=['bold'])) elif diff < 0: print(colored(f"L'abonnement est expiré depuis {abs(diff)} jours.", 'red', attrs=['bold'])) # abs() to remove minus sign elif diff == 0: print(colored("Il s'agit du dernier jour de l'abonnement, il expirera demain.", 'yellow', attrs=['bold'])) print('\n1 - Modifier', "2 - Renouveller l'abonnement / Choisir un nouvelle date d'expiration", '3 - Imprimer la carte', '0 - Menu principal', sep='\n') choix = input('Choix : ') if choix == '0': os.system('cls') return # We update values with new (edited) values : newtitre, newfirstname, newsurname, newdateexp = memberdo(choix, member) member[0], member[1], member[2], member[5] = newtitre, newfirstname, newsurname, newdateexp else: print(colored("Aucun membre n'a été trouvé.", 'red', attrs=['bold'])) os.system("pause") def memberdo(choix, member): titre = member[0] firstname = member[1] surname = member[2] clientID = member[3] dateinsc = member[4] dateexp = member[5] os.system('cls') if choix == '1': # Edit member titre, firstname, surname, dateexp = memberedit(titre, firstname, surname, clientID, dateinsc, dateexp) elif choix == '2': # Renew subscription changeexp = yes_or_no("Voulez-vous choisir la date d'expiration ?") if changeexp: dateexp = getdateexp() else: dateexp = datetime.strptime(dateexp, '%d/%m/%Y').date() diff = (dateexp - date.today()).days if diff >= 0: dateexp = dateexp + timedelta(days=365) elif diff < 0: dateexp = date.today() + timedelta(days=365) dateexp = dateexp.strftime('%d/%m/%Y') if titre == 'Dr.': titrename = titre + ' ' + surname else: titrename = surname wantnewpic = yes_or_no("Voulez-vous prendre une nouvelle photo ?") os.system('cls') if wantnewpic: os.system('cls') if version != 'devnocam': picture = getpic() else: # We crop pic from the previous card cartefilename = os.path.join(imgdir, clientID + '.pdf') pdf = fitz.open(cartefilename) page = pdf.loadPage(0) mat = fitz.Matrix(4.165, 4.165) # To obtain good resolution pix = page.getPixmap(matrix=mat) pageimg = Image.frombytes("RGBA", [pix.width, pix.height], pix.samples) picture = pageimg.crop((47, 49, 343, 378)) barcode = barcode_gen(clientID) fillcard(clientID, titrename, firstname, dateexp, barcode, picture) cartefilename = mergepdf(clientID) if version not in ('dev', 'devnocam'): bkpdb() printcard(cartefilename) writeindb(titre, firstname, surname, clientID, dateinsc, dateexp, new=False) os.system('cls') print(colored("La date d'expiration a bien été mise à jour !\n", 'blue', attrs=['bold'])) elif choix == '3': # Print card cartefilename = os.path.join(imgdir, clientID + '.pdf') printcard(cartefilename) else: print(colored('Choix incorrect !\n', 'red', attrs=['bold'])) return titre, firstname, surname, dateexp def memberedit(titre, firstname, surname, clientID, dateinsc, dateexp): while "the informations are incorrect": # Loop Filling informations titre, firstname, surname, titrename = get_fullname(titre=titre, firstname=firstname, surname=surname) print("Titre : ", colored(titre, 'green')) print("Prénom : ", colored(firstname, 'green')) print("Nom : ", colored(surname, 'green')) correct = yes_or_no("Ces informations sont elles correctes ?") os.system('cls') if correct: wantnewpic = yes_or_no("Voulez-vous prendre une nouvelle photo ?") os.system('cls') if wantnewpic: os.system('cls') if version != 'devnocam': picture = getpic() else: # We crop pic from the previous card cartefilename = os.path.join(imgdir, clientID + '.pdf') pdf = fitz.open(cartefilename) page = pdf.loadPage(0) mat = fitz.Matrix(4.165, 4.165) # To obtain good resolution pix = page.getPixmap(matrix=mat) pageimg = Image.frombytes("RGBA", [pix.width, pix.height], pix.samples) picture = pageimg.crop((47, 49, 343, 378)) barcode = barcode_gen(clientID) fillcard(clientID, titrename, firstname, dateexp, barcode, picture) cartefilename = mergepdf(clientID) if version not in ('dev', 'devnocam'): bkpdb() printcard(cartefilename) writeindb(titre, firstname, surname, clientID, dateinsc, dateexp, new=False) os.system('cls') break return titre, firstname, surname, dateexp def main(): global IFPassDBdir, clientsfile, imgdir, clientsbkpfile, templatesdir, pngtemplate, fonttemplate, pdftemplate, printername, AcrobatReader while "The program is running": init() # Initialisation of colorama IFPassDBdir, printername, AcrobatReader, clientsfile, clientsbkpfile, imgdir, templatesdir, pdftemplate, pngtemplate, fonttemplate = initialisation() os.system('cls') f = Figlet(font='big') print(colored(f.renderText('IFPass'), 'cyan', attrs=['bold'])) print('Version : ', version) if version in ('dev', 'devnocam'): print(colored("\nATTENTION : Il s'agit d'une version en cours de développement, potentiellement instable !", 'red')) print("\nCe programme est developpé par par Jordan ERNST pour l'Institut Français en Hongrie.") print("Il est disponible sou licence MIT à cette addresse : https://framagit.org/SecT0uch/IFPass\n") print('Pour toute question, problème ou requête contactez-moi à pro.ernst@gmail.com.\n') print('1 - Nouveau membre', '2 - Rechercher un membre', '0 - Quitter', sep='\n') choix = input('Choix : ') if choix == '1': newmember() elif choix == '2': os.system('cls') membersearch() elif choix == '0': sys.exit() else: os.system('cls') print(colored('Choix incorrect !', 'red', attrs=['bold']))