Skip to content

Instantly share code, notes, and snippets.

@BenMcLean
Created June 9, 2026 14:31
Show Gist options
  • Select an option

  • Save BenMcLean/a2cb70c0321288320c14c1014e333059 to your computer and use it in GitHub Desktop.

Select an option

Save BenMcLean/a2cb70c0321288320c14c1014e333059 to your computer and use it in GitHub Desktop.
Split full-disc FLAC images into frame-accurate, lossless individual tracks using EAC .cue sheets and FFmpeg
@ECHO OFF
cd /d "%~dp0"
PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "& '%~dpn0.ps1' \"%1\" \"%2\" \"%3\" \"%4\" \"%5\" \"%6\" \"%7\" \"%8\""
@PAUSE
param([string]$cueFile)
# Ensure strict error handling
$ErrorActionPreference = "Stop"
if (-not $cueFile -or -not (Test-Path $cueFile)) {
Write-Host "Error: Please drag and drop a valid .cue file." -ForegroundColor Red
pause
return
}
$workingDir = Split-Path $cueFile
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($cueFile)
$flacFile = Join-Path $workingDir "$baseName.flac"
if (-not (Test-Path $flacFile)) {
Write-Host "Error: Could not find matching FLAC file: $flacFile" -ForegroundColor Red
pause
return
}
# Explicitly cast global matches to strings before parsing
function Convert-CueTime ([string]$cueTime) {
if ($cueTime -match "(\d{2}):(\d{2}):(\d{2})") {
[int]$mins = [int]$Matches[1]
[int]$secs = [int]$Matches[2]
[int]$frames = [int]$Matches[3]
[int]$hours = [math]::Floor($mins / 60)
[int]$remMins = $mins % 60
[int]$ms = [math]::Round(($frames / 75) * 1000)
return "{0:D2}:{1:D2}:{2:D2}.{3:D3}" -f $hours, $remMins, $secs, $ms
}
throw "Failed to parse timestamp string: $cueTime"
}
# Parse CUE file securely
try {
$cueContent = Get-Content $cueFile
}
catch {
Write-Host "Error: Failed to read CUE sheet layout text." -ForegroundColor Red
pause
return
}
$tracks = @()
$currentTrack = $null
foreach ($line in $cueContent) {
$line = $line.Trim()
if ($line -match '^TRACK\s+(\d+)\s+AUDIO') {
if ($currentTrack) { $tracks += $currentTrack }
$currentTrack = @{ Number = $Matches[1]; Title = "Track_" + $Matches[1]; Start = $null }
}
elseif ($line -match '^TITLE\s+"([^"]+)"' -and $currentTrack) {
$currentTrack.Title = $Matches[1] -replace '[\/?:*""<>|]', ''
}
elseif ($line -match '^INDEX\s+01\s+(\d{2}:\d{2}:\d{2})' -and $currentTrack) {
$currentTrack.Start = Convert-CueTime $Matches[1]
}
}
if ($currentTrack) { $tracks += $currentTrack }
if ($tracks.Count -eq 0) {
Write-Host "Error: No valid tracks found in the CUE sheet." -ForegroundColor Red
pause
return
}
# Execute processing loop safely
for ($i = 0; $i -lt $tracks.Count; $i++) {
$track = $tracks[$i]
$num = $track.Number
$title = $track.Title
$start = $track.Start
$outputName = Join-Path $workingDir "${num} - ${title}.flac"
# Accurate Stream Copy Ordering: TIMESTAMPS MUST COME BEFORE INPUT (-i)
$ffmpegArgs = @("-ss", $start)
if ($i -lt ($tracks.Count - 1)) {
$end = $tracks[$i + 1].Start
$ffmpegArgs += @("-to", $end)
}
#To skip re-encoding: $ffmpegArgs += @("-i", $flacFile, "-c", "copy", $outputName, "-y")
$ffmpegArgs += @("-i", $flacFile, "-c:a", "flac", "-compression_level", "5", $outputName, "-y")
Write-Host "Stream Copying Track ${num}: ${title} (Start: $start)..." -ForegroundColor Cyan
# Run FFmpeg and capture validation exceptions
& ffmpeg -hide_banner -loglevel error @ffmpegArgs
if ($LASTEXITCODE -ne 0) {
Write-Host "FFmpeg aborted with exit code $LASTEXITCODE processing Track ${num}." -ForegroundColor Red
pause
return
}
}
Write-Host "`nProcessing complete! All tracks successfully split using direct stream copy." -ForegroundColor Green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment