IFPass/IFPass.py

552 lines
22 KiB
Python

#!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.0-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 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:
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')
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", '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
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('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']))