IKRAMELHADI commited on
Commit
e137539
·
1 Parent(s): 4ed7c05
app.py CHANGED
@@ -1,24 +1,22 @@
1
- import os
2
- import tempfile
3
- import numpy as np
4
- import pandas as pd
5
  import gradio as gr
6
-
 
7
  import joblib
8
- import soundfile as sf
9
- from pydub import AudioSegment
10
- import opensmile
11
-
12
- import freesound
13
  import xgboost as xgb
 
 
 
 
 
 
 
14
 
15
 
16
  # =========================
17
- # RÈGLES DURÉE
18
  # =========================
19
  MIN_EFFECT, MAX_EFFECT = 0.5, 3.0
20
  MIN_MUSIC, MAX_MUSIC = 10.0, 60.0
21
- SR_TARGET = 16000
22
 
23
 
24
  # =========================
@@ -35,45 +33,21 @@ CSS = """
35
  border-color: #fca5a5;
36
  background: #fff1f2;
37
  }
38
- .card-title{
39
- font-weight: 950;
40
- margin-bottom: 8px;
41
- }
42
- .badges{
43
- display:flex;
44
- gap:10px;
45
- flex-wrap:wrap;
46
- margin-bottom:12px;
47
- }
48
  .badge{
49
- padding:6px 10px;
50
- border-radius:999px;
51
- font-weight:900;
52
- font-size: 13px;
53
  border: 1px solid #e5e7eb;
54
  }
55
  .badge-type{ background:#eef2ff; color:#3730a3;}
56
  .badge-time{ background:#ecfeff; color:#155e75;}
57
 
58
- .grid{
59
- display:grid;
60
- grid-template-columns: 1fr;
61
- gap:10px;
62
- }
63
- .box{
64
- border:1px solid #e5e7eb;
65
- border-radius:14px;
66
- padding:12px;
67
- background:#fafafa;
68
- }
69
  .box-title{ font-weight:900; margin-bottom:4px; }
70
  .box-value{ font-size:18px; font-weight:800; }
71
 
72
- .hint{
73
- margin-top:10px;
74
- color:#6b7280;
75
- font-size:12px;
76
- }
77
 
78
  #header-title { font-size: 28px; font-weight: 950; margin-bottom: 6px; }
79
  #header-sub { color:#6b7280; margin-top:0px; line-height:1.45; }
@@ -118,68 +92,50 @@ def html_result(badge_text, duration, rating_text, downloads_text, extra_html=""
118
 
119
 
120
  # =========================
121
- # INTERPRETATION (COMMUNE)
122
  # =========================
123
  def interpret_results(avg_class: int, dl_class: int) -> str:
124
- """
125
- avg_class: 0=Missed info, 1=Low, 2=Medium, 3=High
126
- dl_class: 0=Low, 1=Medium, 2=High
127
- """
128
  if avg_class == 0:
129
  return (
130
  "ℹ️ <b>Interprétation</b> :<br>"
131
- "Aucune évaluation possible (rating manquant).<br>"
132
-
133
  )
134
 
135
  rating_txt = {1: "faible", 2: "moyenne", 3: "élevée"}.get(avg_class, "inconnue")
136
  downloads_txt = {0: "faible", 1: "modérée", 2: "élevée"}.get(dl_class, "inconnue")
137
 
138
  if avg_class == 3 and dl_class == 2:
139
- potentiel = "très fort"
140
- detail = "contenu de haute qualité et très populaire."
141
  elif avg_class == 3 and dl_class == 1:
142
- potentiel = "fort"
143
- detail = "contenu bien apprécié, en croissance."
144
  elif avg_class == 3 and dl_class == 0:
145
- potentiel = "prometteur"
146
- detail = "bonne qualité mais faible visibilité (peut gagner en popularité)."
147
  elif avg_class == 2 and dl_class == 2:
148
- potentiel = "modéré à fort"
149
- detail = "populaire mais qualité perçue moyenne."
150
  elif avg_class == 2 and dl_class == 1:
151
- potentiel = "modéré"
152
- detail = "profil standard, popularité stable."
153
  elif avg_class == 2 and dl_class == 0:
154
- potentiel = "limité"
155
- detail = "engagement faible, diffusion limitée."
156
  elif avg_class == 1 and dl_class == 2:
157
- potentiel = "contradictoire"
158
- detail = "très téléchargé mais peu apprécié (usage pratique possible)."
159
  elif avg_class == 1 and dl_class == 1:
160
- potentiel = "faible"
161
- detail = "peu attractif pour les utilisateurs."
162
  else:
163
- potentiel = "très faible"
164
- detail = "faible intérêt global."
165
 
166
  return (
167
- "<b>Interprétation</b> :<br>"
168
- f"Potentiel estimé : <b>{potentiel}</b> — {detail}"
 
 
169
  )
170
 
171
 
172
  def avg_label_to_class(avg_label: str) -> int:
173
- """
174
- Convertit un label texte (LabelEncoder) en classe 0..3 :
175
- 0=Missed info, 1=Low, 2=Medium, 3=High
176
- Robuste aux variantes.
177
- """
178
  if avg_label is None:
179
  return 0
180
-
181
  s = str(avg_label).strip().lower()
182
-
183
  if "miss" in s or "missing" in s or "none" in s or "no" in s:
184
  return 0
185
  if "high" in s or "élev" in s or "eleve" in s:
@@ -191,168 +147,16 @@ def avg_label_to_class(avg_label: str) -> int:
191
  return 0
192
 
193
 
194
- # ============================================================
195
- # PARTIE A — Upload audio → openSMILE → modèles (toi)
196
- # ============================================================
197
-
198
- MODEL_EFFECT = joblib.load("xgb_model_EffectSound.pkl")
199
- MODEL_MUSIC = joblib.load("xgb_model_Music.pkl")
200
-
201
- RATING_DISPLAY_AUDIO = {
202
- 0: "❌ Informations manquantes",
203
- 1: "⭐ Faible",
204
- 2: "⭐⭐ Moyen",
205
- 3: "⭐⭐⭐ Élevé",
206
- }
207
- DOWNLOADS_DISPLAY_AUDIO = {
208
- 0: "⭐ Faible",
209
- 1: "⭐⭐ Moyen",
210
- 2: "⭐⭐⭐ Élevé",
211
- }
212
-
213
- SMILE = opensmile.Smile(
214
- feature_set=opensmile.FeatureSet.eGeMAPSv02,
215
- feature_level=opensmile.FeatureLevel.Functionals,
216
- )
217
-
218
-
219
- def get_duration_seconds(filepath):
220
- ext = os.path.splitext(filepath)[1].lower()
221
- if ext == ".mp3":
222
- audio = AudioSegment.from_file(filepath)
223
- return len(audio) / 1000.0
224
- with sf.SoundFile(filepath) as f:
225
- return len(f) / f.samplerate
226
-
227
-
228
- def to_wav_16k_mono(filepath):
229
- ext = os.path.splitext(filepath)[1].lower()
230
- if ext == ".wav":
231
- try:
232
- with sf.SoundFile(filepath) as f:
233
- if f.samplerate == SR_TARGET and f.channels == 1:
234
- return filepath
235
- except Exception:
236
- pass
237
-
238
- audio = AudioSegment.from_file(filepath)
239
- audio = audio.set_channels(1).set_frame_rate(SR_TARGET)
240
-
241
- tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
242
- tmp.close()
243
- audio.export(tmp.name, format="wav")
244
- return tmp.name
245
-
246
-
247
- def extract_opensmile_features(filepath):
248
- wav_path = to_wav_16k_mono(filepath)
249
- feats = SMILE.process_file(wav_path)
250
- feats = feats.select_dtypes(include=[np.number]).reset_index(drop=True)
251
- return feats
252
-
253
-
254
- def predict_upload_with_dmatrix(model, X_df: pd.DataFrame):
255
- """
256
- Résout 'data did not contain feature names' en passant via Booster + DMatrix(feature_names=...).
257
- Retour: array shape (1, n_outputs)
258
- """
259
- if hasattr(model, "estimators_"):
260
- preds = []
261
- for est in model.estimators_:
262
- booster = est.get_booster() if hasattr(est, "get_booster") else est
263
- dm = xgb.DMatrix(X_df.values, feature_names=list(X_df.columns))
264
- p = booster.predict(dm)
265
- preds.append(np.asarray(p).reshape(-1))
266
- return np.column_stack(preds)
267
-
268
- booster = model.get_booster() if hasattr(model, "get_booster") else model
269
- dm = xgb.DMatrix(X_df.values, feature_names=list(X_df.columns))
270
- p = booster.predict(dm)
271
- return np.asarray(p).reshape(1, -1)
272
-
273
-
274
- def predict_from_uploaded_audio(audio_file):
275
- if audio_file is None:
276
- return html_error("Aucun fichier", "Veuillez importer un fichier audio (wav, mp3, flac…).")
277
-
278
- # Durée
279
- try:
280
- duration = get_duration_seconds(audio_file)
281
- except Exception as e:
282
- return html_error("Audio illisible", f"Impossible de lire l'audio.<br>Détail : <code>{e}</code>")
283
-
284
- # Vérif durées
285
- if duration < MIN_EFFECT:
286
- return html_error(
287
- "Audio trop court",
288
- f"Durée détectée : <b>{duration:.2f} s</b><br><br>"
289
- f"Plages acceptées :<br>"
290
- f"• Effet sonore : <b>{MIN_EFFECT}–{MAX_EFFECT} s</b><br>"
291
- f"• Musique : <b>{MIN_MUSIC}–{MAX_MUSIC} s</b>"
292
- )
293
-
294
- if (MAX_EFFECT < duration < MIN_MUSIC) or duration > MAX_MUSIC:
295
- return html_error(
296
- "Audio hors plage",
297
- f"Durée détectée : <b>{duration:.2f} s</b><br><br>"
298
- f"Plages acceptées :<br>"
299
- f"• Effet sonore : <b>{MIN_EFFECT}–{MAX_EFFECT} s</b><br>"
300
- f"• Musique : <b>{MIN_MUSIC}–{MAX_MUSIC} s</b>"
301
- )
302
-
303
- # Type + modèle
304
- if duration <= MAX_EFFECT:
305
- badge = "🔊 Effet sonore (upload)"
306
- model = MODEL_EFFECT
307
- else:
308
- badge = "🎵 Musique (upload)"
309
- model = MODEL_MUSIC
310
-
311
- # openSMILE
312
- try:
313
- X = extract_opensmile_features(audio_file)
314
- except Exception as e:
315
- return html_error("Extraction openSMILE échouée", f"Détail : <code>{e}</code>")
316
-
317
- # Align features
318
- try:
319
- expected = model.estimators_[0].feature_names_in_ if hasattr(model, "estimators_") else model.feature_names_in_
320
- X = X.reindex(columns=list(expected), fill_value=0)
321
- except Exception as e:
322
- return html_error("Alignement des features échoué", f"Détail : <code>{e}</code>")
323
-
324
- # Predict
325
- try:
326
- y = predict_upload_with_dmatrix(model, X)
327
- except Exception as e:
328
- return html_error("Prédiction échouée", f"Détail : <code>{e}</code>")
329
-
330
- y = np.array(y)
331
- avg_class = int(y[0, 0])
332
- dl_class = int(y[0, 1])
333
-
334
- rating_text = RATING_DISPLAY_AUDIO.get(avg_class, "Inconnu")
335
- downloads_text = DOWNLOADS_DISPLAY_AUDIO.get(dl_class, "Inconnu")
336
-
337
- conclusion = interpret_results(avg_class, dl_class)
338
- extra = f"""
339
- <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">
340
- {conclusion}
341
- </div>
342
- """
343
-
344
- return html_result(badge, duration, rating_text, downloads_text, extra_html=extra)
345
-
346
-
347
- # ============================================================
348
- # PARTIE B — URL FreeSound → API → modèles (collègue)
349
- # ============================================================
350
-
351
- API_TOKEN = "zE9NjEOgUMzH9K7mjiGBaPJiNwJLjSM53LevarRK" # <-- tu remplaces ici
352
 
353
- fs_client = freesound.FreesoundClient()
354
- fs_client.set_token(API_TOKEN, "token")
355
 
 
 
 
356
  # Music
357
  xgb_music_num = joblib.load("xgb_num_downloads_music_model.pkl")
358
  xgb_music_feat_num = joblib.load("xgb_num_downloads_music_features.pkl")
@@ -377,41 +181,40 @@ def safe_float(v):
377
  return 0.0
378
 
379
 
380
- def predict_with_model_fs(model, features_dict, feat_list, label_encoder=None):
381
  row = []
382
  for col in feat_list:
383
- val = features_dict.get(col, 0)
384
  if val is None or isinstance(val, (list, dict)):
385
  val = 0
386
  row.append(safe_float(val))
387
 
388
  X = pd.DataFrame([row], columns=feat_list)
389
  dmatrix = xgb.DMatrix(X.values, feature_names=feat_list)
390
-
391
  pred_int = int(model.get_booster().predict(dmatrix)[0])
392
 
393
- if label_encoder is not None:
394
- return label_encoder.inverse_transform([pred_int])[0]
395
  return pred_int
396
 
397
 
398
- def predict_from_freesound_url(url: str):
399
  if not url or not url.strip():
400
  return html_error("URL vide", "Collez une URL FreeSound du type <code>https://freesound.org/s/123456/</code>")
401
 
402
- # ID
403
  try:
404
  sound_id = int(url.rstrip("/").split("/")[-1])
405
  except Exception:
406
  return html_error("URL invalide", "Impossible d'extraire l'ID depuis l'URL.")
407
 
 
408
  all_features = list(set(
409
  xgb_music_feat_num + xgb_music_feat_avg + xgb_effect_feat_num + xgb_effect_feat_avg
410
  ))
411
  fields = "duration," + ",".join(all_features)
412
 
413
  try:
414
- results = fs_client.search(query="", filter=f"id:{sound_id}", fields=fields)
415
  except Exception as e:
416
  return html_error("Erreur API FreeSound", f"Détail : <code>{e}</code>")
417
 
@@ -421,11 +224,25 @@ def predict_from_freesound_url(url: str):
421
  sound = results.results[0]
422
  duration = safe_float(sound.get("duration", 0))
423
 
424
- # Effect Sound
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  if MIN_EFFECT <= duration <= MAX_EFFECT:
426
- badge = "🔊 Effet sonore (FreeSound URL)"
427
- dl_class = int(predict_with_model_fs(xgb_effect_num, sound, xgb_effect_feat_num))
428
- avg_text = str(predict_with_model_fs(xgb_effect_avg, sound, xgb_effect_feat_avg, le_effect_avg))
429
  dl_text = NUM_DOWNLOADS_MAP.get(dl_class, str(dl_class))
430
 
431
  avg_class = avg_label_to_class(avg_text)
@@ -433,76 +250,47 @@ def predict_from_freesound_url(url: str):
433
 
434
  extra = f"""
