diff --git a/backend/library_scan.py b/backend/library_scan.py index 6b00b4c..b1acbb2 100644 --- a/backend/library_scan.py +++ b/backend/library_scan.py @@ -132,7 +132,10 @@ def _read_tags(path: Path) -> Dict: def scan_music() -> List[Dict]: - """Music///. Prefers embedded tags, falls back to path/filename.""" + """Music///. Filename/path-derived only — no file reads, so this + stays fast even over a network share with a large library. Embedded tags (better titles, real + duration) are read later, only for the tracks actually selected for import — see tags_for_file(). + """ if not MUSIC_DIR.is_dir(): return [] out = [] @@ -145,23 +148,24 @@ def scan_music() -> List[Dict]: for f in sorted(album_dir.iterdir()): if not f.is_file() or f.suffix.lower() not in AUDIO_EXTS: continue - tags = _read_tags(f) - track_number = tags["track_number"] - title = tags["title"] - if not title: - fm = _TRACK_NUM_RE.match(f.stem) - if fm: - track_number = track_number or int(fm.group(1)) - title = fm.group(2) - else: - title = f.stem + track_number = None + title = f.stem + fm = _TRACK_NUM_RE.match(f.stem) + if fm: + track_number = int(fm.group(1)) + title = fm.group(2) out.append({ - "artist": tags["artist"] or artist_dir.name, - "album": tags["album"] or album_dir.name, + "artist": artist_dir.name, + "album": album_dir.name, "title": _clean_title(title) or title, "track_number": track_number, - "duration_seconds": round(tags["duration"], 1), + "duration_seconds": 0, "storage_path": str(f), "size_bytes": f.stat().st_size, }) return out + + +def tags_for_file(path_str: str) -> Dict: + """Embedded-tag lookup for a single file — used at import time, not during scan.""" + return _read_tags(Path(path_str)) diff --git a/backend/server.py b/backend/server.py index c7ad3c3..62d6b48 100644 --- a/backend/server.py +++ b/backend/server.py @@ -1431,10 +1431,13 @@ async def scan_import(payload: dict, user: dict = Depends(require_admin)): for it in items: if await db.tracks.find_one({"storage_path": it["storage_path"]}, {"_id": 1}): continue + tags = await asyncio.to_thread(library_scan.tags_for_file, it["storage_path"]) track = Track( - title=it.get("title") or "Untitled", artist=it.get("artist") or "Unknown Artist", - album=it.get("album") or "Unknown Album", track_number=it.get("track_number"), - duration_seconds=it.get("duration_seconds") or 0, storage_path=it["storage_path"], + title=tags.get("title") or it.get("title") or "Untitled", + artist=tags.get("artist") or it.get("artist") or "Unknown Artist", + album=tags.get("album") or it.get("album") or "Unknown Album", + track_number=tags.get("track_number") or it.get("track_number"), + duration_seconds=round(tags.get("duration") or 0, 1), storage_path=it["storage_path"], ).model_dump() await db.tracks.insert_one(track) created += 1