Check the audio's length, since the filename can agree and still be wrong

The file that got filed as Michael Jackson's "Chicago" had every textual signal
in its favour: the title matched exactly, and once approved it landed under the
right artist, album and track number. The only thing that disagreed was the
running time -- 359 seconds against Deezer's 245 -- because it was The Fall.

Duration is the one signal a stranger's filename cannot fake, and muse already
had it: Deezer supplies it at request time and it has been sitting unused in the
jobs table. On import the real length is read off the downloaded audio and
compared; a drift past MUSE_LENGTH_TOLERANCE multiplies confidence down and
explains itself in the review queue rather than leaving a bare number.

For this case that is 0.75 -> 0.26, which falls below REVIEW_CONFIDENCE and so
reads as "almost certainly wrong" instead of "worth a look". It runs on import
rather than in confidence() because the file does not exist when the candidate
is chosen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Aryan SinghandClaude Opus 5 committed 2026-09-02 14:12:16 -05:00
1 parent 5b17d5fc8d
commit e9fba3bb1d
3 files changed
+96 -2

No files matched your search

+54 -2
View File
@@ -43,6 +43,7 @@ from pathlib import Path
from urllib.parse import urlencode
import httpx
import mutagen
from fastapi import FastAPI, HTTPException, Query, Request
from fastapi.responses import (FileResponse, HTMLResponse, JSONResponse,
RedirectResponse, Response)
@@ -1304,6 +1305,43 @@ def tag_and_file(job: sqlite3.Row, src: Path) -> Path:
return dest
# How far a file's length may drift from what Deezer said before it stops being
# plausibly the same recording. Generous, because edits, fades, hidden tracks
# and a stray second of silence are all normal; a different song is not.
LENGTH_TOLERANCE = float(os.getenv("MUSE_LENGTH_TOLERANCE", "0.2"))
def length_check(src: Path, want_ms) -> tuple[float, str] | None:
"""Compare the downloaded audio's real length against the request.
The one signal filename matching cannot fake. A file named exactly right,
in a folder named exactly right, is still the wrong song if it runs six
minutes when the track is four -- and that is not hypothetical: The Fall's
"Chicago, Now!" (359s) was filed as Michael Jackson's "Chicago" (245s)
because every *textual* signal agreed.
Returns a multiplier and a reason, or None when there is nothing to say.
"""
want = (want_ms or 0) / 1000.0
if want <= 0:
return None
try:
got = mutagen.File(str(src)).info.length
except Exception:
return None # unreadable is review's problem
if got <= 0:
return None
drift = abs(got - want) / want
if drift <= LENGTH_TOLERANCE:
return None
return (0.35, f"runs {mmss(got)} but the track is {mmss(want)} "
f"({drift:.0%} out), so this is probably a different recording")
def mmss(seconds: float) -> str:
return f"{int(seconds) // 60}:{int(seconds) % 60:02d}"
def downloaded_index() -> dict[str, Path]:
"""Every completed file, by basename.
@@ -1470,12 +1508,26 @@ async def import_worker():
actual = job["size"]
conf = job["confidence"]
why = job["match_why"] or ""
# Applied here rather than in confidence(), because the
# file does not exist yet when the candidate is chosen.
penalty = length_check(src, job["duration"])
if penalty is not None and conf is not None:
mult, reason = penalty
conf = round(conf * mult, 3)
why = f"{why}; {reason}" if why else reason
with db() as c:
c.execute("UPDATE jobs SET confidence=?,"
" match_why=? WHERE id=?",
(conf, why, job["id"]))
log.info("job %s length mismatch: %s",
job["id"][:8], reason)
if conf is not None and conf < AUTO_CONFIDENCE:
band = ("almost certainly wrong"
if conf < REVIEW_CONFIDENCE else "worth a look")
set_state(job["id"], "review",
f"{conf:.0%} confident, {band} -- "
f"{job['match_why'] or ''}", size=actual)
f"{conf:.0%} confident, {band} -- {why}",
size=actual)
await notify_review(job)
continue