435
  <div class="hint">ID FreeSound : <b>{sound_id}</b></div>
436
- <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">
437
- {conclusion}
438
- </div>
439
  """
440
  return html_result(badge, duration, avg_text, dl_text, extra_html=extra)
441
 
442
  # Music
443
- if MIN_MUSIC <= duration <= MAX_MUSIC:
444
- badge = "🎵 Musique (FreeSound URL)"
445
- dl_class = int(predict_with_model_fs(xgb_music_num, sound, xgb_music_feat_num))
446
- avg_text = str(predict_with_model_fs(xgb_music_avg, sound, xgb_music_feat_avg, le_music_avg))
447
- dl_text = NUM_DOWNLOADS_MAP.get(dl_class, str(dl_class))
448
 
449
- avg_class = avg_label_to_class(avg_text)
450
- conclusion = interpret_results(avg_class, dl_class)
451
 
452
- extra = f"""
453
  <div class="hint">ID FreeSound : <b>{sound_id}</b></div>
454
- <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">
455
- {conclusion}
456
- </div>
457
  """
458
- return html_result(badge, duration, avg_text, dl_text, extra_html=extra)
459
-
460
- return html_error(
461
- "Durée non supportée",
462
- f"Durée détectée : <b>{duration:.2f} s</b><br><br>"
463
- f"Plages acceptées :<br>"
464
- f"• Effet sonore : <b>{MIN_EFFECT}–{MAX_EFFECT} s</b><br>"
465
- f"• Musique : <b>{MIN_MUSIC}–{MAX_MUSIC} s</b>"
466
- )
467
 
468
 
469
  # =========================
470
- # APP UI (2 onglets)
471
  # =========================
472
  theme = gr.themes.Soft()
473
 
474
- with gr.Blocks(title="DémoPopularité Audio", css=CSS) as demo:
475
  gr.HTML(
476
  f"""
