Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
577400a
1
Parent(s): e4e1e7d
zoom scribe + versioning
Browse files- app.py +80 -11
- init.py +354 -5
- recipes/registry.py +9 -0
- recipes/zoom_scribe/evaluate.py +84 -0
- storage.py +3 -0
app.py
CHANGED
|
@@ -22,10 +22,13 @@ from metrics_config import LIVE_SCENARIO_KEYS, SCENARIO_METRICS
|
|
| 22 |
from recipes.registry import RECIPE_CHOICES
|
| 23 |
|
| 24 |
from init import (
|
|
|
|
| 25 |
is_model_on_hub,
|
|
|
|
| 26 |
load_raw_results,
|
| 27 |
load_results,
|
| 28 |
normalize_legacy_csv_row,
|
|
|
|
| 29 |
)
|
| 30 |
from utils_display import (
|
| 31 |
AutoEvalColumn,
|
|
@@ -51,6 +54,11 @@ ALWAYS_VISIBLE_COLS = (MODEL_COL, AVG_WER_COL)
|
|
| 51 |
# Empty table at import; real data loaded on demo.load (avoids blocking Space "preparing").
|
| 52 |
original_df = pd.DataFrame(columns=COLS)
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
_NEXT_UP_PLACEHOLDER = (
|
| 55 |
"<div class='next-up-panel' style='font-size:0.9em;opacity:0.85'>"
|
| 56 |
"<em>Loading upcoming evaluations…</em></div>"
|
|
@@ -81,7 +89,7 @@ def filter_main_table(search_query, selected_columns):
|
|
| 81 |
columns_to_hide = set(all_toggleable) - set(selected_columns)
|
| 82 |
filtered_df = filtered_df[[c for c in filtered_df.columns if c not in columns_to_hide]]
|
| 83 |
|
| 84 |
-
raw_rows = load_raw_results()
|
| 85 |
for r in raw_rows:
|
| 86 |
normalize_legacy_csv_row(r)
|
| 87 |
raw_by_mid = {r["model_id"]: r for r in raw_rows}
|
|
@@ -117,6 +125,28 @@ def filter_main_table(search_query, selected_columns):
|
|
| 117 |
return filtered_df
|
| 118 |
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
# ---------------------------------------------------------------------------
|
| 121 |
# Submission handler
|
| 122 |
# ---------------------------------------------------------------------------
|
|
@@ -353,6 +383,13 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 353 |
placeholder="Filter by model name -- separate multiple terms with commas, e.g. whisper, nvidia",
|
| 354 |
scale=4,
|
| 355 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
leaderboard_update_btn = gr.Button(
|
| 357 |
"Update", variant="primary", scale=1, min_width=120
|
| 358 |
)
|
|
@@ -377,34 +414,61 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 377 |
wrap=False,
|
| 378 |
)
|
| 379 |
|
| 380 |
-
def
|
| 381 |
-
"""
|
| 382 |
-
global original_df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
from init import invalidate_results_cache
|
| 384 |
|
| 385 |
try:
|
| 386 |
invalidate_results_cache()
|
| 387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
return filter_main_table(search, cols)
|
| 389 |
except Exception:
|
| 390 |
return original_df
|
| 391 |
|
| 392 |
def _on_startup():
|
| 393 |
"""Load leaderboard + next-up list after UI is up (keeps Space prepare step fast)."""
|
| 394 |
-
global original_df
|
| 395 |
from init import invalidate_results_cache
|
| 396 |
|
|
|
|
| 397 |
try:
|
| 398 |
invalidate_results_cache()
|
|
|
|
| 399 |
original_df = load_results()
|
| 400 |
table = filter_main_table("", _default_visible_cols)
|
|
|
|
| 401 |
except Exception as exc:
|
| 402 |
table = original_df
|
| 403 |
next_html = (
|
| 404 |
"<div class='next-up-panel'><p style='color:orange'>"
|
| 405 |
f"<strong>Startup warning:</strong> {exc}</p></div>"
|
| 406 |
)
|
| 407 |
-
return table, next_html
|
| 408 |
try:
|
| 409 |
job_queue.ensure_worker_started()
|
| 410 |
next_html = job_queue.next_up_html()
|
|
@@ -413,15 +477,20 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 413 |
"<div class='next-up-panel'><p style='color:orange'>"
|
| 414 |
f"<strong>Queue unavailable:</strong> {exc}</p></div>"
|
| 415 |
)
|
| 416 |
-
return table, next_html
|
| 417 |
|
| 418 |
filter_inputs = [search_box, column_checkboxes]
|
| 419 |
search_box.change(filter_main_table, inputs=filter_inputs, outputs=leaderboard_table)
|
| 420 |
column_checkboxes.change(filter_main_table, inputs=filter_inputs, outputs=leaderboard_table)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
leaderboard_update_btn.click(
|
| 422 |
fn=_leaderboard_refresh,
|
| 423 |
-
inputs=
|
| 424 |
-
outputs=leaderboard_table,
|
| 425 |
)
|
| 426 |
|
| 427 |
with gr.TabItem(tab_label("submit", "Submit"), elem_id="submit-tab", id=1):
|
|
@@ -1345,7 +1414,7 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 1345 |
_next_up_timer.tick(fn=job_queue.next_up_html, outputs=[next_up_panel])
|
| 1346 |
_progress_timer.tick(fn=_mod_progress_gated, inputs=[mod_unlocked], outputs=[mod_progress])
|
| 1347 |
|
| 1348 |
-
demo.load(fn=_on_startup, outputs=[leaderboard_table, next_up_panel])
|
| 1349 |
demo.load(
|
| 1350 |
fn=_analytics_initial,
|
| 1351 |
outputs=[
|
|
|
|
| 22 |
from recipes.registry import RECIPE_CHOICES
|
| 23 |
|
| 24 |
from init import (
|
| 25 |
+
LATEST_VERSION,
|
| 26 |
is_model_on_hub,
|
| 27 |
+
list_leaderboard_versions,
|
| 28 |
load_raw_results,
|
| 29 |
load_results,
|
| 30 |
normalize_legacy_csv_row,
|
| 31 |
+
raw_rows_for_version,
|
| 32 |
)
|
| 33 |
from utils_display import (
|
| 34 |
AutoEvalColumn,
|
|
|
|
| 54 |
# Empty table at import; real data loaded on demo.load (avoids blocking Space "preparing").
|
| 55 |
original_df = pd.DataFrame(columns=COLS)
|
| 56 |
|
| 57 |
+
# Raw CSV rows backing the currently displayed leaderboard version. ``None`` means "live"
|
| 58 |
+
# (filter_main_table falls back to load_raw_results()). Set whenever a version is selected
|
| 59 |
+
# so the Avg-WER recompute in filter_main_table matches the displayed snapshot.
|
| 60 |
+
_active_raw_rows: list[dict] | None = None
|
| 61 |
+
|
| 62 |
_NEXT_UP_PLACEHOLDER = (
|
| 63 |
"<div class='next-up-panel' style='font-size:0.9em;opacity:0.85'>"
|
| 64 |
"<em>Loading upcoming evaluations…</em></div>"
|
|
|
|
| 89 |
columns_to_hide = set(all_toggleable) - set(selected_columns)
|
| 90 |
filtered_df = filtered_df[[c for c in filtered_df.columns if c not in columns_to_hide]]
|
| 91 |
|
| 92 |
+
raw_rows = _active_raw_rows if _active_raw_rows is not None else load_raw_results()
|
| 93 |
for r in raw_rows:
|
| 94 |
normalize_legacy_csv_row(r)
|
| 95 |
raw_by_mid = {r["model_id"]: r for r in raw_rows}
|
|
|
|
| 125 |
return filtered_df
|
| 126 |
|
| 127 |
|
| 128 |
+
def _format_version_recorded_at(ts: str) -> str:
|
| 129 |
+
from datetime import datetime
|
| 130 |
+
|
| 131 |
+
try:
|
| 132 |
+
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
| 133 |
+
return dt.strftime("%Y-%m-%d %H:%M UTC")
|
| 134 |
+
except Exception:
|
| 135 |
+
return str(ts)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _version_choices() -> list[tuple[str, str]]:
|
| 139 |
+
"""(label, value) pairs for the version dropdown; first entry is always 'Latest'."""
|
| 140 |
+
choices: list[tuple[str, str]] = [("Latest", LATEST_VERSION)]
|
| 141 |
+
try:
|
| 142 |
+
for v in list_leaderboard_versions():
|
| 143 |
+
label = f"{_format_version_recorded_at(v['recorded_at'])} — {v['label']}"
|
| 144 |
+
choices.append((label, v["version"]))
|
| 145 |
+
except Exception:
|
| 146 |
+
pass
|
| 147 |
+
return choices
|
| 148 |
+
|
| 149 |
+
|
| 150 |
# ---------------------------------------------------------------------------
|
| 151 |
# Submission handler
|
| 152 |
# ---------------------------------------------------------------------------
|
|
|
|
| 383 |
placeholder="Filter by model name -- separate multiple terms with commas, e.g. whisper, nvidia",
|
| 384 |
scale=4,
|
| 385 |
)
|
| 386 |
+
version_dropdown = gr.Dropdown(
|
| 387 |
+
choices=_version_choices(),
|
| 388 |
+
value=LATEST_VERSION,
|
| 389 |
+
label="Version",
|
| 390 |
+
scale=2,
|
| 391 |
+
min_width=200,
|
| 392 |
+
)
|
| 393 |
leaderboard_update_btn = gr.Button(
|
| 394 |
"Update", variant="primary", scale=1, min_width=120
|
| 395 |
)
|
|
|
|
| 414 |
wrap=False,
|
| 415 |
)
|
| 416 |
|
| 417 |
+
def _set_active_version(version):
|
| 418 |
+
"""Load the chosen version into the module globals used by filter_main_table."""
|
| 419 |
+
global original_df, _active_raw_rows
|
| 420 |
+
if version:
|
| 421 |
+
_active_raw_rows = raw_rows_for_version(version)
|
| 422 |
+
original_df = load_results(version)
|
| 423 |
+
else:
|
| 424 |
+
_active_raw_rows = None
|
| 425 |
+
original_df = load_results()
|
| 426 |
+
|
| 427 |
+
def _leaderboard_refresh(search, cols, version):
|
| 428 |
+
"""Reload results from storage for the selected version, refresh version list."""
|
| 429 |
from init import invalidate_results_cache
|
| 430 |
|
| 431 |
try:
|
| 432 |
invalidate_results_cache()
|
| 433 |
+
choices = _version_choices()
|
| 434 |
+
valid = {v for _, v in choices}
|
| 435 |
+
if version not in valid:
|
| 436 |
+
version = LATEST_VERSION
|
| 437 |
+
_set_active_version(version)
|
| 438 |
+
return (
|
| 439 |
+
filter_main_table(search, cols),
|
| 440 |
+
gr.update(choices=choices, value=version),
|
| 441 |
+
)
|
| 442 |
+
except Exception:
|
| 443 |
+
return original_df, gr.update()
|
| 444 |
+
|
| 445 |
+
def _on_version_change(version, search, cols):
|
| 446 |
+
"""Swap the displayed leaderboard to the selected version, keep filters."""
|
| 447 |
+
try:
|
| 448 |
+
_set_active_version(version)
|
| 449 |
return filter_main_table(search, cols)
|
| 450 |
except Exception:
|
| 451 |
return original_df
|
| 452 |
|
| 453 |
def _on_startup():
|
| 454 |
"""Load leaderboard + next-up list after UI is up (keeps Space prepare step fast)."""
|
| 455 |
+
global original_df, _active_raw_rows
|
| 456 |
from init import invalidate_results_cache
|
| 457 |
|
| 458 |
+
version_update = gr.update()
|
| 459 |
try:
|
| 460 |
invalidate_results_cache()
|
| 461 |
+
_active_raw_rows = None
|
| 462 |
original_df = load_results()
|
| 463 |
table = filter_main_table("", _default_visible_cols)
|
| 464 |
+
version_update = gr.update(choices=_version_choices(), value=LATEST_VERSION)
|
| 465 |
except Exception as exc:
|
| 466 |
table = original_df
|
| 467 |
next_html = (
|
| 468 |
"<div class='next-up-panel'><p style='color:orange'>"
|
| 469 |
f"<strong>Startup warning:</strong> {exc}</p></div>"
|
| 470 |
)
|
| 471 |
+
return table, next_html, version_update
|
| 472 |
try:
|
| 473 |
job_queue.ensure_worker_started()
|
| 474 |
next_html = job_queue.next_up_html()
|
|
|
|
| 477 |
"<div class='next-up-panel'><p style='color:orange'>"
|
| 478 |
f"<strong>Queue unavailable:</strong> {exc}</p></div>"
|
| 479 |
)
|
| 480 |
+
return table, next_html, version_update
|
| 481 |
|
| 482 |
filter_inputs = [search_box, column_checkboxes]
|
| 483 |
search_box.change(filter_main_table, inputs=filter_inputs, outputs=leaderboard_table)
|
| 484 |
column_checkboxes.change(filter_main_table, inputs=filter_inputs, outputs=leaderboard_table)
|
| 485 |
+
version_dropdown.change(
|
| 486 |
+
fn=_on_version_change,
|
| 487 |
+
inputs=[version_dropdown, search_box, column_checkboxes],
|
| 488 |
+
outputs=leaderboard_table,
|
| 489 |
+
)
|
| 490 |
leaderboard_update_btn.click(
|
| 491 |
fn=_leaderboard_refresh,
|
| 492 |
+
inputs=[search_box, column_checkboxes, version_dropdown],
|
| 493 |
+
outputs=[leaderboard_table, version_dropdown],
|
| 494 |
)
|
| 495 |
|
| 496 |
with gr.TabItem(tab_label("submit", "Submit"), elem_id="submit-tab", id=1):
|
|
|
|
| 1414 |
_next_up_timer.tick(fn=job_queue.next_up_html, outputs=[next_up_panel])
|
| 1415 |
_progress_timer.tick(fn=_mod_progress_gated, inputs=[mod_unlocked], outputs=[mod_progress])
|
| 1416 |
|
| 1417 |
+
demo.load(fn=_on_startup, outputs=[leaderboard_table, next_up_panel, version_dropdown])
|
| 1418 |
demo.load(
|
| 1419 |
fn=_analytics_initial,
|
| 1420 |
outputs=[
|
init.py
CHANGED
|
@@ -14,6 +14,7 @@ import pandas as pd
|
|
| 14 |
from storage import (
|
| 15 |
HF_BUCKET_ID,
|
| 16 |
HF_TOKEN,
|
|
|
|
| 17 |
RESULTS_PATH,
|
| 18 |
STORAGE_BACKEND,
|
| 19 |
upload_to_bucket,
|
|
@@ -40,9 +41,15 @@ __all__ = [
|
|
| 40 |
"load_audio_file",
|
| 41 |
"load_transcript_file",
|
| 42 |
"normalize_legacy_csv_row",
|
|
|
|
|
|
|
|
|
|
| 43 |
]
|
| 44 |
|
| 45 |
csv_lock = threading.Lock()
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
LEADERBOARD_COLUMNS = [c.name for c in fields(AutoEvalColumn)]
|
| 48 |
|
|
@@ -59,6 +66,14 @@ CSV_FIELDS = [
|
|
| 59 |
"submitted_at",
|
| 60 |
]
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
# Legacy CSV / evaluation wire-format keys -> canonical ``SCENARIO_KEYS`` column names.
|
| 63 |
_LEGACY_WER_TO_CANONICAL: tuple[tuple[str, str], ...] = (
|
| 64 |
("wer_clean", "wer_anechoic_speech"),
|
|
@@ -281,10 +296,19 @@ def load_raw_results(*, refresh: bool = False) -> list[dict]:
|
|
| 281 |
return []
|
| 282 |
|
| 283 |
|
| 284 |
-
def save_raw_results(rows: list[dict]):
|
| 285 |
-
"""Write the leaderboard CSV back to storage.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
if not rows:
|
| 287 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
with csv_lock:
|
| 289 |
normalized = [{k: row.get(k, "") for k in CSV_FIELDS} for row in rows]
|
| 290 |
output = io.StringIO()
|
|
@@ -301,17 +325,342 @@ def save_raw_results(rows: list[dict]):
|
|
| 301 |
)
|
| 302 |
invalidate_results_cache()
|
| 303 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
"""Load results from storage and return a display-ready DataFrame.
|
| 307 |
|
|
|
|
|
|
|
|
|
|
| 308 |
The leaderboard intentionally does NOT show eval_family, wall time, or submitted_at.
|
| 309 |
Those fields remain in the CSV (for moderators and analytics) but are omitted from the
|
| 310 |
public table.
|
| 311 |
"""
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
| 315 |
|
| 316 |
if not raw_rows:
|
| 317 |
return pd.DataFrame(columns=LEADERBOARD_COLUMNS)
|
|
|
|
| 14 |
from storage import (
|
| 15 |
HF_BUCKET_ID,
|
| 16 |
HF_TOKEN,
|
| 17 |
+
HISTORY_PATH,
|
| 18 |
RESULTS_PATH,
|
| 19 |
STORAGE_BACKEND,
|
| 20 |
upload_to_bucket,
|
|
|
|
| 41 |
"load_audio_file",
|
| 42 |
"load_transcript_file",
|
| 43 |
"normalize_legacy_csv_row",
|
| 44 |
+
"list_leaderboard_versions",
|
| 45 |
+
"raw_rows_for_version",
|
| 46 |
+
"LATEST_VERSION",
|
| 47 |
]
|
| 48 |
|
| 49 |
csv_lock = threading.Lock()
|
| 50 |
+
# Serializes read-modify-write of the append-only history file (separate from the
|
| 51 |
+
# leaderboard CSV lock so history bookkeeping can never deadlock a result save).
|
| 52 |
+
_history_lock = threading.Lock()
|
| 53 |
|
| 54 |
LEADERBOARD_COLUMNS = [c.name for c in fields(AutoEvalColumn)]
|
| 55 |
|
|
|
|
| 66 |
"submitted_at",
|
| 67 |
]
|
| 68 |
|
| 69 |
+
# Sentinel for "show the live leaderboard" (no historical reconstruction).
|
| 70 |
+
LATEST_VERSION = ""
|
| 71 |
+
|
| 72 |
+
# History rows are leaderboard rows plus version bookkeeping. Stored append-only:
|
| 73 |
+
# each leaderboard change appends only the model row(s) that changed, tagged with a
|
| 74 |
+
# version id (timestamp) so any past state can be reconstructed without full copies.
|
| 75 |
+
HISTORY_FIELDS = [*CSV_FIELDS, "version", "recorded_at", "version_label"]
|
| 76 |
+
|
| 77 |
# Legacy CSV / evaluation wire-format keys -> canonical ``SCENARIO_KEYS`` column names.
|
| 78 |
_LEGACY_WER_TO_CANONICAL: tuple[tuple[str, str], ...] = (
|
| 79 |
("wer_clean", "wer_anechoic_speech"),
|
|
|
|
| 296 |
return []
|
| 297 |
|
| 298 |
|
| 299 |
+
def save_raw_results(rows: list[dict], *, record_version: bool = True):
|
| 300 |
+
"""Write the leaderboard CSV back to storage.
|
| 301 |
+
|
| 302 |
+
When ``record_version`` is set (default), the model row(s) that changed relative to
|
| 303 |
+
the previously persisted state are appended to the history file so the new state is
|
| 304 |
+
selectable from the leaderboard version dropdown.
|
| 305 |
+
"""
|
| 306 |
if not rows:
|
| 307 |
return
|
| 308 |
+
# Snapshot the previously persisted state BEFORE acquiring csv_lock (load_raw_results
|
| 309 |
+
# also takes csv_lock, which is non-reentrant) so we can diff for versioning.
|
| 310 |
+
prev_rows = load_raw_results() if record_version else None
|
| 311 |
+
|
| 312 |
with csv_lock:
|
| 313 |
normalized = [{k: row.get(k, "") for k in CSV_FIELDS} for row in rows]
|
| 314 |
output = io.StringIO()
|
|
|
|
| 325 |
)
|
| 326 |
invalidate_results_cache()
|
| 327 |
|
| 328 |
+
if record_version:
|
| 329 |
+
# Never let history bookkeeping break a successful result save.
|
| 330 |
+
try:
|
| 331 |
+
_record_history_from_diff(prev_rows or [], normalized)
|
| 332 |
+
except Exception:
|
| 333 |
+
pass
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
# ---------------------------------------------------------------------------
|
| 337 |
+
# Leaderboard version history (append-only, delta-based)
|
| 338 |
+
# ---------------------------------------------------------------------------
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def _now_iso() -> str:
|
| 342 |
+
"""UTC timestamp with microseconds so successive versions sort and stay unique."""
|
| 343 |
+
from datetime import datetime, timezone
|
| 344 |
+
|
| 345 |
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def load_history_rows() -> list[dict]:
|
| 349 |
+
"""All rows of the append-only history file (chronological), or ``[]`` if absent."""
|
| 350 |
+
if STORAGE_BACKEND != "hf_bucket":
|
| 351 |
+
return []
|
| 352 |
+
try:
|
| 353 |
+
local_path = download_bucket_file(HISTORY_PATH)
|
| 354 |
+
except Exception:
|
| 355 |
+
return []
|
| 356 |
+
try:
|
| 357 |
+
with open(local_path, "r") as f:
|
| 358 |
+
rows = list(csv.DictReader(f))
|
| 359 |
+
except Exception:
|
| 360 |
+
rows = []
|
| 361 |
+
finally:
|
| 362 |
+
try:
|
| 363 |
+
os.unlink(local_path)
|
| 364 |
+
except Exception:
|
| 365 |
+
pass
|
| 366 |
+
return rows
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def _append_history_rows(new_rows: list[dict]) -> None:
|
| 370 |
+
"""Append rows to the history file (read-modify-write of the whole file)."""
|
| 371 |
+
if STORAGE_BACKEND != "hf_bucket" or not new_rows:
|
| 372 |
+
return
|
| 373 |
+
with _history_lock:
|
| 374 |
+
all_rows = load_history_rows() + new_rows
|
| 375 |
+
output = io.StringIO()
|
| 376 |
+
writer = csv.DictWriter(output, fieldnames=HISTORY_FIELDS, extrasaction="ignore")
|
| 377 |
+
writer.writeheader()
|
| 378 |
+
for row in all_rows:
|
| 379 |
+
writer.writerow({k: row.get(k, "") for k in HISTORY_FIELDS})
|
| 380 |
+
upload_to_bucket(
|
| 381 |
+
HF_BUCKET_ID,
|
| 382 |
+
add=[(output.getvalue().encode("utf-8"), HISTORY_PATH)],
|
| 383 |
+
token=HF_TOKEN,
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def _history_label(changed: list[dict]) -> str:
|
| 388 |
+
if len(changed) == 1:
|
| 389 |
+
mid = (changed[0].get("model_id") or "").strip()
|
| 390 |
+
return mid or "leaderboard updated"
|
| 391 |
+
return f"{len(changed)} models updated"
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def _record_history_from_diff(prev_rows: list[dict], curr_rows: list[dict]) -> None:
|
| 395 |
+
"""Append the model rows that changed between ``prev_rows`` and ``curr_rows``.
|
| 396 |
+
|
| 397 |
+
The first ever call seeds a full snapshot ("Initial release") so the earliest
|
| 398 |
+
selectable version reconstructs the complete board; later calls store only deltas.
|
| 399 |
+
"""
|
| 400 |
+
now = _now_iso()
|
| 401 |
+
existing = load_history_rows()
|
| 402 |
+
|
| 403 |
+
if not existing:
|
| 404 |
+
seed = [
|
| 405 |
+
{**{k: r.get(k, "") for k in CSV_FIELDS},
|
| 406 |
+
"version": now, "recorded_at": now, "version_label": "Initial release"}
|
| 407 |
+
for r in curr_rows
|
| 408 |
+
]
|
| 409 |
+
_append_history_rows(seed)
|
| 410 |
+
return
|
| 411 |
+
|
| 412 |
+
prev_by_mid = {(r.get("model_id") or "").strip(): {k: r.get(k, "") for k in CSV_FIELDS}
|
| 413 |
+
for r in prev_rows}
|
| 414 |
+
changed: list[dict] = []
|
| 415 |
+
for r in curr_rows:
|
| 416 |
+
mid = (r.get("model_id") or "").strip()
|
| 417 |
+
if not mid:
|
| 418 |
+
continue
|
| 419 |
+
if prev_by_mid.get(mid) != {k: r.get(k, "") for k in CSV_FIELDS}:
|
| 420 |
+
changed.append(r)
|
| 421 |
+
if not changed:
|
| 422 |
+
return
|
| 423 |
+
|
| 424 |
+
label = _history_label(changed)
|
| 425 |
+
_append_history_rows([
|
| 426 |
+
{**{k: r.get(k, "") for k in CSV_FIELDS},
|
| 427 |
+
"version": now, "recorded_at": now, "version_label": label}
|
| 428 |
+
for r in changed
|
| 429 |
+
])
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
def list_leaderboard_versions() -> list[dict]:
|
| 433 |
+
"""Distinct versions, newest first: ``[{version, label, recorded_at}, ...]``."""
|
| 434 |
+
rows = load_history_rows()
|
| 435 |
+
by_version: dict[str, dict] = {}
|
| 436 |
+
for r in rows:
|
| 437 |
+
v = (r.get("version") or "").strip()
|
| 438 |
+
if not v:
|
| 439 |
+
continue
|
| 440 |
+
by_version[v] = {
|
| 441 |
+
"version": v,
|
| 442 |
+
"label": (r.get("version_label") or v),
|
| 443 |
+
"recorded_at": (r.get("recorded_at") or v),
|
| 444 |
+
}
|
| 445 |
+
items = list(by_version.values())
|
| 446 |
+
items.sort(key=lambda d: d["recorded_at"], reverse=True)
|
| 447 |
+
return items
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
def raw_rows_for_version(version: str) -> list[dict]:
|
| 451 |
+
"""Reconstruct the leaderboard (CSV_FIELDS rows) as of ``version``.
|
| 452 |
+
|
| 453 |
+
Accumulates every history entry recorded at or before the chosen version and keeps
|
| 454 |
+
the most recent row per model. Falls back to the live results if history is missing.
|
| 455 |
+
"""
|
| 456 |
+
if not version:
|
| 457 |
+
return load_raw_results()
|
| 458 |
+
rows = load_history_rows()
|
| 459 |
+
if not rows:
|
| 460 |
+
return load_raw_results()
|
| 461 |
+
|
| 462 |
+
target = None
|
| 463 |
+
for r in rows:
|
| 464 |
+
if (r.get("version") or "").strip() == version:
|
| 465 |
+
ra = r.get("recorded_at") or ""
|
| 466 |
+
if target is None or ra > target:
|
| 467 |
+
target = ra
|
| 468 |
+
if target is None:
|
| 469 |
+
return load_raw_results()
|
| 470 |
+
|
| 471 |
+
# File order is chronological, so the last entry per model wins.
|
| 472 |
+
by_mid: dict[str, dict] = {}
|
| 473 |
+
for r in rows:
|
| 474 |
+
if (r.get("recorded_at") or "") <= target:
|
| 475 |
+
mid = (r.get("model_id") or "").strip()
|
| 476 |
+
if mid:
|
| 477 |
+
by_mid[mid] = r
|
| 478 |
+
|
| 479 |
+
out: list[dict] = []
|
| 480 |
+
for r in by_mid.values():
|
| 481 |
+
row = {k: r.get(k, "") for k in CSV_FIELDS}
|
| 482 |
+
if not row.get("eval_family"):
|
| 483 |
+
row["eval_family"] = "N/A"
|
| 484 |
+
row.setdefault("submission_notes", "")
|
| 485 |
+
normalize_legacy_csv_row(row)
|
| 486 |
+
out.append(row)
|
| 487 |
+
return out
|
| 488 |
|
| 489 |
+
|
| 490 |
+
# ---------------------------------------------------------------------------
|
| 491 |
+
# Leaderboard version history (append-only, delta-based)
|
| 492 |
+
# ---------------------------------------------------------------------------
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
def _now_iso() -> str:
|
| 496 |
+
"""UTC timestamp with microseconds so successive versions sort and stay unique."""
|
| 497 |
+
from datetime import datetime, timezone
|
| 498 |
+
|
| 499 |
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def load_history_rows() -> list[dict]:
|
| 503 |
+
"""All rows of the append-only history file (chronological), or ``[]`` if absent."""
|
| 504 |
+
if STORAGE_BACKEND != "hf_bucket":
|
| 505 |
+
return []
|
| 506 |
+
try:
|
| 507 |
+
local_path = download_bucket_file(HISTORY_PATH)
|
| 508 |
+
except Exception:
|
| 509 |
+
return []
|
| 510 |
+
try:
|
| 511 |
+
with open(local_path, "r") as f:
|
| 512 |
+
rows = list(csv.DictReader(f))
|
| 513 |
+
except Exception:
|
| 514 |
+
rows = []
|
| 515 |
+
finally:
|
| 516 |
+
try:
|
| 517 |
+
os.unlink(local_path)
|
| 518 |
+
except Exception:
|
| 519 |
+
pass
|
| 520 |
+
return rows
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
def _append_history_rows(new_rows: list[dict]) -> None:
|
| 524 |
+
"""Append rows to the history file (read-modify-write of the whole file)."""
|
| 525 |
+
if STORAGE_BACKEND != "hf_bucket" or not new_rows:
|
| 526 |
+
return
|
| 527 |
+
with _history_lock:
|
| 528 |
+
all_rows = load_history_rows() + new_rows
|
| 529 |
+
output = io.StringIO()
|
| 530 |
+
writer = csv.DictWriter(output, fieldnames=HISTORY_FIELDS, extrasaction="ignore")
|
| 531 |
+
writer.writeheader()
|
| 532 |
+
for row in all_rows:
|
| 533 |
+
writer.writerow({k: row.get(k, "") for k in HISTORY_FIELDS})
|
| 534 |
+
upload_to_bucket(
|
| 535 |
+
HF_BUCKET_ID,
|
| 536 |
+
add=[(output.getvalue().encode("utf-8"), HISTORY_PATH)],
|
| 537 |
+
token=HF_TOKEN,
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
def _history_label(changed: list[dict]) -> str:
|
| 542 |
+
if len(changed) == 1:
|
| 543 |
+
mid = (changed[0].get("model_id") or "").strip()
|
| 544 |
+
return mid or "leaderboard updated"
|
| 545 |
+
return f"{len(changed)} models updated"
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
def _record_history_from_diff(prev_rows: list[dict], curr_rows: list[dict]) -> None:
|
| 549 |
+
"""Append the model rows that changed between ``prev_rows`` and ``curr_rows``.
|
| 550 |
+
|
| 551 |
+
The first ever call seeds a full snapshot ("Initial release") so the earliest
|
| 552 |
+
selectable version reconstructs the complete board; later calls store only deltas.
|
| 553 |
+
"""
|
| 554 |
+
now = _now_iso()
|
| 555 |
+
existing = load_history_rows()
|
| 556 |
+
|
| 557 |
+
if not existing:
|
| 558 |
+
seed = [
|
| 559 |
+
{**{k: r.get(k, "") for k in CSV_FIELDS},
|
| 560 |
+
"version": now, "recorded_at": now, "version_label": "Initial release"}
|
| 561 |
+
for r in curr_rows
|
| 562 |
+
]
|
| 563 |
+
_append_history_rows(seed)
|
| 564 |
+
return
|
| 565 |
+
|
| 566 |
+
prev_by_mid = {(r.get("model_id") or "").strip(): {k: r.get(k, "") for k in CSV_FIELDS}
|
| 567 |
+
for r in prev_rows}
|
| 568 |
+
changed: list[dict] = []
|
| 569 |
+
for r in curr_rows:
|
| 570 |
+
mid = (r.get("model_id") or "").strip()
|
| 571 |
+
if not mid:
|
| 572 |
+
continue
|
| 573 |
+
if prev_by_mid.get(mid) != {k: r.get(k, "") for k in CSV_FIELDS}:
|
| 574 |
+
changed.append(r)
|
| 575 |
+
if not changed:
|
| 576 |
+
return
|
| 577 |
+
|
| 578 |
+
label = _history_label(changed)
|
| 579 |
+
_append_history_rows([
|
| 580 |
+
{**{k: r.get(k, "") for k in CSV_FIELDS},
|
| 581 |
+
"version": now, "recorded_at": now, "version_label": label}
|
| 582 |
+
for r in changed
|
| 583 |
+
])
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
def list_leaderboard_versions() -> list[dict]:
|
| 587 |
+
"""Distinct versions, newest first: ``[{version, label, recorded_at}, ...]``."""
|
| 588 |
+
rows = load_history_rows()
|
| 589 |
+
by_version: dict[str, dict] = {}
|
| 590 |
+
for r in rows:
|
| 591 |
+
v = (r.get("version") or "").strip()
|
| 592 |
+
if not v:
|
| 593 |
+
continue
|
| 594 |
+
by_version[v] = {
|
| 595 |
+
"version": v,
|
| 596 |
+
"label": (r.get("version_label") or v),
|
| 597 |
+
"recorded_at": (r.get("recorded_at") or v),
|
| 598 |
+
}
|
| 599 |
+
items = list(by_version.values())
|
| 600 |
+
items.sort(key=lambda d: d["recorded_at"], reverse=True)
|
| 601 |
+
return items
|
| 602 |
+
|
| 603 |
+
|
| 604 |
+
def raw_rows_for_version(version: str) -> list[dict]:
|
| 605 |
+
"""Reconstruct the leaderboard (CSV_FIELDS rows) as of ``version``.
|
| 606 |
+
|
| 607 |
+
Accumulates every history entry recorded at or before the chosen version and keeps
|
| 608 |
+
the most recent row per model. Falls back to the live results if history is missing.
|
| 609 |
+
"""
|
| 610 |
+
if not version:
|
| 611 |
+
return load_raw_results()
|
| 612 |
+
rows = load_history_rows()
|
| 613 |
+
if not rows:
|
| 614 |
+
return load_raw_results()
|
| 615 |
+
|
| 616 |
+
target = None
|
| 617 |
+
for r in rows:
|
| 618 |
+
if (r.get("version") or "").strip() == version:
|
| 619 |
+
ra = r.get("recorded_at") or ""
|
| 620 |
+
if target is None or ra > target:
|
| 621 |
+
target = ra
|
| 622 |
+
if target is None:
|
| 623 |
+
return load_raw_results()
|
| 624 |
+
|
| 625 |
+
# File order is chronological, so the last entry per model wins.
|
| 626 |
+
by_mid: dict[str, dict] = {}
|
| 627 |
+
for r in rows:
|
| 628 |
+
if (r.get("recorded_at") or "") <= target:
|
| 629 |
+
mid = (r.get("model_id") or "").strip()
|
| 630 |
+
if mid:
|
| 631 |
+
by_mid[mid] = r
|
| 632 |
+
|
| 633 |
+
out: list[dict] = []
|
| 634 |
+
for r in by_mid.values():
|
| 635 |
+
row = {k: r.get(k, "") for k in CSV_FIELDS}
|
| 636 |
+
if not row.get("eval_family"):
|
| 637 |
+
row["eval_family"] = "N/A"
|
| 638 |
+
row.setdefault("submission_notes", "")
|
| 639 |
+
normalize_legacy_csv_row(row)
|
| 640 |
+
out.append(row)
|
| 641 |
+
return out
|
| 642 |
+
|
| 643 |
+
|
| 644 |
+
def load_results(version: str | None = None) -> pd.DataFrame:
|
| 645 |
"""Load results from storage and return a display-ready DataFrame.
|
| 646 |
|
| 647 |
+
With ``version`` empty/``None`` the live leaderboard is returned. Otherwise the
|
| 648 |
+
leaderboard is reconstructed from the append-only history as of that version.
|
| 649 |
+
|
| 650 |
The leaderboard intentionally does NOT show eval_family, wall time, or submitted_at.
|
| 651 |
Those fields remain in the CSV (for moderators and analytics) but are omitted from the
|
| 652 |
public table.
|
| 653 |
"""
|
| 654 |
+
if version:
|
| 655 |
+
raw_rows = raw_rows_for_version(version)
|
| 656 |
+
else:
|
| 657 |
+
raw_rows = load_raw_results()
|
| 658 |
+
return _display_df_from_raw_rows(raw_rows)
|
| 659 |
|
| 660 |
+
|
| 661 |
+
def _display_df_from_raw_rows(raw_rows: list[dict]) -> pd.DataFrame:
|
| 662 |
+
"""Transform raw CSV rows into the public, sorted, display-formatted DataFrame."""
|
| 663 |
+
import analytics
|
| 664 |
|
| 665 |
if not raw_rows:
|
| 666 |
return pd.DataFrame(columns=LEADERBOARD_COLUMNS)
|
recipes/registry.py
CHANGED
|
@@ -29,6 +29,15 @@ RECIPES: dict[str, dict] = {
|
|
| 29 |
},
|
| 30 |
"evaluate_relative": "recipes/mega_asr/evaluate.py",
|
| 31 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
RECIPE_CHOICES: list[tuple[str, str]] = [
|
|
|
|
| 29 |
},
|
| 30 |
"evaluate_relative": "recipes/mega_asr/evaluate.py",
|
| 31 |
},
|
| 32 |
+
"zoom_scribe": {
|
| 33 |
+
"title": "Zoom Scribe API (zoom/scribe_v1)",
|
| 34 |
+
"model_id_patterns": ("zoom/", "zoom-scribe", "zoom_scribe"),
|
| 35 |
+
# API model: no checkpoint download. Requires ZOOM_API_KEY as a Space secret.
|
| 36 |
+
"extra_deps": ["requests"],
|
| 37 |
+
"setup_commands": [],
|
| 38 |
+
"env": {},
|
| 39 |
+
"evaluate_relative": "recipes/zoom_scribe/evaluate.py",
|
| 40 |
+
},
|
| 41 |
}
|
| 42 |
|
| 43 |
RECIPE_CHOICES: list[tuple[str, str]] = [
|
recipes/zoom_scribe/evaluate.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Zoom Scribe API custom evaluator for FFASR.
|
| 3 |
+
|
| 4 |
+
Calls the Zoom Scribe v1 transcription endpoint once per audio sample. Adapted from the
|
| 5 |
+
open_asr_leaderboard provider (huggingface/open_asr_leaderboard#130). FFASR serves a
|
| 6 |
+
private held-out set as local files (no public URLs), so audio is uploaded inline as a
|
| 7 |
+
base64 ``data:`` payload; it is downmixed to mono FLAC to stay under Zoom's ~2MB
|
| 8 |
+
local-upload limit.
|
| 9 |
+
|
| 10 |
+
Requires ``ZOOM_API_KEY`` in the environment. For local (on-Space) evaluation, add it as
|
| 11 |
+
a Space repository secret. ``requests`` is pulled in via the recipe's extra deps.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import base64
|
| 17 |
+
import io
|
| 18 |
+
import os
|
| 19 |
+
import time
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
import soundfile as sf
|
| 23 |
+
|
| 24 |
+
ENDPOINT = "https://api.zoom.us/v2/aiservices/scribe/transcribe"
|
| 25 |
+
MAX_UPLOAD_BYTES = 2_000_000 # Zoom Scribe ~2MB local-file limit
|
| 26 |
+
_TIMEOUT_S = 300
|
| 27 |
+
_RETRYABLE_STATUS = (429, 500, 502, 503, 504)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _encode_payload(file: Path) -> tuple[str, float]:
|
| 31 |
+
"""Return ``(data-URI base64 payload, duration_s)``; mono FLAC keeps us under 2MB."""
|
| 32 |
+
audio, sr = sf.read(str(file), dtype="float32", always_2d=True)
|
| 33 |
+
audio = audio.mean(axis=1) # downmix to mono
|
| 34 |
+
duration = len(audio) / float(sr)
|
| 35 |
+
|
| 36 |
+
buf = io.BytesIO()
|
| 37 |
+
sf.write(buf, audio, int(sr), format="FLAC")
|
| 38 |
+
raw = buf.getvalue()
|
| 39 |
+
if len(raw) > MAX_UPLOAD_BYTES:
|
| 40 |
+
raise RuntimeError(
|
| 41 |
+
f"{file.name}: {len(raw)} bytes exceeds Zoom's {MAX_UPLOAD_BYTES}-byte upload limit"
|
| 42 |
+
)
|
| 43 |
+
return f"data:audio/flac;base64,{base64.b64encode(raw).decode('ascii')}", duration
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def evaluate(file: Path) -> str:
|
| 47 |
+
import requests
|
| 48 |
+
|
| 49 |
+
api_key = (os.getenv("ZOOM_API_KEY") or "").strip()
|
| 50 |
+
if not api_key or api_key == "your_api_key":
|
| 51 |
+
raise ValueError(
|
| 52 |
+
"ZOOM_API_KEY environment variable not set (add it as a Space secret)."
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
file_payload, duration = _encode_payload(Path(file))
|
| 56 |
+
# Short clips transcribe in a single pass; longer ones need auto segmentation.
|
| 57 |
+
segmentation_mode = "none" if duration <= 29.9 else "auto"
|
| 58 |
+
|
| 59 |
+
body = {
|
| 60 |
+
"file": file_payload,
|
| 61 |
+
"config": {
|
| 62 |
+
"language": "en-US",
|
| 63 |
+
"segmentation_mode": segmentation_mode,
|
| 64 |
+
"experimental_feature": {"model_pro": True},
|
| 65 |
+
"timestamps": True,
|
| 66 |
+
},
|
| 67 |
+
}
|
| 68 |
+
headers = {
|
| 69 |
+
"Authorization": f"Bearer {api_key}",
|
| 70 |
+
"Content-Type": "application/json",
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
last_err: Exception | None = None
|
| 74 |
+
for attempt in range(4):
|
| 75 |
+
try:
|
| 76 |
+
resp = requests.post(ENDPOINT, headers=headers, json=body, timeout=_TIMEOUT_S)
|
| 77 |
+
if resp.status_code in _RETRYABLE_STATUS:
|
| 78 |
+
raise RuntimeError(f"Zoom API {resp.status_code}: {resp.text[:200]}")
|
| 79 |
+
resp.raise_for_status()
|
| 80 |
+
return resp.json().get("result", {}).get("text_display", "") or ""
|
| 81 |
+
except Exception as e: # noqa: BLE001
|
| 82 |
+
last_err = e
|
| 83 |
+
time.sleep(2.0 * (2 ** attempt))
|
| 84 |
+
raise RuntimeError(f"Zoom Scribe request failed after retries: {last_err}")
|
storage.py
CHANGED
|
@@ -33,6 +33,9 @@ HF_BUCKET_ID = os.environ.get("FFASR_BUCKET_ID", "treble-technologies/FFASR_Lead
|
|
| 33 |
DATASET_PREFIX = ""
|
| 34 |
RESULTS_PATH = os.environ.get("FFASR_RESULTS_PATH", "results/leaderboard.csv")
|
| 35 |
JOBS_STATE_PATH = os.environ.get("FFASR_JOBS_PATH", "results/jobs_state.csv")
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("ds_token")
|
| 38 |
|
|
|
|
| 33 |
DATASET_PREFIX = ""
|
| 34 |
RESULTS_PATH = os.environ.get("FFASR_RESULTS_PATH", "results/leaderboard.csv")
|
| 35 |
JOBS_STATE_PATH = os.environ.get("FFASR_JOBS_PATH", "results/jobs_state.csv")
|
| 36 |
+
# Append-only leaderboard history (one row per changed model per version). Used to
|
| 37 |
+
# reconstruct and display past leaderboard states from the version dropdown.
|
| 38 |
+
HISTORY_PATH = os.environ.get("FFASR_HISTORY_PATH", "results/leaderboard_history.csv")
|
| 39 |
|
| 40 |
HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("ds_token")
|
| 41 |
|