]> diplodocus.org Git - flac-archive/blobdiff - fa-rip
rejigger this to a stand-alone tool rather than a py module
[flac-archive] / fa-rip
diff --git a/fa-rip b/fa-rip
index 4b7f76ddff63ff3cb02cbac8ca679c9a2485ae64..be1d5ca4ec65f00461325ecabeef382e35473b2f 100755 (executable)
--- a/fa-rip
+++ b/fa-rip
@@ -1,4 +1,4 @@
-#! /usr/bin/env python2.4
+#!/usr/bin/python
 
 """
 =head1 NAME
 
 """
 =head1 NAME
@@ -7,7 +7,7 @@ B<fa-rip> - rip a CD for B<fa-flacd>
 
 =head1 SYNOPSIS
 
 
 =head1 SYNOPSIS
 
-B<fa-rip> [B<-d> I<device>] [B<-p> I<post-processor> [B<-t> I<track-count>]
+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>]
 
 =head1 DESCRIPTION
 
 
 =head1 DESCRIPTION
 
@@ -27,17 +27,32 @@ have to fill out the candidate-tags-0 template.
 
 =over 4
 
 
 =over 4
 
+=item B<--artist> I<artist> B<--title> I<title>
+
+Write candidate-tags files based on I<artist> and album I<title>.
+Useful if you've already ripped wav files with some other program and
+just need to set things up for B<fa-flacd>.
+
 =item B<-d> [B<--device>] I<device>
 
 Use I<device> as the CD-ROM device, instead of the default
 "/dev/cdrom" or the environment variable CDDEV.
 
 =item B<-d> [B<--device>] I<device>
 
 Use I<device> as the CD-ROM device, instead of the default
 "/dev/cdrom" or the environment variable CDDEV.
 
+=item B<-m> [B<--no-musicbrainz>]
+
+Don't connect to MusicBrainz, just write candidate-tags-0.
+
 =item B<-p> [B<--post-processor>] I<post-processor>
 
 Create a "post-processor" file in the temporary directory containing
 the line 'I<post-processor> "$@"'.  See B<fa-flacd>'s man page for
 information about this hook.
 
 =item B<-p> [B<--post-processor>] I<post-processor>
 
 Create a "post-processor" file in the temporary directory containing
 the line 'I<post-processor> "$@"'.  See B<fa-flacd>'s man page for
 information about this hook.
 
+=item B<-s> [B<--single-file>]
+
+Rip whole disc to one wav file and configure B<fa-flacd> to encode it
+to one FLAC file with embedded cuesheet.
+
 =item B<-t> [B<--tracks>] I<track-count>
 
 Archive only the first I<track-count> tracks.  This is handy for
 =item B<-t> [B<--tracks>] I<track-count>
 
 Archive only the first I<track-count> tracks.  This is handy for
@@ -67,7 +82,7 @@ it under the same terms as Perl itself.
 
 """
 
 
 """
 
-import os, re, sys, tempfile, traceback
+import os, re, sys, tempfile, time, traceback
 from optparse import OptionParser
 import urllib
 
 from optparse import OptionParser
 import urllib
 
@@ -76,21 +91,21 @@ import musicbrainz2.webservice
 
 from org.diplodocus.util import catch_EnvironmentError as c
 
 
 from org.diplodocus.util import catch_EnvironmentError as c
 
+# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=439790
+MSF_OFFSET = 150
+
 def mkcue(disc, trackcount=None):
     fp = c(file, 'cue', 'w')
     c(fp.write, 'FILE "dummy.wav" WAVE\n')
 def mkcue(disc, trackcount=None):
     fp = c(file, 'cue', 'w')
     c(fp.write, 'FILE "dummy.wav" WAVE\n')
-    c(fp.write, '  TRACK 01 AUDIO\n')
-    c(fp.write, '    INDEX 01 00:00:00\n')
 
     if trackcount == None:
         trackcount = disc.lastTrackNum
     else:
         trackcount = min(trackcount, disc.lastTrackNum)
 
 
     if trackcount == None:
         trackcount = disc.lastTrackNum
     else:
         trackcount = min(trackcount, disc.lastTrackNum)
 
-    pregap = disc.tracks[0][0]
-    for i in xrange(disc.firstTrackNum, trackcount):
-        offset = disc.tracks[i][0]
-        offset -= pregap
+    for i in xrange(disc.firstTrackNum, trackcount+1):
+        offset = disc.tracks[i-1][0]
+        offset -= MSF_OFFSET
 
         minutes = seconds = 0
         sectors = offset % 75
 
         minutes = seconds = 0
         sectors = offset % 75
