#!/usr/bin/env python3 """ cuesplit.py — split FLAC files using a .cue sheet, via ffmpeg. Handles multi-FILE cues, frame-accurate (MM:SS:FF) timestamps, and uses INDEX 01 (track start, skipping pregap) as the split point. Usage: python3 cuesplit.py "album.cue" [output_dir] Output dir defaults to "split". """ import re import subprocess import sys from pathlib import Path def parse_cue(cue_path): """Returns list of (filename, [(track_num, index01_time_str), ...])""" files = [] current_file = None current_tracks = None pending_track = None with open(cue_path, encoding="utf-8", errors="replace") as f: for line in f: line = line.strip() m = re.match(r'FILE\s+"([^"]+)"', line, re.IGNORECASE) if m: if current_file is not None: files.append((current_file, current_tracks)) current_file = m.group(1) current_tracks = [] continue m = re.match(r'TRACK\s+(\d+)\s+AUDIO', line, re.IGNORECASE) if m: pending_track = int(m.group(1)) continue m = re.match(r'INDEX\s+01\s+(\d+:\d+:\d+)', line, re.IGNORECASE) if m and pending_track is not None: current_tracks.append((pending_track, m.group(1))) pending_track = None continue if current_file is not None: files.append((current_file, current_tracks)) return files def cue_time_to_seconds(t): mm, ss, ff = map(int, t.split(":")) return mm * 60 + ss + ff / 75.0 def seconds_to_ffmpeg_ts(total_seconds): mm = int(total_seconds // 60) ss = total_seconds - mm * 60 return f"{mm:02d}:{ss:06.3f}" def main(): if len(sys.argv) < 2: print("Usage: python3 cuesplit.py [output_dir]") sys.exit(1) cue_path = Path(sys.argv[1]) out_dir = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("split") out_dir.mkdir(parents=True, exist_ok=True) if not cue_path.exists(): print(f"Cue file not found: {cue_path}") sys.exit(1) files = parse_cue(cue_path) if not files: print("No FILE/TRACK entries found in cue — check the cue format.") sys.exit(1) track_counter = 1 for flac_name, tracks in files: flac_path = cue_path.parent / flac_name if not flac_path.exists(): print(f"WARNING: referenced audio file not found: {flac_path}") continue if not tracks: print(f"WARNING: no tracks found for {flac_name}") continue starts = [cue_time_to_seconds(t) for _, t in tracks] for i, start in enumerate(starts): out_file = out_dir / f"{track_counter:02d}.flac" ss = seconds_to_ffmpeg_ts(start) cmd = ["ffmpeg", "-y", "-i", str(flac_path), "-ss", ss] if i + 1 < len(starts): to = seconds_to_ffmpeg_ts(starts[i + 1]) cmd += ["-to", to] cmd += [str(out_file)] print(f"Track {track_counter}: {flac_name} [{ss}" f"{' -> ' + to if i + 1 < len(starts) else ' -> end'}]") result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: print(f" FAILED: {result.stderr[-500:]}") else: print(f" OK -> {out_file}") track_counter += 1 print(f"\nDone. {track_counter - 1} tracks written to {out_dir}/") if __name__ == "__main__": main()