diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..22f3257 --- /dev/null +++ b/LICENSE @@ -0,0 +1,193 @@ +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Photo_heure.py b/Photo_heure.py index 49a575e..07ca86e 100644 --- a/Photo_heure.py +++ b/Photo_heure.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD import os import shutil from datetime import datetime @@ -101,4 +102,113 @@ for i, group in enumerate(groups, start=1): shutil.move(src, dst) print(f" Déplacé : {photo}") +======= +import os +import shutil +from datetime import datetime +import google.generativeai as genai +import PIL.Image + +# --------------------- +# CONFIGURATION +# --------------------- +photo_folder = "C:\\Users\\Antoine\\PycharmProjects\\PHOTO\\photo a organiser" +dossier_folder = "C:\\Users\\Antoine\\PycharmProjects\\PHOTO\\dossier" +genai.configure(api_key="VOTRE_CLE_API") +model = genai.GenerativeModel('gemini-1.5-flash') + +# Créer le dossier de sortie s'il n'existe pas +if not os.path.exists(dossier_folder): + os.makedirs(dossier_folder) + +# --------------------- +# FONCTION POUR EXTRAIRE L'HEURE DEPUIS LE NOM DE FICHIER +# --------------------- +def get_time_from_filename(filename): + try: + base = os.path.splitext(filename)[0] # on met les filename dans la base + parts = base.split("_") + time_part = parts[1] + time_obj = datetime.strptime(time_part, "%H%M%S") # on traite l'infor en temps + return time_obj + except Exception as e: + print(f"Impossible d'extraire l'heure de {filename}: {e}") + return None + +# --------------------- +# LISTER ET TRIER LES PHOTOS PAR HEURE +# --------------------- +photos_raw = [f for f in os.listdir(photo_folder) if f.lower().endswith(".jpg")] + +photo_times = [] +for photo in photos_raw: + time_obj = get_time_from_filename(photo) + if time_obj: + photo_times.append((photo, time_obj)) + else: + print(f"Photo ignorée (nom invalide) : {photo}") + +photo_times.sort(key=lambda x: x[1]) # faire un sort pour mettre l'heure du plus jeune au plus vieux. + +# --------------------- +# GROUPEMENT PAR MINUTE +# --------------------- +groups = [] + +for photo, time_obj in photo_times: + heure_lisible = time_obj.strftime("%H:%M:%S") # met le texte lisible + print(f" {photo} → {heure_lisible}") + + if not groups: + groups.append({"photos": [photo], "reference": time_obj}) + img = PIL.Image.open("photos") + response = model.generate_content(["Retourne moi unqiuement les lettres et les chiffres de cette image, uniquement sur le code barre",img + groups.append({"photos": [photo], "reference": time_obj}) + ]) + print(f" → Nouveau groupe 1 (référence : {heure_lisible})") + else: + current_group = groups[-1] + reference_time = current_group["reference"] + difference = abs((time_obj - reference_time).total_seconds()) # on soustrait la reference of temps de référence + + if difference <= 60: + current_group["photos"].append(photo) + print(f" → Ajouté au groupe {len(groups)} (écart : {difference:.0f}s)") + else: + groups.append({"photos": [photo], "reference": time_obj}) + print(f" → Nouveau groupe {len(groups)} (référence : {heure_lisible})") + +# --------------------- +# FUSIONNER LES GROUPES AVEC UNE SEULE PHOTO +# --------------------- +merged_groups = [] + +for i, group in enumerate(groups): + if len(group["photos"]) == 1 and merged_groups: + # Une seule photo → on la fusionne dans le groupe précédent + photo = group["photos"][0] + merged_groups[-1]["photos"].append(photo) + print(f" Photo seule '{photo}' fusionnée dans le groupe précédent") + else: + merged_groups.append(group) + +groups = merged_groups + +# --------------------- +# CRÉER LES DOSSIERS NUMÉROTÉS ET DÉPLACER LES PHOTOS +# --------------------- +for i, group in enumerate(groups, start=1): + group_folder = os.path.join(dossier_folder, str(i)) + os.makedirs(group_folder, exist_ok=True) + + ref_time = group["reference"].strftime("%H:%M:%S") + print(f"\nGroupe {i} (référence : {ref_time}) → dossier '{i}'") + + for photo in group["photos"]: + src = os.path.join(photo_folder, photo) + dst = os.path.join(group_folder, photo) + shutil.move(src, dst) + print(f" Déplacé : {photo}") + +>>>>>>> fd17b0aa2007e272777fcf6570af9c6af1b2b0a3 print("\nRegroupement terminé !") \ No newline at end of file diff --git a/Photo_heure_dossier_dans_dossier.py b/Photo_heure_dossier_dans_dossier.py index d90ef8d..6723b9b 100644 --- a/Photo_heure_dossier_dans_dossier.py +++ b/Photo_heure_dossier_dans_dossier.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD import os import shutil from datetime import datetime @@ -106,4 +107,114 @@ for i, group in enumerate(groups, start=1): shutil.move(src_path, dst_path) +======= +import os +import shutil +from datetime import datetime +import google.generativeai as genai +import PIL.Image + +# --------------------- CONFIGURATION --------------------- +photo_folder = r"C:\Users\Antoine\PycharmProjects\PHOTO\photo a organiser" +dossier_folder = r"C:\Users\Antoine\PycharmProjects\PHOTO\dossier" + +# REMPLACER PAR VOTRE CLÉ +genai.configure(api_key="AIzaSyDq2LmX_fwKGAwxGAtmBfX940vT2wDQzBU") + +# Gemini 1.5 Flash is excellent for OCR/Barcodes +model = genai.GenerativeModel('models/gemini-2.5-flash') + +if not os.path.exists(dossier_folder): + os.makedirs(dossier_folder) + +# --------------------- FONCTIONS --------------------- +def get_time_from_filename(filename): + try: + # Supposant un format : QuelqueChose_123045.jpg (HHMMSS) + base = os.path.splitext(filename)[0] + parts = base.split("_") + time_part = parts[-1] # On prend la dernière partie avant l'extension + return datetime.strptime(time_part, "%H%M%S") + except Exception: + return None + +def read_barcode(photo_path): + """ + Détecte le contenu d'un code-barre. + Retourne la valeur ou 'inconnu' s'il n'y en a pas. + """ + try: + with PIL.Image.open(photo_path) as img: + response = model.generate_content([ + "Retourne uniquement le texte ou les chiffres du code-barre présent sur l'image. " + "Si aucun code-barre n'est visible, répond 'inconnu'.", + img + ]) + res = response.text.strip().lower() + if "inconnu" in res or len(res) > 50: # Sécurité si le modèle divague + return "inconnu" + return response.text.strip().replace(" ", "") + except Exception as e: + print(f"Erreur lors de la lecture de {photo_path} : {e}") + return "inconnu" + +# --------------------- TRAITEMENT --------------------- +photos_raw = [f for f in os.listdir(photo_folder) if f.lower().endswith((".jpg", ".jpeg", ".png"))] +photo_times = [] + +for photo in photos_raw: + time_obj = get_time_from_filename(photo) + if time_obj: + photo_times.append((photo, time_obj)) + +# Trier par heure +photo_times.sort(key=lambda x: x[1]) + +groups = [] +for photo, time_obj in photo_times: + if not groups: + groups.append({"photos": [photo], "reference_time": time_obj}) + else: + current_group = groups[-1] + # On compare avec la dernière photo du groupe actuel + difference = abs((time_obj - current_group["reference_time"]).total_seconds()) + + if difference <= 60: + current_group["photos"].append(photo) + current_group["reference_time"] = time_obj # Update reference to the latest photo + else: + groups.append({"photos": [photo], "reference_time": time_obj}) + +# --------------------- DÉPLACEMENT ET SOUS-DOSSIERS --------------------- +for i, group in enumerate(groups, start=1): + # Dossier principal pour le groupe (ex: Dossier_1) + group_folder_name = f"{i}" + group_folder_path = os.path.join(dossier_folder, group_folder_name) + os.makedirs(group_folder_path, exist_ok=True) + + print(f"Traitement du groupe {i} ({len(group['photos'])} photos)...") + + for photo_name in group["photos"]: + src_path = os.path.join(photo_folder, photo_name) + + # Vérifier si CETTE photo est un code-barre + barcode_value = read_barcode(src_path) + + if barcode_value != "inconnu": + # Nettoyage du nom pour Windows + barcode_clean = "".join(c for c in barcode_value if c.isalnum() or c in "-_").strip() + + # Création du SOUS-DOSSIER dans le dossier du groupe + subfolder_path = os.path.join(group_folder_path, barcode_clean) + os.makedirs(subfolder_path, exist_ok=True) + + dst_path = os.path.join(subfolder_path, photo_name) + print(f" -> Code-barre détecté ({barcode_clean}), déplacé dans sous-dossier.") + else: + # Photo normale, reste à la racine du dossier groupe + dst_path = os.path.join(group_folder_path, photo_name) + + shutil.move(src_path, dst_path) + +>>>>>>> fd17b0aa2007e272777fcf6570af9c6af1b2b0a3 print("\nOpération terminée avec succès !") \ No newline at end of file diff --git a/Photo_heure_dossier_nommer.py b/Photo_heure_dossier_nommer.py index b1466d3..f09daf5 100644 --- a/Photo_heure_dossier_nommer.py +++ b/Photo_heure_dossier_nommer.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD import os import shutil from datetime import datetime @@ -89,4 +90,97 @@ for i, group in enumerate(groups, start=1): dst = os.path.join(group_folder, photo) shutil.move(src, dst) +======= +import os +import shutil +from datetime import datetime +import google.generativeai as genai +import PIL.Image + +# --------------------- CONFIGURATION --------------------- +# Utilisation de r"" pour les chemins Windows +photo_folder = r"C:\Users\Antoine\PycharmProjects\PHOTO\photo a organiser" +dossier_folder = r"C:\Users\Antoine\PycharmProjects\PHOTO\dossier" + +# REMPLACER PAR VOTRE NOUVELLE CLÉ +genai.configure(api_key="AIzaSyDq2LmX_fwKGAwxGAtmBfX940vT2wDQzBU") +for m in genai.list_models(): + if 'generateContent' in m.supported_generation_methods: + print(m.name) +model = genai.GenerativeModel('models/gemini-2.5-flash') # 1.5 est plus stable pour l'OCR + +if not os.path.exists(dossier_folder): + os.makedirs(dossier_folder) + +# --------------------- FONCTIONS --------------------- +def get_time_from_filename(filename): + try: + # Supposant un format : QuelqueChose_123045.jpg (HHMMSS) + base = os.path.splitext(filename)[0] + parts = base.split("_") + time_part = parts[1] + return datetime.strptime(time_part, "%H%M%S") + except Exception as e: + return None + +def read_barcode(photo_path): + try: + # Utiliser 'with' permet de fermer l'image AUTOMATIQUEMENT après la lecture + with PIL.Image.open(photo_path) as img: + response = model.generate_content([ + "Retourne uniquement le texte ou les chiffres du code-barre. " + "Si aucun code-barre n'est visible, répond 'inconnu'.", + img + ]) + return response.text.strip().replace(" ", "") + except Exception as e: + print(f"Erreur lors de la lecture : {e}") + return "erreur_lecture" +# --------------------- TRAITEMENT --------------------- +photos_raw = [f for f in os.listdir(photo_folder) if f.lower().endswith(".jpg")] +photo_times = [] + +for photo in photos_raw: + time_obj = get_time_from_filename(photo) + if time_obj: + photo_times.append((photo, time_obj)) + +photo_times.sort(key=lambda x: x[1]) + +groups = [] +for photo, time_obj in photo_times: + full_path = os.path.join(photo_folder, photo) + + if not groups: + barcode = read_barcode(full_path) + groups.append({"photos": [photo], "reference_time": time_obj, "barcode": barcode}) + else: + current_group = groups[-1] + # Comparaison avec la dernière photo ajoutée pour plus de souplesse + difference = abs((time_obj - current_group["reference_time"]).total_seconds()) + + if difference <= 60: + current_group["photos"].append(photo) + else: + barcode = read_barcode(full_path) + groups.append({"photos": [photo], "reference_time": time_obj, "barcode": barcode}) + +# --------------------- DÉPLACEMENT --------------------- +for i, group in enumerate(groups, start=1): + barcode = group["barcode"] + # Nettoyage pour nom de dossier Windows valide + barcode_clean = "".join(c for c in barcode if c.isalnum() or c in "-_").strip() + + # Format demandé : 1_CodeBarre + folder_name = f"{i}_{barcode_clean}" if barcode_clean else str(i) + group_folder = os.path.join(dossier_folder, folder_name) + + os.makedirs(group_folder, exist_ok=True) + + for photo in group["photos"]: + src = os.path.join(photo_folder, photo) + dst = os.path.join(group_folder, photo) + shutil.move(src, dst) + +>>>>>>> fd17b0aa2007e272777fcf6570af9c6af1b2b0a3 print("\nRegroupement et déplacement terminés !") \ No newline at end of file diff --git a/Photo_heure_photo_nommer.py b/Photo_heure_photo_nommer.py index 25e65b8..a7cffdb 100644 --- a/Photo_heure_photo_nommer.py +++ b/Photo_heure_photo_nommer.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD import os import shutil from datetime import datetime @@ -132,4 +133,140 @@ for i, group in enumerate(groups, start=1): dst_path = os.path.join(group_folder_path, f"{base_name}_{counter}{extension}") counter += 1 +======= +import os +import shutil +from datetime import datetime +import google.generativeai as genai +import PIL.Image +import time + +# --------------------- CONFIGURATION --------------------- +photo_folder = r"C:\Users\Antoine\PycharmProjects\PHOTO\photo a organiser" +dossier_folder = r"C:\Users\Antoine\PycharmProjects\PHOTO\dossier" + +genai.configure(api_key="AIzaSyDq2LmX_fwKGAwxGAtmBfX940vT2wDQzBU") +model = genai.GenerativeModel('models/gemini-2.5-flash') + +if not os.path.exists(dossier_folder): + os.makedirs(dossier_folder) + +# --------------------- FONCTIONS --------------------- +def get_time_from_filename(filename): + try: + base = os.path.splitext(filename)[0] + parts = base.split("_") + time_part = parts[-1] + return datetime.strptime(time_part, "%H%M%S") + except Exception: + return None + +def analyze_group_photos(group_paths): + """ + Envoie les images et demande de lier les valeurs aux noms de fichiers. + """ + prompt = ( + "Analyze these images one by one. For each image, if you see a 6-character Barcode " + "or a 5-character LCLC, identify it.\n" + "Return the results in this exact format for each relevant file:\n" + "FILENAME: [filename], TYPE: [BARCODE or LCLC], VALUE: [value]\n" + "If a file has nothing, don't list it." + ) + + content = [prompt] + + # On ouvre les images (avec gestion de fermeture automatique) + images_to_close = [] + for path in group_paths: + img = PIL.Image.open(path) + content.append(f"Filename: {os.path.basename(path)}") + content.append(img) + images_to_close.append(img) + + try: + # Note: Utilisez 'gemini-1.5-flash' car 2.5 n'existe pas encore + response = model.generate_content(content) + res_text = response.text + print(f"--- Gemini Analysis ---\n{res_text}\n-----------------------") + + # Fermeture des images pour libérer les fichiers + for i in images_to_close: i.close() + + return res_text + except Exception as e: + print(f"Error calling Gemini: {e}") + return "" + +# --------------------- TRAITEMENT --------------------- +photos_raw = [f for f in os.listdir(photo_folder) if f.lower().endswith((".jpg", ".jpeg", ".png"))] +photo_times = [] + +for photo in photos_raw: + time_obj = get_time_from_filename(photo) + if time_obj: + photo_times.append((photo, time_obj)) + +photo_times.sort(key=lambda x: x[1]) + +groups = [] +for photo, time_obj in photo_times: + if not groups: + groups.append({"photos": [photo], "reference_time": time_obj}) + else: + current_group = groups[-1] + difference = abs((time_obj - current_group["reference_time"]).total_seconds()) + if difference <= 60: + current_group["photos"].append(photo) + current_group["reference_time"] = time_obj + else: + groups.append({"photos": [photo], "reference_time": time_obj}) + +# --------------------- ANALYSE ET RENOMMAGE --------------------- +for i, group in enumerate(groups, start=1): + print(f"Analyzing Group {i}...") + group_paths = [os.path.join(photo_folder, p) for p in group["photos"]] + analysis_result = analyze_group_photos(group_paths) + + group_folder_path = os.path.join(dossier_folder, str(i)) + os.makedirs(group_folder_path, exist_ok=True) + + # Créer un dictionnaire pour mapper Filename -> NouveauNom + # Exemple: {"IMG_123.jpg": "ABC123.jpg"} + filename_mapping = {} + + # Parsing de la réponse de Gemini + for line in analysis_result.splitlines(): + if "FILENAME:" in line and "VALUE:" in line: + try: + fname = line.split("FILENAME:")[1].split(",")[0].strip() + vtype = line.split("TYPE:")[1].split(",")[0].strip() + val = line.split("VALUE:")[1].strip() + + # On vérifie la validité des données + if (vtype == "BARCODE" and len(val) == 6) or (vtype == "LCLC" and len(val) == 5): + filename_mapping[fname] = val + except: + continue + + for photo_name in group["photos"]: + src_path = os.path.join(photo_folder, photo_name) + extension = os.path.splitext(photo_name)[1] + + # On vérifie si Gemini a trouvé une valeur spécifique pour CE fichier + if photo_name in filename_mapping: + new_name = f"{filename_mapping[photo_name]}{extension}" + else: + # Sinon on garde le nom original + new_name = photo_name + + dst_path = os.path.join(group_folder_path, new_name) + + # Gestion des doublons (si deux photos ont le même code) + counter = 1 + base_name = os.path.splitext(new_name)[0] + while os.path.exists(dst_path): + dst_path = os.path.join(group_folder_path, f"{base_name}_{counter}{extension}") + counter += 1 + +>>>>>>> fd17b0aa2007e272777fcf6570af9c6af1b2b0a3 shutil.move(src_path, dst_path) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3ccfd4 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Photo + +Grouper photo par dossier selon une logique de temps pour automatiser le placement de ces photos dans la map grace au pathfinder de Antoine \ No newline at end of file diff --git a/VSUC7L.jpg b/VSUC7L.jpg new file mode 100644 index 0000000..ae68f15 Binary files /dev/null and b/VSUC7L.jpg differ diff --git a/YOB7I.JPG b/YOB7I.JPG new file mode 100644 index 0000000..1ca600c Binary files /dev/null and b/YOB7I.JPG differ diff --git a/delete_dossier.py b/delete_dossier.py index 5b74033..2c5ee91 100644 --- a/delete_dossier.py +++ b/delete_dossier.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD import os import shutil @@ -51,4 +52,59 @@ for temp_path, final_path in temp_names: os.rename(temp_path, final_path) print(f"Renommé : {os.path.basename(temp_path).replace('temp_', '')} → {os.path.basename(final_path)}") +======= +import os +import shutil + +# --------------------- +# CONFIGURATION +# --------------------- +dossier_folder = "C:\\Users\\Antoine\\PycharmProjects\\PHOTO\\dossier" + +# --------------------- +# INPUT +# --------------------- +user_input = input("Entrez le(s) numéro(s) de dossier à supprimer (ex: 2 ou 2,4,5) : ") +to_delete = set(int(x.strip()) for x in user_input.split(",")) + +# --------------------- +# LISTER LES DOSSIERS EXISTANTS TRIÉS +# --------------------- +existing = sorted( + int(f) for f in os.listdir(dossier_folder) + if os.path.isdir(os.path.join(dossier_folder, f)) and f.isdigit() +) + +# --------------------- +# SUPPRIMER LES DOSSIERS DEMANDÉS +# --------------------- +for num in to_delete: + folder_path = os.path.join(dossier_folder, str(num)) + if os.path.exists(folder_path): + shutil.rmtree(folder_path) + print(f"Dossier {num} supprimé.") + else: + print(f"Dossier {num} introuvable, ignoré.") + +# --------------------- +# RENOMMER LES DOSSIERS RESTANTS EN ORDRE +# --------------------- +remaining = sorted( + int(f) for f in os.listdir(dossier_folder) + if os.path.isdir(os.path.join(dossier_folder, f)) and f.isdigit() +) + +# Renommage en deux passes pour éviter les conflits (ex: 2→1 alors que 1 existe encore) +temp_names = [] +for i, num in enumerate(remaining, start=1): + old_path = os.path.join(dossier_folder, str(num)) + temp_path = os.path.join(dossier_folder, f"temp_{i}") + os.rename(old_path, temp_path) + temp_names.append((temp_path, os.path.join(dossier_folder, str(i)))) + +for temp_path, final_path in temp_names: + os.rename(temp_path, final_path) + print(f"Renommé : {os.path.basename(temp_path).replace('temp_', '')} → {os.path.basename(final_path)}") + +>>>>>>> fd17b0aa2007e272777fcf6570af9c6af1b2b0a3 print("\nSuppression et renumérotation terminées !") \ No newline at end of file