Merge branch 'main' of https://git.targo.ca/antoinewg/Photo
This commit is contained in:
commit
fed5717d20
193
LICENSE
Normal file
193
LICENSE
Normal file
|
|
@ -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.
|
||||
110
Photo_heure.py
110
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é !")
|
||||
|
|
@ -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 !")
|
||||
|
|
@ -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 !")
|
||||
|
|
@ -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)
|
||||
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -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
|
||||
BIN
VSUC7L.jpg
Normal file
BIN
VSUC7L.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
|
|
@ -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 !")
|
||||
Loading…
Reference in New Issue
Block a user