@@ -100,7 +115,9 @@ def mkcue(disc, trackcount=None):
                 minutes = seconds / 60
                 seconds = seconds % 60
 
                 minutes = seconds / 60
                 seconds = seconds % 60
 
-        c(fp.write, '  TRACK %02d AUDIO\n' % (i + 1,))
+        c(fp.write, '  TRACK %02d AUDIO\n' % (i,))
+        if i == 1 and offset > 0:
+            c(fp.write, '    INDEX 00 00:00:00\n')
         c(fp.write,
           '    INDEX 01 %02d:%02d:%02d\n' % (minutes, seconds, sectors))
 
         c(fp.write,
           '    INDEX 01 %02d:%02d:%02d\n' % (minutes, seconds, sectors))
 
@@ -111,7 +128,10 @@ def mkcue(disc, trackcount=None):
 def tags_file(fn, trackcount, various, artist=None, album=None,
               release_dates={}, tracks=[]):
     fp = c(file, fn, 'w')
 def tags_file(fn, trackcount, various, artist=None, album=None,
               release_dates={}, tracks=[]):
     fp = c(file, fn, 'w')
-    c(fp.write, 'ARTIST=')
+    if various:
+        c(fp.write, 'ALBUMARTIST=')
+    else:
+        c(fp.write, 'ARTIST=')
     if artist != None:
         c(fp.write, artist.encode('utf-8'))
     c(fp.write, '\nALBUM=')
     if artist != None:
         c(fp.write, artist.encode('utf-8'))
     c(fp.write, '\nALBUM=')
