Woziii commited on
Commit
a74c3ba
·
verified ·
1 Parent(s): 9e18032

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +249 -0
app.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import zipfile
4
+ from pathlib import Path
5
+
6
+ import gradio as gr
7
+ import torch
8
+ from pydub import AudioSegment
9
+ from transformers import pipeline
10
+
11
+ # ------------------------
12
+ # CONFIG
13
+ # ------------------------
14
+
15
+ MODEL_NAME = "openai/whisper-large-v3"
16
+ device = 0 if torch.cuda.is_available() else "cpu"
17
+
18
+ pipe = pipeline(
19
+ task="automatic-speech-recognition",
20
+ model=MODEL_NAME,
21
+ device=device,
22
+ model_kwargs={"low_cpu_mem_usage": True},
23
+ )
24
+
25
+ TEMP_DIR = "./temp_audio"
26
+ os.makedirs(TEMP_DIR, exist_ok=True)
27
+
28
+ # On stocke la liste des métadonnées (segments) dans un State
29
+ # pour la conserver entre les étapes (transcription, découpe, zip).
30
+ def init_metadata_state():
31
+ return []
32
+
33
+ # ------------------------
34
+ # FONCTIONS
35
+ # ------------------------
36
+
37
+ def transcribe_audio(audio_path):
38
+ """
39
+ Étape 2 : Transcription du fichier audio via Whisper
40
+ + récupération de la transcription brute.
41
+ """
42
+ if not audio_path:
43
+ return "Aucun fichier audio fourni.", [], None
44
+
45
+ # Transcription Whisper
46
+ result = pipe(audio_path, return_timestamps="word")
47
+ text = result["text"]
48
+ chunks = result["chunks"] # liste de { 'timestamp': (start, end), 'text': ... }
49
+
50
+ raw_transcription = " ".join([c["text"] for c in chunks])
51
+
52
+ # Le 3e retour = chemin du fichier audio, qu'on renverra tel quel pour la découpe
53
+ return raw_transcription, [], audio_path
54
+
55
+
56
+ def validate_segments(audio_path, table_data, metadata_state):
57
+ """
58
+ Étape 5 : Découpe de l'audio en fonction des segments
59
+ et mise à jour du State `metadata_state`.
60
+ - `table_data` doit contenir : [ [Texte, Début(s), Fin(s), ID], ... ]
61
+ - Retourne :
62
+ 1) Une liste de chemins (extraits audio) pour les players
63
+ 2) La liste des nouvelles métadonnées (mise à jour).
64
+ """
65
+ if not audio_path:
66
+ return ["Aucun fichier audio..."], metadata_state
67
+
68
+ # Nettoyage du dossier temporaire avant recréation des extraits
69
+ if os.path.exists(TEMP_DIR):
70
+ shutil.rmtree(TEMP_DIR)
71
+ os.makedirs(TEMP_DIR, exist_ok=True)
72
+
73
+ original_audio = AudioSegment.from_file(audio_path)
74
+ segment_paths = []
75
+ updated_metadata = []
76
+
77
+ for i, row in enumerate(table_data):
78
+ # row = [ segment_text, start_time, end_time, seg_id ]
79
+ if len(row) < 4:
80
+ continue
81
+
82
+ seg_text, start_time, end_time, seg_id = row
83
+ if not seg_text or start_time is None or end_time is None:
84
+ continue
85
+
86
+ # Si l'utilisateur n'a pas mis d'ID, en créer un
87
+ if not seg_id:
88
+ seg_id = f"seg_{i+1:02d}"
89
+
90
+ # Découpe
91
+ start_ms = int(float(start_time) * 1000)
92
+ end_ms = int(float(end_time) * 1000)
93
+ extract = original_audio[start_ms:end_ms]
94
+
95
+ # Nom de fichier
96
+ stem_name = Path(audio_path).stem
97
+ segment_filename = f"{stem_name}_{seg_id}.wav"
98
+ segment_filepath = os.path.join(TEMP_DIR, segment_filename)
99
+ extract.export(segment_filepath, format="wav")
100
+
101
+ segment_paths.append(segment_filepath)
102
+
103
+ # Stocker la métadonnée
104
+ updated_metadata.append({
105
+ "audio_file": segment_filename,
106
+ "text": seg_text,
107
+ "start_time": start_time,
108
+ "end_time": end_time,
109
+ "id": seg_id,
110
+ })
111
+
112
+ return segment_paths, updated_metadata
113
+
114
+
115
+ def generate_zip(metadata_state):
116
+ """
117
+ Étape 8 : Générer un ZIP contenant tous les extraits + un metadata.csv
118
+ Retourne le chemin vers le ZIP final.
119
+ """
120
+ if not metadata_state:
121
+ return None
122
+
123
+ # Supprimer un ancien zip si présent
124
+ zip_path = os.path.join(TEMP_DIR, "dataset.zip")
125
+ if os.path.exists(zip_path):
126
+ os.remove(zip_path)
127
+
128
+ # Créer metadata.csv
129
+ metadata_csv_path = os.path.join(TEMP_DIR, "metadata.csv")
130
+ with open(metadata_csv_path, "w", encoding="utf-8") as f:
131
+ f.write("audio_file|text|speaker_name|API\n")
132
+ for seg in metadata_state:
133
+ # Ajuste speaker_name ou API selon ton besoin
134
+ line = f"{seg['audio_file']}|{seg['text']}|projectname|/API_PHONETIC/\n"
135
+ f.write(line)
136
+
137
+ # Créer le ZIP
138
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
139
+ # Ajouter chaque extrait
140
+ for seg in metadata_state:
141
+ seg_file = os.path.join(TEMP_DIR, seg["audio_file"])
142
+ if os.path.exists(seg_file):
143
+ zf.write(seg_file, seg["audio_file"])
144
+
145
+ # Ajouter le metadata.csv
146
+ zf.write(metadata_csv_path, "metadata.csv")
147
+
148
+ return zip_path
149
+
150
+
151
+ def distribute_segments_to_players(segments):
152
+ """
153
+ Transforme la liste de segments en un tuple de 20 valeurs max
154
+ (pour 20 players).
155
+ Si moins de 20 segments, on complète avec None.
156
+ """
157
+ max_players = 20
158
+ result = []
159
+ for i in range(max_players):
160
+ if i < len(segments):
161
+ result.append(segments[i])
162
+ else:
163
+ result.append(None)
164
+ return tuple(result)
165
+
166
+
167
+ # ------------------------
168
+ # CONSTRUCTION UI GRADIO
169
+ # ------------------------
170
+ with gr.Blocks(css="style.css") as demo:
171
+
172
+ gr.Markdown("# Application de Découpage Audio + Transcription (jusqu'à 20 extraits)")
173
+ metadata_state = gr.State(init_metadata_state())
174
+
175
+ # Étape 1 : Chargement de l'audio
176
+ with gr.Box():
177
+ gr.Markdown("### 1. Téléversez votre fichier audio")
178
+ audio_input = gr.Audio(source="upload", type="filepath", label="Fichier audio")
179
+
180
+ # Étape 3 : Transcription brute
181
+ raw_transcription = gr.Textbox(
182
+ label="Transcription brute (Whisper)",
183
+ placeholder="Le texte s'affichera ici après la transcription...",
184
+ interactive=False
185
+ )
186
+
187
+ # Étape 4 : Tableau pour 20 segments max
188
+ gr.Markdown("### 2. Définissez jusqu'à 20 segments")
189
+ gr.Markdown("""**Colonnes :**
190
+ 1) Texte (phrase ou portion copiée depuis la transcription)
191
+ 2) Début (en secondes)
192
+ 3) Fin (en secondes)
193
+ 4) ID segment (optionnel)""")
194
+
195
+ table = gr.Dataframe(
196
+ headers=["Texte", "Début (s)", "Fin (s)", "ID"],
197
+ datatype=["str", "number", "number", "str"],
198
+ row_count=20, # <-- 20 lignes
199
+ col_count=4
200
+ )
201
+
202
+ validate_button = gr.Button("Valider et générer les extraits")
203
+
204
+ # Étape 6 : 20 players audio pour l'écoute
205
+ # On les organise en 5 rangées de 4 players
206
+ players = []
207
+ for i in range(20):
208
+ players.append(gr.Audio(label=f"Extrait {i+1}", interactive=False))
209
+
210
+ # Groupons-les en blocs de 4
211
+ for i in range(0, 20, 4):
212
+ with gr.Row():
213
+ for j in range(i, i+4):
214
+ players[j]
215
+
216
+ # Étape 8 : Génération ZIP
217
+ generate_button = gr.Button("Générer le fichier ZIP")
218
+ zip_file = gr.File(label="Télécharger le ZIP (audios + metadata.csv)")
219
+
220
+ # 1) Callback quand on charge l'audio => Transcription
221
+ audio_input.change(
222
+ fn=transcribe_audio,
223
+ inputs=audio_input,
224
+ outputs=[raw_transcription, table, audio_input]
225
+ )
226
+
227
+ # 2) Callback quand on valide => Découpe audio + maj metadata
228
+ validate_button.click(
229
+ fn=validate_segments,
230
+ inputs=[audio_input, table, metadata_state],
231
+ outputs=[ # 1) chemins extraits (list) 2) metadata (list)
232
+ players, # Les 20 players
233
+ metadata_state
234
+ ],
235
+ # On va mapper la liste de segments sur 20 players
236
+ ).then(
237
+ fn=distribute_segments_to_players,
238
+ inputs=None, # la sortie "players" (chemins) est déjà captée
239
+ outputs=players
240
+ )
241
+
242
+ # 3) Génération ZIP
243
+ generate_button.click(
244
+ fn=generate_zip,
245
+ inputs=metadata_state,
246
+ outputs=zip_file
247
+ )
248
+
249
+ demo.queue().launch()