X-Git-Url: https://diplodocus.org/git/flac-archive/blobdiff_plain/64eb2d5f080a1fa083d3cb23f3fb03a8c1665d05..fee6350f22d80d9f54f9ad4dccbfa7696e3e861a:/flac2mp3 diff --git a/flac2mp3 b/flac2mp3 index b951859..d82e61d 100755 --- a/flac2mp3 +++ b/flac2mp3 @@ -1,13 +1,12 @@ -#!/usr/bin/python +#!/usr/local/bin/perl -""" =head1 NAME B - transcode FLAC file to MP3 files =head1 SYNOPSIS -B [B<--lame-options> I] [B<-j> I] [B<-q>] [B<-v>] I [...] +B [B<--lame-options> I] [B<-q>] [B<-v>] I [...] =head1 DESCRIPTION @@ -16,6 +15,10 @@ may be the kind of FLAC file B generates. That is, it contains a cue sheet, one TITLE tag per track listed therein, and ARTIST, ALBUM, and DATE tags. +Note that lame is retarded, and parses B directly itself! So, in order +for it to transcode textual tags, you must specify the encoding in LANG, e.g. +LANG=en_US.utf-8 + =head1 OPTIONS =over 4 @@ -26,10 +29,6 @@ Pass I to B. This ends up being passed to the shell, so feel free to take advantage of that. You'll almost certainly have to put I in single quotes. -=item B<-j> [B<--jobs>] I - -Run up to I jobs instead of the default 1. - =item B<-q> [B<--quiet>] Suppress status information. This option is passed along to B @@ -48,227 +47,111 @@ Written by Eric Gillespie . =cut -""" - -import os, re, sys, tempfile, traceback -from optparse import OptionParser -from subprocess import Popen, PIPE - -import org.diplodocus.jobs -from org.diplodocus.util import run_or_die - -from flac_archive import flac -from flac_archive.tags import Tags - -################################################################################ -# The child processes - -def flac2mp3(fn, title, artist, album_artist, album, date, - track, skip_until, pics=None): - (title, artist, album) = [(x == None and 'unknown') or x - for x in (title, artist, album)] - if date == None: - date = '' - - if quiet: - flac_options = '--silent' - else: - flac_options = '' - - tmp = [] - global lame_options - if lame_options != None: - tmp.append(lame_options) - else: - tmp.append('--preset standard'); - quiet and tmp.append('--quiet') - verbose and tmp.append('--verbose') - lame_options = ' '.join(tmp) - - outfile = ('%s (%s) %02d %s.mp3' % (artist, album, - track, title)).replace('/', '_') - - # Escape any single quotes ' so we can quote this. - (fn, title, artist, album_artist, - album, date) = [(x or '').replace("'", r"'\''") - for x in [fn, title, artist, album_artist, album, date]] - - album_artist_options = '' - if album_artist: - album_artist_options = "--tv 'TPE2=%s'" % album_artist - - quoted_outfile = ('%s (%s) %02d %s.mp3' % (artist, album, - track, title)).replace('/', '_') - - picfn = None - if pics: - (fd, picfn) = tempfile.mkstemp() - f = os.fdopen(fd, 'wb') - f.write(pics[0][7]) - f.close() - try: - run_or_die(3, "flac %s -cd %s '%s' | lame --id3v2-only --id3v2-latin1 --pad-id3v2-size 0 %s --tt '%s' --ta '%s' --tl '%s' --ty '%s' --tn %d --ti '%s' %s - '%s'" - % (flac_options, ' '.join(skip_until), fn, - lame_options, title, artist, album, date, track, - picfn, album_artist_options, quoted_outfile)) - finally: - if picfn: - try: - os.unlink(picfn) - except: - pass - - return 0 - -################################################################################ -# The master process - -def tformat(m, s, c): - return '%02d:%02d.%02d' % (m, s, c) - -def get_decode_args(fn): - l = [] - - p = Popen(['metaflac', '--export-cuesheet-to=-', fn], stdout=PIPE) - for line in (x.rstrip() for x in p.stdout): - m = re.search(r'INDEX 01 (\d\d):(\d\d):(\d\d)$', line) - if m != None: - l.append(map(int, m.groups())) - status = p.wait() - # XXX dataloss! check status - - args = [] - for i in xrange(len(l)): - arg = ['--skip=' + tformat(*l[i])] - try: - next = l[i + 1] - except IndexError: - next = None - if next != None: - if next[2] == 0: - if next[1] == 0: - arg.append('--until=' + tformat(next[0] - 1, 59, 74)) - else: - arg.append('--until=' + tformat(next[0], next[1] - 1, - 74)) - else: - arg.append('--until=' + tformat(next[0], next[1], - next[2] - 1)) - - args.append(arg) - - # If no cue sheet, stick a dummy in here. - if len(args) == 0: - args = [[]] - - return args - -def get_tags(fn): - """Return the ARTIST, ALBUM, and DATE tags followed by the TITLE tags - in the file FN.""" - - tags = Tags() - - p = Popen(['metaflac', '--export-tags-to=-', fn], stdout=PIPE) - tags.load(p.stdout) - - # XXX dataloss! check status - status = p.wait() - - return tags - -def main(argv): - # Control the exit code for any uncaught exceptions. - try: - parser = OptionParser() - parser.disable_interspersed_args() - parser.add_option('-X', '--debug', action='store_true', default=False) - parser.add_option('-j', '--jobs', type='int', default=1) - parser.add_option('--lame-options') - parser.add_option('-q', '--quiet', action='store_true', default=False) - parser.add_option('-v', '--verbose', action='store_true', default=False) - except: - traceback.print_exc() - return 2 - - try: - # Raises SystemExit on invalid options in argv. - (options, args) = parser.parse_args(argv[1:]) - except Exception, error: - if isinstance(error, SystemExit): - return 1 - traceback.print_exc() - return 2 - - separator = ' ' - try: - global debug, flac_options, lame_options, quiet, verbose - debug = options.debug - lame_options = options.lame_options - quiet = options.quiet - verbose = options.verbose - - jobs = [] - for fn in args: - try: - args = get_decode_args(fn) - - tags = get_tags(fn) - album = tags.gets('ALBUM', separator=separator) - discnum = tags.gets('DISCNUMBER') - track = tags.gets('TRACKNUMBER') - - # lame doesn't seem to support disc number. - if discnum != None: - album = '%s (disc %s)' % (album, discnum) - - # Stupid hack: only a single-track file should have the - # TRACKNUMBER tag, so use it if set for the first pass through - # the loop. At the end of the loop, we'll set $track for the - # next run, so this continues to work for multi-track files. - if track == None: - track = 1 - else: - track = int(track) - - pics = flac.get_pictures(fn) - - for i in range(len(tags)): - title = tags.gets('TITLE', track, separator) - part = tags.gets('PART', track) - if part != None: - title = '%s - %s' % (title, part) - artist = tags.get('ARTIST', track) - artist.extend(tags.get('FEATURING', track)) - album_artist = tags.gets('ALBUMARTIST', track) - jobs.append([fn, title, - ', '.join(artist), - album_artist, album, - tags.gets('DATE', track), - track, args[i], pics]) - track = i + 2 - except Exception, error: - sys.stderr.write(getattr(error, 'msg', '')) - traceback.print_exc() - sys.stderr.write('Continuing...\n') - - def getjob(reap): - try: - job = jobs.pop(0) - except IndexError: - return - return lambda: flac2mp3(*job) - org.diplodocus.jobs.run(maxjobs=options.jobs, debug=debug, get_job=getjob) - except Exception, error: - if isinstance(error, SystemExit): - raise - # check all print_exc and format_exc in fa-flacd.py; i think - # for some i don't do this msg print check - sys.stderr.write(getattr(error, 'msg', '')) - traceback.print_exc() - return 2 - - return 0 - -if __name__ == '__main__': - sys.exit(main(sys.argv)) +package epg::flac::archive::mp3; + +use v5.12; +use warnings; + +use File::Temp; +use FindBin; + +require "$FindBin::Bin/tags.pl"; +epg::flac::archive::tags->import( + qw[ + disc_tags + read_tags + mangle_for_file_name + quote + two_digits + ]); + +sub flac2mp3 { + my $quoted_flac = quote(shift); + my $tags = shift; + my ($artist, $album, $date, $discnumber) = disc_tags(%$tags); + + # TODO resurrect whole-disc FLAC? + # Stupid hack: only a single-track file should have the + # TRACKNUMBER tag, so use it if set for the first pass through + # the loop. At the end of the loop, we'll set $track for the + # next run, so this continues to work for multi-track files. + # if track == None: + # track = 1 + # else: + # track = int(track) + + my $tracknumber = epg::flac::archive::tags::one(TRACKNUMBER => $tags); + my $title = epg::flac::archive::tags::one(TITLE => $tags); + # TODO restore PARTNUMBER and VERSION next time i need them + + say('metaflac --export-picture-to=flac2mp3.cover.$$', " $quoted_flac && pic_options=", '"--ti flac2mp3.cover.$$"'); + + # This is an old TODO; what's wrong with --ty ? + # TODO: Look at TDOR, TDRL, TDRC for date. + say(join(' ', + 'flac', + '-cd', + $quoted_flac, + '|', + 'lame', + '--id3v2-only', + '--id3v2-latin1', + '--pad-id3v2-size', 0, + '--preset standard', + '--ta', + quote($artist), + '--tl', + quote($album), + '--tn', + quote($tracknumber), + '--tt', + quote($title), + '--ty', + quote($date), + '$pic_options', + #(map { ('--tv', quote("TPE2=$_")) } @{$albumartist}), + (map { ('--tv', quote("TPOS=$_")) } @{$discnumber}), + '-', + quote( + mangle_for_file_name( + join(' ', + $artist, + $album, + (map { two_digits($_) } @{$discnumber}), + two_digits($tracknumber), + $title, + )) + . '.mp3' + ) + )); + say('unset pic_options'); +} + +sub read_tags_metaflac { + my $fn = shift; + open(my $fh, '-|', 'metaflac', '--no-utf8-convert', '--export-tags-to=-', $fn) || die("metalfac: $!"); + my @result = read_tags($fh); + if (!close($fh)) { + if ($! == 0) { + die("metaflac exited $?") + } + die("close(metaflac): $!") + } + @result +} + +sub main { + for my $fn (@_) { + my ($tags) = read_tags_metaflac($fn); + flac2mp3($fn, $tags); + } + say('rm -f flac2mp3.cover.$$'); + + return 0; +} + +if (!caller) { + exit(main(@ARGV)) +} + +1;