IKRAMELHADI commited on
Commit
d469b87
·
1 Parent(s): 58fae89

testtest3

Browse files
Files changed (2) hide show
  1. app.py +94 -40
  2. requirements.txt +8 -0
app.py CHANGED
@@ -1,21 +1,27 @@
1
  import os
 
2
  import gradio as gr
3
  import pandas as pd
 
4
  import joblib
5
  import xgboost as xgb
6
- import freesound
 
 
7
 
8
 
9
  # =========================
10
  # CONFIG
11
  # =========================
12
- API_TOKEN = "zE9NjEOgUMzH9K7mjiGBaPJiNwJLjSM53LevarRK" # <-- remplace ici
13
 
14
  MIN_EFFECT, MAX_EFFECT = 0.5, 3.0
15
  MIN_MUSIC, MAX_MUSIC = 10.0, 60.0
16
 
17
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
18
 
 
 
19
 
20
  # =========================
21
  # UI (CSS)
@@ -79,10 +85,6 @@ def html_result(badge_text, duration, rating_text, downloads_text, extra_html=""
79
  # INTERPRETATION
80
  # =========================
81
  def interpret_results(avg_class: int, dl_class: int) -> str:
82
- """
83
- avg_class: 0=Missed info, 1=Low, 2=Medium, 3=High (déduit du label texte)
84
- dl_class: 0=Low, 1=Medium, 2=High (sortie num_downloads)
85
- """
86
  if avg_class == 0:
87
  return (
88
  "ℹ️ <b>Interprétation</b> :<br>"
@@ -121,13 +123,9 @@ def interpret_results(avg_class: int, dl_class: int) -> str:
121
 
122
 
123
  def avg_label_to_class(avg_label: str) -> int:
124
- """
125
- Convertit label texte du label encoder en 0..3
126
- """
127
  if avg_label is None:
128
  return 0
129
  s = str(avg_label).strip().lower()
130
-
131
  if "miss" in s or "missing" in s or "none" in s or "no" in s:
132
  return 0
133
  if "high" in s or "élev" in s or "eleve" in s:
@@ -140,23 +138,62 @@ def avg_label_to_class(avg_label: str) -> int:
140
 
141
 
142
  # =========================
143
- # FreeSound client
144
  # =========================
145
- client = freesound.FreesoundClient()
146
- client.set_token(API_TOKEN, "token")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
 
149
  # =========================
150
- # Charger les modèles (NOMS EXACTS DU REPO)
151
  # =========================
152
-
153
- # Music
154
  music_num_model = joblib.load(os.path.join(BASE_DIR, "music_model_num_downloads.joblib"))
155
  music_feat_list = joblib.load(os.path.join(BASE_DIR, "music_model_features_list.joblib"))
156
  music_avg_model = joblib.load(os.path.join(BASE_DIR, "music_xgb_avg_rating.joblib"))
157
  music_avg_le = joblib.load(os.path.join(BASE_DIR, "music_xgb_avg_rating_label_encoder.joblib"))
158
 
159
- # Effect sound
160
  effect_num_model = joblib.load(os.path.join(BASE_DIR, "effectSound_model_num_downloads.joblib"))
161
  effect_feat_list = joblib.load(os.path.join(BASE_DIR, "effect_model_features_list.joblib"))
162
  effect_avg_model = joblib.load(os.path.join(BASE_DIR, "effectSound_xgb_avg_rating.joblib"))
@@ -172,19 +209,29 @@ def safe_float(v):
172
  return 0.0
173
 
174
 
175
- def predict_with_model(model, features_dict, feat_list, le=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  row = []
177
  for col in feat_list:
178
- val = features_dict.get(col, 0)
179
  if val is None or isinstance(val, (list, dict)):
180
  val = 0
181
  row.append(safe_float(val))
182
 
183
  X = pd.DataFrame([row], columns=feat_list)
184
-
185
- # DMatrix (avec feature names)
186
  dm = xgb.DMatrix(X.values, feature_names=feat_list)
187
-
188
  pred_int = int(model.get_booster().predict(dm)[0])
189
 
190
  if le is not None:
@@ -194,44 +241,46 @@ def predict_with_model(model, features_dict, feat_list, le=None):
194
 
195
  def extract_and_predict(url: str):
196
  if not url or not url.strip():
197
- return html_error("URL vide", "Collez une URL FreeSound du type <code>https://freesound.org/s/123456/</code>")
198
 
199
  # Parse ID
200
  try:
201
  sound_id = int(url.rstrip("/").split("/")[-1])
202
  except Exception:
203
- return html_error("URL invalide", "Impossible d'extraire l'ID depuis l'URL.")
204
 
205
- # champs nécessaires (union music+effect)
206
- all_features = list(set(music_feat_list + effect_feat_list))
207
  fields = "duration," + ",".join(all_features)
208
 
 
209
  try:
210
- results = client.search(query="", filter=f"id:{sound_id}", fields=fields)
211
  except Exception as e:
212
- return html_error("Erreur API FreeSound", f"Détail : <code>{e}</code>")
213
-
214
- if len(results.results) == 0:
215
- return html_error("Son introuvable", "Aucun résultat pour cet ID.")
 
216
 
217
- sound = results.results[0]
218
  duration = safe_float(sound.get("duration", 0))
219
 
220
- # Durées supportées
221
  if duration < MIN_EFFECT:
222
  return html_error(
223
  "Audio trop court",
224
  f"Durée : <b>{duration:.2f}s</b><br><br>"
225
  f"Plages : Effet sonore <b>{MIN_EFFECT}-{MAX_EFFECT}s</b> | Musique <b>{MIN_MUSIC}-{MAX_MUSIC}s</b>"
226
- )
 
227
  if (MAX_EFFECT < duration < MIN_MUSIC) or duration > MAX_MUSIC:
228
  return html_error(
229
  "Audio hors plage",
230
  f"Durée : <b>{duration:.2f}s</b><br><br>"
231
  f"Plages : Effet sonore <b>{MIN_EFFECT}-{MAX_EFFECT}s</b> | Musique <b>{MIN_MUSIC}-{MAX_MUSIC}s</b>"
232
- )
233
 
234
- # Décision type
235
  if MIN_EFFECT <= duration <= MAX_EFFECT:
236
  badge = "🔊 Effet sonore (metadata FreeSound)"
237
  dl_class = int(predict_with_model(effect_num_model, sound, effect_feat_list))
@@ -245,7 +294,8 @@ def extract_and_predict(url: str):
245
  <div class="hint">ID FreeSound : <b>{sound_id}</b></div>
246
  <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">{conclusion}</div>
247
  """
248
- return html_result(badge, duration, avg_text, dl_text, extra_html=extra)
 
249
 
250
  # Music
251
  badge = "🎵 Musique (metadata FreeSound)"
@@ -260,7 +310,8 @@ def extract_and_predict(url: str):
260
  <div class="hint">ID FreeSound : <b>{sound_id}</b></div>
261
  <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">{conclusion}</div>
262
  """
263
- return html_result(badge, duration, avg_text, dl_text, extra_html=extra)
 
264
 
265
 
266
  # =========================
@@ -282,8 +333,11 @@ Collez une URL FreeSound. L'app récupère les <b>metadata</b> via l'API et pré
282
 
283
  url = gr.Textbox(label="URL FreeSound", placeholder="https://freesound.org/s/123456/")
284
  btn = gr.Button("🚀 Tester la prédiction", variant="primary")
285
- out = gr.HTML()
286
 
287
- btn.click(extract_and_predict, inputs=url, outputs=out)
 
 
 
 
288
 
289
  demo.launch(theme=theme)
 
1
  import os
2
+ import time
3
  import gradio as gr
4
  import pandas as pd
5
+ import numpy as np
6
  import joblib
7
  import xgboost as xgb
8
+ import requests
9
+ from requests.adapters import HTTPAdapter
10
+ from urllib3.util.retry import Retry
11
 
12
 
13
  # =========================
14
  # CONFIG
15
  # =========================
16
+ API_TOKEN = "A ECRIRE" # <-- remplace ici
17
 
18
  MIN_EFFECT, MAX_EFFECT = 0.5, 3.0
19
  MIN_MUSIC, MAX_MUSIC = 10.0, 60.0
20
 
21
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
22
 
23
+ FREESOUND_API_BASE = "https://freesound.org/apiv2"
24
+
25
 
26
  # =========================
27
  # UI (CSS)
 
85
  # INTERPRETATION
86
  # =========================
87
  def interpret_results(avg_class: int, dl_class: int) -> str:
 
 
 
 
88
  if avg_class == 0:
89
  return (
90
  "ℹ️ <b>Interprétation</b> :<br>"
 
123
 
124
 
125
  def avg_label_to_class(avg_label: str) -> int:
 
 
 
126
  if avg_label is None:
127
  return 0
128
  s = str(avg_label).strip().lower()
 
129
  if "miss" in s or "missing" in s or "none" in s or "no" in s:
130
  return 0
131
  if "high" in s or "élev" in s or "eleve" in s:
 
138
 
139
 
140
  # =========================
141
+ # HTTP SESSION (retries)
142
  # =========================
143
+ def make_session():
144
+ session = requests.Session()
145
+ retry = Retry(
146
+ total=5,
147
+ backoff_factor=0.8,
148
+ status_forcelist=[429, 500, 502, 503, 504],
149
+ allowed_methods=["GET"],
150
+ raise_on_status=False,
151
+ )
152
+ adapter = HTTPAdapter(max_retries=retry)
153
+ session.mount("https://", adapter)
154
+ session.mount("http://", adapter)
155
+ return session
156
+
157
+
158
+ SESSION = make_session()
159
+
160
+
161
+ def fetch_sound_metadata_by_id(sound_id: int, fields: str) -> dict:
162
+ """
163
+ Appel API FreeSound directement (plus stable) + retries + timeout.
164
+ """
165
+ url = f"{FREESOUND_API_BASE}/search/text/"
166
+ headers = {"Authorization": f"Token {API_TOKEN}"}
167
+
168
+ params = {
169
+ "query": "",
170
+ "filter": f"id:{sound_id}",
171
+ "fields": fields,
172
+ "page_size": 1,
173
+ }
174
+
175
+ # timeout séparé (connect, read)
176
+ resp = SESSION.get(url, headers=headers, params=params, timeout=(6, 20))
177
+ if resp.status_code == 401:
178
+ raise RuntimeError("Token invalide ou non autorisé (401).")
179
+ if resp.status_code >= 400:
180
+ raise RuntimeError(f"Erreur HTTP {resp.status_code}: {resp.text[:200]}")
181
+
182
+ data = resp.json()
183
+ results = data.get("results", [])
184
+ if not results:
185
+ raise RuntimeError("Sound not found (aucun résultat pour cet ID).")
186
+ return results[0]
187
 
188
 
189
  # =========================
190
+ # Charger modèles (NOMS EXACTS)
191
  # =========================
 
 
192
  music_num_model = joblib.load(os.path.join(BASE_DIR, "music_model_num_downloads.joblib"))
193
  music_feat_list = joblib.load(os.path.join(BASE_DIR, "music_model_features_list.joblib"))
194
  music_avg_model = joblib.load(os.path.join(BASE_DIR, "music_xgb_avg_rating.joblib"))
195
  music_avg_le = joblib.load(os.path.join(BASE_DIR, "music_xgb_avg_rating_label_encoder.joblib"))
196
 
 
197
  effect_num_model = joblib.load(os.path.join(BASE_DIR, "effectSound_model_num_downloads.joblib"))
198
  effect_feat_list = joblib.load(os.path.join(BASE_DIR, "effect_model_features_list.joblib"))
199
  effect_avg_model = joblib.load(os.path.join(BASE_DIR, "effectSound_xgb_avg_rating.joblib"))
 
209
  return 0.0
210
 
211
 
212
+ def build_feature_df(sound: dict, feat_list: list) -> pd.DataFrame:
213
+ """
214
+ Tableau lisible des features utilisées (valeur API + NaN si absent).
215
+ """
216
+ rows = []
217
+ for col in feat_list:
218
+ val = sound.get(col, np.nan)
219
+ if val is None or isinstance(val, (list, dict)):
220
+ val = np.nan
221
+ rows.append({"feature": col, "value": val})
222
+ return pd.DataFrame(rows)
223
+
224
+
225
+ def predict_with_model(model, sound: dict, feat_list: list, le=None):
226
  row = []
227
  for col in feat_list:
228
+ val = sound.get(col, 0)
229
  if val is None or isinstance(val, (list, dict)):
230
  val = 0
231
  row.append(safe_float(val))
232
 
233
  X = pd.DataFrame([row], columns=feat_list)
 
 
234
  dm = xgb.DMatrix(X.values, feature_names=feat_list)
 
235
  pred_int = int(model.get_booster().predict(dm)[0])
236
 
237
  if le is not None:
 
241
 
242
  def extract_and_predict(url: str):
243
  if not url or not url.strip():
244
+ return html_error("URL vide", "Collez une URL FreeSound du type <code>https://freesound.org/s/123456/</code>"), pd.DataFrame()
245
 
246
  # Parse ID
247
  try:
248
  sound_id = int(url.rstrip("/").split("/")[-1])
249
  except Exception:
250
+ return html_error("URL invalide", "Impossible d'extraire l'ID depuis l'URL."), pd.DataFrame()
251
 
252
+ # Fields nécessaires : union music/effect + duration
253
+ all_features = sorted(list(set(music_feat_list + effect_feat_list)))
254
  fields = "duration," + ",".join(all_features)
255
 
256
+ # Fetch API (avec retries)
257
  try:
258
+ sound = fetch_sound_metadata_by_id(sound_id, fields=fields)
259
  except Exception as e:
260
+ return html_error(
261
+ "Erreur API FreeSound",
262
+ f"Détail : <code>{e}</code><br><br>"
263
+ "Astuce : si ça arrive aléatoirement, c'est souvent un souci réseau/rate limit → réessayez."
264
+ ), pd.DataFrame()
265
 
 
266
  duration = safe_float(sound.get("duration", 0))
267
 
268
+ # Vérif durées
269
  if duration < MIN_EFFECT:
270
  return html_error(
271
  "Audio trop court",
272
  f"Durée : <b>{duration:.2f}s</b><br><br>"
273
  f"Plages : Effet sonore <b>{MIN_EFFECT}-{MAX_EFFECT}s</b> | Musique <b>{MIN_MUSIC}-{MAX_MUSIC}s</b>"
274
+ ), pd.DataFrame()
275
+
276
  if (MAX_EFFECT < duration < MIN_MUSIC) or duration > MAX_MUSIC:
277
  return html_error(
278
  "Audio hors plage",
279
  f"Durée : <b>{duration:.2f}s</b><br><br>"
280
  f"Plages : Effet sonore <b>{MIN_EFFECT}-{MAX_EFFECT}s</b> | Musique <b>{MIN_MUSIC}-{MAX_MUSIC}s</b>"
281
+ ), pd.DataFrame()
282
 
283
+ # Effect
284
  if MIN_EFFECT <= duration <= MAX_EFFECT:
285
  badge = "🔊 Effet sonore (metadata FreeSound)"
286
  dl_class = int(predict_with_model(effect_num_model, sound, effect_feat_list))
 
294
  <div class="hint">ID FreeSound : <b>{sound_id}</b></div>
295
  <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">{conclusion}</div>
296
  """
297
+ df_feat = build_feature_df(sound, effect_feat_list)
298
+ return html_result(badge, duration, avg_text, dl_text, extra_html=extra), df_feat
299
 
300
  # Music
301
  badge = "🎵 Musique (metadata FreeSound)"
 
310
  <div class="hint">ID FreeSound : <b>{sound_id}</b></div>
311
  <div style="margin-top:12px; padding-top:10px; border-top:1px dashed #d1d5db">{conclusion}</div>
312
  """
313
+ df_feat = build_feature_df(sound, music_feat_list)
314
+ return html_result(badge, duration, avg_text, dl_text, extra_html=extra), df_feat
315
 
316
 
317
  # =========================
 
333
 
334
  url = gr.Textbox(label="URL FreeSound", placeholder="https://freesound.org/s/123456/")
335
  btn = gr.Button("🚀 Tester la prédiction", variant="primary")
 
336
 
337
+ with gr.Row():
338
+ out_html = gr.HTML(label="Résultat")
339
+ out_df = gr.Dataframe(label="Features utilisées (metadata)", interactive=False)
340
+
341
+ btn.click(extract_and_predict, inputs=url, outputs=[out_html, out_df])
342
 
343
  demo.launch(theme=theme)
requirements.txt CHANGED
@@ -9,6 +9,14 @@ pydub
9
  opensmile
10
  requests
11
  pytz
 
 
 
 
 
 
 
 
12
  imblearn
13
  matplotlib
14
  git+https://github.com/MTG/freesound-python.git
 
9
  opensmile
10
  requests
11
  pytz
12
+ gradio
13
+ pandas
14
+ numpy
15
+ joblib
16
+ xgboost
17
+ requests
18
+ urllib3
19
+ scikit-learn
20
  imblearn
21
  matplotlib
22
  git+https://github.com/MTG/freesound-python.git