477
- <div id="header-title">Démo — Prédiction de popularité audio</div>
478
  <p id="header-sub">
479
- Deux modes : <b>Upload audio</b> (openSMILE) ou <b>URL FreeSound</b> (features API).<br><br>
 
480
  <b>Durées acceptées :</b> 🔊 Effet sonore {MIN_EFFECT}–{MAX_EFFECT}s · 🎵 Musique {MIN_MUSIC}–{MAX_MUSIC}s
481
  </p>
482
  """
483
  )
484
 
485
- with gr.Tabs():
486
- with gr.Tab("1) Upload audio (openSMILE)"):
487
- with gr.Row():
488
- with gr.Column(scale=1):
489
- gr.Markdown("### Importer un fichier")
490
- audio_in = gr.Audio(type="filepath", label="Fichier audio")
491
- btn_audio = gr.Button("🚀 Prédire (upload)", variant="primary")
492
- with gr.Column(scale=1):
493
- gr.Markdown("### Résultat")
494
- out_audio = gr.HTML()
495
- btn_audio.click(predict_from_uploaded_audio, inputs=audio_in, outputs=out_audio)
496
-
497
- with gr.Tab("2) URL FreeSound (features API)"):
498
- with gr.Row():
499
- with gr.Column(scale=1):
500
- gr.Markdown("### Coller une URL FreeSound")
501
- url_in = gr.Textbox(label="URL FreeSound", placeholder="https://freesound.org/s/123456/")
502
- btn_url = gr.Button("🚀 Prédire (URL FreeSound)", variant="primary")
503
- with gr.Column(scale=1):
504
- gr.Markdown("### Résultat")
505
- out_url = gr.HTML()
506
- btn_url.click(predict_from_freesound_url, inputs=url_in, outputs=out_url)
507
 
508
  demo.launch(theme=theme)
 
 
 
 
 
1
  import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
  import joblib
 
 
 
 
 
5
  import xgboost as xgb
6
+ import freesound
7
+
8
+
9
+ # =========================
10
+ # Token FreeSound
11
+ # =========================
12
+ API_TOKEN = "A ECRIRE" # <- tu remplaces ici
13
 
14
 
15
  # =========================
16
+ # Durées
17
  # =========================
18
  MIN_EFFECT, MAX_EFFECT = 0.5, 3.0
19
  MIN_MUSIC, MAX_MUSIC = 10.0, 60.0
 
20
 
21
 
22
  # =========================
 
33
  border-color: #fca5a5;
34
  background: #fff1f2;
35
  }
36
+ .card-title{ font-weight: 950; margin-bottom: 8px; }
37
+ .badges{ display:flex; gap:10px; flex-wrap:wrap; margin-bottom:12px; }
 
 
 
 
 
 
 
 
38
  .badge{
39
+ padding:6px 10px; border-radius:999px; font-weight:900; font-size:13px;
 
 
 
40
  border: 1px solid #e5e7eb;
41
  }
42
  .badge-type{ background:#eef2ff; color:#3730a3;}
43
  .badge-time{ background:#ecfeff; color:#155e75;}
44
 
45
+ .grid{ display:grid; grid-template-columns:1fr; gap:10px; }
46
+ .box{ border:1px solid #e5e7eb; border-radius:14px; padding:12px; background:#fafafa; }
 
 
 
 
 
 
 
 
 
47
  .box-title{ font-weight:900; margin-bottom:4px; }
48
  .box-value{ font-size:18px; font-weight:800; }
49
 
50
+ .hint{ margin-top:10px; color:#6b7280; font-size:12px; }
 
 
 
 
51
 
52
  #header-title { font-size: 28px; font-weight: 950; margin-bottom: 6px; }
53
  #header-sub { color:#6b7280; margin-top:0px; line-height:1.45; }
 
92
 
93
 
94
  # =========================
95
+ # Interprétation
96
  # =========================
97
  def interpret_results(avg_class: int, dl_class: int) -> str:
 
 
 
 
98
  if avg_class == 0:
99
  return (
100
  "ℹ️ <b>Interprétation</b> :<br>"
101
+ "Aucune/peu d'évaluations utilisateurs (rating manquant).<br>"
102
+ "La popularité est donc probablement liée à l'usage (téléchargements) plutôt qu'à la qualité perçue."
103
  )
104
 
105
  rating_txt = {1: "faible", 2: "moyenne", 3: "élevée"}.get(avg_class, "inconnue")
106
  downloads_txt = {0: "faible", 1: "modérée", 2: "élevée"}.get(dl_class, "inconnue")
107
 
108
  if avg_class == 3 and dl_class == 2:
109
+ potentiel, detail = "très fort", "contenu de haute qualité et très populaire."
 
110
  elif avg_class == 3 and dl_class == 1:
111
+ potentiel, detail = "fort", "contenu bien apprécié, en croissance."
 
112
  elif avg_class == 3 and dl_class == 0:
113
+ potentiel, detail = "prometteur", "bonne qualité mais faible visibilité."
 
114
  elif avg_class == 2 and dl_class == 2:
115
+ potentiel, detail = "modéré à fort", "populaire mais qualité perçue moyenne."
 
116
  elif avg_class == 2 and dl_class == 1:
117
+ potentiel, detail = "modéré", "profil standard, popularité stable."
 
118
  elif avg_class == 2 and dl_class == 0:
119
+ potentiel, detail = "limité", "engagement faible, diffusion limitée."
 
120
  elif avg_class == 1 and dl_class == 2:
121
+ potentiel, detail = "contradictoire", "très téléchargé mais peu apprécié."
 
122
  elif avg_class == 1 and dl_class == 1:
123
+ potentiel, detail = "faible", "peu attractif pour les utilisateurs."
 
124
  else:
125
+ potentiel, detail = "très faible", "faible intérêt global."
 
126
 
127
  return (
128
+ "🧠 <b>Interprétation</b> :<br>"
129
+ f"- Qualité perçue : <b>{rating_txt}</b><br>"
130
+ f"- Popularité : <b>{downloads_txt}</b><br><br>"
131
+ f"👉 Potentiel estimé : <b>{potentiel}</b> — {detail}"
132
  )
133
 
134
 
135
  def avg_label_to_class(avg_label: str) -> int:
 
 
 
 
 
136
  if avg_label is None:
137
  return 0
 
138
  s = str(avg_label).strip().lower()
 
139
  if "miss" in s or "missing" in s or "none" in s or "no" in s:
140
  return 0
141
  if "high" in s or "élev" in s or "eleve" in s:
 
147
  return 0
148
 
149
 
150
+ # =========================
151
+ # Init FreeSound Client
152
+ # =========================
153
+ client = freesound.FreesoundClient()
154
+ client.set_token(API_TOKEN, "token")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
 
 
156
 
157
+ # =========================
158
+ # Charger modèles (metadata)
159
+ # =========================
160
  # Music
161
  xgb_music_num = joblib.load("xgb_num_downloads_music_model.pkl")
162
  xgb_music_feat_num = joblib.load("xgb_num_downloads_music_features.pkl")
 
181
  return 0.0
182
 
183
 
184
+ def predict_with_model(model, features, feat_list, le=None):
185
  row = []
186
  for col in feat_list:
187
+ val = features.get(col, 0)
188
  if val is None or isinstance(val, (list, dict)):
189
  val = 0
190
  row.append(safe_float(val))
191
 
192
  X = pd.DataFrame([row], columns=feat_list)
193
  dmatrix = xgb.DMatrix(X.values, feature_names=feat_list)
 
194
  pred_int = int(model.get_booster().predict(dmatrix)[0])
195
 
196
+ if le:
197
+ return le.inverse_transform([pred_int])[0]
198
  return pred_int
199
 
200
 
201
+ def extract_and_predict(url: str):
202
  if not url or not url.strip():
203
  return html_error("URL vide", "Collez une URL FreeSound du type <code>https://freesound.org/s/123456/</code>")
204
 
 
205
  try:
206
  sound_id = int(url.rstrip("/").split("/")[-1])
207
  except Exception:
208
  return html_error("URL invalide", "Impossible d'extraire l'ID depuis l'URL.")
209
 
210
+ # Champs nécessaires
211
  all_features = list(set(
212
  xgb_music_feat_num + xgb_music_feat_avg + xgb_effect_feat_num + xgb_effect_feat_avg
213
  ))
214
  fields = "duration," + ",".join(all_features)
215
 
216
  try:
217
+ results = client.search(query="", filter=f"id:{sound_id}", fields=fields)
218
  except Exception as e:
219
  return html_error("Erreur API FreeSound", f"Détail : <code>{e}</code>")
220
 
 
224
  sound = results.results[0]
225
  duration = safe_float(sound.get("duration", 0))
226
 
227
+ # Vérif durées
228
+ if duration < MIN_EFFECT:
229
+ return html_error(
230
+ "Audio trop court",
231
+ f"Durée : <b>{duration:.2f}s</b><br><br>"
232
+ f"Plages : Effet sonore <b>{MIN_EFFECT}-{MAX_EFFECT}s</b> | Musique <b>{MIN_MUSIC}-{MAX_MUSIC}s</b>"
233
+ )
234
+ if (MAX_EFFECT < duration < MIN_MUSIC) or duration > MAX_MUSIC:
235
+ return html_error(
236
+ "Audio hors plage",
237
+ f"Durée : <b>{duration:.2f}s</b><br><br>"
238
+ f"Plages : Effet sonore <b>{MIN_EFFECT}-{MAX_EFFECT}s</b> | Musique <b>{MIN_MUSIC}-{MAX_MUSIC}s</b>"
239
+ )
240
+
241
+ # Effect
242
  if MIN_EFFECT <= duration <= MAX_EFFECT:
243
+ badge = "🔊 Effet sonore (metadata FreeSound)"
244
+ dl_class = int(predict_with_model(xgb_effect_num, sound, xgb_effect_feat_num))
245
+ avg_text = str(predict_with_model(xgb_effect_avg, sound, xgb_effect_feat_avg, le_effect_avg))
246
  dl_text = NUM_DOWNLOADS_MAP.get(dl_class, str(dl_class))
247
 
248
  avg_class = avg_label_to_class(avg_text)
 
250
 
251
  extra = f"""
