mozed54 commited on
Commit
83eae80
Β·
verified Β·
1 Parent(s): 8c6d4cf

Upload app(1).py

Browse files
Files changed (1) hide show
  1. app(1).py +253 -0
app(1).py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import os
4
+ import numpy as np
5
+ import soundfile as sf
6
+ import tempfile
7
+ import json
8
+ import uuid
9
+ from datetime import datetime
10
+ from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
11
+ from peft import PeftModel
12
+ from huggingface_hub import snapshot_download, HfApi, hf_hub_download
13
+ import shutil
14
+
15
+ # ── Config ────────────────────────────────────────────────────────
16
+ BASE_MODEL = "openai/whisper-large-v3-turbo"
17
+ ADAPTER_REPO = "mozed54/whisper-finetuned"
18
+ FEEDBACK_REPO = "mozed54/whisper-feedback"
19
+ HF_TOKEN = os.environ.get("HF_TOKEN")
20
+
21
+ # ── Chargement modΓ¨le ─────────────────────────────────────────────
22
+ device = "cuda" if torch.cuda.is_available() else "cpu"
23
+ torch_dtype = torch.float16 if device == "cuda" else torch.float32
24
+
25
+ print(f"Device : {device.upper()}")
26
+ print("Chargement du modele de base...")
27
+
28
+ base_model = AutoModelForSpeechSeq2Seq.from_pretrained(
29
+ BASE_MODEL, torch_dtype=torch_dtype, use_safetensors=True,
30
+ )
31
+
32
+ print("Chargement adapter LoRA...")
33
+ adapter_path = snapshot_download(
34
+ repo_id=ADAPTER_REPO,
35
+ repo_type="dataset",
36
+ token=HF_TOKEN if HF_TOKEN else None,
37
+ )
38
+ model = PeftModel.from_pretrained(base_model, adapter_path)
39
+ model = model.merge_and_unload()
40
+ model.to(device)
41
+
42
+ processor = AutoProcessor.from_pretrained(BASE_MODEL)
43
+
44
+ pipe = pipeline(
45
+ "automatic-speech-recognition",
46
+ model=model,
47
+ tokenizer=processor.tokenizer,
48
+ feature_extractor=processor.feature_extractor,
49
+ torch_dtype=torch_dtype,
50
+ device=device,
51
+ generate_kwargs={"language": "arabic", "task": "transcribe"},
52
+ )
53
+
54
+ print("Modele pret !")
55
+
56
+ # ── Γ‰tat global ───────────────────────────────────────────────────
57
+ current_audio = {"path": None, "filename": None}
58
+
59
+ # ── Fonctions ─────────────────────────────────────────────────────
60
+ def generate_audio_name():
61
+ """ex: 2026-05-20_10-30-45_a3f9b2.wav"""
62
+ datetime_str = datetime.utcnow().strftime("%Y-%m-%d_%H-%M-%S")
63
+ uid = uuid.uuid4().hex[:6]
64
+ return f"{datetime_str}_{uid}.wav"
65
+
66
+
67
+ def process_audio(audio):
68
+ if audio is None:
69
+ return None
70
+ if isinstance(audio, tuple):
71
+ sample_rate, data = audio
72
+ if data.ndim > 1:
73
+ data = data.mean(axis=1)
74
+ data = data.astype(np.float32)
75
+ if np.abs(data).max() > 1.0:
76
+ data = data / 32768.0
77
+ tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
78
+ sf.write(tmp.name, data, sample_rate)
79
+ return tmp.name
80
+ return audio
81
+
82
+
83
+ def transcribe(audio):
84
+ path = process_audio(audio)
85
+ if path is None:
86
+ return "Aucun audio recu.", gr.update(visible=False)
87
+ try:
88
+ result = pipe(path, return_timestamps=False)
89
+ text = result["text"].strip()
90
+ # Sauvegarde le chemin et génère le nom du fichier audio
91
+ current_audio["path"] = path
92
+ current_audio["filename"] = generate_audio_name()
93
+ return text, gr.update(visible=True)
94
+ except Exception as e:
95
+ return f"Erreur : {str(e)}", gr.update(visible=False)
96
+
97
+
98
+ def save_feedback(transcription, note):
99
+ if not transcription or transcription.strip() == "":
100
+ return "⚠️ Aucune transcription a evaluer."
101
+ if note is None:
102
+ return "⚠️ Choisis une note avant de valider."
103
+
104
+ try:
105
+ api = HfApi(token=HF_TOKEN)
106
+
107
+ # CrΓ©e le repo feedback s'il n'existe pas
108
+ api.create_repo(
109
+ repo_id=FEEDBACK_REPO,
110
+ repo_type="dataset",
111
+ private=True,
112
+ exist_ok=True,
113
+ )
114
+
115
+ timestamp = datetime.utcnow().isoformat()
116
+ audio_name = current_audio["filename"] or "inconnu.wav"
117
+ audio_repo_path = f"audios/{audio_name}"
118
+
119
+ # EntrΓ©e JSONL
120
+ entry = {
121
+ "timestamp": timestamp,
122
+ "audio_file": audio_name,
123
+ "transcription": transcription,
124
+ "note": int(note),
125
+ }
126
+
127
+ # Charge le feedback.jsonl existant ou crΓ©e-en un
128
+ feedback_file = "/tmp/feedback.jsonl"
129
+ try:
130
+ existing = hf_hub_download(
131
+ repo_id=FEEDBACK_REPO,
132
+ filename="feedback.jsonl",
133
+ repo_type="dataset",
134
+ token=HF_TOKEN,
135
+ )
136
+ shutil.copy(existing, feedback_file)
137
+ except Exception:
138
+ pass
139
+
140
+ # Ajoute la ligne
141
+ with open(feedback_file, "a", encoding="utf-8") as f:
142
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
143
+
144
+ # Upload feedback.jsonl
145
+ api.upload_file(
146
+ path_or_fileobj=feedback_file,
147
+ path_in_repo="feedback.jsonl",
148
+ repo_id=FEEDBACK_REPO,
149
+ repo_type="dataset",
150
+ token=HF_TOKEN,
151
+ )
152
+
153
+ # Upload fichier audio dans audios/
154
+ if current_audio["path"] and os.path.exists(current_audio["path"]):
155
+ api.upload_file(
156
+ path_or_fileobj=current_audio["path"],
157
+ path_in_repo=audio_repo_path,
158
+ repo_id=FEEDBACK_REPO,
159
+ repo_type="dataset",
160
+ token=HF_TOKEN,
161
+ )
162
+
163
+ stars = "⭐" * int(note)
164
+ return f"βœ… Feedback enregistre β€” {audio_name} β€” Note : {stars}"
165
+
166
+ except Exception as e:
167
+ return f"❌ Erreur sauvegarde : {str(e)}"
168
+
169
+
170
+ # ── CSS ───────────────────────────────────────────────────────────
171
+ css = """
172
+ @import url("https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&display=swap");
173
+ body, .gradio-container { font-family: "DM Sans", sans-serif !important; background: #0f0f13 !important; }
174
+ .gradio-container { max-width: 860px !important; margin: 0 auto !important; padding: 2rem 1.5rem !important; }
175
+ #app-header { text-align: center; margin-bottom: 2rem; padding: 2rem 0 1.5rem; border-bottom: 1px solid #1e1e2a; }
176
+ #app-header h1 { font-size: 2rem; font-weight: 600; color: #f0f0f5; margin: 0 0 0.4rem; }
177
+ #app-header p { font-size: 0.9rem; color: #6b6b80; margin: 0; }
178
+ .section-label { font-size: 0.72rem; font-weight: 600; color: #4a4a60; letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: 0.6rem; }
179
+ button.primary { background: #5a5aff !important; color: #fff !important; border: none !important; border-radius: 10px !important; font-weight: 500 !important; padding: 0.7rem 1.4rem !important; transition: background 0.2s !important; }
180
+ button.primary:hover { background: #4848e0 !important; }
181
+ button.secondary { background: #1e1e2a !important; color: #a0a0b8 !important; border: 1px solid #2a2a38 !important; border-radius: 10px !important; padding: 0.7rem 1.4rem !important; }
182
+ #transcription-box textarea { background: #0f0f13 !important; border: 1.5px solid #1e1e2a !important; border-radius: 14px !important; color: #e0e0ec !important; font-size: 1rem !important; line-height: 1.7 !important; padding: 1.2rem !important; direction: rtl !important; text-align: right !important; transition: border-color 0.4s, box-shadow 0.4s !important; min-height: 160px !important; }
183
+ #transcription-box.has-text textarea { border-color: #22c97a !important; box-shadow: 0 0 0 3px rgba(34,201,122,0.12) !important; }
184
+ #feedback-box { background: #16161f; border: 1px solid #1e1e2a; border-radius: 14px; padding: 1.2rem 1.5rem; margin-top: 1rem; }
185
+ footer { display: none !important; }
186
+ """
187
+
188
+ js = """
189
+ function() {
190
+ const observer = new MutationObserver(() => {
191
+ const box = document.querySelector("#transcription-box textarea");
192
+ const wrapper = document.querySelector("#transcription-box");
193
+ if (!box || !wrapper) return;
194
+ if (box.value && box.value.trim().length > 0) { wrapper.classList.add("has-text"); }
195
+ else { wrapper.classList.remove("has-text"); }
196
+ });
197
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true });
198
+ return [];
199
+ }
200
+ """
201
+
202
+ # ── Interface ─────────────────────────────────────────────────────
203
+ with gr.Blocks(title="Whisper Darija", css=css, js=js) as demo:
204
+
205
+ gr.HTML('<div id="app-header"><h1>πŸŽ™ Whisper Β· Darija</h1><p>Transcription automatique β€” dialecte algerien</p></div>')
206
+
207
+ gr.HTML('<div class="section-label">Source audio</div>')
208
+ with gr.Row():
209
+ with gr.Column(scale=1):
210
+ mic_input = gr.Audio(
211
+ sources=["microphone"],
212
+ type="numpy",
213
+ label="🎀 Enregistrer ma voix",
214
+ )
215
+ mic_btn = gr.Button("Transcrire la voix", variant="primary")
216
+
217
+ with gr.Column(scale=1):
218
+ file_input = gr.Audio(
219
+ sources=["upload"],
220
+ type="numpy",
221
+ label="πŸ“‚ Charger un fichier audio",
222
+ )
223
+ file_btn = gr.Button("Transcrire le fichier", variant="secondary")
224
+
225
+ gr.HTML("<hr style='border:none;border-top:1px solid #1e1e2a;margin:1.5rem 0;'>")
226
+ gr.HTML('<div class="section-label">Transcription</div>')
227
+
228
+ output = gr.Textbox(
229
+ label="",
230
+ lines=6,
231
+ placeholder="Ψ§Ω„Ω†Ψ΅ Ψ³ΩŠΨΈΩ‡Ψ± Ω‡Ω†Ψ§",
232
+ interactive=False,
233
+ elem_id="transcription-box",
234
+ show_label=False,
235
+ )
236
+
237
+ # ── Feedback β€” cachΓ© jusqu'Γ  la premiΓ¨re transcription ────────
238
+ with gr.Column(visible=False, elem_id="feedback-box") as feedback_block:
239
+ gr.HTML('<div class="section-label" style="margin-top:0;">Evaluer la transcription</div>')
240
+ note_input = gr.Radio(
241
+ choices=["1", "2", "3", "4", "5"],
242
+ label="",
243
+ info="1 = tres mauvais Β· 5 = parfait",
244
+ show_label=False,
245
+ )
246
+ feedback_btn = gr.Button("Envoyer le feedback", variant="secondary")
247
+ feedback_status = gr.Textbox(label="", show_label=False, interactive=False, lines=1)
248
+
249
+ mic_btn.click(fn=transcribe, inputs=[mic_input], outputs=[output, feedback_block])
250
+ file_btn.click(fn=transcribe, inputs=[file_input], outputs=[output, feedback_block])
251
+ feedback_btn.click(fn=save_feedback, inputs=[output, note_input], outputs=[feedback_status])
252
+
253
+ demo.launch()