diff --git a/.gitignore b/.gitignore index f1e7e19..948b931 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/create_windows_exe.sh b/create_windows_exe.sh new file mode 100755 index 0000000..5edf382 --- /dev/null +++ b/create_windows_exe.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +export WINEPREFIX="$HOME/.wine_mixxx" +winecfg + +wine pyinstaller --onefile --windowed --name mixxx_import mixxx_import.py \ No newline at end of file diff --git a/mixxx_export.py b/mixxx_export.py index 5826911..b6cc134 100755 --- a/mixxx_export.py +++ b/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() diff --git a/mixxx_import.py b/mixxx_import.py index 645e1fc..daaa5b5 100755 --- a/mixxx_import.py +++ b/mixxx_import.py @@ -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() \ No newline at end of file + ImportGUI().run() \ No newline at end of file