Cue + FLAC or separate FLACs?
Each has it’s own benefit.
If you want to preserve original cd layout, like I originally did, or you downloaded an album rip, it is likely that whole album/cd is a single .flac file accompanied by a .cue file with timestamps.
Now if you want to play songs separately in a player, car, etc, it is much easier to use separate .flac files. Or maybe your player can’t even play flacs…
FFMPEG
FFMPEG can do the hard work of splitting and transcoding as necessary, but it is quite a typing excercize to input all timestamps and options, so I used a script to read cue file and prepare ffmpeg command line.
This is not perfect approach, since cue files may be inaccurate and vary in their time contents. So some checking is required.
FFmpeg does accept milliseconds in parameters, but CUE doesn’t always provide them. In my case CUE has them, but if yours don’t, tweak the script multipliers on lines “millis= “ cmd time output.
Example output of the script for Nightwish Storytime cd flac:
ffmpeg -i "Nightwish - Showtime, Storytime.flac" -ss 00:00:00 -t 00:04:32 -metadata album="Showtime, Storytime" -metadata title="Dark Chest of Wonders" -metadata track="1/16" -metadata artist="Nightwish" -metadata date="2013" -metadata genre=""Symphonic Metal"" "01 - Dark Chest of Wonders.flac"
…
ffmpeg -i "Nightwish - Showtime, Storytime.flac" -ss 00:25:42 -t 00:05:37 -metadata album="Showtime, Storytime" -metadata title="Storytime" -metadata track="6/16" -metadata artist="Nightwish" -metadata date="2013" -metadata genre=""Symphonic Metal"" "06 - Storytime.flac"
When compared to Audacity’s view, this album lines up pretty nice without any tweaking.
The Script
import sys
import re
if len(sys.argv) < 2:
raise ValueError('USAGE: cuesplit.py <filename.cue> [/output/path]')
cuefile = sys.argv[1]
outputpath = ""
if len(sys.argv) == 3:
outputpath = sys.argv[2]
filecontents = open(cuefile).read().splitlines()
common = {}
tracks = []
curFile = None
for line in filecontents:
# parsing file line by line, storing found known values in arrays
# splitting lines by space, using second value and removing quotes from it
if line.startswith('REM GENRE '):
common['genre'] = ' '.join(line.split(' ')[2:]).replace('"', '')
if line.startswith('REM DATE '):
common['date'] = ' '.join(line.split(' ')[2:])
if line.startswith('PERFORMER '):
common['artist'] = ' '.join(line.split(' ')[1:]).replace('"', '')
if line.startswith('TITLE '):
common['album'] = ' '.join(line.split(' ')[1:]).replace('"', '')
if line.startswith('FILE '):
curFile = ' '.join(line.split(' ')[1:-1]).replace('"', '')
if line.startswith(' TRACK '):
track = common.copy()
track['track'] = int(line.strip().split(' ')[1], 10)
tracks.append(track)
if line.startswith(' TITLE '):
tracks[-1]['title'] = ' '.join(line.strip().split(' ')[1:]).replace('"', '')
if line.startswith(' PERFORMER '):
tracks[-1]['artist'] = ' '.join(line.strip().split(' ')[1:]).replace('"', '')
if line.startswith(' INDEX 01 '):
timestring = ' '.join(line.strip().split(' ')[2:]).replace('"', '')
# in python 2x map produced list, 3+ map behaviour was changed
time = list(map(int, timestring.split(':')))
# convert time to milliseconds for calculations
millis=time[-1] + 1000 * time[-2] + 60000 * time[-3]
# in case of hours
if len(time) == 4:
millis += 3600000 * time [-4]
tracks[-1]['start'] = millis
for i in range(len(tracks)):
if i != len(tracks) - 1:
tracks[i]['duration'] = tracks[i + 1]['start'] - tracks[i]['start']
for track in tracks:
metadata = {
'artist': track['artist'],
'title': track['title'],
'album': track['album'],
'track': str(track['track']) + '/' + str(len(tracks))
}
#add optional values if defined in cuefile
if 'date' in track:
metadata['date'] = track['date']
if 'genre' in track:
metadata['genre'] = track['genre']
#construct ffmpeg command
cmd = 'ffmpeg -i "%s"' % curFile
cmd += ' -ss %.2d:%.2d:%.2d.%.2d' % (track['start'] / 3600000 % 60, track['start'] / 60000 % 60, track['start'] /1000 % 60, track['start'] % 1000 )
if 'duration' in track:
cmd += ' -t %.2d:%.2d:%.2d.%.2d' % (track['duration'] / 3600000 % 60, track['duration'] / 60000 % 60, track['duration'] /1000 % 60, track['duration'] % 1000)
cmd += ' ' + ' '.join('-metadata %s="%s"' % (item, value) for (item, value) in metadata.items())
#Filenames in Windows cannot contain few characters, where as Linux only disallows NUL
cmd += ' "' + outputpath +'%.2d - %s.flac"' % (track['track'], re.sub(r'[\0\\/\:*"<>\|\.%\$\^&£]', '', track['title']))
print(cmd)