diff --git a/linedance-api/app/routers/sync.py b/linedance-api/app/routers/sync.py index 08f3cc91..9ade08b7 100644 --- a/linedance-api/app/routers/sync.py +++ b/linedance-api/app/routers/sync.py @@ -30,6 +30,7 @@ class SongData(BaseModel): bpm: int = 0 duration_sec: int = 0 file_format: str = "" + mbid: str = "" class DanceData(BaseModel): name: str @@ -97,22 +98,30 @@ def push( for s in payload.songs: if not s.title: continue - # Match globalt på titel+artist — samme sang deles på tværs af brugere - existing = db.query(Song).filter( - Song.title == s.title, - Song.artist == s.artist, - ).first() + # Match 1: MBID — sikrest + existing = None + if s.mbid: + existing = db.query(Song).filter_by(mbid=s.mbid).first() + # Match 2: titel+artist globalt + if not existing: + existing = db.query(Song).filter( + Song.title == s.title, + Song.artist == s.artist, + ).first() if existing: song_id_map[s.local_id] = existing.id - # Opdater BPM hvis det mangler + # Opdater BPM og MBID hvis de mangler if s.bpm and not existing.bpm: existing.bpm = s.bpm + if s.mbid and not existing.mbid: + existing.mbid = s.mbid else: song = Song( owner_id=me.id, title=s.title, artist=s.artist, album=s.album, bpm=s.bpm, duration_sec=s.duration_sec, file_format=s.file_format, + mbid=s.mbid or None, ) db.add(song) db.flush() diff --git a/linedance-app/local/scanner.py b/linedance-app/local/scanner.py index a525c40b..8a570189 100644 --- a/linedance-app/local/scanner.py +++ b/linedance-app/local/scanner.py @@ -89,10 +89,25 @@ def scan_library(library_id: int, library_path: str, db_path: str, tags = read_tags(fp) extra = json.dumps(tags.get("extra_tags", {}), ensure_ascii=False) + # Match 0: MBID-match — sikrest mulige match + existing = None + mbid_from_file = tags.get("mbid", "") + if mbid_from_file: + existing = conn.execute( + "SELECT id, bpm FROM songs WHERE mbid=? LIMIT 1", + (mbid_from_file,) + ).fetchone() + if existing: + conn.execute( + "UPDATE songs SET local_path=? WHERE id=?", + (path_str, existing["id"]) + ) + # Match 1: præcis sti-match - existing = conn.execute( - "SELECT id, bpm FROM songs WHERE local_path=?", (path_str,) - ).fetchone() + if not existing: + existing = conn.execute( + "SELECT id, bpm FROM songs WHERE local_path=?", (path_str,) + ).fetchone() # Match 2: titel+artist match — fil er flyttet eller var missing if not existing: @@ -131,28 +146,31 @@ def scan_library(library_id: int, library_path: str, db_path: str, bpm = tags.get("bpm", 0) if not overwrite_bpm and existing["bpm"] and existing["bpm"] > 0: bpm = existing["bpm"] # behold eksisterende BPM + mbid = tags.get("mbid", "") conn.execute(""" UPDATE songs SET library_id=?, title=?, artist=?, album=?, bpm=?, duration_sec=?, file_format=?, - file_modified_at=?, file_missing=0, extra_tags=? + file_modified_at=?, file_missing=0, extra_tags=?, + mbid=CASE WHEN ? != '' THEN ? ELSE mbid END WHERE id=? """, (library_id, tags.get("title",""), tags.get("artist",""), tags.get("album",""), bpm, tags.get("duration_sec",0), - tags.get("file_format",""), mtime, extra, existing["id"])) + tags.get("file_format",""), mtime, extra, + mbid, mbid, existing["id"])) song_id = existing["id"] else: song_id = str(uuid.uuid4()) conn.execute(""" INSERT OR IGNORE INTO songs (id, library_id, local_path, title, artist, album, - bpm, duration_sec, file_format, file_modified_at, extra_tags) - VALUES (?,?,?,?,?,?,?,?,?,?,?) + bpm, duration_sec, file_format, file_modified_at, extra_tags, mbid) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?) """, (song_id, library_id, path_str, tags.get("title",""), tags.get("artist",""), tags.get("album",""), tags.get("bpm",0), tags.get("duration_sec",0), tags.get("file_format",""), - mtime, extra)) + mtime, extra, tags.get("mbid",""))) # Importer dans-tags fra filen hvis de ikke allerede er i DB file_dances = tags.get("dances", []) diff --git a/linedance-app/local/sync_manager.py b/linedance-app/local/sync_manager.py index 06634204..db7298e4 100644 --- a/linedance-app/local/sync_manager.py +++ b/linedance-app/local/sync_manager.py @@ -132,7 +132,7 @@ class SyncManager: # Sange songs = [] for row in conn.execute( - "SELECT id, title, artist, album, bpm, duration_sec, file_format " + "SELECT id, title, artist, album, bpm, duration_sec, file_format, mbid " "FROM songs WHERE file_missing=0" ).fetchall(): songs.append({ @@ -143,6 +143,7 @@ class SyncManager: "bpm": row["bpm"] or 0, "duration_sec": row["duration_sec"] or 0, "file_format": row["file_format"] or "", + "mbid": row["mbid"] or "", }) # Danse diff --git a/linedance-app/local/tag_reader.py b/linedance-app/local/tag_reader.py index c6456078..1284125b 100644 --- a/linedance-app/local/tag_reader.py +++ b/linedance-app/local/tag_reader.py @@ -175,6 +175,29 @@ def _read_mp3(audio, result: dict): result["dances"] = [dances[k] for k in sorted(dances.keys())] result["extra_tags"] = extra + # MusicBrainz Recording ID (MBID) + mbid = None + # Metode 1: UFID frame + for key, frame in tags.items(): + if key.startswith("UFID") and "musicbrainz.org" in key.lower(): + try: + mbid = frame.data.decode("utf-8", errors="replace").strip() + except Exception: + pass + break + # Metode 2: TXXX:MusicBrainz Recording Id + if not mbid: + for key, frame in tags.items(): + if key.lower() in ("txxx:musicbrainz recording id", + "txxx:musicbrainz_trackid"): + try: + mbid = str(frame.text[0]).strip() + except Exception: + pass + break + if mbid: + result["mbid"] = mbid + def _read_vorbis(audio, result: dict): """FLAC og OGG/Opus bruger begge Vorbis Comments.""" @@ -212,9 +235,14 @@ def _read_vorbis(audio, result: dict): except Exception: pass result["extra_tags"] = extra - - -def _read_m4a(audio, result: dict): + # MBID + for key in ("musicbrainz_trackid", "musicbrainz recording id"): + if key in tags: + try: + result["mbid"] = str(tags[key][0]).strip() + except Exception: + pass + break tags = audio.tags if not tags: return @@ -256,9 +284,17 @@ def _read_m4a(audio, result: dict): except Exception: pass result["extra_tags"] = extra - - -def _read_generic(audio, result: dict): + # MBID — gemmes som freeform atom ----:com.apple.iTunes:MusicBrainz Recording Id + for key in tags: + if "musicbrainz" in key.lower() and "recording" in key.lower(): + try: + val = tags[key][0] + if isinstance(val, (bytes, MP4FreeForm)): + val = val.decode("utf-8", errors="replace") + result["mbid"] = str(val).strip() + except Exception: + pass + break try: easy = MutagenFile(result["local_path"], easy=True) if easy and easy.tags: