vic3610 commited on
Commit
f98bbe8
·
verified ·
1 Parent(s): 45a56af

Upload 2 files

Browse files
Files changed (2) hide show
  1. portable_env.py +87 -0
  2. transcribe_audio.py +211 -0
portable_env.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Bootstrap environnement portable pour BOB.
4
+ Force l'utilisation des ressources locales (ffmpeg, modèles, caches) et
5
+ offre un point unique pour configurer les variables d'environnement.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from pathlib import Path
12
+
13
+
14
+ def _prepend_path(dir_path: Path) -> None:
15
+ p = str(dir_path)
16
+ current = os.environ.get("PATH", "")
17
+ if p and p not in current:
18
+ os.environ["PATH"] = p + (";" if current else "") + current
19
+
20
+
21
+ def setup_portable_env(base_dir: Path | None = None, force_ollama_portable: bool | None = None) -> Path:
22
+ """
23
+ Configure l'environnement pour n'utiliser que le dossier courant.
24
+
25
+ - Ajoute vendor/ffmpeg/bin au PATH
26
+ - Fige les caches HF/Transformers/Whisper dans resources/models
27
+ - Définit le répertoire des modèles Ollama local
28
+ - Optionnellement force l'hôte Ollama portable (localhost:11435 par défaut)
29
+
30
+ Retourne base_dir normalisé.
31
+ """
32
+ if base_dir is None:
33
+ # base_dir = racine du projet (.. depuis EXE)
34
+ here = Path(__file__).resolve().parent
35
+ base_dir = (here.parent).resolve()
36
+
37
+ # Expo pour d'autres modules
38
+ os.environ.setdefault("BOB_BASE_DIR", str(base_dir))
39
+
40
+ # 1) FFmpeg local
41
+ ffmpeg_bin = base_dir / "vendor" / "ffmpeg" / "bin"
42
+ if ffmpeg_bin.exists():
43
+ _prepend_path(ffmpeg_bin)
44
+
45
+ # 2) Caches et modèles locaux
46
+ models_root = base_dir / "resources" / "models"
47
+ hf_cache = models_root / "huggingface"
48
+ whisper_cache = models_root / "whisper"
49
+ numba_cache = models_root / "numba_cache"
50
+ for d in (hf_cache, whisper_cache, numba_cache):
51
+ d.mkdir(parents=True, exist_ok=True)
52
+
53
+ # Hugging Face caches
54
+ os.environ.setdefault("HF_HOME", str(hf_cache))
55
+ os.environ.setdefault("TRANSFORMERS_CACHE", str(hf_cache))
56
+ os.environ.setdefault("HUGGINGFACE_HUB_CACHE", str(hf_cache))
57
+ # Whisper cache (openai-whisper regarde XDG_CACHE_HOME/WHISPER_CACHE_DIR)
58
+ os.environ.setdefault("WHISPER_CACHE_DIR", str(whisper_cache))
59
+ os.environ.setdefault("XDG_CACHE_HOME", str(models_root))
60
+ # NumPy/Numba
61
+ os.environ.setdefault("NUMBA_CACHE_DIR", str(numba_cache))
62
+
63
+ # 3) Dossiers d'E/S par défaut
64
+ os.environ.setdefault("BOB_INPUT_DIR", str(base_dir / "input"))
65
+ os.environ.setdefault("BOB_TRANSCRIPTIONS_DIR", str(base_dir / "output" / "transcriptions"))
66
+ os.environ.setdefault("BOB_OUTPUT_FILE", str(base_dir / "output" / "resume_bob.txt"))
67
+
68
+ # 4) Ollama portable (modèles + host)
69
+ ollama_models = base_dir / "resources" / "ollama" / "models"
70
+ ollama_models.mkdir(parents=True, exist_ok=True)
71
+ # OLLAMA_MODELS est reconnu par Ollama pour localiser les modèles
72
+ os.environ.setdefault("OLLAMA_MODELS", str(ollama_models))
73
+
74
+ # Forçage Ollama portable si demandé
75
+ if force_ollama_portable is None:
76
+ force_ollama_portable = os.environ.get("BOB_FORCE_PORTABLE_OLLAMA", "0") == "1"
77
+
78
+ if force_ollama_portable:
79
+ os.environ["BOB_FORCE_PORTABLE_OLLAMA"] = "1"
80
+ # Permettre override externe, sinon 11435
81
+ portable_host = os.environ.get("PORTABLE_OLLAMA_HOST", "http://localhost:11435")
82
+ os.environ["OLLAMA_HOST"] = portable_host
83
+
84
+ return base_dir
85
+
86
+
87
+ __all__ = ["setup_portable_env"]
transcribe_audio.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Script de transcription audio avec Whisper
4
+ Traite tous les fichiers audio du dossier input et génère les transcriptions dans output/transcriptions
5
+ """
6
+
7
+ import os
8
+ import whisper
9
+ from pathlib import Path
10
+ import time
11
+ from datetime import datetime
12
+
13
+ # Bootstrap environnement portable
14
+ try:
15
+ from portable_env import setup_portable_env
16
+ setup_portable_env()
17
+ except Exception:
18
+ pass
19
+ # Ajouter FFmpeg au PATH si nécessaire
20
+ ffmpeg_paths = [
21
+ r"C:\FFmpeg\bin",
22
+ r"C:\Program Files\FFmpeg\bin",
23
+ r"C:\Users\victo\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.0-full_build\bin"
24
+ ]
25
+
26
+ for ffmpeg_path in ffmpeg_paths:
27
+ if os.path.exists(ffmpeg_path) and ffmpeg_path not in os.environ.get("PATH", ""):
28
+ os.environ["PATH"] = ffmpeg_path + ";" + os.environ.get("PATH", "")
29
+ # FFmpeg: déjà géré via portable_env (vendor/ffmpeg/bin)
30
+
31
+ # Configuration
32
+ INPUT_DIR = Path(os.environ.get("BOB_INPUT_DIR", Path(__file__).parent.parent / "input"))
33
+ OUTPUT_DIR = Path(os.environ.get("BOB_TRANSCRIPTIONS_DIR", Path(__file__).parent.parent / "output" / "transcriptions"))
34
+ WHISPER_MODEL = os.environ.get("WHISPER_MODEL", "small") # peut être override par GUI
35
+ SUPPORTED_FORMATS = ['.mp3', '.wav', '.m4a', '.flac', '.ogg', '.mp4', '.avi', '.mov']
36
+
37
+ def load_whisper_model(model_name):
38
+ """Charge un modèle de transcription.
39
+ - 'faster-whisper:small|medium|large-v3|...' utilise faster_whisper.WhisperModel
40
+ - sinon utilise openai whisper.load_model
41
+ Retourne un tuple (backend, model)
42
+ backend in {"openai", "faster"}
43
+ """
44
+ print(f"Chargement du modèle Whisper: {model_name}")
45
+ if isinstance(model_name, str) and model_name.startswith("faster-whisper"):
46
+ # Extraire la taille après le ':'
47
+ size = model_name.split(":", 1)[1] if ":" in model_name else "small"
48
+ try:
49
+ from faster_whisper import WhisperModel
50
+ except Exception as e:
51
+ raise RuntimeError(f"faster-whisper non disponible: {e}")
52
+
53
+ # Le téléchargement ira dans HF_HOME/HUGGINGFACE cache local
54
+ compute_type = "int8" # CPU-friendly par défaut; peut être ajusté
55
+ model = WhisperModel(size, device="cpu", compute_type=compute_type)
56
+ print("Modèle faster-whisper prêt.")
57
+ return ("faster", model)
58
+ else:
59
+ model = whisper.load_model(model_name)
60
+ print("Modèle OpenAI Whisper prêt.")
61
+ return ("openai", model)
62
+
63
+ def get_audio_files(input_dir):
64
+ """Récupère tous les fichiers audio du dossier input"""
65
+ audio_files = []
66
+ if not input_dir.exists():
67
+ return audio_files
68
+ # Récupérer tous les fichiers du dossier
69
+ for file in input_dir.iterdir():
70
+ if file.is_file():
71
+ # Vérifier l'extension (case insensitive)
72
+ file_ext = file.suffix.lower()
73
+ if file_ext in [ext.lower() for ext in SUPPORTED_FORMATS]:
74
+ audio_files.append(file)
75
+
76
+ return sorted(list(set(audio_files))) # Éliminer les doublons
77
+
78
+ def transcribe_file(model, audio_file, output_dir):
79
+ """Transcrit un fichier audio et sauvegarde le résultat"""
80
+ print(f"Transcription de: {audio_file.name}")
81
+ print(f"Chemin complet: {audio_file.absolute()}")
82
+ print(f"Fichier existe: {audio_file.exists()}")
83
+ print(f"Taille du fichier: {audio_file.stat().st_size if audio_file.exists() else 'N/A'} bytes")
84
+
85
+ try:
86
+ # Vérifier que le fichier existe
87
+ if not audio_file.exists():
88
+ print(f"✗ Fichier introuvable: {audio_file}")
89
+ return False
90
+
91
+ # Transcription avec Whisper - utiliser le chemin sans espaces si possible
92
+ audio_path = str(audio_file.absolute())
93
+ print(f"Chemin utilisé pour Whisper: {audio_path}")
94
+
95
+ start_time = time.time()
96
+ # Support des deux backends
97
+ if isinstance(model, tuple) and model and model[0] in ("openai", "faster"):
98
+ backend, engine = model
99
+ else:
100
+ # Rétrocompatibilité: ancien code passait juste l'objet
101
+ backend, engine = ("openai", model)
102
+
103
+ if backend == "openai":
104
+ result = engine.transcribe(audio_path, language="fr", verbose=False)
105
+ text = result["text"]
106
+ else:
107
+ # faster-whisper retourne des segments + info
108
+ segments, info = engine.transcribe(audio_path, language="fr")
109
+ pieces = []
110
+ for seg in segments:
111
+ pieces.append(seg.text)
112
+ text = " ".join(pieces).strip()
113
+ end_time = time.time()
114
+
115
+ # Nom du fichier de sortie
116
+ output_filename = audio_file.stem + "_transcription.txt"
117
+ output_path = output_dir / output_filename
118
+
119
+ # Sauvegarde de la transcription
120
+ with open(output_path, 'w', encoding='utf-8') as f:
121
+ f.write(f"Fichier source: {audio_file.name}\n")
122
+ f.write(f"Date de transcription: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n")
123
+ f.write(f"Durée de traitement: {end_time - start_time:.2f} secondes\n")
124
+ f.write(f"Modèle utilisé: {WHISPER_MODEL}\n")
125
+ f.write("-" * 50 + "\n\n")
126
+ f.write(text)
127
+
128
+ print(f"✓ Transcription sauvegardée: {output_filename}")
129
+ print(f" Durée de traitement: {end_time - start_time:.2f}s")
130
+
131
+ return True
132
+
133
+ except Exception as e:
134
+ print(f"✗ Erreur lors de la transcription de {audio_file.name}: {e}")
135
+ print(f"Type d'erreur: {type(e).__name__}")
136
+ import traceback
137
+ traceback.print_exc()
138
+ return False
139
+
140
+ def main():
141
+ """Fonction principale"""
142
+ print("=" * 60)
143
+ print("TRANSCRIPTION AUTOMATIQUE DES BOB")
144
+ print("=" * 60)
145
+
146
+ # Vérification des dossiers - chemins absolus
147
+ script_dir = Path(__file__).parent.absolute()
148
+ input_dir = Path(os.environ.get("BOB_INPUT_DIR", script_dir.parent / "input"))
149
+ output_dir = Path(os.environ.get("BOB_TRANSCRIPTIONS_DIR", script_dir.parent / "output" / "transcriptions"))
150
+
151
+ print(f"Dossier script: {script_dir}")
152
+ print(f"Dossier input: {input_dir}")
153
+ print(f"Dossier output: {output_dir}")
154
+ print()
155
+
156
+ if not input_dir.exists():
157
+ print(f"Erreur: Le dossier input n'existe pas: {input_dir}")
158
+ return
159
+
160
+ # Création du dossier de sortie si nécessaire
161
+ output_dir.mkdir(parents=True, exist_ok=True)
162
+
163
+ # Recherche des fichiers audio
164
+ audio_files = get_audio_files(input_dir)
165
+
166
+ if not audio_files:
167
+ print(f"Aucun fichier audio trouvé dans {input_dir}")
168
+ print(f"Formats supportés: {', '.join(SUPPORTED_FORMATS)}")
169
+ return
170
+
171
+ print(f"Trouvé {len(audio_files)} fichier(s) audio à traiter:")
172
+ for i, file in enumerate(audio_files, 1):
173
+ print(f" {i}. {file.name}")
174
+
175
+ print()
176
+
177
+ # Chargement du modèle Whisper
178
+ try:
179
+ model = load_whisper_model(WHISPER_MODEL)
180
+ except Exception as e:
181
+ print(f"Erreur lors du chargement du modèle: {e}")
182
+ return
183
+
184
+ print()
185
+
186
+ # Traitement des fichiers
187
+ success_count = 0
188
+ total_start_time = time.time()
189
+
190
+ for i, audio_file in enumerate(audio_files, 1):
191
+ print(f"[{i}/{len(audio_files)}] ", end="")
192
+
193
+ if transcribe_file(model, audio_file, output_dir):
194
+ success_count += 1
195
+
196
+ print()
197
+
198
+ total_end_time = time.time()
199
+
200
+ # Résumé
201
+ print("=" * 60)
202
+ print("RÉSUMÉ")
203
+ print("=" * 60)
204
+ print(f"Fichiers traités: {len(audio_files)}")
205
+ print(f"Réussites: {success_count}")
206
+ print(f"Échecs: {len(audio_files) - success_count}")
207
+ print(f"Durée totale: {total_end_time - total_start_time:.2f} secondes")
208
+ print(f"Transcriptions sauvegardées dans: {output_dir}")
209
+
210
+ if __name__ == "__main__":
211
+ main()