252
  <div class="hint">ID FreeSound : <b>{sound_id}</b></div>
253
+ <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">{conclusion}</div>
 
 
254
  """
255
  return html_result(badge, duration, avg_text, dl_text, extra_html=extra)
256
 
257
  # Music
258
+ badge = "🎵 Musique (metadata FreeSound)"
259
+ dl_class = int(predict_with_model(xgb_music_num, sound, xgb_music_feat_num))
260
+ avg_text = str(predict_with_model(xgb_music_avg, sound, xgb_music_feat_avg, le_music_avg))
261
+ dl_text = NUM_DOWNLOADS_MAP.get(dl_class, str(dl_class))
 
262
 
263
+ avg_class = avg_label_to_class(avg_text)
264
+ conclusion = interpret_results(avg_class, dl_class)
265
 
266
+ extra = f"""
267
  <div class="hint">ID FreeSound : <b>{sound_id}</b></div>
268
+ <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">{conclusion}</div>
 
 
269
  """
270
+ return html_result(badge, duration, avg_text, dl_text, extra_html=extra)
 
 
 
 
 
 
 
 
271
 
272
 
273
  # =========================
274
+ # UI Gradio (metadata only)
275
  # =========================
276
  theme = gr.themes.Soft()
277
 
278
+ with gr.Blocks(title="TestMetadata FreeSound", css=CSS) as demo:
279
  gr.HTML(
280
  f"""
