Spaces:
Sleeping
Sleeping
Ludovic commited on
Commit ·
04969c3
1
Parent(s): 4474779
V2
Browse files- .DS_Store +0 -0
- app/main.py +96 -86
- app/static/css/style.css +159 -105
- app/static/js/script.js +57 -38
- app/templates/index.html +9 -14
- generer_doc.py +262 -0
.DS_Store
CHANGED
|
Binary files a/.DS_Store and b/.DS_Store differ
|
|
|
app/main.py
CHANGED
|
@@ -2,16 +2,18 @@ import os
|
|
| 2 |
import shutil
|
| 3 |
import uuid
|
| 4 |
import secrets # Pour la comparaison sécurisée des identifiants
|
|
|
|
|
|
|
| 5 |
|
| 6 |
from fastapi import Depends, FastAPI, File, UploadFile, Request, HTTPException, status
|
| 7 |
-
from fastapi.responses import HTMLResponse, JSONResponse
|
| 8 |
from fastapi.staticfiles import StaticFiles
|
| 9 |
from fastapi.templating import Jinja2Templates
|
| 10 |
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
| 11 |
from passlib.context import CryptContext # Pour hacher les mots de passe
|
| 12 |
from typing import List
|
| 13 |
|
| 14 |
-
from . import processing # Contient la logique du modèle
|
| 15 |
from . import utils
|
| 16 |
|
| 17 |
# Configuration de l'application FastAPI
|
|
@@ -22,16 +24,13 @@ security = HTTPBasic()
|
|
| 22 |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 23 |
|
| 24 |
# Lire les identifiants depuis les variables d'environnement (pour Hugging Face Spaces Secrets)
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
APP_USERNAME_DEFAULT = "admin"
|
| 28 |
-
APP_PASSWORD_DEFAULT = "changezceci" # Changez ce mot de passe par défaut si vous testez localement
|
| 29 |
|
| 30 |
APP_USERNAME = os.environ.get("APP_USERNAME", APP_USERNAME_DEFAULT)
|
| 31 |
APP_PASSWORD_RAW = os.environ.get("APP_PASSWORD", APP_PASSWORD_DEFAULT)
|
| 32 |
|
| 33 |
-
|
| 34 |
-
CORRECT_PASSWORD_HASH = pwd_context.hash(APP_PASSWORD_RAW) if APP_PASSWORD_RAW else None
|
| 35 |
|
| 36 |
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
| 37 |
if hashed_password is None:
|
|
@@ -43,7 +42,7 @@ async def get_current_username(credentials: HTTPBasicCredentials = Depends(secur
|
|
| 43 |
correct_username_bytes = APP_USERNAME.encode("utf8")
|
| 44 |
|
| 45 |
is_correct_username = secrets.compare_digest(current_username_bytes, correct_username_bytes)
|
| 46 |
-
is_correct_password = verify_password(credentials.password, CORRECT_PASSWORD_HASH)
|
| 47 |
|
| 48 |
if not (is_correct_username and is_correct_password):
|
| 49 |
raise HTTPException(
|
|
@@ -58,7 +57,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Répertoire /app
|
|
| 58 |
PROJECT_ROOT = os.path.dirname(BASE_DIR) # Répertoire AUTOCAPTION
|
| 59 |
|
| 60 |
UPLOAD_DIR = os.path.join(PROJECT_ROOT, "uploads")
|
| 61 |
-
OUTPUT_IMAGE_DIR = os.path.join(PROJECT_ROOT, "outputs", "images")
|
| 62 |
OUTPUT_CAPTION_DIR = os.path.join(PROJECT_ROOT, "outputs", "captions")
|
| 63 |
|
| 64 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
|
@@ -68,7 +67,7 @@ os.makedirs(OUTPUT_CAPTION_DIR, exist_ok=True)
|
|
| 68 |
app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
|
| 69 |
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
|
| 70 |
|
| 71 |
-
# Pré-chargement
|
| 72 |
# print("Tentative de pré-chargement du modèle actif au démarrage de l'application...")
|
| 73 |
# try:
|
| 74 |
# processing.load_active_model()
|
|
@@ -77,7 +76,7 @@ templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
|
|
| 77 |
# else:
|
| 78 |
# print(f"AVERTISSEMENT: Le modèle actif ({processing.ACTIVE_MODEL}) n'a pas pu être pré-chargé.")
|
| 79 |
# except Exception as e:
|
| 80 |
-
# print(f"AVERTISSEMENT: Erreur lors du pré-chargement du modèle actif ({processing.ACTIVE_MODEL}): {e}")
|
| 81 |
|
| 82 |
@app.get("/", response_class=HTMLResponse)
|
| 83 |
async def get_root(request: Request, current_user: str = Depends(get_current_username)):
|
|
@@ -91,89 +90,100 @@ async def upload_images_for_captioning(
|
|
| 91 |
if not files:
|
| 92 |
raise HTTPException(status_code=400, detail="Aucun fichier n'a été téléversé.")
|
| 93 |
|
| 94 |
-
processed_files_info = []
|
| 95 |
-
|
| 96 |
if not processing.is_active_model_loaded():
|
| 97 |
-
print(f"Le modèle actif ({processing.ACTIVE_MODEL}) n'est pas chargé. Tentative de chargement maintenant...")
|
| 98 |
try:
|
| 99 |
processing.load_active_model()
|
| 100 |
if not processing.is_active_model_loaded():
|
| 101 |
raise HTTPException(status_code=503, detail=f"Le modèle IA ({processing.ACTIVE_MODEL}) n'a pas pu être chargé (vérification post-tentative).")
|
| 102 |
print(f"Modèle actif ({processing.ACTIVE_MODEL}) chargé avec succès à la demande.")
|
| 103 |
except Exception as e:
|
| 104 |
-
error_detail_str = str(e) if str(e) else f"Le modèle IA ({processing.ACTIVE_MODEL}) n'a pas pu être chargé."
|
| 105 |
raise HTTPException(status_code=503, detail=f"Erreur serveur critique : {error_detail_str}")
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
)
|
| 175 |
|
| 176 |
if __name__ == "__main__":
|
| 177 |
-
print("Pour lancer l'application, utilisez la commande : uvicorn app.main:app --host 0.0.0.0 --port 8000")
|
| 178 |
-
# import uvicorn
|
| 179 |
-
# uvicorn.run(app, host="127.0.0.1", port=8000) # Pour test local direct
|
|
|
|
| 2 |
import shutil
|
| 3 |
import uuid
|
| 4 |
import secrets # Pour la comparaison sécurisée des identifiants
|
| 5 |
+
import zipfile # Ajouté pour créer des archives ZIP
|
| 6 |
+
import io # Ajouté pour manipuler des flux de bytes en mémoire
|
| 7 |
|
| 8 |
from fastapi import Depends, FastAPI, File, UploadFile, Request, HTTPException, status
|
| 9 |
+
from fastapi.responses import HTMLResponse, StreamingResponse # JSONResponse n'est plus utilisé pour le retour principal de upload
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from fastapi.templating import Jinja2Templates
|
| 12 |
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
| 13 |
from passlib.context import CryptContext # Pour hacher les mots de passe
|
| 14 |
from typing import List
|
| 15 |
|
| 16 |
+
from . import processing # Contient la logique du modèle [cite: 48]
|
| 17 |
from . import utils
|
| 18 |
|
| 19 |
# Configuration de l'application FastAPI
|
|
|
|
| 24 |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 25 |
|
| 26 |
# Lire les identifiants depuis les variables d'environnement (pour Hugging Face Spaces Secrets)
|
| 27 |
+
APP_USERNAME_DEFAULT = "admin" # [cite: 51]
|
| 28 |
+
APP_PASSWORD_DEFAULT = "changezceci" # [cite: 51]
|
|
|
|
|
|
|
| 29 |
|
| 30 |
APP_USERNAME = os.environ.get("APP_USERNAME", APP_USERNAME_DEFAULT)
|
| 31 |
APP_PASSWORD_RAW = os.environ.get("APP_PASSWORD", APP_PASSWORD_DEFAULT)
|
| 32 |
|
| 33 |
+
CORRECT_PASSWORD_HASH = pwd_context.hash(APP_PASSWORD_RAW) if APP_PASSWORD_RAW else None # [cite: 50]
|
|
|
|
| 34 |
|
| 35 |
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
| 36 |
if hashed_password is None:
|
|
|
|
| 42 |
correct_username_bytes = APP_USERNAME.encode("utf8")
|
| 43 |
|
| 44 |
is_correct_username = secrets.compare_digest(current_username_bytes, correct_username_bytes)
|
| 45 |
+
is_correct_password = verify_password(credentials.password, CORRECT_PASSWORD_HASH) # [cite: 52]
|
| 46 |
|
| 47 |
if not (is_correct_username and is_correct_password):
|
| 48 |
raise HTTPException(
|
|
|
|
| 57 |
PROJECT_ROOT = os.path.dirname(BASE_DIR) # Répertoire AUTOCAPTION
|
| 58 |
|
| 59 |
UPLOAD_DIR = os.path.join(PROJECT_ROOT, "uploads")
|
| 60 |
+
OUTPUT_IMAGE_DIR = os.path.join(PROJECT_ROOT, "outputs", "images") # [cite: 53]
|
| 61 |
OUTPUT_CAPTION_DIR = os.path.join(PROJECT_ROOT, "outputs", "captions")
|
| 62 |
|
| 63 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
|
|
|
| 67 |
app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
|
| 68 |
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
|
| 69 |
|
| 70 |
+
# Optionnel: Pré-chargement du modèle
|
| 71 |
# print("Tentative de pré-chargement du modèle actif au démarrage de l'application...")
|
| 72 |
# try:
|
| 73 |
# processing.load_active_model()
|
|
|
|
| 76 |
# else:
|
| 77 |
# print(f"AVERTISSEMENT: Le modèle actif ({processing.ACTIVE_MODEL}) n'a pas pu être pré-chargé.")
|
| 78 |
# except Exception as e:
|
| 79 |
+
# print(f"AVERTISSEMENT: Erreur lors du pré-chargement du modèle actif ({processing.ACTIVE_MODEL}): {e}") [cite: 54]
|
| 80 |
|
| 81 |
@app.get("/", response_class=HTMLResponse)
|
| 82 |
async def get_root(request: Request, current_user: str = Depends(get_current_username)):
|
|
|
|
| 90 |
if not files:
|
| 91 |
raise HTTPException(status_code=400, detail="Aucun fichier n'a été téléversé.")
|
| 92 |
|
|
|
|
|
|
|
| 93 |
if not processing.is_active_model_loaded():
|
| 94 |
+
print(f"Le modèle actif ({processing.ACTIVE_MODEL}) n'est pas chargé. Tentative de chargement maintenant...") # [cite: 55]
|
| 95 |
try:
|
| 96 |
processing.load_active_model()
|
| 97 |
if not processing.is_active_model_loaded():
|
| 98 |
raise HTTPException(status_code=503, detail=f"Le modèle IA ({processing.ACTIVE_MODEL}) n'a pas pu être chargé (vérification post-tentative).")
|
| 99 |
print(f"Modèle actif ({processing.ACTIVE_MODEL}) chargé avec succès à la demande.")
|
| 100 |
except Exception as e:
|
| 101 |
+
error_detail_str = str(e) if str(e) else f"Le modèle IA ({processing.ACTIVE_MODEL}) n'a pas pu être chargé." # [cite: 56]
|
| 102 |
raise HTTPException(status_code=503, detail=f"Erreur serveur critique : {error_detail_str}")
|
| 103 |
|
| 104 |
+
# Créer un buffer en mémoire pour le fichier ZIP
|
| 105 |
+
zip_buffer = io.BytesIO()
|
| 106 |
+
|
| 107 |
+
# Compteur pour s'assurer qu'au moins un fichier est traité pour le ZIP
|
| 108 |
+
files_added_to_zip = 0
|
| 109 |
+
|
| 110 |
+
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
| 111 |
+
for file in files:
|
| 112 |
+
temp_file_path = None
|
| 113 |
+
try:
|
| 114 |
+
if not file.content_type or not file.content_type.startswith("image/"):
|
| 115 |
+
print(f"Fichier ignoré (type non supporté: {file.content_type}): {file.filename}") # [cite: 57]
|
| 116 |
+
continue
|
| 117 |
+
|
| 118 |
+
unique_base_name = utils.generate_simple_unique_name()
|
| 119 |
+
original_extension = os.path.splitext(file.filename)[1].lower()
|
| 120 |
+
|
| 121 |
+
if not original_extension and file.content_type: # Déduction d'extension
|
| 122 |
+
ext_map = {"image/jpeg": ".jpg", "image/png": ".png", "image/gif": ".gif", "image/webp": ".webp"} # [cite: 58]
|
| 123 |
+
original_extension = ext_map.get(file.content_type, ".img")
|
| 124 |
+
elif not original_extension:
|
| 125 |
+
original_extension = ".jpg"
|
| 126 |
+
|
| 127 |
+
# unique_image_name = f"{unique_base_name}{original_extension}" # Non utilisé directement pour le nom dans le zip
|
| 128 |
+
unique_caption_name = f"{unique_base_name}.txt"
|
| 129 |
+
|
| 130 |
+
temp_upload_filename = f"temp_{uuid.uuid4().hex}{original_extension}" # [cite: 59]
|
| 131 |
+
temp_file_path = os.path.join(UPLOAD_DIR, temp_upload_filename)
|
| 132 |
+
|
| 133 |
+
with open(temp_file_path, "wb") as buffer:
|
| 134 |
+
shutil.copyfileobj(file.file, buffer)
|
| 135 |
+
|
| 136 |
+
image_description = "Description non générée par défaut." # [cite: 60]
|
| 137 |
+
if processing.is_active_model_loaded():
|
| 138 |
+
print(f"Génération de description pour {temp_file_path} avec le modèle {processing.ACTIVE_MODEL}")
|
| 139 |
+
image_description = processing.generate_active_description(temp_file_path)
|
| 140 |
+
else:
|
| 141 |
+
print(f"ERREUR: Tentative de génération alors que le modèle {processing.ACTIVE_MODEL} n'est pas chargé.")
|
| 142 |
+
image_description = f"ERREUR CRITIQUE: Le modèle IA ({processing.ACTIVE_MODEL}) n'est pas disponible." # [cite: 61]
|
| 143 |
+
|
| 144 |
+
# Sauvegarde de l'image traitée (optionnel de l'inclure dans le zip)
|
| 145 |
+
# output_image_path = os.path.join(OUTPUT_IMAGE_DIR, unique_image_name)
|
| 146 |
+
# shutil.copy(temp_file_path, output_image_path)
|
| 147 |
+
|
| 148 |
+
# Écrire la description dans le fichier ZIP
|
| 149 |
+
# Utiliser le nom original du fichier image + .txt pour le nom dans le ZIP pour clarté
|
| 150 |
+
caption_filename_in_zip = f"{os.path.splitext(file.filename)[0]}_caption.txt"
|
| 151 |
+
zf.writestr(caption_filename_in_zip, image_description)
|
| 152 |
+
files_added_to_zip += 1
|
| 153 |
+
|
| 154 |
+
# Sauvegarde du fichier de caption localement (si toujours nécessaire)
|
| 155 |
+
output_caption_path = os.path.join(OUTPUT_CAPTION_DIR, unique_caption_name)
|
| 156 |
+
with open(output_caption_path, "w", encoding="utf-8") as caption_file:
|
| 157 |
+
caption_file.write(image_description) # [cite: 62]
|
| 158 |
+
|
| 159 |
+
except HTTPException:
|
| 160 |
+
raise
|
| 161 |
+
except Exception as e:
|
| 162 |
+
print(f"Erreur inattendue lors du traitement du fichier {file.filename}: {e}")
|
| 163 |
+
processing.traceback.print_exc() # [cite: 64]
|
| 164 |
+
finally:
|
| 165 |
+
if hasattr(file, 'file') and file.file and not file.file.closed:
|
| 166 |
+
file.file.close()
|
| 167 |
+
if temp_file_path and os.path.exists(temp_file_path):
|
| 168 |
+
try:
|
| 169 |
+
os.remove(temp_file_path) # [cite: 65]
|
| 170 |
+
except Exception as e_remove:
|
| 171 |
+
print(f"Erreur lors de la suppression du fichier temporaire {temp_file_path}: {e_remove}")
|
| 172 |
+
|
| 173 |
+
if files_added_to_zip == 0:
|
| 174 |
+
raise HTTPException(status_code=400, detail="Aucun fichier image valide n'a été traité pour générer des descriptions.")
|
| 175 |
+
|
| 176 |
+
# Rembobiner le buffer
|
| 177 |
+
zip_buffer.seek(0)
|
| 178 |
+
|
| 179 |
+
# Nom du fichier ZIP pour le téléchargement
|
| 180 |
+
zip_filename = f"captions_output_{utils.generate_simple_unique_name()}.zip"
|
| 181 |
+
|
| 182 |
+
return StreamingResponse(
|
| 183 |
+
zip_buffer,
|
| 184 |
+
media_type="application/zip",
|
| 185 |
+
headers={"Content-Disposition": f"attachment; filename={zip_filename}"}
|
| 186 |
)
|
| 187 |
|
| 188 |
if __name__ == "__main__":
|
| 189 |
+
print("Pour lancer l'application, utilisez la commande : uvicorn app.main:app --host 0.0.0.0 --port 8000")
|
|
|
|
|
|
app/static/css/style.css
CHANGED
|
@@ -1,171 +1,225 @@
|
|
| 1 |
-
/*
|
| 2 |
:root {
|
| 3 |
-
--font-primary: 'Montserrat', sans-serif;
|
| 4 |
-
--font-secondary: 'Cormorant Garamond', serif;
|
| 5 |
-
--color-text: #333333; /*
|
| 6 |
-
--color-primary: #0A0A0A; /*
|
| 7 |
-
--color-secondary: #B08D57; /*
|
| 8 |
-
--color-background: #FDFDFD; /*
|
| 9 |
--color-light-gray: #e9e9e9;
|
| 10 |
--color-border: #d1d1d1;
|
| 11 |
}
|
| 12 |
|
| 13 |
body {
|
| 14 |
-
font-family: var(--font-secondary); /*
|
| 15 |
-
line-height: 1.7;
|
| 16 |
-
margin: 0;
|
| 17 |
-
padding: 0;
|
| 18 |
-
background-color: var(--color-background);
|
| 19 |
-
color: var(--color-text);
|
| 20 |
-
font-weight: 400; /*
|
| 21 |
}
|
| 22 |
|
| 23 |
h1, h2, h3, h4, h5, h6 {
|
| 24 |
-
font-family: var(--font-primary); /*
|
| 25 |
-
font-weight: 500; /*
|
| 26 |
-
color: var(--color-primary);
|
| 27 |
}
|
| 28 |
|
| 29 |
header {
|
| 30 |
-
background: var(--color-primary);
|
| 31 |
-
color: var(--color-background);
|
| 32 |
-
padding: 2rem 1rem; /*
|
| 33 |
-
text-align: center;
|
| 34 |
-
border-bottom: 3px solid var(--color-secondary);
|
| 35 |
}
|
| 36 |
|
| 37 |
header h1 {
|
| 38 |
-
font-weight: 300; /*
|
| 39 |
-
letter-spacing: 2px; /*
|
| 40 |
-
font-size: 2.5em;
|
| 41 |
-
color: var(--color-background); /*
|
| 42 |
}
|
| 43 |
|
| 44 |
main {
|
| 45 |
-
padding: 30px;
|
| 46 |
-
max-width: 900px; /*
|
| 47 |
-
margin: 30px auto;
|
| 48 |
-
background: #ffffff; /*
|
| 49 |
-
box-shadow: 0 5px 25px rgba(0,0,0,0.05); /*
|
| 50 |
-
border-radius: 8px;
|
| 51 |
}
|
| 52 |
|
| 53 |
section {
|
| 54 |
-
margin-bottom: 30px;
|
| 55 |
-
padding: 25px;
|
| 56 |
-
border: 1px solid var(--color-light-gray);
|
| 57 |
-
border-radius: 5px;
|
| 58 |
}
|
| 59 |
|
| 60 |
h2 {
|
| 61 |
-
font-size: 1.8em;
|
| 62 |
-
font-weight: 400;
|
| 63 |
-
margin-bottom: 20px;
|
| 64 |
-
padding-bottom: 10px;
|
| 65 |
-
border-bottom: 1px solid var(--color-light-gray);
|
| 66 |
-
color: var(--color-primary);
|
| 67 |
}
|
| 68 |
|
| 69 |
input[type="file"] {
|
| 70 |
-
font-family: var(--font-primary);
|
| 71 |
-
padding: 10px;
|
| 72 |
-
border: 1px solid var(--color-border);
|
| 73 |
-
border-radius: 4px;
|
| 74 |
-
background-color: #fff;
|
| 75 |
-
margin-bottom: 20px; /*
|
| 76 |
-
display: block; /*
|
| 77 |
-
width: calc(100% - 22px); /*
|
| 78 |
}
|
| 79 |
|
| 80 |
button[type="submit"] {
|
| 81 |
-
font-family: var(--font-primary);
|
| 82 |
-
background: var(--color-secondary);
|
| 83 |
-
color: white;
|
| 84 |
-
border: none;
|
| 85 |
-
padding: 12px 25px;
|
| 86 |
-
cursor: pointer;
|
| 87 |
-
border-radius: 4px;
|
| 88 |
-
text-transform: uppercase; /*
|
| 89 |
-
font-weight: 500;
|
| 90 |
-
letter-spacing: 1px;
|
| 91 |
-
transition: background-color 0.3s ease;
|
| 92 |
}
|
| 93 |
|
| 94 |
button[type="submit"]:hover {
|
| 95 |
-
background: #9c7b4d; /*
|
| 96 |
}
|
| 97 |
|
| 98 |
#message-area {
|
| 99 |
-
margin-bottom: 15px;
|
| 100 |
-
padding: 15px;
|
| 101 |
-
border-radius: 4px;
|
| 102 |
-
font-family: var(--font-primary);
|
| 103 |
-
font-size: 0.95em;
|
| 104 |
}
|
| 105 |
|
| 106 |
#message-area.success {
|
| 107 |
-
background-color: #e6f4ea; /*
|
| 108 |
-
color: #3d8b50;
|
| 109 |
-
border: 1px solid #c3e0c9;
|
| 110 |
}
|
| 111 |
|
| 112 |
#message-area.error {
|
| 113 |
-
background-color: #f8d7da; /*
|
| 114 |
-
color: #721c24;
|
| 115 |
-
border: 1px solid #f5c6cb;
|
| 116 |
}
|
| 117 |
|
|
|
|
| 118 |
#file-list {
|
| 119 |
-
padding-left: 0; /*
|
| 120 |
}
|
| 121 |
#file-list li {
|
| 122 |
-
list-style: none;
|
| 123 |
-
padding: 10px;
|
| 124 |
-
border-bottom: 1px var(--color-light-gray) dashed;
|
| 125 |
-
font-size: 0.9em;
|
| 126 |
-
font-family: var(--font-secondary); /*
|
| 127 |
}
|
| 128 |
#file-list li:last-child {
|
| 129 |
-
border-bottom: none;
|
| 130 |
}
|
| 131 |
|
| 132 |
footer {
|
| 133 |
-
text-align: center;
|
| 134 |
-
padding: 20px;
|
| 135 |
-
background: var(--color-primary);
|
| 136 |
-
color: var(--color-light-gray);
|
| 137 |
-
margin-top: 40px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
font-family: var(--font-primary);
|
| 139 |
-
font-size: 0.9em;
|
| 140 |
-
font-weight: 300;
|
| 141 |
}
|
| 142 |
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
#progress-indicator {
|
| 145 |
-
/* text-align: center; */
|
| 146 |
}
|
| 147 |
|
| 148 |
.loader-container {
|
| 149 |
-
display: flex;
|
| 150 |
-
flex-direction: column;
|
| 151 |
-
justify-content: center;
|
| 152 |
-
align-items: center;
|
| 153 |
-
padding: 20px;
|
| 154 |
-
min-height: 100px;
|
| 155 |
-
font-family: var(--font-primary); /*
|
| 156 |
}
|
| 157 |
|
| 158 |
.spinner {
|
| 159 |
-
border: 6px solid var(--color-light-gray); /*
|
| 160 |
-
border-top: 6px solid var(--color-secondary); /*
|
| 161 |
-
border-radius: 50%;
|
| 162 |
-
width: 50px;
|
| 163 |
-
height: 50px;
|
| 164 |
-
animation: spin 1s linear infinite;
|
| 165 |
-
margin-bottom: 15px;
|
| 166 |
}
|
| 167 |
|
| 168 |
@keyframes spin {
|
| 169 |
-
0% { transform: rotate(0deg); }
|
| 170 |
-
100% { transform: rotate(360deg); }
|
| 171 |
}
|
|
|
|
| 1 |
+
/* ... (styles existants de :root à footer) ... */
|
| 2 |
:root {
|
| 3 |
+
--font-primary: 'Montserrat', sans-serif; /* [cite: 97] */
|
| 4 |
+
--font-secondary: 'Cormorant Garamond', serif; /* [cite: 97] */
|
| 5 |
+
--color-text: #333333; /* [cite: 98] */
|
| 6 |
+
--color-primary: #0A0A0A; /* [cite: 99] */
|
| 7 |
+
--color-secondary: #B08D57; /* [cite: 100] */
|
| 8 |
+
--color-background: #FDFDFD; /* [cite: 101] */
|
| 9 |
--color-light-gray: #e9e9e9;
|
| 10 |
--color-border: #d1d1d1;
|
| 11 |
}
|
| 12 |
|
| 13 |
body {
|
| 14 |
+
font-family: var(--font-secondary); /* [cite: 102] */
|
| 15 |
+
line-height: 1.7; /* [cite: 102] */
|
| 16 |
+
margin: 0; /* [cite: 102] */
|
| 17 |
+
padding: 0; /* [cite: 102] */
|
| 18 |
+
background-color: var(--color-background); /* [cite: 102] */
|
| 19 |
+
color: var(--color-text); /* [cite: 102] */
|
| 20 |
+
font-weight: 400; /* [cite: 103] */
|
| 21 |
}
|
| 22 |
|
| 23 |
h1, h2, h3, h4, h5, h6 {
|
| 24 |
+
font-family: var(--font-primary); /* [cite: 104] */
|
| 25 |
+
font-weight: 500; /* [cite: 105] */
|
| 26 |
+
color: var(--color-primary); /* [cite: 105] */
|
| 27 |
}
|
| 28 |
|
| 29 |
header {
|
| 30 |
+
background: var(--color-primary); /* [cite: 106] */
|
| 31 |
+
color: var(--color-background); /* [cite: 106] */
|
| 32 |
+
padding: 2rem 1rem; /* [cite: 106] */
|
| 33 |
+
text-align: center; /* [cite: 106] */
|
| 34 |
+
border-bottom: 3px solid var(--color-secondary); /* [cite: 107] */
|
| 35 |
}
|
| 36 |
|
| 37 |
header h1 {
|
| 38 |
+
font-weight: 300; /* [cite: 108] */
|
| 39 |
+
letter-spacing: 2px; /* [cite: 108] */
|
| 40 |
+
font-size: 2.5em; /* [cite: 108] */
|
| 41 |
+
color: var(--color-background); /* [cite: 109] */
|
| 42 |
}
|
| 43 |
|
| 44 |
main {
|
| 45 |
+
padding: 30px; /* [cite: 110] */
|
| 46 |
+
max-width: 900px; /* [cite: 110] */
|
| 47 |
+
margin: 30px auto; /* [cite: 110] */
|
| 48 |
+
background: #ffffff; /* [cite: 111] */
|
| 49 |
+
box-shadow: 0 5px 25px rgba(0,0,0,0.05); /* [cite: 112] */
|
| 50 |
+
border-radius: 8px; /* [cite: 112] */
|
| 51 |
}
|
| 52 |
|
| 53 |
section {
|
| 54 |
+
margin-bottom: 30px; /* [cite: 113] */
|
| 55 |
+
padding: 25px; /* [cite: 113] */
|
| 56 |
+
border: 1px solid var(--color-light-gray); /* [cite: 113] */
|
| 57 |
+
border-radius: 5px; /* [cite: 113] */
|
| 58 |
}
|
| 59 |
|
| 60 |
h2 {
|
| 61 |
+
font-size: 1.8em; /* [cite: 114] */
|
| 62 |
+
font-weight: 400; /* [cite: 114] */
|
| 63 |
+
margin-bottom: 20px; /* [cite: 114] */
|
| 64 |
+
padding-bottom: 10px; /* [cite: 114] */
|
| 65 |
+
border-bottom: 1px solid var(--color-light-gray); /* [cite: 114] */
|
| 66 |
+
color: var(--color-primary); /* [cite: 114] */
|
| 67 |
}
|
| 68 |
|
| 69 |
input[type="file"] {
|
| 70 |
+
font-family: var(--font-primary); /* [cite: 115] */
|
| 71 |
+
padding: 10px; /* [cite: 115] */
|
| 72 |
+
border: 1px solid var(--color-border); /* [cite: 115] */
|
| 73 |
+
border-radius: 4px; /* [cite: 115] */
|
| 74 |
+
background-color: #fff; /* [cite: 115] */
|
| 75 |
+
margin-bottom: 20px; /* [cite: 115] */
|
| 76 |
+
display: block; /* [cite: 116] */
|
| 77 |
+
width: calc(100% - 22px); /* [cite: 117] */
|
| 78 |
}
|
| 79 |
|
| 80 |
button[type="submit"] {
|
| 81 |
+
font-family: var(--font-primary); /* [cite: 118] */
|
| 82 |
+
background: var(--color-secondary); /* [cite: 118] */
|
| 83 |
+
color: white; /* [cite: 118] */
|
| 84 |
+
border: none; /* [cite: 118] */
|
| 85 |
+
padding: 12px 25px; /* [cite: 118] */
|
| 86 |
+
cursor: pointer; /* [cite: 118] */
|
| 87 |
+
border-radius: 4px; /* [cite: 118] */
|
| 88 |
+
text-transform: uppercase; /* [cite: 119] */
|
| 89 |
+
font-weight: 500; /* [cite: 119] */
|
| 90 |
+
letter-spacing: 1px; /* [cite: 119] */
|
| 91 |
+
transition: background-color 0.3s ease; /* [cite: 120] */
|
| 92 |
}
|
| 93 |
|
| 94 |
button[type="submit"]:hover {
|
| 95 |
+
background: #9c7b4d; /* [cite: 120] */
|
| 96 |
}
|
| 97 |
|
| 98 |
#message-area {
|
| 99 |
+
margin-bottom: 15px; /* [cite: 121] */
|
| 100 |
+
padding: 15px; /* [cite: 121] */
|
| 101 |
+
border-radius: 4px; /* [cite: 121] */
|
| 102 |
+
font-family: var(--font-primary); /* [cite: 121] */
|
| 103 |
+
font-size: 0.95em; /* [cite: 121] */
|
| 104 |
}
|
| 105 |
|
| 106 |
#message-area.success {
|
| 107 |
+
background-color: #e6f4ea; /* [cite: 122] */
|
| 108 |
+
color: #3d8b50; /* [cite: 122] */
|
| 109 |
+
border: 1px solid #c3e0c9; /* [cite: 122] */
|
| 110 |
}
|
| 111 |
|
| 112 |
#message-area.error {
|
| 113 |
+
background-color: #f8d7da; /* [cite: 123] */
|
| 114 |
+
color: #721c24; /* [cite: 123] */
|
| 115 |
+
border: 1px solid #f5c6cb; /* [cite: 123] */
|
| 116 |
}
|
| 117 |
|
| 118 |
+
/* L'ancienne #file-list n'est plus directement utilisée pour les résultats */
|
| 119 |
#file-list {
|
| 120 |
+
padding-left: 0; /* [cite: 124] */
|
| 121 |
}
|
| 122 |
#file-list li {
|
| 123 |
+
list-style: none; /* [cite: 125] */
|
| 124 |
+
padding: 10px; /* [cite: 125] */
|
| 125 |
+
border-bottom: 1px var(--color-light-gray) dashed; /* [cite: 125] */
|
| 126 |
+
font-size: 0.9em; /* [cite: 125] */
|
| 127 |
+
font-family: var(--font-secondary); /* [cite: 125] */
|
| 128 |
}
|
| 129 |
#file-list li:last-child {
|
| 130 |
+
border-bottom: none; /* [cite: 126] */
|
| 131 |
}
|
| 132 |
|
| 133 |
footer {
|
| 134 |
+
text-align: center; /* [cite: 127] */
|
| 135 |
+
padding: 20px; /* [cite: 127] */
|
| 136 |
+
background: var(--color-primary); /* [cite: 127] */
|
| 137 |
+
color: var(--color-light-gray); /* [cite: 127] */
|
| 138 |
+
margin-top: 40px; /* [cite: 127] */
|
| 139 |
+
font-family: var(--font-primary); /* [cite: 127] */
|
| 140 |
+
font-size: 0.9em; /* [cite: 127] */
|
| 141 |
+
font-weight: 300; /* [cite: 127] */
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
/* Styles pour la barre de progression */
|
| 145 |
+
#progress-container {
|
| 146 |
+
margin-top: 20px;
|
| 147 |
font-family: var(--font-primary);
|
|
|
|
|
|
|
| 148 |
}
|
| 149 |
|
| 150 |
+
#progress-text {
|
| 151 |
+
text-align: center;
|
| 152 |
+
margin-bottom: 10px;
|
| 153 |
+
color: var(--color-text);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.progress-bar-wrapper {
|
| 157 |
+
width: 100%;
|
| 158 |
+
background-color: var(--color-light-gray);
|
| 159 |
+
border-radius: 5px;
|
| 160 |
+
padding: 3px;
|
| 161 |
+
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.progress-bar-fill {
|
| 165 |
+
height: 20px;
|
| 166 |
+
background-color: var(--color-secondary);
|
| 167 |
+
border-radius: 3px;
|
| 168 |
+
width: 0%; /* Initialement à 0% */
|
| 169 |
+
transition: width 0.3s ease-out;
|
| 170 |
+
text-align: center;
|
| 171 |
+
color: white;
|
| 172 |
+
font-size: 0.8em;
|
| 173 |
+
line-height: 20px; /* Centrer le texte verticalement */
|
| 174 |
+
animation: pulse-indeterminate 2s infinite ease-in-out; /* Animation indéterminée */
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.progress-bar-fill.determinate {
|
| 178 |
+
animation: none; /* Stopper l'animation quand la progression est déterminée */
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
@keyframes pulse-indeterminate {
|
| 183 |
+
0% {
|
| 184 |
+
width: 0%;
|
| 185 |
+
opacity: 0.7;
|
| 186 |
+
}
|
| 187 |
+
50% {
|
| 188 |
+
width: 100%;
|
| 189 |
+
opacity: 1;
|
| 190 |
+
}
|
| 191 |
+
100% {
|
| 192 |
+
width: 0%;
|
| 193 |
+
opacity: 0.7;
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
/* Styles pour le Spinner / Chargeur (non utilisé mais conservé au cas où) */
|
| 198 |
#progress-indicator {
|
| 199 |
+
/* text-align: center; */ /* [cite: 128] */
|
| 200 |
}
|
| 201 |
|
| 202 |
.loader-container {
|
| 203 |
+
display: flex; /* [cite: 129] */
|
| 204 |
+
flex-direction: column; /* [cite: 129] */
|
| 205 |
+
justify-content: center; /* [cite: 129] */
|
| 206 |
+
align-items: center; /* [cite: 129] */
|
| 207 |
+
padding: 20px; /* [cite: 129] */
|
| 208 |
+
min-height: 100px; /* [cite: 129] */
|
| 209 |
+
font-family: var(--font-primary); /* [cite: 129] */
|
| 210 |
}
|
| 211 |
|
| 212 |
.spinner {
|
| 213 |
+
border: 6px solid var(--color-light-gray); /* [cite: 130] */
|
| 214 |
+
border-top: 6px solid var(--color-secondary); /* [cite: 131] */
|
| 215 |
+
border-radius: 50%; /* [cite: 131] */
|
| 216 |
+
width: 50px; /* [cite: 132] */
|
| 217 |
+
height: 50px; /* [cite: 132] */
|
| 218 |
+
animation: spin 1s linear infinite; /* [cite: 132] */
|
| 219 |
+
margin-bottom: 15px; /* [cite: 132] */
|
| 220 |
}
|
| 221 |
|
| 222 |
@keyframes spin {
|
| 223 |
+
0% { transform: rotate(0deg); } /* [cite: 133] */
|
| 224 |
+
100% { transform: rotate(360deg); } /* [cite: 133] */
|
| 225 |
}
|
app/static/js/script.js
CHANGED
|
@@ -2,71 +2,90 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 2 |
const uploadForm = document.getElementById('upload-form');
|
| 3 |
const imageFilesInput = document.getElementById('image-files');
|
| 4 |
const messageArea = document.getElementById('message-area');
|
| 5 |
-
const fileList = document.getElementById('file-list');
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
const
|
| 9 |
-
|
| 10 |
|
| 11 |
uploadForm.addEventListener('submit', async (event) => {
|
| 12 |
event.preventDefault();
|
| 13 |
messageArea.textContent = '';
|
| 14 |
-
messageArea.className = ''; // Effacer les classes précédentes
|
| 15 |
-
fileList.innerHTML = ''; //
|
| 16 |
|
| 17 |
-
const
|
|
|
|
| 18 |
|
| 19 |
if (numFiles === 0) {
|
| 20 |
messageArea.textContent = 'Veuillez sélectionner au moins une image.';
|
| 21 |
-
messageArea.classList.add('error');
|
| 22 |
return;
|
| 23 |
}
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
progressIndicator.style.display = 'block';
|
| 31 |
-
|
| 32 |
|
| 33 |
const formData = new FormData();
|
| 34 |
-
for (const file of
|
| 35 |
-
formData.append('files', file);
|
| 36 |
}
|
| 37 |
|
| 38 |
try {
|
|
|
|
|
|
|
| 39 |
const response = await fetch('/api/upload-images/', {
|
| 40 |
method: 'POST',
|
| 41 |
body: formData,
|
| 42 |
});
|
| 43 |
|
| 44 |
-
// Masquer l'indicateur de progression une fois la réponse reçue
|
| 45 |
-
progressIndicator.style.display = 'none';
|
| 46 |
-
const result = await response.json();
|
| 47 |
-
|
| 48 |
if (response.ok) {
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
} else if (result.processed_files && result.processed_files.length === 0 && numFiles > 0) {
|
| 58 |
-
messageArea.textContent = result.message || "Aucun fichier n'a été traité avec succès parmi ceux envoyés.";
|
| 59 |
-
messageArea.classList.add('error'); // Ou une classe 'warning'
|
| 60 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
} else {
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
messageArea.classList.add('error');
|
|
|
|
| 64 |
}
|
| 65 |
} catch (error) {
|
| 66 |
-
|
| 67 |
-
messageArea.textContent = 'Erreur de connexion ou le serveur ne répond pas. Vérifiez la console du navigateur et du serveur.';
|
| 68 |
-
messageArea.classList.add('error');
|
| 69 |
-
|
| 70 |
}
|
| 71 |
});
|
| 72 |
});
|
|
|
|
| 2 |
const uploadForm = document.getElementById('upload-form');
|
| 3 |
const imageFilesInput = document.getElementById('image-files');
|
| 4 |
const messageArea = document.getElementById('message-area');
|
| 5 |
+
// const fileList = document.getElementById('file-list'); // N'est plus utilisé pour afficher les résultats
|
| 6 |
+
|
| 7 |
+
const progressContainer = document.getElementById('progress-container');
|
| 8 |
+
const progressBarFill = document.getElementById('progress-bar');
|
| 9 |
+
const progressTextElement = document.getElementById('progress-text');
|
| 10 |
|
| 11 |
uploadForm.addEventListener('submit', async (event) => {
|
| 12 |
event.preventDefault();
|
| 13 |
messageArea.textContent = '';
|
| 14 |
+
messageArea.className = ''; // Effacer les classes précédentes [cite: 134]
|
| 15 |
+
// fileList.innerHTML = ''; // Plus nécessaire [cite: 134]
|
| 16 |
|
| 17 |
+
const files = imageFilesInput.files;
|
| 18 |
+
const numFiles = files.length;
|
| 19 |
|
| 20 |
if (numFiles === 0) {
|
| 21 |
messageArea.textContent = 'Veuillez sélectionner au moins une image.';
|
| 22 |
+
messageArea.classList.add('error'); // [cite: 135]
|
| 23 |
return;
|
| 24 |
}
|
| 25 |
|
| 26 |
+
progressTextElement.textContent = `Préparation de ${numFiles} image(s)...`;
|
| 27 |
+
progressBarFill.style.width = '0%';
|
| 28 |
+
progressBarFill.classList.remove('determinate'); // Activer l'animation indéterminée
|
| 29 |
+
progressBarFill.textContent = '';
|
| 30 |
+
progressContainer.style.display = 'block';
|
|
|
|
|
|
|
| 31 |
|
| 32 |
const formData = new FormData();
|
| 33 |
+
for (const file of files) { // [cite: 138]
|
| 34 |
+
formData.append('files', file); // [cite: 139]
|
| 35 |
}
|
| 36 |
|
| 37 |
try {
|
| 38 |
+
progressTextElement.textContent = `Traitement de ${numFiles} image(s) en cours, veuillez patienter...`;
|
| 39 |
+
|
| 40 |
const response = await fetch('/api/upload-images/', {
|
| 41 |
method: 'POST',
|
| 42 |
body: formData,
|
| 43 |
});
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
if (response.ok) {
|
| 46 |
+
// Le serveur renvoie un fichier ZIP
|
| 47 |
+
const blob = await response.blob();
|
| 48 |
+
const contentDisposition = response.headers.get('content-disposition');
|
| 49 |
+
let filename = "captions.zip"; // Nom par défaut
|
| 50 |
+
if (contentDisposition) {
|
| 51 |
+
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i);
|
| 52 |
+
if (filenameMatch && filenameMatch.length === 2)
|
| 53 |
+
filename = filenameMatch[1];
|
|
|
|
|
|
|
|
|
|
| 54 |
}
|
| 55 |
+
|
| 56 |
+
const link = document.createElement('a');
|
| 57 |
+
link.href = URL.createObjectURL(blob);
|
| 58 |
+
link.download = filename;
|
| 59 |
+
document.body.appendChild(link);
|
| 60 |
+
link.click();
|
| 61 |
+
document.body.removeChild(link);
|
| 62 |
+
URL.revokeObjectURL(link.href); // Libérer l'objet URL
|
| 63 |
+
|
| 64 |
+
messageArea.textContent = `${numFiles} image(s) traitée(s). Le fichier ZIP '${filename}' est en cours de téléchargement.`;
|
| 65 |
+
messageArea.classList.add('success');
|
| 66 |
+
|
| 67 |
+
progressBarFill.style.width = '100%';
|
| 68 |
+
progressBarFill.classList.add('determinate'); // Stopper l'animation
|
| 69 |
+
progressBarFill.textContent = 'Terminé !';
|
| 70 |
+
progressTextElement.textContent = "Traitement terminé avec succès !";
|
| 71 |
+
|
| 72 |
+
|
| 73 |
} else {
|
| 74 |
+
// Gérer les erreurs HTTP (ex: 400, 401, 500, 503)
|
| 75 |
+
const errorResult = await response.json().catch(() => null); // Essayer de parser JSON, sinon null
|
| 76 |
+
if (errorResult && errorResult.detail) {
|
| 77 |
+
messageArea.textContent = `Erreur: ${errorResult.detail}`;
|
| 78 |
+
} else {
|
| 79 |
+
messageArea.textContent = `Une erreur est survenue (code: ${response.status}). Veuillez réessayer.`;
|
| 80 |
+
}
|
| 81 |
messageArea.classList.add('error');
|
| 82 |
+
progressContainer.style.display = 'none'; // Cacher la barre en cas d'erreur
|
| 83 |
}
|
| 84 |
} catch (error) {
|
| 85 |
+
console.error('Upload error:', error); // [cite: 149]
|
| 86 |
+
messageArea.textContent = 'Erreur de connexion ou le serveur ne répond pas. Vérifiez la console du navigateur et du serveur.'; // [cite: 148]
|
| 87 |
+
messageArea.classList.add('error'); // [cite: 148]
|
| 88 |
+
progressContainer.style.display = 'none'; // Cacher la barre
|
| 89 |
}
|
| 90 |
});
|
| 91 |
});
|
app/templates/index.html
CHANGED
|
@@ -3,7 +3,8 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>AutoCaption IA - Studio Luxe</title>
|
|
|
|
| 7 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 8 |
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;500;700&family=Montserrat:wght@300;400;500;700&display=swap" rel="stylesheet">
|
| 9 |
<link rel="stylesheet" href="{{ url_for('static', path='/css/style.css') }}">
|
|
@@ -14,28 +15,22 @@
|
|
| 14 |
</header>
|
| 15 |
<main>
|
| 16 |
<section id="upload-section">
|
| 17 |
-
<h2>Téléverser des Images</h2>
|
| 18 |
-
<form id="upload-form" enctype="multipart/form-data">
|
| 19 |
<input type="file" id="image-files" name="files" multiple accept="image/*">
|
| 20 |
<button type="submit">Générer les Descriptions</button>
|
| 21 |
</form>
|
| 22 |
-
<div id="progress-
|
| 23 |
-
<div class="
|
| 24 |
-
<div class="
|
| 25 |
-
<p>Traitement des images en cours, veuillez patienter...</p>
|
| 26 |
</div>
|
| 27 |
</div>
|
| 28 |
</section>
|
| 29 |
<section id="results-section">
|
| 30 |
-
<h2>Résultats</h2>
|
| 31 |
-
<
|
| 32 |
-
<ul id="file-list">
|
| 33 |
-
</ul>
|
| 34 |
-
</section>
|
| 35 |
</main>
|
| 36 |
<footer>
|
| 37 |
-
<p>© 2025 Votre Application AutoCaption</p>
|
| 38 |
-
</footer>
|
| 39 |
<script src="{{ url_for('static', path='/js/script.js') }}"></script>
|
| 40 |
</body>
|
| 41 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AutoCaption IA - Studio Luxe</title>
|
| 7 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 9 |
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;500;700&family=Montserrat:wght@300;400;500;700&display=swap" rel="stylesheet">
|
| 10 |
<link rel="stylesheet" href="{{ url_for('static', path='/css/style.css') }}">
|
|
|
|
| 15 |
</header>
|
| 16 |
<main>
|
| 17 |
<section id="upload-section">
|
| 18 |
+
<h2>Téléverser des Images</h2> <form id="upload-form" enctype="multipart/form-data">
|
|
|
|
| 19 |
<input type="file" id="image-files" name="files" multiple accept="image/*">
|
| 20 |
<button type="submit">Générer les Descriptions</button>
|
| 21 |
</form>
|
| 22 |
+
<div id="progress-container" style="display:none;"> <p id="progress-text">Traitement des images en cours...</p>
|
| 23 |
+
<div class="progress-bar-wrapper">
|
| 24 |
+
<div id="progress-bar" class="progress-bar-fill"></div>
|
|
|
|
| 25 |
</div>
|
| 26 |
</div>
|
| 27 |
</section>
|
| 28 |
<section id="results-section">
|
| 29 |
+
<h2>Résultats</h2> <div id="message-area"></div>
|
| 30 |
+
</section>
|
|
|
|
|
|
|
|
|
|
| 31 |
</main>
|
| 32 |
<footer>
|
| 33 |
+
<p>© 2025 Votre Application AutoCaption</p> </footer>
|
|
|
|
| 34 |
<script src="{{ url_for('static', path='/js/script.js') }}"></script>
|
| 35 |
</body>
|
| 36 |
</html>
|
generer_doc.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
# --- Fonctions Auxiliaires ---
|
| 4 |
+
|
| 5 |
+
def _ecrire_structure_partie(f_out, chemin_a_parcourir, chemin_base_relativisation, exclusions_dossiers, exclusions_fichiers, exclusions_extensions):
|
| 6 |
+
"""
|
| 7 |
+
Écrit la structure des dossiers/fichiers pour un chemin donné.
|
| 8 |
+
L'indentation est relative à chemin_a_parcourir.
|
| 9 |
+
Les chemins affichés sont relatifs à chemin_base_relativisation.
|
| 10 |
+
"""
|
| 11 |
+
for root, dirs, files in os.walk(chemin_a_parcourir, topdown=True):
|
| 12 |
+
# Exclure les dossiers spécifiés
|
| 13 |
+
dirs[:] = [d for d in dirs if d not in exclusions_dossiers]
|
| 14 |
+
dirs.sort() # Trier les dossiers pour un ordre cohérent
|
| 15 |
+
|
| 16 |
+
# Calcul de l'indentation
|
| 17 |
+
if root == chemin_a_parcourir:
|
| 18 |
+
level = 0
|
| 19 |
+
# Nom à afficher pour le dossier racine de cette section
|
| 20 |
+
current_dir_display_name = os.path.relpath(root, chemin_base_relativisation)
|
| 21 |
+
if current_dir_display_name == ".": # chemin_a_parcourir == chemin_base_relativisation
|
| 22 |
+
current_dir_display_name = os.path.basename(root)
|
| 23 |
+
else:
|
| 24 |
+
# Calculer le niveau de profondeur par rapport à chemin_a_parcourir
|
| 25 |
+
relative_to_scan_root = os.path.relpath(root, chemin_a_parcourir)
|
| 26 |
+
level = relative_to_scan_root.count(os.sep) + 1 if relative_to_scan_root != "." else 1
|
| 27 |
+
current_dir_display_name = os.path.basename(root)
|
| 28 |
+
|
| 29 |
+
indent = ' ' * 4 * level
|
| 30 |
+
f_out.write(f"{indent}{current_dir_display_name}/\n")
|
| 31 |
+
|
| 32 |
+
sub_indent = ' ' * 4 * (level + 1)
|
| 33 |
+
for file_name in sorted(files): # Trier les fichiers
|
| 34 |
+
if file_name not in exclusions_fichiers and not any(file_name.endswith(ext) for ext in exclusions_extensions):
|
| 35 |
+
f_out.write(f"{sub_indent}{file_name}\n")
|
| 36 |
+
|
| 37 |
+
def _ecrire_contenu_partie(f_out, chemin_a_parcourir, chemin_base_relativisation, exclusions_dossiers, exclusions_fichiers, exclusions_extensions):
|
| 38 |
+
"""
|
| 39 |
+
Écrit le contenu des fichiers pour un chemin donné.
|
| 40 |
+
Les chemins des fichiers sont relatifs à chemin_base_relativisation.
|
| 41 |
+
"""
|
| 42 |
+
for root, dirs, files in os.walk(chemin_a_parcourir, topdown=True):
|
| 43 |
+
dirs[:] = [d for d in dirs if d not in exclusions_dossiers]
|
| 44 |
+
|
| 45 |
+
for file_name in sorted(files): # Trier les fichiers
|
| 46 |
+
if file_name not in exclusions_fichiers and not any(file_name.endswith(ext) for ext in exclusions_extensions):
|
| 47 |
+
chemin_fichier_absolu = os.path.join(root, file_name)
|
| 48 |
+
chemin_fichier_relatif_display = os.path.relpath(chemin_fichier_absolu, chemin_base_relativisation)
|
| 49 |
+
|
| 50 |
+
# Construction du chemin affiché pour qu'il commence par / s'il n'est pas à la racine immédiate.
|
| 51 |
+
# os.path.relpath peut retourner "fichier.txt" ou "dossier/fichier.txt"
|
| 52 |
+
if chemin_fichier_relatif_display == "." or chemin_fichier_relatif_display == os.path.basename(chemin_fichier_relatif_display):
|
| 53 |
+
# Fichier à la racine de chemin_base_relativisation
|
| 54 |
+
display_path_final = os.path.join(os.path.sep, os.path.basename(chemin_fichier_absolu))
|
| 55 |
+
else:
|
| 56 |
+
# Fichier dans un sous-dossier de chemin_base_relativisation
|
| 57 |
+
display_path_final = os.path.join(os.path.sep, chemin_fichier_relatif_display)
|
| 58 |
+
|
| 59 |
+
# Nettoyage pour éviter les "./" au début après la jointure
|
| 60 |
+
if display_path_final.startswith(os.path.sep + '.' + os.path.sep):
|
| 61 |
+
display_path_final = display_path_final.replace(os.path.sep + '.' + os.path.sep, os.path.sep, 1)
|
| 62 |
+
elif display_path_final == os.path.sep + '.': # cas où chemin_fichier_relatif_display était '.'
|
| 63 |
+
display_path_final = os.path.join(os.path.sep, os.path.basename(chemin_fichier_absolu))
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
f_out.write("-" * 30 + "\n")
|
| 67 |
+
f_out.write(f"Fichier : {display_path_final}\n")
|
| 68 |
+
f_out.write("-" * 30 + "\n")
|
| 69 |
+
try:
|
| 70 |
+
with open(chemin_fichier_absolu, 'r', encoding='utf-8', errors='ignore') as f_in:
|
| 71 |
+
contenu = f_in.read()
|
| 72 |
+
f_out.write(contenu)
|
| 73 |
+
f_out.write("\n\n")
|
| 74 |
+
except Exception as e:
|
| 75 |
+
f_out.write(f"Erreur lors de la lecture du fichier {file_name}: {e}\n\n")
|
| 76 |
+
|
| 77 |
+
# --- Fonctions de Génération de Document ---
|
| 78 |
+
|
| 79 |
+
def generer_document_unique(nom_section_document, chemin_a_scanner, chemin_base_relativisation, fichier_sortie, exclusions_dossiers, exclusions_fichiers, exclusions_extensions):
|
| 80 |
+
"""
|
| 81 |
+
Génère un document (structure et contenu) pour un seul chemin racine.
|
| 82 |
+
"""
|
| 83 |
+
if not os.path.isdir(chemin_a_scanner):
|
| 84 |
+
print(f"AVERTISSEMENT : Le dossier '{chemin_a_scanner}' pour la section '{nom_section_document}' est introuvable. Le fichier '{fichier_sortie}' ne sera pas généré.")
|
| 85 |
+
return
|
| 86 |
+
|
| 87 |
+
with open(fichier_sortie, 'w', encoding='utf-8') as f_out:
|
| 88 |
+
f_out.write(f"Structure pour : {nom_section_document}\n")
|
| 89 |
+
f_out.write(f"(Chemin de base pour l'analyse : {chemin_a_scanner})\n")
|
| 90 |
+
f_out.write(f"(Chemins affichés relatifs à : {chemin_base_relativisation})\n")
|
| 91 |
+
f_out.write("=" * 50 + "\n\n")
|
| 92 |
+
|
| 93 |
+
_ecrire_structure_partie(f_out, chemin_a_scanner, chemin_base_relativisation, exclusions_dossiers, exclusions_fichiers, exclusions_extensions)
|
| 94 |
+
|
| 95 |
+
f_out.write("\n\n" + "=" * 50 + "\n")
|
| 96 |
+
f_out.write(f"Contenu des fichiers pour : {nom_section_document}\n")
|
| 97 |
+
f_out.write("=" * 50 + "\n\n")
|
| 98 |
+
|
| 99 |
+
_ecrire_contenu_partie(f_out, chemin_a_scanner, chemin_base_relativisation, exclusions_dossiers, exclusions_fichiers, exclusions_extensions)
|
| 100 |
+
|
| 101 |
+
print(f"Le fichier '{fichier_sortie}' ({nom_section_document}) a été généré avec succès !")
|
| 102 |
+
|
| 103 |
+
def generer_document_combine(titre_global_document, liste_dossiers_sources_avec_noms, chemin_base_relativisation_global, fichier_sortie, exclusions_dossiers, exclusions_fichiers, exclusions_extensions):
|
| 104 |
+
"""
|
| 105 |
+
Génère un document combinant la structure et le contenu de plusieurs dossiers sources.
|
| 106 |
+
liste_dossiers_sources_avec_noms: une liste de tuples [("Nom Section", "/chemin/vers/dossier"), ...]
|
| 107 |
+
Les chemins dans le document sont relatifs à chemin_base_relativisation_global.
|
| 108 |
+
"""
|
| 109 |
+
with open(fichier_sortie, 'w', encoding='utf-8') as f_out:
|
| 110 |
+
f_out.write(f"{titre_global_document}\n")
|
| 111 |
+
f_out.write(f"(Chemins affichés relatifs à : {chemin_base_relativisation_global})\n")
|
| 112 |
+
f_out.write("=" * 50 + "\n\n")
|
| 113 |
+
|
| 114 |
+
f_out.write("Structure des dossiers :\n")
|
| 115 |
+
f_out.write("-" * 50 + "\n\n")
|
| 116 |
+
for nom_section, chemin_a_scanner in liste_dossiers_sources_avec_noms:
|
| 117 |
+
if not os.path.isdir(chemin_a_scanner):
|
| 118 |
+
f_out.write(f"--- Dossier '{nom_section}' ({chemin_a_scanner}) introuvable. Section de structure ignorée. ---\n\n")
|
| 119 |
+
continue
|
| 120 |
+
f_out.write(f"--- Structure pour : {nom_section} (depuis {chemin_a_scanner}) ---\n")
|
| 121 |
+
_ecrire_structure_partie(f_out, chemin_a_scanner, chemin_base_relativisation_global, exclusions_dossiers, exclusions_fichiers, exclusions_extensions)
|
| 122 |
+
f_out.write(f"--- Fin Structure pour : {nom_section} ---\n\n")
|
| 123 |
+
|
| 124 |
+
f_out.write("\n\n" + "=" * 50 + "\n")
|
| 125 |
+
f_out.write("Contenu des fichiers :\n")
|
| 126 |
+
f_out.write("=" * 50 + "\n\n")
|
| 127 |
+
for nom_section, chemin_a_scanner in liste_dossiers_sources_avec_noms:
|
| 128 |
+
if not os.path.isdir(chemin_a_scanner):
|
| 129 |
+
f_out.write(f"--- Dossier '{nom_section}' ({chemin_a_scanner}) introuvable. Section de contenu ignorée. ---\n\n")
|
| 130 |
+
continue
|
| 131 |
+
f_out.write(f"--- Contenu pour : {nom_section} (depuis {chemin_a_scanner}) ---\n")
|
| 132 |
+
_ecrire_contenu_partie(f_out, chemin_a_scanner, chemin_base_relativisation_global, exclusions_dossiers, exclusions_fichiers, exclusions_extensions)
|
| 133 |
+
f_out.write(f"--- Fin Contenu pour : {nom_section} ---\n\n")
|
| 134 |
+
|
| 135 |
+
print(f"Le fichier combiné '{fichier_sortie}' ({titre_global_document}) a été généré avec succès !")
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
if __name__ == "__main__":
|
| 139 |
+
# --- Configuration ---
|
| 140 |
+
home_dir = os.path.expanduser("~")
|
| 141 |
+
|
| 142 |
+
# Nom du dossier de votre projet cible
|
| 143 |
+
nom_dossier_projet = "autocaption-hf-space"
|
| 144 |
+
|
| 145 |
+
# Chemin de base où se trouve votre dossier de projet (Documents sur Mac)
|
| 146 |
+
# Si "autocaption-hf-space" est directement dans "Documents"
|
| 147 |
+
chemin_base_parent_projet = os.path.join(home_dir, "Documents")
|
| 148 |
+
|
| 149 |
+
# Chemin complet vers le dossier du projet cible
|
| 150 |
+
chemin_projet_cible = os.path.join(chemin_base_parent_projet, nom_dossier_projet)
|
| 151 |
+
|
| 152 |
+
# Chemins spécifiques pour d'éventuels sous-dossiers src et backend (si votre projet les utilise)
|
| 153 |
+
# Si votre projet "autocaption-hf-space" n'a pas ces sous-dossiers distincts,
|
| 154 |
+
# les rapports pour "SRC" et "BACKEND" ne seront simplement pas générés (ou seront vides).
|
| 155 |
+
chemin_src = os.path.join(chemin_projet_cible, "src")
|
| 156 |
+
chemin_backend = os.path.join(chemin_projet_cible, "backend")
|
| 157 |
+
|
| 158 |
+
# Dossiers à exclure (vous pouvez personnaliser cette liste)
|
| 159 |
+
dossiers_a_exclure = [
|
| 160 |
+
"node_modules", "venv", ".venv", "__pycache__", ".git", ".vscode",
|
| 161 |
+
".idea", "dist", "build", "target", "docs", "coverage", "logs",
|
| 162 |
+
"temp", "tmp", "env",
|
| 163 |
+
# Ajoutez "cesium" ou tout autre dossier spécifique à "autocaption-hf-space" que vous souhaitez exclure
|
| 164 |
+
# "nom_dossier_specifique_a_exclure_dans_autocaption"
|
| 165 |
+
]
|
| 166 |
+
|
| 167 |
+
# Fichiers spécifiques à exclure (vous pouvez personnaliser cette liste)
|
| 168 |
+
fichiers_a_exclure = [
|
| 169 |
+
"package-lock.json", "yarn.lock", "composer.lock", ".env",
|
| 170 |
+
".DS_Store", "Thumbs.db", "npm-debug.log", "yarn-debug.log",
|
| 171 |
+
"yarn-error.log"
|
| 172 |
+
]
|
| 173 |
+
|
| 174 |
+
# Extensions de fichiers à exclure (vous pouvez personnaliser cette liste)
|
| 175 |
+
extensions_a_exclure = [
|
| 176 |
+
".pyc", ".pyo", ".o", ".so", ".dll", ".exe", ".jar", ".war", ".ear",
|
| 177 |
+
".class", ".zip", ".tar", ".gz", ".rar", ".7z", ".pdf", ".doc",
|
| 178 |
+
".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp",
|
| 179 |
+
".img", ".iso", ".dmg", ".mp3", ".mp4", ".avi", ".mov", ".wav",
|
| 180 |
+
".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".lock",
|
| 181 |
+
# ".xml" # Décommentez si vous souhaitez exclure les fichiers XML, sinon laissez commenté
|
| 182 |
+
]
|
| 183 |
+
# --- Fin de la configuration ---
|
| 184 |
+
|
| 185 |
+
if not os.path.isdir(chemin_projet_cible):
|
| 186 |
+
print(f"ERREUR : Le dossier du projet '{chemin_projet_cible}' n'a pas été trouvé.")
|
| 187 |
+
print("Veuillez vérifier le chemin et la configuration 'nom_dossier_projet' et 'chemin_base_parent_projet' dans le script.")
|
| 188 |
+
else:
|
| 189 |
+
# Les fichiers de sortie seront sauvegardés dans le dossier "Documents" (le parent de votre projet)
|
| 190 |
+
dossier_de_sortie = chemin_base_parent_projet # Ou os.path.dirname(chemin_projet_cible)
|
| 191 |
+
|
| 192 |
+
# 1. Document pour le dossier SRC uniquement (si ce sous-dossier existe dans autocaption-hf-space)
|
| 193 |
+
nom_fichier_src = f"structure_et_contenu_{nom_dossier_projet}_SRC.txt"
|
| 194 |
+
fichier_sortie_src = os.path.join(dossier_de_sortie, nom_fichier_src)
|
| 195 |
+
if os.path.isdir(chemin_src):
|
| 196 |
+
generer_document_unique(
|
| 197 |
+
nom_section_document=f"Dossier SRC de {nom_dossier_projet}",
|
| 198 |
+
chemin_a_scanner=chemin_src,
|
| 199 |
+
chemin_base_relativisation=chemin_src, # Chemins relatifs à la racine de SRC
|
| 200 |
+
fichier_sortie=fichier_sortie_src,
|
| 201 |
+
exclusions_dossiers=dossiers_a_exclure,
|
| 202 |
+
exclusions_fichiers=fichiers_a_exclure,
|
| 203 |
+
exclusions_extensions=extensions_a_exclure
|
| 204 |
+
)
|
| 205 |
+
else:
|
| 206 |
+
print(f"INFO : Sous-dossier SRC '{chemin_src}' non trouvé dans '{nom_dossier_projet}'. Le fichier '{nom_fichier_src}' ne sera pas généré.")
|
| 207 |
+
|
| 208 |
+
# 2. Document pour le dossier BACKEND uniquement (si ce sous-dossier existe dans autocaption-hf-space)
|
| 209 |
+
nom_fichier_backend = f"structure_et_contenu_{nom_dossier_projet}_BACKEND.txt"
|
| 210 |
+
fichier_sortie_backend = os.path.join(dossier_de_sortie, nom_fichier_backend)
|
| 211 |
+
if os.path.isdir(chemin_backend):
|
| 212 |
+
generer_document_unique(
|
| 213 |
+
nom_section_document=f"Dossier BACKEND de {nom_dossier_projet}",
|
| 214 |
+
chemin_a_scanner=chemin_backend,
|
| 215 |
+
chemin_base_relativisation=chemin_backend, # Chemins relatifs à la racine de BACKEND
|
| 216 |
+
fichier_sortie=fichier_sortie_backend,
|
| 217 |
+
exclusions_dossiers=dossiers_a_exclure,
|
| 218 |
+
exclusions_fichiers=fichiers_a_exclure,
|
| 219 |
+
exclusions_extensions=extensions_a_exclure
|
| 220 |
+
)
|
| 221 |
+
else:
|
| 222 |
+
print(f"INFO : Sous-dossier BACKEND '{chemin_backend}' non trouvé dans '{nom_dossier_projet}'. Le fichier '{nom_fichier_backend}' ne sera pas généré.")
|
| 223 |
+
|
| 224 |
+
# 3. Document combinant SRC et Backend (si ces sous-dossiers existent)
|
| 225 |
+
nom_fichier_combine = f"structure_et_contenu_{nom_dossier_projet}_SRC_ET_BACKEND.txt"
|
| 226 |
+
fichier_sortie_combine = os.path.join(dossier_de_sortie, nom_fichier_combine)
|
| 227 |
+
dossiers_a_combiner = []
|
| 228 |
+
if os.path.isdir(chemin_src):
|
| 229 |
+
dossiers_a_combiner.append((f"SRC ({nom_dossier_projet})", chemin_src))
|
| 230 |
+
else:
|
| 231 |
+
print(f"INFO : Sous-dossier SRC '{chemin_src}' non trouvé. Ne sera pas inclus dans le fichier combiné '{nom_fichier_combine}'.")
|
| 232 |
+
if os.path.isdir(chemin_backend):
|
| 233 |
+
dossiers_a_combiner.append((f"BACKEND ({nom_dossier_projet})", chemin_backend))
|
| 234 |
+
else:
|
| 235 |
+
print(f"INFO : Sous-dossier BACKEND '{chemin_backend}' non trouvé. Ne sera pas inclus dans le fichier combiné '{nom_fichier_combine}'.")
|
| 236 |
+
|
| 237 |
+
if dossiers_a_combiner:
|
| 238 |
+
generer_document_combine(
|
| 239 |
+
titre_global_document=f"Combinaison des dossiers SRC et BACKEND de {nom_dossier_projet}",
|
| 240 |
+
liste_dossiers_sources_avec_noms=dossiers_a_combiner,
|
| 241 |
+
chemin_base_relativisation_global=chemin_projet_cible, # Chemins relatifs à la racine du projet
|
| 242 |
+
fichier_sortie=fichier_sortie_combine,
|
| 243 |
+
exclusions_dossiers=dossiers_a_exclure,
|
| 244 |
+
exclusions_fichiers=fichiers_a_exclure,
|
| 245 |
+
exclusions_extensions=extensions_a_exclure
|
| 246 |
+
)
|
| 247 |
+
elif not os.path.isdir(chemin_src) and not os.path.isdir(chemin_backend) and (os.path.exists(fichier_sortie_combine) or (not os.path.exists(fichier_sortie_combine) and (os.path.isdir(chemin_src) or os.path.isdir(chemin_backend)))): # Condition pour éviter message si aucun des deux n'est attendu
|
| 248 |
+
print(f"INFO : Ni le sous-dossier SRC ni le sous-dossier BACKEND n'ont été trouvés pour {nom_dossier_projet}. Le fichier combiné '{nom_fichier_combine}' ne sera pas généré.")
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
# 4. Document pour le projet complet "autocaption-hf-space"
|
| 252 |
+
nom_fichier_complet = f"structure_et_contenu_{nom_dossier_projet}_COMPLET.txt"
|
| 253 |
+
fichier_sortie_complet = os.path.join(dossier_de_sortie, nom_fichier_complet)
|
| 254 |
+
generer_document_unique(
|
| 255 |
+
nom_section_document=f"Projet Complet : {nom_dossier_projet}",
|
| 256 |
+
chemin_a_scanner=chemin_projet_cible,
|
| 257 |
+
chemin_base_relativisation=chemin_projet_cible, # Chemins relatifs à la racine du projet
|
| 258 |
+
fichier_sortie=fichier_sortie_complet,
|
| 259 |
+
exclusions_dossiers=dossiers_a_exclure,
|
| 260 |
+
exclusions_fichiers=fichiers_a_exclure,
|
| 261 |
+
exclusions_extensions=extensions_a_exclure
|
| 262 |
+
)
|