]> diplodocus.org Git - flac-archive/blob - fa-rip
Force ID3v2 tags; when i add ID3v2 APIC, iTunes starts ignoring the
[flac-archive] / fa-rip
1 #! /usr/bin/env python2.4
2
3 '''
4 =head1 NAME
5
6 B<fa-rip> - rip a CD for B<fa-flacd>
7
8 =head1 SYNOPSIS
9
10 B<fa-rip> [B<-d> I<device>] [B<-p> I<post-processor> [B<-t> I<track-count>]
11
12 =head1 DESCRIPTION
13
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
17 "wav" file.
18
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.
25
26 =head1 OPTIONS
27
28 =over 4
29
30 =item B<-d> [B<--device>] I<device>
31
32 Use I<device> as the CD-ROM device, instead of the default
33 "/dev/cdrom" or the environment variable CDDEV.
34
35 =item B<-p> [B<--post-processor>] I<post-processor>
36
37 Create a "post-processor" file in the temporary directory containing
38 the line 'I<post-processor> "$@"'. See B<fa-flacd>'s man page for
39 information about this hook.
40
41 =item B<-t> [B<--tracks>] I<track-count>
42
43 Archive only the first I<track-count> tracks. This is handy for
44 ignoring data tracks.
45
46 =back
47
48 =head1 ENVIRONMENT
49
50 =over 4
51
52 =item CDDEV
53
54 B<fa-rip> uses this to rip audio and save the cuesheet for a CD.
55 MusicBrainz::Client can usually figure this out automatically.
56
57 =back
58
59 =head1 AUTHORS
60
61 Written by Eric Gillespie <epg@pretzelnet.org>.
62
63 flac-archive is free software; you may redistribute it and/or modify
64 it under the same terms as Perl itself.
65
66 =cut
67
68 ''' #' # python-mode is sucks
69
70 import os, sys, tempfile, traceback
71 from optparse import OptionParser
72 import urllib
73
74 import musicbrainz2.disc
75 import musicbrainz2.webservice
76
77 from org.diplodocus.util import catch_EnvironmentError as c
78
79 def mkcue(disc, trackcount=None):
80 fp = c(file, 'cue', 'w')
81 c(fp.write, 'FILE "dummy.wav" WAVE\n')
82 c(fp.write, ' TRACK 01 AUDIO\n')
83 c(fp.write, ' INDEX 01 00:00:00\n')
84
85 if trackcount == None:
86 trackcount = disc.lastTrackNum
87 else:
88 trackcount = min(trackcount, disc.lastTrackNum)
89
90 pregap = disc.tracks[0][0]
91 for i in xrange(disc.firstTrackNum, trackcount):
92 offset = disc.tracks[i][0]
93 offset -= pregap
94
95 minutes = seconds = 0
96 sectors = offset % 75
97 if offset >= 75:
98 seconds = offset / 75
99 if seconds >= 60:
100 minutes = seconds / 60
101 seconds = seconds % 60
102
103 c(fp.write, ' TRACK %02d AUDIO\n' % (i + 1,))
104 c(fp.write,
105 ' INDEX 01 %02d:%02d:%02d\n' % (minutes, seconds, sectors))
106
107 c(fp.close)
108
109 return trackcount
110
111 def tags_file(fn, trackcount, various, artist=None, album=None,
112 release_dates={}, tracks=[]):
113 fp = c(file, fn, 'w')
114 c(fp.write, 'ARTIST=')
115 if artist != None:
116 c(fp.write, artist)
117 c(fp.write, '\nALBUM=')
118 if album != None:
119 c(fp.write, album)
120 c(fp.write, '\n')
121
122 have_date = False
123 for (country, date) in release_dates.items():
124 have_date = True
125 c(fp.write, 'DATE[%s]=%s\n' % (country, date))
126 have_date or c(fp.write, 'DATE=\n')
127
128 for i in xrange(1, trackcount + 1):
129 try:
130 track = tracks.pop(0)
131 title = track.title
132 artist = track.artist
133 except IndexError:
134 title = ''
135 artist = ''
136 various and c(fp.write, 'ARTIST[%d]=%s\n' % (i, artist))
137 c(fp.write, 'TITLE[%d]=%s\n' % (i, title))
138
139 c(fp.close)
140
141 def cover_art(i, asin):
142 url = 'http://images.amazon.com/images/P/%s.01.MZZZZZZZ.jpg' % (asin,)
143 fp = file('cover.front-' + i, 'w')
144 fp.write(urllib.urlopen(url).read())
145 fp.close()
146
147 def tags(disc, trackcount, mb=True):
148 results = []
149 seen_various = False
150
151 tags_file('candidate-tags-0', trackcount, False)
152
153 if not mb:
154 return
155
156 include = musicbrainz2.webservice.ReleaseIncludes(tracks=True)
157 q = musicbrainz2.webservice.Query()
158 filter = musicbrainz2.webservice.ReleaseFilter(discId=disc.getId())
159
160 i = 0
161 for album in q.getReleases(filter):
162 i += 1
163 various = not album.release.isSingleArtistRelease()
164
165 if various and not seen_various:
166 seen_various = True
167 tags_file('candidate-tags-0v', trackcount, True)
168
169 tags_file('candidate-tags-' + str(i), trackcount, various,
170 album.release.artist.name, album.release.title,
171 album.release.getReleaseEventsAsDict(),
172 q.getReleaseById(album.release.id, include).tracks)
173
174 cover_art(str(i), album.release.asin)
175
176 def rip(device, trackcount, single_file):
177 if device == None:
178 device = '/dev/cdrom'
179
180 argv = ['cdparanoia', '-d', device, '1-' + str(trackcount)]
181
182 if single_file:
183 argv.append('wav')
184 else:
185 argv.append('-B')
186
187 c(os.execvp, argv[0], argv)
188
189 def make_post_processor(command):
190 if command == None:
191 return
192
193 fd = c(os.open, 'post-processor', os.O_CREAT | os.O_WRONLY, 0555)
194 fp = c(os.fdopen, fd, 'w')
195 c(fp.write, command +' "$@"\n')
196 c(fp.close)
197
198 def main(argv):
199 # Control the exit code for any uncaught exceptions.
200 try:
201 parser = OptionParser()
202 parser.disable_interspersed_args()
203 parser.add_option('-d', '--device')
204 parser.add_option('-m', '--no-musicbrainz',
205 action='store_true', default=False)
206 parser.add_option('-p', '--post-processor')
207 parser.add_option('-s', '--single-file',
208 action='store_true', default=False)
209 parser.add_option('-t', '--tracks', type='int', default=99)
210 except:
211 traceback.print_exc()
212 return 2
213
214 try:
215 # Raises SystemExit on invalid options in argv.
216 (options, args) = parser.parse_args(argv[1:])
217 except Exception, error:
218 if isinstance(error, SystemExit):
219 return 1
220 traceback.print_exc()
221 return 2
222
223 try:
224 device = options.device
225 trackcount = options.tracks
226
227 tempdir = c((lambda x: tempfile.mkdtemp(prefix=x, dir='.')),
228 'flac-archive.')
229 c(os.chdir, tempdir)
230
231 make_post_processor(options.post_processor)
232
233 disc = musicbrainz2.disc.readDisc(device)
234
235 trackcount = mkcue(disc, trackcount)
236 tags(disc, trackcount, mb=not options.no_musicbrainz)
237 rip(device, trackcount, options.single_file)
238 except Exception, error:
239 if isinstance(error, SystemExit):
240 raise
241 # check all print_exc and format_exc in fa-flacd.py; i think
242 # for some i don't do this msg print check
243 sys.stderr.write(getattr(error, 'msg', ''))
244 traceback.print_exc()
245 return 2
246
247 return 0
248
249 if __name__ == '__main__':
250 sys.exit(main(sys.argv))