@@ -128,13 +148,12 @@ def tags_file(fn, trackcount, various, artist=None, album=None,
     if len(tracks) > 0:
         trackcount = min(trackcount, len(tracks))
     for i in xrange(1, trackcount + 1):
     if len(tracks) > 0:
         trackcount = min(trackcount, len(tracks))
     for i in xrange(1, trackcount + 1):
-        try:
+        artist = title = ''
+        if len(tracks) > 0:
             track = tracks.pop(0)
             title = track.title
             track = tracks.pop(0)
             title = track.title
-            artist = track.artist
-        except IndexError:
-            title = ''
-            artist = ''
+            if track.artist:
+                artist = track.artist.name
         various and c(fp.write, 'ARTIST[%d]=%s\n' % (i,
                                                      artist.encode('utf-8')))
         c(fp.write, 'TITLE[%d]=%s\n' % (i, title.encode('utf-8')))
         various and c(fp.write, 'ARTIST[%d]=%s\n' % (i,
                                                      artist.encode('utf-8')))
         c(fp.write, 'TITLE[%d]=%s\n' % (i, title.encode('utf-8')))
@@ -147,29 +166,38 @@ def cover_art(i, asin):
     fp.write(urllib.urlopen(url).read())
     fp.close()
 
     fp.write(urllib.urlopen(url).read())
     fp.close()
 
-def tags(q, releases, trackcount):
+def tags(releases, trackcount):
     results = []
     results = []
+    seen_asins = set()
     seen_various = False
 
     tags_file('candidate-tags-0', trackcount, False)
 
     seen_various = False
 
     tags_file('candidate-tags-0', trackcount, False)
 
-    include = musicbrainz2.webservice.ReleaseIncludes(tracks=True)
+    include = musicbrainz2.webservice.ReleaseIncludes(artist=True, tracks=True)
 
     i = 0
 
     i = 0
-    for album in releases:
+    for release in releases:
         i += 1
         i += 1
-        various = not album.release.isSingleArtistRelease()
+        various = not release.isSingleArtistRelease()
 
         if various and not seen_various:
             seen_various = True
 
         if various and not seen_various:
             seen_various = True
-            tags_file('candidate-tags-0v', trackcount, True)
+            tags_file('candidate-tags-0v', trackcount, various)
 
         tags_file('candidate-tags-' + str(i), trackcount, various,
 
         tags_file('candidate-tags-' + str(i), trackcount, various,
-                  album.release.artist.name, album.release.title,
-                  album.release.getReleaseEventsAsDict(),
-                  q.getReleaseById(album.release.id, include).tracks)
-
-        cover_art(str(i), album.release.asin)
+                  release.artist.name, release.title,
+                  release.getReleaseEventsAsDict(),
+                  release.tracks)
+        if release.asin:
+            # See also:
+            # for i in release.getRelations(): print i.type
+            # http://musicbrainz.org/ns/rel-1.0#Wikipedia
+            # ...
+            # http://musicbrainz.org/ns/rel-1.0#AmazonAsin
+            asin = release.asin
+            if asin not in seen_asins:
+                seen_asins.add(asin)
+                cover_art(str(i), asin)
 
 def rip(device, trackcount, single_file):
     if device == None:
 
 def rip(device, trackcount, single_file):
     if device == None:
@@ -193,18 +221,37 @@ def make_post_processor(command):
     c(fp.write, command +' "$@"\n')
     c(fp.close)
 
     c(fp.write, command +' "$@"\n')
     c(fp.close)
 
-def releases_by_disc(q, disc):
-    filter = musicbrainz2.webservice.ReleaseFilter(discId=disc.getId())
-    return q.getReleases(filter)
+def get_releases(filter_, tries=5):
+    sleep = 1
+    query = musicbrainz2.webservice.Query()
+    while True:
+        try:
+            return query.getReleases(filter_)
+        except musicbrainz2.webservice.WebServiceError, e:
+            if '503' not in e.msg:
+                raise
+            tries -= 1
+            sys.stderr.write('getReleases: %s: ' % e)
+            if tries == 0:
+                sys.stderr.write('giving up\n')
+                raise
+            sleep *= 2
+            sys.stderr.write('sleeping %ds before retry...\n' % sleep)
+            time.sleep(sleep)
+
+def releases_by_disc(disc_id):
+    filter_ = musicbrainz2.webservice.ReleaseFilter(discId=disc_id)
+    return (result.release for result in get_releases(filter_))
 
 def releases_by(q, title, artist=None):
 
 def releases_by(q, title, artist=None):
-    r = q.getReleases(musicbrainz2.webservice.ReleaseFilter(title=title))
-    if artist == None:
-        return r
-
-    artist = re.sub(r'\s+', r'\s+', artist.strip())
-    return [x for x in r if re.match(artist, x.release.artist.name,
-                                     re.IGNORECASE) != None]
+    filter_ = musicbrainz2.webservice.ReleaseFilter(title=title)
+    results = get_releases(filter_)
+    releases = (result.release for result in results)
+    if artist:
+        pattern = re.sub(r'\s+', r'\s+', artist.strip())
+        releases = (x for x in releases
+                    if re.match(pattern, x.artist.name, re.IGNORECASE))
+    return releases
 
 def main(argv):
     # Control the exit code for any uncaught exceptions.
 
 def main(argv):
     # Control the exit code for any uncaught exceptions.
@@ -212,6 +259,7 @@ def main(argv):
         parser = OptionParser()
         parser.disable_interspersed_args()
         parser.add_option('--artist')
         parser = OptionParser()
         parser.disable_interspersed_args()
         parser.add_option('--artist')
+        parser.add_option('--discid')
         parser.add_option('--title')
         parser.add_option('-d', '--device')
         parser.add_option('-m', '--no-musicbrainz',
         parser.add_option('--title')
         parser.add_option('-d', '--device')
         parser.add_option('-m', '--no-musicbrainz',
@@ -239,24 +287,23 @@ def main(argv):
 
         tempdir = c((lambda x: tempfile.mkdtemp(prefix=x, dir='.')),
                     'flac-archive.')
 
         tempdir = c((lambda x: tempfile.mkdtemp(prefix=x, dir='.')),
                     'flac-archive.')
+        sys.stderr.write('ripping to %s\n\n' % (tempdir,))
         c(os.chdir, tempdir)
 
         make_post_processor(options.post_processor)
 
         c(os.chdir, tempdir)
 
         make_post_processor(options.post_processor)
 
-        q = musicbrainz2.webservice.Query()
         if options.title != None:
         if options.title != None:
-            releases = releases_by(q, options.title, options.artist)
+            tags(releases_by(q, options.title, options.artist), trackcount)
+        elif options.discid != None:
+            tags(releases_by_disc(options.discid), trackcount)
         else:
             disc = musicbrainz2.disc.readDisc(device)
             trackcount = mkcue(disc, trackcount)
             if options.no_musicbrainz:
                 releases = []
             else:
         else:
             disc = musicbrainz2.disc.readDisc(device)
             trackcount = mkcue(disc, trackcount)
             if options.no_musicbrainz:
                 releases = []
             else:
-                releases = releases_by_disc(q, disc)
-
-        tags(q, releases, trackcount)
-
-        if options.title == None:
+                releases = releases_by_disc(disc.getId())
+            tags(releases, trackcount)
             rip(device, trackcount, options.single_file)
     except Exception, error:
         if isinstance(error, SystemExit):
             rip(device, trackcount, options.single_file)
     except Exception, error:
         if isinstance(error, SystemExit):