#!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.1' # 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 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): 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') 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("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 print("1 - Renouveller l'abonnement automatiquement", "2 - Choisir la date d'expiration", sep='\n') choix = input("Choix : ") if choix == 1: 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') elif choix == 2: dateexp = getdateexp() 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('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']))