MBID
This commit is contained in:
@@ -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 = 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(
|
existing = db.query(Song).filter(
|
||||||
Song.title == s.title,
|
Song.title == s.title,
|
||||||
Song.artist == s.artist,
|
Song.artist == s.artist,
|
||||||
).first()
|
).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()
|
||||||
|
|||||||
@@ -89,7 +89,22 @@ 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
|
||||||
|
if not existing:
|
||||||
existing = conn.execute(
|
existing = conn.execute(
|
||||||
"SELECT id, bpm FROM songs WHERE local_path=?", (path_str,)
|
"SELECT id, bpm FROM songs WHERE local_path=?", (path_str,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
@@ -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", [])
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user