feat: first tested version
This commit is contained in:
106
mixxx_export.py
106
mixxx_export.py
@@ -1,9 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Exporte tous les cue points de Mixxx vers des fichiers .mixxx (JSON)
|
||||
placés à côté de chaque fichier audio.
|
||||
|
||||
Usage : python3 mixxx_export.py [chemin_db]
|
||||
mixxx_export.py - Exporte TOUTES les métadonnées de préparation Mixxx
|
||||
(cues, BPM, grille de beat, clé) vers des fichiers .mixxx JSON.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
@@ -18,55 +16,109 @@ def export_cues(db_path: Path, output_dir: Path = None):
|
||||
con = sqlite3.connect(db_path)
|
||||
con.row_factory = sqlite3.Row
|
||||
|
||||
# Récupérer cues + métadonnées de library en une seule requête par piste
|
||||
rows = con.execute("""
|
||||
SELECT
|
||||
tl.location AS audio_path,
|
||||
tl.filename AS filename,
|
||||
c.type AS type,
|
||||
c.position AS position,
|
||||
c.length AS length,
|
||||
c.hotcue AS hotcue,
|
||||
c.label AS label,
|
||||
c.color AS color
|
||||
tl.location AS audio_path,
|
||||
tl.filename AS filename,
|
||||
lib.artist AS artist,
|
||||
lib.title AS title,
|
||||
lib.album AS album,
|
||||
lib.genre AS genre,
|
||||
lib.year AS year,
|
||||
lib.comment AS comment,
|
||||
lib.bpm AS bpm,
|
||||
lib.bpm_lock AS bpm_lock,
|
||||
lib.key AS key,
|
||||
lib.key_id AS key_id,
|
||||
lib.cuepoint AS cuepoint,
|
||||
lib.beats AS beats,
|
||||
lib.beats_version AS beats_version,
|
||||
lib.beats_sub_version AS beats_sub_version,
|
||||
c.type AS type,
|
||||
c.position AS position,
|
||||
c.length AS length,
|
||||
c.hotcue AS hotcue,
|
||||
c.label AS label,
|
||||
c.color AS color
|
||||
FROM cues c
|
||||
JOIN library l ON l.id = c.track_id
|
||||
JOIN track_locations tl ON tl.id = l.location
|
||||
JOIN library lib ON lib.id = c.track_id
|
||||
JOIN track_locations tl ON tl.id = lib.location
|
||||
WHERE tl.fs_deleted = 0
|
||||
ORDER BY tl.location, c.hotcue, c.type
|
||||
""").fetchall()
|
||||
|
||||
by_file = {}
|
||||
# Grouper par fichier audio
|
||||
tracks: dict[str, dict] = {}
|
||||
for r in rows:
|
||||
path = r["audio_path"]
|
||||
by_file.setdefault(path, []).append({
|
||||
"type": r["type"],
|
||||
if path not in tracks:
|
||||
tracks[path] = {
|
||||
"filename": r["filename"],
|
||||
"artist": r["artist"] or "",
|
||||
"title": r["title"] or "",
|
||||
"album": r["album"] or "",
|
||||
"genre": r["genre"] or "",
|
||||
"year": r["year"] or "",
|
||||
"comment": r["comment"] or "",
|
||||
"bpm": r["bpm"] or 0.0,
|
||||
"bpm_lock": bool(r["bpm_lock"]),
|
||||
"key": r["key"] or "",
|
||||
"key_id": r["key_id"] or 0,
|
||||
"cuepoint": r["cuepoint"] or 0,
|
||||
"beats_version": r["beats_version"] or "",
|
||||
"beats_sub_version": r["beats_sub_version"] or "",
|
||||
"beats": None, # sera rempli une seule fois
|
||||
"cues": []
|
||||
}
|
||||
# Encoder le BLOB beats en base64 s'il existe
|
||||
if r["beats"] is not None:
|
||||
import base64
|
||||
tracks[path]["beats"] = base64.b64encode(r["beats"]).decode("ascii")
|
||||
|
||||
tracks[path]["cues"].append({
|
||||
"type": r["type"],
|
||||
"position": r["position"],
|
||||
"length": r["length"],
|
||||
"hotcue": r["hotcue"],
|
||||
"label": r["label"] or "",
|
||||
"color": r["color"],
|
||||
"length": r["length"],
|
||||
"hotcue": r["hotcue"],
|
||||
"label": r["label"] or "",
|
||||
"color": r["color"],
|
||||
})
|
||||
|
||||
con.close()
|
||||
|
||||
exported = 0
|
||||
for audio_path, cues in by_file.items():
|
||||
for audio_path, data in tracks.items():
|
||||
audio_file = Path(audio_path)
|
||||
if not audio_file.exists():
|
||||
print(f"[SKIP] fichier introuvable : {audio_path}")
|
||||
continue
|
||||
|
||||
# Déterminer où écrire le .mixxx
|
||||
if output_dir:
|
||||
out = output_dir / (audio_file.stem + audio_file.suffix + ".mixxx")
|
||||
else:
|
||||
out = audio_file.with_suffix(audio_file.suffix + ".mixxx")
|
||||
|
||||
payload = {
|
||||
"version": 1,
|
||||
"filename": audio_file.name,
|
||||
"cues": cues,
|
||||
"version": 2, # version incrémentée car nouveau format
|
||||
"filename": data["filename"],
|
||||
"artist": data["artist"],
|
||||
"title": data["title"],
|
||||
"album": data["album"],
|
||||
"genre": data["genre"],
|
||||
"year": data["year"],
|
||||
"comment": data["comment"],
|
||||
"bpm": data["bpm"],
|
||||
"bpm_lock": data["bpm_lock"],
|
||||
"key": data["key"],
|
||||
"key_id": data["key_id"],
|
||||
"cuepoint": data["cuepoint"],
|
||||
"beats": data["beats"],
|
||||
"beats_version": data["beats_version"],
|
||||
"beats_sub_version": data["beats_sub_version"],
|
||||
"cues": data["cues"],
|
||||
}
|
||||
|
||||
try:
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(json.dumps(payload, indent=2, ensure_ascii=False))
|
||||
@@ -79,7 +131,7 @@ def export_cues(db_path: Path, output_dir: Path = None):
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser = argparse.ArgumentParser(description="Export complet Mixxx (cues + BPM + grille + clé)")
|
||||
parser.add_argument("db_path", nargs="?", default=str(DEFAULT_DB), help="Chemin vers mixxxdb.sqlite")
|
||||
parser.add_argument("--output-dir", help="Dossier de sortie pour les .mixxx (par défaut : à côté de l'audio)")
|
||||
args = parser.parse_args()
|
||||
|
||||
Reference in New Issue
Block a user