This commit is contained in:
2026-04-14 15:59:02 +02:00
parent d4356e7337
commit 287477753e
4 changed files with 85 additions and 21 deletions

View File

@@ -30,6 +30,7 @@ class SongData(BaseModel):
bpm: int = 0 bpm: int = 0
duration_sec: int = 0 duration_sec: int = 0
file_format: str = "" file_format: str = ""
mbid: str = ""
class DanceData(BaseModel): class DanceData(BaseModel):
name: str name: str
@@ -97,22 +98,30 @@ def push(
for s in payload.songs: for s in payload.songs:
if not s.title: if not s.title:
continue continue
# Match globalt på titel+artist — samme sang deles på tværs af brugere # Match 1: MBID — sikrest
existing = db.query(Song).filter( existing = None
Song.title == s.title, if s.mbid:
Song.artist == s.artist, existing = db.query(Song).filter_by(mbid=s.mbid).first()
).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: if existing:
song_id_map[s.local_id] = existing.id 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: if s.bpm and not existing.bpm:
existing.bpm = s.bpm existing.bpm = s.bpm
if s.mbid and not existing.mbid:
existing.mbid = s.mbid
else: else:
song = Song( song = Song(
owner_id=me.id, owner_id=me.id,
title=s.title, artist=s.artist, album=s.album, title=s.title, artist=s.artist, album=s.album,
bpm=s.bpm, duration_sec=s.duration_sec, bpm=s.bpm, duration_sec=s.duration_sec,
file_format=s.file_format, file_format=s.file_format,
mbid=s.mbid or None,
) )
db.add(song) db.add(song)
db.flush() db.flush()

View File

@@ -89,10 +89,25 @@ def scan_library(library_id: int, library_path: str, db_path: str,
tags = read_tags(fp) tags = read_tags(fp)
extra = json.dumps(tags.get("extra_tags", {}), ensure_ascii=False) 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 # Match 1: præcis sti-match
existing = conn.execute( if not existing:
"SELECT id, bpm FROM songs WHERE local_path=?", (path_str,) existing = conn.execute(
).fetchone() "SELECT id, bpm FROM songs WHERE local_path=?", (path_str,)
).fetchone()
# Match 2: titel+artist match — fil er flyttet eller var missing # Match 2: titel+artist match — fil er flyttet eller var missing
if not existing: if not existing:
@@ -131,28 +146,31 @@ def scan_library(library_id: int, library_path: str, db_path: str,
bpm = tags.get("bpm", 0) bpm = tags.get("bpm", 0)
if not overwrite_bpm and existing["bpm"] and existing["bpm"] > 0: if not overwrite_bpm and existing["bpm"] and existing["bpm"] > 0:
bpm = existing["bpm"] # behold eksisterende BPM bpm = existing["bpm"] # behold eksisterende BPM
mbid = tags.get("mbid", "")
conn.execute(""" conn.execute("""
UPDATE songs SET UPDATE songs SET
library_id=?, title=?, artist=?, album=?, library_id=?, title=?, artist=?, album=?,
bpm=?, duration_sec=?, file_format=?, 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=? WHERE id=?
""", (library_id, tags.get("title",""), tags.get("artist",""), """, (library_id, tags.get("title",""), tags.get("artist",""),
tags.get("album",""), bpm, tags.get("duration_sec",0), 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"] song_id = existing["id"]
else: else:
song_id = str(uuid.uuid4()) song_id = str(uuid.uuid4())
conn.execute(""" conn.execute("""
INSERT OR IGNORE INTO songs INSERT OR IGNORE INTO songs
(id, library_id, local_path, title, artist, album, (id, library_id, local_path, title, artist, album,
bpm, duration_sec, file_format, file_modified_at, extra_tags) bpm, duration_sec, file_format, file_modified_at, extra_tags, mbid)
VALUES (?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
""", (song_id, library_id, path_str, """, (song_id, library_id, path_str,
tags.get("title",""), tags.get("artist",""), tags.get("title",""), tags.get("artist",""),
tags.get("album",""), tags.get("bpm",0), tags.get("album",""), tags.get("bpm",0),
tags.get("duration_sec",0), tags.get("file_format",""), 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 # Importer dans-tags fra filen hvis de ikke allerede er i DB
file_dances = tags.get("dances", []) file_dances = tags.get("dances", [])

View File

@@ -132,7 +132,7 @@ class SyncManager:
# Sange # Sange
songs = [] songs = []
for row in conn.execute( 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" "FROM songs WHERE file_missing=0"
).fetchall(): ).fetchall():
songs.append({ songs.append({
@@ -143,6 +143,7 @@ class SyncManager:
"bpm": row["bpm"] or 0, "bpm": row["bpm"] or 0,
"duration_sec": row["duration_sec"] or 0, "duration_sec": row["duration_sec"] or 0,
"file_format": row["file_format"] or "", "file_format": row["file_format"] or "",
"mbid": row["mbid"] or "",
}) })
# Danse # Danse

View File

@@ -175,6 +175,29 @@ def _read_mp3(audio, result: dict):
result["dances"] = [dances[k] for k in sorted(dances.keys())] result["dances"] = [dances[k] for k in sorted(dances.keys())]
result["extra_tags"] = extra 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): def _read_vorbis(audio, result: dict):
"""FLAC og OGG/Opus bruger begge Vorbis Comments.""" """FLAC og OGG/Opus bruger begge Vorbis Comments."""
@@ -212,9 +235,14 @@ def _read_vorbis(audio, result: dict):
except Exception: except Exception:
pass pass
result["extra_tags"] = extra result["extra_tags"] = extra
# MBID
for key in ("musicbrainz_trackid", "musicbrainz recording id"):
def _read_m4a(audio, result: dict): if key in tags:
try:
result["mbid"] = str(tags[key][0]).strip()
except Exception:
pass
break
tags = audio.tags tags = audio.tags
if not tags: if not tags:
return return
@@ -256,9 +284,17 @@ def _read_m4a(audio, result: dict):
except Exception: except Exception:
pass pass
result["extra_tags"] = extra result["extra_tags"] = extra
# MBID — gemmes som freeform atom ----:com.apple.iTunes:MusicBrainz Recording Id
for key in tags:
def _read_generic(audio, result: dict): 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: try:
easy = MutagenFile(result["local_path"], easy=True) easy = MutagenFile(result["local_path"], easy=True)
if easy and easy.tags: if easy and easy.tags: