]>
diplodocus.org Git - flac-archive/blob - fa-rip
6 B<fa-rip> - rip a CD for B<fa-flacd>
10 B<fa-rip> [B<--artist> I<artist> B<--title> I<title>] [B<-d> I<device>] [B<-m>] [B<-p> I<post-processor>] [B<-s>] [B<-t> I<track-count>]
14 B<fa-rip> creates a temporary directory for storage of its
15 intermediate files, uses MusicBrainz to create the "cue" file and
16 candidate tags files, and runs C<cdparanoia(1)> to rip the CD to the
19 In order for this CD to be processed by B<fa-flacd>, you must create a
20 "tags" file. This is usually done by renaming one of the
21 candidate-tags files and deleting the others. Don't forget to fill in
22 the DATE tag in the selected candidate before renaming it. If
23 B<fa-rip> could not find any tag information from MusicBrainz, you'll
24 have to fill out the candidate-tags-0 template.
30 =item B<--artist> I<artist> B<--title> I<title>
32 Write candidate-tags files based on I<artist> and album I<title>.
33 Useful if you've already ripped wav files with some other program and
34 just need to set things up for B<fa-flacd>.
36 =item B<-d> [B<--device>] I<device>
38 Use I<device> as the CD-ROM device, instead of the default
39 "/dev/cdrom" or the environment variable CDDEV.
41 =item B<-m> [B<--no-musicbrainz>]
43 Don't connect to MusicBrainz, just write candidate-tags-0.
45 =item B<-p> [B<--post-processor>] I<post-processor>
47 Create a "post-processor" file in the temporary directory containing
48 the line 'I<post-processor> "$@"'. See B<fa-flacd>'s man page for
49 information about this hook.
51 =item B<-s> [B<--single-file>]
53 Rip whole disc to one wav file and configure B<fa-flacd> to encode it
54 to one FLAC file with embedded cuesheet.
56 =item B<-t> [B<--tracks>] I<track-count>
58 Archive only the first I<track-count> tracks. This is handy for
69 B<fa-rip> uses this to rip audio and save the cuesheet for a CD.
70 MusicBrainz::Client can usually figure this out automatically.
76 Written by Eric Gillespie <epg@pretzelnet.org>.
78 flac-archive is free software; you may redistribute it and/or modify
79 it under the same terms as Perl itself.
85 import os
, re
, sys
, tempfile
, time
, traceback
86 from optparse
import OptionParser
90 import musicbrainzngs
.musicbrainz
92 from org
.diplodocus
.util
import catch_EnvironmentError
as c
94 musicbrainzngs
.musicbrainz
.set_useragent(
95 'flac-archive', '0.1', 'https://diplodocus.org/git/flac-archive')
98 #logging.basicConfig(level=logging.DEBUG)
100 # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=439790
103 def mkcue(fp
, disc
, trackcount
=None):
104 c(fp
.write
, 'FILE "dummy.wav" WAVE\n')
106 if trackcount
== None:
107 trackcount
= len(disc
.tracks
)
109 trackcount
= min(trackcount
, len(disc
.tracks
))
111 for i
in xrange(1, trackcount
+1):
112 track
= disc
.tracks
[i
-1]
113 offset
= track
.offset
- MSF_OFFSET
115 minutes
= seconds
= 0
116 sectors
= offset
% 75
118 seconds
= offset
/ 75
120 minutes
= seconds
/ 60
121 seconds
= seconds
% 60
123 c(fp
.write
, ' TRACK %02d AUDIO\n' % (track
.number
,))
124 if i
== 1 and offset
> 0:
125 c(fp
.write
, ' INDEX 00 00:00:00\n')
127 ' INDEX 01 %02d:%02d:%02d\n' % (minutes
, seconds
, sectors
))
131 def tags_file(fn
, trackcount
, various
, artist
=None, album
=None,
132 release_dates
={}, tracks
=[]):
133 fp
= c(file, fn
, 'w')
135 c(fp
.write
, 'ALBUMARTIST=')
137 c(fp
.write
, 'ARTIST=')
139 c(fp
.write
, artist
.encode('utf-8'))
140 c(fp
.write
, '\nALBUM=')
142 c(fp
.write
, album
.encode('utf-8'))
146 for (country
, date
) in release_dates
.items():
148 c(fp
.write
, 'DATE[%s]=%s\n' % (country
, date
))
149 have_date
or c(fp
.write
, 'DATE=\n')
152 trackcount
= min(trackcount
, len(tracks
))
153 for i
in xrange(1, trackcount
+ 1):
156 track
= tracks
.pop(0)
159 artist
= track
.artist
.name
160 various
and c(fp
.write
, 'ARTIST[%d]=%s\n' % (i
,
161 artist
.encode('utf-8')))
162 c(fp
.write
, 'TITLE[%d]=%s\n' % (i
, title
.encode('utf-8')))
166 def cover_art(i
, asin
):
167 url
= 'http://images.amazon.com/images/P/%s.01.MZZZZZZZ.jpg' % (asin
,)
168 fp
= file('cover.front-' + i
, 'w')
169 fp
.write(urllib
.urlopen(url
).read())
172 def tags(releases
, trackcount
):
177 tags_file('candidate-tags-0', trackcount
, False)
180 for release
in releases
:
182 various
= not release
.isSingleArtistRelease()
184 if various
and not seen_various
:
186 tags_file('candidate-tags-0v', trackcount
, various
)
188 tags_file('candidate-tags-' + str(i
), trackcount
, various
,
189 release
.artist
.name
, release
.title
,
190 release
.getReleaseEventsAsDict(),
194 # for i in release.getRelations(): print i.type
195 # http://musicbrainz.org/ns/rel-1.0#Wikipedia
197 # http://musicbrainz.org/ns/rel-1.0#AmazonAsin
199 if asin
not in seen_asins
:
201 cover_art(str(i
), asin
)
203 def rip(device
, trackcount
, single_file
):
205 device
= '/dev/cdrom'
207 argv
= ['cdparanoia', '-d', device
, '1-' + str(trackcount
)]
214 c(os
.execvp
, argv
[0], argv
)
216 def make_post_processor(command
):
220 fd
= c(os
.open, 'post-processor', os
.O_CREAT | os
.O_WRONLY
, 0555)
221 fp
= c(os
.fdopen
, fd
, 'w')
222 c(fp
.write
, command
+' "$@"\n')
225 def get_releases(filter_
, tries
=5):
227 query
= musicbrainz2
.webservice
.Query()
230 return query
.getReleases(filter_
)
231 except musicbrainz2
.webservice
.WebServiceError
, e
:
232 if '503' not in e
.msg
:
235 sys
.stderr
.write('getReleases: %s: ' % e
)
237 sys
.stderr
.write('giving up\n')
240 sys
.stderr
.write('sleeping %ds before retry...\n' % sleep
)
243 def releases_by_disc(disc_id
):
245 musicbrainzngs
.musicbrainz
.get_releases_by_discid(disc_id
)
246 except musicbrainzngs
.musicbrainz
.ResponseError
:
250 def releases_by(q
, title
, artist
=None):
251 filter_
= musicbrainz2
.webservice
.ReleaseFilter(title
=title
)
252 results
= get_releases(filter_
)
253 releases
= (result
.release
for result
in results
)
255 pattern
= re
.sub(r
'\s+', r
'\s+', artist
.strip())
256 releases
= (x
for x
in releases
257 if re
.match(pattern
, x
.artist
.name
, re
.IGNORECASE
))
261 # Control the exit code for any uncaught exceptions.
263 parser
= OptionParser()
264 parser
.disable_interspersed_args()
265 parser
.add_option('--artist')
266 parser
.add_option('--discid')
267 parser
.add_option('--title')
268 parser
.add_option('--print-discid', action
='store_true', default
=False)
269 parser
.add_option('-d', '--device')
270 parser
.add_option('-m', '--no-musicbrainz',
271 action
='store_true', default
=False)
272 parser
.add_option('-p', '--post-processor')
273 parser
.add_option('-s', '--single-file',
274 action
='store_true', default
=False)
275 parser
.add_option('-t', '--tracks', type='int', default
=99)
277 traceback
.print_exc()
281 # Raises SystemExit on invalid options in argv.
282 (options
, args
) = parser
.parse_args(argv
[1:])
283 except Exception, error
:
284 if isinstance(error
, SystemExit):
286 traceback
.print_exc()
290 device
= options
.device
291 trackcount
= options
.tracks
293 tempdir
= c((lambda x
: tempfile
.mkdtemp(prefix
=x
, dir='.')),
295 sys
.stderr
.write('ripping to %s\n\n' % (tempdir
,))
298 make_post_processor(options
.post_processor
)
300 if options
.title
!= None:
301 tags(releases_by(q
, options
.title
, options
.artist
), trackcount
)
302 elif options
.discid
!= None:
303 tags(releases_by_disc(options
.discid
), trackcount
)
305 disc
= discid
.disc
.read(device
)
306 if options
.print_discid
:
309 fp
= c(file, 'cue', 'w')
310 trackcount
= mkcue(fp
, disc
, trackcount
)
312 if options
.no_musicbrainz
:
315 releases
= releases_by_disc(disc
.id)
316 tags(releases
, trackcount
)
317 rip(device
, trackcount
, options
.single_file
)
318 except Exception, error
:
319 if isinstance(error
, SystemExit):
321 # check all print_exc and format_exc in fa-flacd.py; i think
322 # for some i don't do this msg print check
323 sys
.stderr
.write(getattr(error
, 'msg', ''))
324 traceback
.print_exc()
329 if __name__
== '__main__':
330 sys
.exit(main(sys
.argv
))