]>
diplodocus.org Git - flac-archive/blob - flac2mp3
1 #! /usr/bin/env python2.4
6 B<flac2mp3> - transcode FLAC file to MP3 files
10 B<flac2mp3> [B<--lame-options> I<lame-options>] [B<-j> I<jobs>] [B<-q>] [B<-v>] I<file> [...]
14 B<flac2mp3> transcodes the FLAC files I<file> to MP3 files. I<file>
15 may be the kind of FLAC file B<fa-flacd> generates. That is, it
16 contains a cue sheet, one TITLE tag per track listed therein, and
17 ARTIST, ALBUM, and DATE tags.
23 =item B<--lame-options> I<lame-options>
25 Pass I<lame-options> to B<lame>. This ends up being passed to the
26 shell, so feel free to take advantage of that. You'll almost
27 certainly have to put I<lame-options> in single quotes.
29 =item B<-j> [B<--jobs>] I<jobs>
31 Run up to I<jobs> jobs instead of the default 1.
33 =item B<-q> [B<--quiet>]
35 Suppress status information. This option is passed along to B<flac>
38 =item B<-v> [B<--verbose>]
40 Print diagnostic information. This option is passed along to B<flac>
47 Written by Eric Gillespie <epg@pretzelnet.org>.
51 ''' #' # python-mode is sucks
53 import re
, sys
, traceback
54 from optparse
import OptionParser
55 from subprocess
import Popen
, PIPE
57 import org
.diplodocus
.jobs
58 from org
.diplodocus
import flac
, taglib
59 from org
.diplodocus
.util
import run_or_die
61 ################################################################################
64 def flac2mp3(fn
, title
, artist
, album
, date
, track
, skip_until
, pics
=None):
65 (title
, artist
, album
, date
) = [(x
== None and 'unknown') or x
66 for x
in (title
, artist
, album
, date
)]
68 (skip_arg
, until_arg
) = skip_until
70 skip_arg
= until_arg
= ''
73 flac_options
= '--silent'
79 if lame_options
!= None:
80 tmp
.append(lame_options
)
82 tmp
.append('--preset standard');
83 quiet
and tmp
.append('--quiet')
84 verbose
and tmp
.append('--verbose')
85 lame_options
= ' '.join(tmp
)
87 outfile
= ('%s (%s) %02d %s.mp3' % (artist
, album
,
88 track
, title
)).replace('/', '_')
90 # Escape any single quotes ' so we can quote this.
92 album
, date
) = [x
.replace("'", r
"'\''")
93 for x
in (fn
, title
, artist
, album
, date
)]
95 quoted_outfile
= ('%s (%s) %02d %s.mp3' % (artist
, album
,
96 track
, title
)).replace('/', '_')
98 run_or_die(3, "flac %s -cd %s %s '%s' | lame --add-id3v2 %s --tt '%s' --ta '%s' --tl '%s' --ty '%s' --tn %d - '%s'"
99 % (flac_options
, skip_arg
or '', until_arg
or '', fn
,
100 lame_options
, title
, artist
, album
, date
, track
,
104 taglib
.add_apic_frame_to_mp3(outfile
, pics
)
108 ################################################################################
111 def tformat(m
, s
, c
):
112 return '%02d:%02d.%02d' % (m
, s
, c
)
114 def get_decode_args(fn
):
117 p
= Popen(['metaflac', '--export-cuesheet-to=-', fn
], stdout
=PIPE
)
118 for line
in (x
.rstrip() for x
in p
.stdout
):
119 m
= re
.search(r
'INDEX 01 (\d\d):(\d\d):(\d\d)$', line
)
121 l
.append(map(int, m
.groups()))
123 # XXX dataloss! check status
126 for i
in xrange(len(l
)):
127 arg
= ['--skip=' + tformat(*l
[i
])]
135 arg
.append('--until=' + tformat(next
[0] - 1, 59, 74))
137 arg
.append('--until=' + tformat(next
[0], next
[1] - 1,
140 arg
.append('--until=' + tformat(next
[0], next
[1],
145 # If no cue sheet, stick a dummy in here.
151 # XXX other things should usue this; flac files, for example, should
152 # get PART as part of the filelname, same as mp3s.
157 return len(self
._tags
)
158 def get(self
, key
, track
=None):
162 return self
._tags
[None][key
]
164 return self
._tags
[track
][key
]
166 return self
._tags
[None][key
]
169 def gets(self
, key
, track
=None):
170 value
= self
.get(key
, track
)
173 return '\n'.join(value
)
174 def set(self
, key
, value
, track
=None):
175 if track
not in self
._tags
:
176 self
._tags
[track
] = {}
177 if key
not in self
._tags
[track
]:
178 self
._tags
[track
][key
] = []
179 self
._tags
[track
][key
].append(value
)
182 '''Return the ARTIST, ALBUM, and DATE tags followed by the TITLE tags
187 p
= Popen(['metaflac', '--export-tags-to=-', fn
], stdout
=PIPE
)
188 for line
in (x
.rstrip() for x
in p
.stdout
):
189 (tag
, value
) = line
.split('=', 1)
191 m
= re
.search(r
'\[([0-9]+)]$', tag
)
193 tag
= tag
[:m
.start()]
194 track
= int(m
.group(1))
198 tags
.set(tag
, value
, track
)
199 # XXX dataloss! check status
204 def find_pics(fn
, tags
):
205 pics
= tags
.get('__flac2mp3_PICTURE')
207 if not isinstance(pics
, list):
208 pics
= flac
.get_pictures(fn
)
209 tags
.set('__flac2mp3_PICTURE', pics
)
214 # Control the exit code for any uncaught exceptions.
216 parser
= OptionParser()
217 parser
.disable_interspersed_args()
218 parser
.add_option('-X', '--debug', action
='store_true', default
=False)
219 parser
.add_option('-j', '--jobs', type='int', default
=1)
220 parser
.add_option('--lame-options')
221 parser
.add_option('-q', '--quiet', action
='store_true', default
=False)
222 parser
.add_option('-v', '--verbose', action
='store_true', default
=False)
224 traceback
.print_exc()
228 # Raises SystemExit on invalid options in argv.
229 (options
, args
) = parser
.parse_args(argv
[1:])
230 except Exception, error
:
231 if isinstance(error
, SystemExit):
233 traceback
.print_exc()
237 global debug
, flac_options
, lame_options
, quiet
, verbose
238 debug
= options
.debug
239 lame_options
= options
.lame_options
240 quiet
= options
.quiet
241 verbose
= options
.verbose
246 args
= get_decode_args(fn
)
249 album
= tags
.gets('ALBUM')
250 discnum
= tags
.gets('DISCNUMBER')
251 track
= tags
.gets('TRACKNUMBER')
253 # lame doesn't seem to support disc number.
255 album
= '%s (disc %s)' % (album
, discnum
)
257 # Stupid hack: only a single-track file should have the
258 # TRACKNUMBER tag, so use it if set for the first pass through
259 # the loop. At the end of the loop, we'll set $track for the
260 # next run, so this continues to work for multi-track files.
266 for i
in range(len(tags
)):
267 title
= tags
.gets('TITLE', track
)
268 part
= tags
.gets('PART', track
)
270 title
= '%s - %s' % (title
, part
)
271 jobs
.append([fn
, title
,
272 tags
.gets('ARTIST', track
),
274 tags
.gets('DATE', track
),
275 track
, args
[i
], find_pics(fn
, tags
)])
277 except Exception, error
:
278 sys
.stderr
.write(getattr(error
, 'msg', ''))
279 traceback
.print_exc()
280 sys
.stderr
.write('Continuing...\n')
287 return lambda: flac2mp3(*job
)
288 org
.diplodocus
.jobs
.run(maxjobs
=options
.jobs
, debug
=debug
, get_job
=getjob
)
289 except Exception, error
:
290 if isinstance(error
, SystemExit):
292 # check all print_exc and format_exc in fa-flacd.py; i think
293 # for some i don't do this msg print check
294 sys
.stderr
.write(getattr(error
, 'msg', ''))
295 traceback
.print_exc()
300 if __name__
== '__main__':
301 sys
.exit(main(sys
.argv
))