feat: first tested version

This commit is contained in:
2026-06-17 21:35:32 +02:00
parent 8c6a25cdb0
commit a82f503e79
4 changed files with 280 additions and 291 deletions

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@ test_*.sqlite
# Ignorer la base de données si jamais elle se retrouve dans le projet
mixxxdb.sqlite
mixxxdb.sqlite.bak
Old/
# Ignorer tous les fichiers .mixxx exportés (sauf exemples)
*.mixxx

6
create_windows_exe.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
export WINEPREFIX="$HOME/.wine_mixxx"
winecfg
wine pyinstaller --onefile --windowed --name mixxx_import mixxx_import.py

View File

@@ -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()

View File

@@ -1,280 +1,249 @@
#!/usr/bin/env python3
"""
mixxx_import.py - Importe les cues Mixxx (fichiers .mixxx JSON) dans la base
de données, crée les pistes manquantes et importe une playlist .m3u.
Version avec interface graphique corrigée.
Mixxx Import Final - Importe les hot cues et la playlist après avoir ajouté
le dossier dans Mixxx manuellement.
Usage : python mixxx_import_final.py
"""
import sqlite3
import json
import sys
import os
import argparse
import base64
import threading
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
# ------------------------------------------------------------
# 1. Fonctions de base
# ------------------------------------------------------------
AUDIO_EXTENSIONS = {".flac", ".mp3", ".opus", ".ogg", ".wav", ".m4a"}
def default_db():
if sys.platform == "win32":
return Path(os.environ["APPDATA"]) / "Mixxx" / "mixxxdb.sqlite"
return Path(os.environ["LOCALAPPDATA"]) / "Mixxx" / "mixxxdb.sqlite"
return Path.home() / ".mixxx" / "mixxxdb.sqlite"
def ensure_track(con, file_path: Path) -> int:
location = str(file_path.resolve())
row = con.execute("SELECT id FROM track_locations WHERE location = ?", (location,)).fetchone()
if row:
return row[0]
filename = file_path.name
directory = str(file_path.parent)
filesize = file_path.stat().st_size
cur = con.execute(
"INSERT INTO track_locations (location, filename, directory, filesize, fs_deleted, needs_verification) VALUES (?,?,?,?,0,0)",
(location, filename, directory, filesize)
)
tl_id = cur.lastrowid
title = file_path.stem
con.execute(
"INSERT INTO library (id, artist, title, album, genre, tracknumber, year, duration, samplerate, bitrate, channels, bpm, key, rating, comment) "
"VALUES (?, '', ?, '', '', '', 0, 0, 0, 0, 0, 0, '', 0, '')",
(tl_id, title)
)
return tl_id
def add_playlist(con, name, track_ids):
cur = con.execute(
"INSERT INTO Playlists (name, position, hidden, date_created, date_modified) VALUES (?, 0, 0, datetime('now'), datetime('now'))",
(name,)
)
playlist_id = cur.lastrowid
for pos, tid in enumerate(track_ids):
con.execute(
"INSERT INTO PlaylistTracks (playlist_id, track_id, position, pl_datetime_added) VALUES (?, ?, ?, datetime('now'))",
(playlist_id, tid, pos)
)
return playlist_id
def import_cues_v2(music_dir: Path, db_path: Path, dry_run=False, overwrite=False, m3u_file=None, log_callback=print):
con = sqlite3.connect(db_path)
def import_cues_and_playlist(music_dir: Path, db_path: Path, log_callback=print):
"""
Importe les cues depuis les fichiers .mixxx et la playlist depuis le .m3u.
Les pistes doivent déjà exister dans la base (ajoutées par Mixxx).
"""
con = sqlite3.connect(str(db_path))
try:
log_callback("Scan des fichiers audio...")
audio_files = {}
for ext in AUDIO_EXTENSIONS:
for f in music_dir.rglob(f"*{ext}"):
if f.name not in audio_files:
audio_files[f.name] = f.resolve()
log_callback(f"{len(audio_files)} fichiers audio trouvés.")
filename_to_trackid = {}
for fname, fpath in audio_files.items():
try:
tid = ensure_track(con, fpath)
filename_to_trackid[fname] = tid
except Exception as e:
log_callback(f"[ERREUR] {fname} : {e}")
# --- 1. Import des cues ---
mixxx_files = list(music_dir.rglob("*.mixxx"))
log_callback(f"{len(mixxx_files)} fichier(s) .mixxx trouvé(s).")
total = 0
log_callback(f"Fichiers .mixxx trouvés : {len(mixxx_files)}")
total_cues = 0
tracks_updated = 0
for mf in mixxx_files:
try:
payload = json.loads(mf.read_text(encoding="utf-8"))
except Exception as e:
log_callback(f"[ERREUR] {mf.name} : {e}")
continue
if payload.get("version") != 1:
log_callback(f"[IGNORÉ] {mf.name} : version inconnue")
continue
target = payload.get("filename", "")
tid = filename_to_trackid.get(target)
if tid is None:
log_callback(f"[PAS TROUVÉ] {target}")
continue
cues = payload.get("cues", [])
if not cues:
continue
if overwrite and not dry_run:
con.execute("DELETE FROM cues WHERE track_id = ?", (tid,))
existing = set()
if not overwrite:
rows = con.execute(
"SELECT type, position, length, hotcue, label FROM cues WHERE track_id = ?",
(tid,)
).fetchall()
existing = {(r[0], r[1], r[2] or 0.0, r[3], r[4] or "") for r in rows}
inserted = 0
for c in cues:
key = (c["type"], c["position"], c.get("length", 0.0) or 0.0, c["hotcue"], c.get("label", "") or "")
if key in existing:
if payload.get("version") != 2:
log_callback(f"[IGNORÉ] {mf.name} : version invalide")
continue
if not dry_run:
fname = payload.get("filename", "")
# Trouver la piste existante (créée par Mixxx)
row = con.execute(
"SELECT id FROM track_locations WHERE filename = ?", (fname,)
).fetchone()
if not row:
log_callback(f"[ABSENT] {fname}")
continue
tid = row[0]
# Supprimer les anciens cues
con.execute("DELETE FROM cues WHERE track_id = ?", (tid,))
# Insérer les nouveaux cues
cues = payload.get("cues", [])
for c in cues:
con.execute(
"INSERT INTO cues (track_id, type, position, length, hotcue, label, color) VALUES (?,?,?,?,?,?,?)",
(tid, c["type"], c["position"], c.get("length", 0.0), c["hotcue"], c.get("label", ""), c.get("color", 16744448))
(tid, c["type"], c["position"], c.get("length", 0),
c["hotcue"], c.get("label", ""), c.get("color", 16744448))
)
inserted += 1
status = "[DRY]" if dry_run else "[OK]"
log_callback(f"{status} {target} : {inserted} cues")
total += inserted
if m3u_file and m3u_file.exists():
log_callback(f"\nImport playlist : {m3u_file.name}")
with open(m3u_file, "r", encoding="utf-8") as f:
lines = [l.strip() for l in f if l.strip() and not l.startswith("#")]
track_ids = []
m3u_dir = m3u_file.parent
for rel in lines:
abs_path = (m3u_dir / rel).resolve()
if not abs_path.exists():
log_callback(f"[FICHIER ABSENT] {abs_path}")
continue
tid = ensure_track(con, abs_path)
track_ids.append(tid)
if track_ids and not dry_run:
name = m3u_file.stem
add_playlist(con, name, track_ids)
log_callback(f"Playlist '{name}' créée ({len(track_ids)} pistes).")
elif track_ids:
log_callback(f"(dry-run) Playlist '{m3u_file.stem}' aurait {len(track_ids)} pistes.")
if not dry_run:
con.commit()
# Mettre à jour les métadonnées (BPM, clé, grille)
artist = payload.get("artist", "")
bpm = payload.get("bpm", 0.0)
bpm_lock = 1 if payload.get("bpm_lock") else 0
key = payload.get("key", "")
key_id = payload.get("key_id", 0)
beats = None
if payload.get("beats"):
try:
beats = base64.b64decode(payload["beats"])
except:
pass
con.execute(
"UPDATE library SET artist=?, bpm=?, bpm_lock=?, key=?, key_id=?, "
"beats=?, beats_version=?, beats_sub_version=? WHERE id=?",
(artist, bpm, bpm_lock, key, key_id,
beats,
payload.get("beats_version", ""),
payload.get("beats_sub_version", ""),
tid)
)
tracks_updated += 1
total_cues += len(cues)
log_callback(f"[OK] {fname} : {len(cues)} cues")
except Exception as e:
log_callback(f"[ERREUR] {mf.name} : {e}")
log_callback(f"\n✅ IMPORT TERMINÉ : {total} cues importés.")
log_callback(f"\nCues importés : {total_cues} sur {tracks_updated} pistes")
# --- 2. Import de la playlist ---
m3u_files = list(music_dir.rglob("*.m3u"))
if m3u_files:
m3u = m3u_files[0]
log_callback(f"\nImport playlist : {m3u.name}")
# Lire le .m3u
content = None
for enc in ['utf-8', 'latin-1', 'cp1252']:
try:
with open(m3u, "r", encoding=enc) as f:
content = f.read()
break
except:
continue
if content:
# Extraire les noms de fichiers
lines = []
for l in content.split('\n'):
l = l.strip()
if l and not l.startswith('#EXT'):
lines.append(l)
log_callback(f"Pistes dans le .m3u : {len(lines)}")
# Trouver les track_ids
track_ids = []
for line in lines:
fname = Path(line).name
row = con.execute(
"SELECT id FROM track_locations WHERE filename = ?", (fname,)
).fetchone()
if row:
track_ids.append(row[0])
else:
log_callback(f" [ABSENT] {fname}")
if track_ids:
# Supprimer l'ancienne playlist
con.execute(
"DELETE FROM PlaylistTracks WHERE playlist_id IN "
"(SELECT id FROM Playlists WHERE name = ?)", (m3u.stem,)
)
con.execute("DELETE FROM Playlists WHERE name = ?", (m3u.stem,))
# Créer la nouvelle playlist
cur = con.execute(
"INSERT INTO Playlists (name, position, hidden, date_created, date_modified) "
"VALUES (?, 0, 0, datetime('now'), datetime('now'))",
(m3u.stem,)
)
pl_id = cur.lastrowid
for pos, tid in enumerate(track_ids):
con.execute(
"INSERT INTO PlaylistTracks (playlist_id, track_id, position, pl_datetime_added) "
"VALUES (?, ?, ?, datetime('now'))",
(pl_id, tid, pos)
)
log_callback(f"Playlist créée : {len(track_ids)} pistes")
else:
log_callback("[ERREUR] Impossible de lire le .m3u")
else:
log_callback("\n[Aucun fichier .m3u trouvé]")
con.commit()
log_callback("\n✅ IMPORT TERMINÉ")
finally:
con.close()
# ------------------------------------------------------------
# 2. Interface graphique Tkinter (CORRIGÉE)
# Interface graphique
# ------------------------------------------------------------
class ImportGUI:
def __init__(self):
self.root = tk.Tk()
self.root.title("Mixxx Import Cues")
self.root.geometry("600x500") # Fenêtre plus grande
self.root.title("Mixxx Import Cues & Playlist")
self.root.geometry("600x500")
self.root.resizable(True, True)
# Variables
self.folder_path = tk.StringVar()
self.m3u_path = tk.StringVar()
self.dry_run = tk.BooleanVar(value=False)
self.overwrite = tk.BooleanVar(value=False)
self.db_path = tk.StringVar(value=str(default_db()))
# Style
self.root.configure(bg="#f0f0f0")
self.create_widgets()
def create_widgets(self):
# Frame principal avec padding
main_frame = ttk.Frame(self.root, padding="20")
main_frame.pack(fill="both", expand=True)
# Titre
title_label = ttk.Label(main_frame, text="Import des Cues Mixxx", font=("Arial", 14, "bold"))
title_label.pack(pady=(0, 20))
ttk.Label(main_frame, text="Import Cues & Playlist Mixxx",
font=("Arial", 14, "bold")).pack(pady=(0, 20))
# Dossier musique
folder_frame = ttk.LabelFrame(main_frame, text="Dossier contenant les fichiers audio et .mixxx", padding="10")
folder_frame.pack(fill="x", pady=(0, 10))
folder_entry_frame = ttk.Frame(folder_frame)
folder_entry_frame.pack(fill="x")
ttk.Entry(folder_entry_frame, textvariable=self.folder_path).pack(side="left", fill="x", expand=True, padx=(0, 5))
ttk.Button(folder_entry_frame, text="📁 Parcourir", command=self.browse_folder).pack(side="right")
# Fichier .m3u
m3u_frame = ttk.LabelFrame(main_frame, text="Fichier .m3u (optionnel)", padding="10")
m3u_frame.pack(fill="x", pady=(0, 10))
m3u_entry_frame = ttk.Frame(m3u_frame)
m3u_entry_frame.pack(fill="x")
ttk.Entry(m3u_entry_frame, textvariable=self.m3u_path).pack(side="left", fill="x", expand=True, padx=(0, 5))
ttk.Button(m3u_entry_frame, text="📁 Parcourir", command=self.browse_m3u).pack(side="right")
frm = ttk.LabelFrame(main_frame, text="Dossier contenant les fichiers .mixxx et .m3u", padding="10")
frm.pack(fill="x", pady=(0, 10))
ef = ttk.Frame(frm); ef.pack(fill="x")
ttk.Entry(ef, textvariable=self.folder_path).pack(side="left", fill="x", expand=True, padx=(0, 5))
ttk.Button(ef, text="📁 Parcourir", command=self.browse_folder).pack(side="right")
# Base de données
db_frame = ttk.LabelFrame(main_frame, text="Base de données Mixxx", padding="10")
db_frame.pack(fill="x", pady=(0, 10))
db_entry_frame = ttk.Frame(db_frame)
db_entry_frame.pack(fill="x")
ttk.Entry(db_entry_frame, textvariable=self.db_path).pack(side="left", fill="x", expand=True, padx=(0, 5))
ttk.Button(db_entry_frame, text="📁 Parcourir", command=self.browse_db).pack(side="right")
frm = ttk.LabelFrame(main_frame, text="Base de données Mixxx", padding="10")
frm.pack(fill="x", pady=(0, 10))
ef = ttk.Frame(frm); ef.pack(fill="x")
ttk.Entry(ef, textvariable=self.db_path).pack(side="left", fill="x", expand=True, padx=(0, 5))
ttk.Button(ef, text="📁 Parcourir", command=self.browse_db).pack(side="right")
# Options
options_frame = ttk.LabelFrame(main_frame, text="Options", padding="10")
options_frame.pack(fill="x", pady=(0, 10))
ttk.Checkbutton(options_frame, text="Dry-run (simulation sans écriture)", variable=self.dry_run).pack(anchor="w", pady=2)
ttk.Checkbutton(options_frame, text="Écraser les cues existants", variable=self.overwrite).pack(anchor="w", pady=2)
# Instructions
instr = ttk.LabelFrame(main_frame, text="Instructions", padding="10")
instr.pack(fill="x", pady=(0, 10))
ttk.Label(instr, text="1. Dans Mixxx : Fichier → Ajouter un dossier (votre dossier Flac)",
font=("Arial", 9)).pack(anchor="w")
ttk.Label(instr, text="2. Fermer Mixxx",
font=("Arial", 9)).pack(anchor="w")
ttk.Label(instr, text="3. Cliquer sur LANCER L'IMPORT ci-dessous",
font=("Arial", 9)).pack(anchor="w")
ttk.Label(instr, text="4. Rouvrir Mixxx",
font=("Arial", 9)).pack(anchor="w")
# Zone de log
log_frame = ttk.LabelFrame(main_frame, text="Journal", padding="5")
log_frame.pack(fill="both", expand=True, pady=(0, 10))
self.log_text = tk.Text(log_frame, height=8, width=70, state="disabled", bg="#1e1e1e", fg="#00ff00", font=("Courier", 9))
logf = ttk.LabelFrame(main_frame, text="Journal", padding="5")
logf.pack(fill="both", expand=True, pady=(0, 10))
self.log_text = tk.Text(logf, height=6, width=70, state="disabled",
bg="#1e1e1e", fg="#00ff00", font=("Courier", 9))
self.log_text.pack(fill="both", expand=True)
scrollbar = ttk.Scrollbar(self.log_text, orient="vertical", command=self.log_text.yview)
scrollbar.pack(side="right", fill="y")
self.log_text.configure(yscrollcommand=scrollbar.set)
scroll = ttk.Scrollbar(self.log_text, orient="vertical", command=self.log_text.yview)
scroll.pack(side="right", fill="y")
self.log_text.configure(yscrollcommand=scroll.set)
# Frame pour les boutons
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill="x", pady=(10, 0))
# Bouton LANCER (bien visible)
self.run_btn = tk.Button(
button_frame,
text="🚀 LANCER L'IMPORT",
command=self.start_import,
bg="#4CAF50", # Vert
fg="white",
font=("Arial", 12, "bold"),
padx=30,
pady=10,
relief="raised",
cursor="hand2"
)
# Boutons
bf = ttk.Frame(main_frame); bf.pack(fill="x", pady=(10, 0))
self.run_btn = tk.Button(bf, text="🚀 LANCER L'IMPORT", command=self.start_import,
bg="#4CAF50", fg="white", font=("Arial", 12, "bold"),
padx=30, pady=10, relief="raised", cursor="hand2")
self.run_btn.pack(side="left", expand=True, fill="x", padx=(0, 5))
# Bouton Quitter
quit_btn = tk.Button(
button_frame,
text="❌ Quitter",
command=self.root.quit,
bg="#f44336", # Rouge
fg="white",
font=("Arial", 12),
padx=20,
pady=10,
cursor="hand2"
)
quit_btn.pack(side="right", expand=True, fill="x", padx=(5, 0))
tk.Button(bf, text="❌ Quitter", command=self.root.quit,
bg="#f44336", fg="white", font=("Arial", 12),
padx=20, pady=10, cursor="hand2").pack(side="right", expand=True, fill="x", padx=(5, 0))
def log(self, message):
self.log_text.configure(state="normal")
@@ -288,28 +257,20 @@ class ImportGUI:
if path:
self.folder_path.set(path)
def browse_m3u(self):
path = filedialog.askopenfilename(title="Sélectionner un fichier .m3u", filetypes=[("M3U files", "*.m3u"), ("All files", "*.*")])
if path:
self.m3u_path.set(path)
def browse_db(self):
path = filedialog.askopenfilename(title="Sélectionner mixxxdb.sqlite", filetypes=[("SQLite", "*.sqlite"), ("All files", "*.*")])
path = filedialog.askopenfilename(
title="Sélectionner mixxxdb.sqlite",
filetypes=[("SQLite", "*.sqlite"), ("All files", "*.*")]
)
if path:
self.db_path.set(path)
def validate(self):
music = Path(self.folder_path.get())
if not music.is_dir():
if not Path(self.folder_path.get()).is_dir():
messagebox.showerror("Erreur", "Le dossier de musique n'existe pas.")
return False
db = Path(self.db_path.get())
if not db.exists():
messagebox.showerror("Erreur", "Le fichier de base de données est introuvable.")
return False
m3u = self.m3u_path.get().strip()
if m3u and not Path(m3u).exists():
messagebox.showerror("Erreur", "Le fichier .m3u indiqué n'existe pas.")
if not Path(self.db_path.get()).exists():
messagebox.showerror("Erreur", "La base de données est introuvable.")
return False
return True
@@ -324,19 +285,15 @@ class ImportGUI:
music_dir = Path(self.folder_path.get())
db_path = Path(self.db_path.get())
dry = self.dry_run.get()
overwrite = self.overwrite.get()
m3u = Path(self.m3u_path.get()) if self.m3u_path.get().strip() else None
def task():
try:
import_cues_v2(music_dir, db_path, dry, overwrite, m3u, log_callback=self.log)
import_cues_and_playlist(music_dir, db_path, log_callback=self.log)
except Exception as e:
self.log(f"❌ ERREUR FATALE : {e}")
finally:
self.run_btn.configure(state="normal", text="🚀 LANCER L'IMPORT")
if not dry:
messagebox.showinfo("Terminé", "Import terminé avec succès !")
messagebox.showinfo("Terminé", "Import terminé !\nRouvrez Mixxx.")
threading.Thread(target=task, daemon=True).start()
@@ -344,32 +301,5 @@ class ImportGUI:
self.root.mainloop()
# ------------------------------------------------------------
# Point d'entrée
# ------------------------------------------------------------
if __name__ == "__main__":
if len(sys.argv) > 1:
parser = argparse.ArgumentParser(description="Import cues Mixxx (console ou GUI)")
parser.add_argument("music_dir", nargs="?", help="Dossier contenant musique + .mixxx")
parser.add_argument("db_path", nargs="?", default=str(default_db()), help="Chemin vers mixxxdb.sqlite")
parser.add_argument("--dry-run", action="store_true", help="Simulation")
parser.add_argument("--overwrite", action="store_true", help="Écraser les cues existants")
parser.add_argument("--m3u", help="Fichier .m3u à importer")
parser.add_argument("--gui", action="store_true", help="Forcer l'interface graphique")
args = parser.parse_args()
if args.gui or not args.music_dir:
ImportGUI().run()
else:
music_dir = Path(args.music_dir)
db_path = Path(args.db_path)
if not music_dir.is_dir():
print("Dossier introuvable")
sys.exit(1)
if not Path(db_path).exists():
print("DB introuvable")
sys.exit(1)
m3u = Path(args.m3u) if args.m3u else None
import_cues_v2(music_dir, db_path, args.dry_run, args.overwrite, m3u, log_callback=print)
else:
ImportGUI().run()
ImportGUI().run()