-
-
Save OscarL/8b8d5e60e69b5d16d80bbf62c9e23ab7 to your computer and use it in GitHub Desktop.
# Changes over the original: | |
# | |
# + Support both Python 2 & 3. | |
# * fixed location of bitrate parameters. | |
# * Fixed track duration so it does't cuts tracks short, nor starts them early | |
# (Margin of error for .flac source files should at most be around +/- 0.013 secs). | |
# + Added pre_gap support. | |
cue_file = 'file.cue' | |
d = open(cue_file).read().splitlines() | |
general = {} | |
tracks = [] | |
current_file = None | |
def index2seconds(line): | |
mm, ss, ff = list(map(int, ' '.join(line.strip().split(' ')[2:]).replace('"', '').split(':'))) | |
return 60 * mm + ss + ff / 75.0 # each FF is 1/75 of a sec | |
def replace_chars(string, chars=r':\/|<>?*"', replacement='_'): | |
""" | |
Return `string` with any char in the `chars` replaced by `replacement`. | |
Defaults to replace problematic/invalid chars for filenames/paths. | |
""" | |
for c in string: | |
if c in chars: | |
string = string.replace(c, replacement) | |
return string | |
for line in d: | |
if line.startswith('REM GENRE '): | |
general['genre'] = ' '.join(line.split(' ')[2:]) | |
if line.startswith('REM DATE '): | |
general['date'] = ' '.join(line.split(' ')[2:]) | |
if line.startswith('PERFORMER '): | |
general['artist'] = ' '.join(line.split(' ')[1:]).replace('"', '') | |
if line.startswith('TITLE '): | |
general['album'] = ' '.join(line.split(' ')[1:]).replace('"', '') | |
if line.startswith('FILE '): | |
current_file = ' '.join(line.split(' ')[1:-1]).replace('"', '') | |
if line.startswith(' TRACK '): | |
track = general.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 00 '): | |
tracks[-1]['pre_gap'] = index2seconds(line) | |
if line.startswith(' INDEX 01 '): | |
tracks[-1]['start'] = index2seconds(line) | |
for i in range(len(tracks) - 1): | |
if 'pre_gap' in tracks[i + 1]: | |
tracks[i]['duration'] = tracks[i + 1]['pre_gap'] - tracks[i]['start'] | |
else: | |
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)) | |
} | |
if 'genre' in track: | |
metadata['genre'] = track['genre'] | |
if 'date' in track: | |
metadata['date'] = track['date'] | |
cmd = 'ffmpeg' | |
cmd += ' -i "%s"' % current_file | |
cmd += ' -b:a 320k' | |
cmd += ' -ss %.2d:%.2d:%09.6f' % (track['start'] / 60 / 60, track['start'] / 60 % 60, track['start'] % 60) | |
if 'duration' in track: | |
cmd += ' -t %.2d:%.2d:%09.6f' % (track['duration'] / 60 / 60, track['duration'] / 60 % 60, track['duration'] % 60) | |
cmd += ' ' + ' '.join('-metadata %s="%s"' % (k, v) for (k, v) in metadata.items()) | |
cmd += replace_chars(' "%.2d - %s - %s.mp3"' % (track['track'], track['artist'], track['title'])) | |
print(cmd) |
@OscarL Hi, thanks for your reply.
Yes, there are pre-gaps for all tracks EXCEPT for the first one, just like the example.
By the way, there is a little bug when ':' exists in TITLE. Maybe because ffmpeg uses ':' as a special token?
@maye9999: that seems to be not so much of a problem with ffmpeg, as to with the under-laying filesystem. AFAIK, Macs forbid ":", Linux forbids "/", and Windows forbids a ton of characters and names (<, >, /, . |, ", *, ?) , and ":" is used in NTFS for alternate datastreams.
Workaround: manually replace ":" for " - " in the TITLE lines of your .cue file (or see if the program that created it has an option to do it automatically, most ripping software I used to use had an option like that).
Some more info on stackoverflow.
We should escape such characters to make sure we are using valid filenames, but I feel that it its out of scope for this little gist.
I have a work-in-progress version that actually calls ffmpeg, lets you select the output format, choose if you want metadata or not, things like that... and a bit more error correction. I'll do my best to keep this issue in mind.
I will create a proper git repository for that script, as I want it to have tests, docs, and things like that... all while being a small, easy to follow project for beginners and old timers, like me, that are just a bit rusty :-D
I'll add a comment here when I upload that code.
Thanks again!
@maye9999, I just added a basic "replace invalid chars in filenames with _" (Untested), just in case I take too long to move this into a proper git repo.
@OscarL Thank you very much❤
@maye9999, hi! Thanks for the feedback!
The .cue files I had for testing, either had NO pre-gap in any track, or had pre-gap in ALL the tracks, even for track 1 with a zero pregap (ie:
INDEX 00 00:00:00
), so using eitherif 'pre_gap' in tracks[i]:
orif 'pre_gap' in tracks[i+1]:
didn't actually changed the calculated durations.Does your .cue file has pre-gaps (INDEX 00) for all the tracks EXCEPT for the first one? Like the examples on this wiki?
Not really sure which case is more common. I guess that I'll go with your suggestion until I come up with something more reliable :-D
Thanks again!
I'll go ahead and also change that silly looking code:
for