X-Git-Url: https://diplodocus.org/git/flac-archive/blobdiff_plain/578decd98560dc6a1ddb55da9b432bf776585a64..refs/heads/fresh:/flac2mp3 diff --git a/flac2mp3 b/flac2mp3 index 6e76188..4dbb927 100755 --- a/flac2mp3 +++ b/flac2mp3 @@ -1,13 +1,12 @@ -#! /usr/bin/env python2.4 +#!/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,213 +47,103 @@ Written by Eric Gillespie . =cut -''' #' # python-mode is sucks - -import re, sys, traceback -from optparse import OptionParser -from subprocess import Popen, PIPE - -import org.diplodocus.jobs -from org.diplodocus.util import run_or_die - -################################################################################ -# The child processes - -def flac2mp3(fn, title, artist, album, date, track, skip_until): - (title, artist, album, date) = [(x == None and 'unknown') or x - for x in (title, artist, album, date)] - track = int(track) # XXX caller should int this - try: - (skip_arg, until_arg) = skip_until - except ValueError: - skip_arg = until_arg = '' - - 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) - - # Escape any single quotes ' so we can quote this. - (fn, title, artist, - album, date) = [x.replace("'", r"'\''") - for x in (fn, title, artist, album, date)] - - outfile = ('%s (%s) %02d %s.mp3' % (artist, album, - track, title)).replace('/', '_') - - run_or_die(3, "flac %s -cd %s %s '%s' | lame %s --tt '%s' --ta '%s' --tl '%s' --ty '%s' --tn %d - '%s'" - % (flac_options, skip_arg or '', until_arg or '', fn, - lame_options, title, artist, album, date, track, outfile)) - - 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.''' - - date = discnum = track = None - artists = [] - titles = [] - - p = Popen(['metaflac', '--export-vc-to=-', fn], stdout=PIPE) - for line in (x.rstrip() for x in p.stdout): - (tag, value) = line.split('=', 1) - - if re.match(r'ARTIST=', line, re.IGNORECASE): - artist = value - elif re.match(r'ALBUM=', line, re.IGNORECASE): - album = value - elif re.match(r'DATE=', line, re.IGNORECASE): - date = value - elif re.match(r'DISCNUMBER=', line, re.IGNORECASE): - discnum = value - elif re.match(r'TRACKNUMBER=', line, re.IGNORECASE): - track = value - - # XXX Need to support per-track of everything but album and - # discnum, not just TITLE... Also unify with fa-flacd get_tags. - elif re.match(r'ARTIST\[', line, re.IGNORECASE): - artists.append(value) - elif re.match(r'TITLE', line, re.IGNORECASE): - titles.append(value) - # XXX dataloss! check status - status = p.wait() - - # If no TITLEs, stick a dummy in here. - if len(titles) == 0: - titles.append(None) - - return (artist, album, date, discnum, track, artists, titles) - -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 - - 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) - (artist, album, date, discnum, track, - artists, titles) = get_tags(fn) - - # lame doesn't seem to support disc number. - if discnum != None: - album = '%s (disc %d)' % (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 - - for i in range(len(titles)): - if len(artists) > 0: - artist = artists[i] - jobs.append([fn, titles[i], artist, album, - date, track, args[i]]) - 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; + +use lib $FindBin::Bin; + +require 'tags.pl'; +epg::flac::archive::tags->import( + qw[ + track_tags + read_tags_metaflac + mangle_for_file_name + quote + two_digits + ] +); + +sub flac2mp3 { + my $mp3 = shift; + my $flac = shift; + my $tags = shift; + + my @version; + if (defined($tags->{version})) { + @version = ('(' . $tags->{version} . ')'); + } + + # This is an old TODO; what's wrong with --ty ? + # TODO: Look at TDOR, TDRL, TDRC for date. + say( + join( + ' ', + 'flac', + '-cd', + quote($flac), + '|', + 'lame', + '--id3v2-only', + '--id3v2-latin1', + '--pad-id3v2-size', 0, + '--preset standard', + '--ta', + quote($tags->{artist}), + '--tl', + quote($tags->{album}), + '--tn', + quote($tags->{tracknumber}), + '--tt', + quote(join(' ', $tags->{title}, @version)), + '--ty', + quote($tags->{date}), + '$pic_options', + + #(map { ('--tv', quote("TPE2=$_")) } @{$albumartist}), + (map { ('--tv', quote("TPOS=$_")) } @{$tags->{discnumber}}), + '-', + quote($mp3), + ) + ); +} + +sub filename { + my $tags = shift; + mangle_for_file_name( + join(' ', + @{$tags->{ARTIST}}, + @{$tags->{ALBUM}}, + (map { two_digits($_) } @{$tags->{DISCNUMBER} // []}), + (map { two_digits($_) } @{$tags->{TRACKNUMBER} // []}), + @{$tags->{TITLE}}, + @{$tags->{VERSION} // []}, + @{$tags->{PARTNUMBER} // []}, + ) + ) . '.mp3'; +} + +sub main { + for my $flac (@_) { + say('metaflac --export-picture-to=flac2mp3.cover.$$ ', + quote($flac), ' && pic_options="--ti flac2mp3.cover.$$"'); + + # TODO multi-track + my ($tags) = read_tags_metaflac($flac); + flac2mp3(filename($tags), $flac, {track_tags($tags)}); + say('unset pic_options'); + } + say('rm -f flac2mp3.cover.$$'); + + return 0; +} + +if (!caller) { + exit(main(@ARGV)); +} + +1;