281
+ <div id="header-title">🔎 Test — Prédiction via Metadata FreeSound</div>
282
  <p id="header-sub">
283
+ Collez une URL FreeSound. L'app récupère les <b>metadata</b> via l'API et prédit la popularité (avg_rating, num_downloads).
284
+ <br><br>
285
  <b>Durées acceptées :</b> 🔊 Effet sonore {MIN_EFFECT}–{MAX_EFFECT}s · 🎵 Musique {MIN_MUSIC}–{MAX_MUSIC}s
286
  </p>
287
  """
288
  )
289
 
290
+ url = gr.Textbox(label="URL FreeSound", placeholder="https://freesound.org/s/123456/")
291
+ btn = gr.Button("🚀 Tester la prédiction", variant="primary")
292
+ out = gr.HTML()
293
+
294
+ btn.click(extract_and_predict, inputs=url, outputs=out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
  demo.launch(theme=theme)
xgb_avg_rating_effectsound_features.pkl → effectSound_model_num_downloads.joblib RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:fa0509103f06306aa1d21f074fc89ee08d90d9cf4b0c2b3a9a5b3c4436d4c5af
3
- size 631
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d1d4c378ad69f9339733e16537a49ca700425404ce7b19f8c4d28f72e32ab087
3
+ size 7327682
xgb_num_downloads_effectsound_features.pkl → effectSound_xgb_avg_rating.joblib RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:fa0509103f06306aa1d21f074fc89ee08d90d9cf4b0c2b3a9a5b3c4436d4c5af
3
- size 631
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:378c975a1ae60a53d18d02f6e3f50567060c7c7a3660216ab487fb3eb7559356
3
+ size 6617890
xgb_avg_rating_effectsound_label_encoder.pkl → effectSound_xgb_avg_rating_label_encoder.joblib RENAMED
File without changes
xgb_avg_rating_effectsound_model.pkl → effect_model_features_list.joblib RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:af8cfbf2e681e80ea641e30b40ac280999e9a72e9bd95b28f33755771ccd51e5
3
- size 10219909
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7e69161319b7759e8ca2c0d0d4352c45e3970c2b2a14ee24db00f51752179b42
3
+ size 201078598
model_features_list.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f3de53a97822783e64c599135996ba7e92b3b53c3a05d8ebd34ad9f4c5664942
3
+ size 125604014
music_model_features_list.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f3de53a97822783e64c599135996ba7e92b3b53c3a05d8ebd34ad9f4c5664942
3
+ size 125604014
xgb_avg_rating_music_features.pkl → music_model_num_downloads.joblib RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:fa0509103f06306aa1d21f074fc89ee08d90d9cf4b0c2b3a9a5b3c4436d4c5af
3
- size 631
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f157e23ef2de700563a45a120e12b3d0827eda1fd82cbd490b5b2d4ad1508a89
3
+ size 8004183
xgb_avg_rating_music_model.pkl → music_xgb_avg_rating.joblib RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:b4e996630b373e8594079d5ae3bf2a52707f8f163aedd6c6330416b9a056b8e9
3
- size 7046656
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3415743a7fb86e1c9bd2d7e59f146ac4aee2e56b4343ea8e92ad486d934dd06b
3
+ size 6578402
xgb_avg_rating_music_label_encoder.pkl → music_xgb_avg_rating_label_encoder.joblib RENAMED
File without changes
xgb_model_EffectSound.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:f41317a1a2ac6916e2fc40a8a43097021520ea0de78632149a30ee946b1c697a
3
- size 16161360
 
 
 
 
xgb_model_Music.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:89dc204e1e774da5b44df74d25d654bce417e4d7304b3bf2efde901dccaf2919
3
- size 16904032
 
 
 
 
xgb_num_downloads_effectsound_model.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:2a33ae31fc84dd8dc080d75f61e4016690fa2730cdd9b7dbb9720d7eb778adca
3
- size 8595460
 
 
 
 
xgb_num_downloads_music_features.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:fa0509103f06306aa1d21f074fc89ee08d90d9cf4b0c2b3a9a5b3c4436d4c5af
3
- size 631
 
 
 
 
xgb_num_downloads_music_model.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:5b8abbccd5ee7f195386936b8447c7d9c5b336f6f8b5740563d6803389a2c45a
3
- size 8754226