diff --git a/ChangeLog b/ChangeLog index 51a7aeb..08d1c40 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2009-06-07 Thomas Vander Stichele + + * morituri/result/logger.py: + Fix for toctable->table + * morituri/common/program.py: + * morituri/rip/cd.py: + Move lots of functionality to program module. + 2009-06-07 Thomas Vander Stichele * morituri/result/result.py: diff --git a/morituri/common/program.py b/morituri/common/program.py index 9c674a2..27fd2f1a 100644 --- a/morituri/common/program.py +++ b/morituri/common/program.py @@ -24,12 +24,151 @@ Common functionality and class for all programs using morituri. """ -from morituri.common import common -from morituri.result import result -from morituri.program import cdrdao - import os +from morituri.common import common, log +from morituri.result import result +from morituri.program import cdrdao, cdparanoia + +import gst + +class MusicBrainzException(Exception): + def __init__(self, exc): + self.args = (exc, ) + self.exception = exc + +class TrackMetadata(object): + artist = None + title = None + +class DiscMetadata(object): + """ + @param release: earliest release date, in YYYY-MM-DD + @type release: unicode + """ + artist = None + title = None + various = False + tracks = None + release = None + + def __init__(self): + self.tracks = [] + +def filterForPath(text): + return "-".join(text.split("/")) + +def getMetadata(release): + metadata = DiscMetadata() + + isSingleArtist = release.isSingleArtistRelease() + metadata.various = not isSingleArtist + metadata.title = release.title + # getUniqueName gets disambiguating names like Muse (UK rock band) + metadata.artist = release.artist.name + metadata.release = release.getEarliestReleaseDate() + + for t in release.tracks: + track = TrackMetadata() + if isSingleArtist: + track.artist = metadata.artist + track.title = t.title + else: + track.artist = t.artist.name + track.title = t.title + metadata.tracks.append(track) + + return metadata + + +def musicbrainz(discid): + #import musicbrainz2.disc as mbdisc + import musicbrainz2.webservice as mbws + + + # Setup a Query object. + service = mbws.WebService() + query = mbws.Query(service) + + + # Query for all discs matching the given DiscID. + # FIXME: let mbws.WebServiceError go through for now + try: + rfilter = mbws.ReleaseFilter(discId=discid) + results = query.getReleases(rfilter) + except mbws.WebServiceError, e: + raise MusicBrainzException(e) + + # No disc matching this DiscID has been found. + if len(results) == 0: + return None + + # Display the returned results to the user. + ret = [] + + for result in results: + release = result.release + # The returned release object only contains title and artist, but no + # tracks. Query the web service once again to get all data we need. + try: + inc = mbws.ReleaseIncludes(artist=True, tracks=True, + releaseEvents=True) + release = query.getReleaseById(release.getId(), inc) + except mbws.WebServiceError, e: + raise MusicBrainzException(e) + + ret.append(getMetadata(release)) + + return ret + +def getPath(outdir, template, metadata, mbdiscid, i): + """ + Based on the template, get a complete path for the given track, + minus extension. + Also works for the disc name, using disc variables for the template. + + @param outdir: the directory where to write the files + @type outdir: str + @param template: the template for writing the file + @type template: str + @param metadata: + @type metadata: L{DiscMetadata} + @param i: track number (0 for HTOA) + @type i: int + """ + # returns without extension + + v = {} + + v['t'] = '%02d' % i + + # default values + v['A'] = 'Unknown Artist' + v['d'] = mbdiscid + + v['a'] = v['A'] + v['n'] = 'Unknown Track %d' % i + + if metadata: + v['A'] = filterForPath(metadata.artist) + v['d'] = filterForPath(metadata.title) + if i > 0: + try: + v['a'] = filterForPath(metadata.tracks[i - 1].artist) + v['n'] = filterForPath(metadata.tracks[i - 1].title) + except IndexError, e: + print 'ERROR: no track %d found, %r' % (i, e) + raise + else: + # htoa defaults to disc's artist + v['a'] = filterForPath(metadata.artist) + v['n'] = filterForPath('Hidden Track One Audio') + + import re + template = re.sub(r'%(\w)', r'%(\1)s', template) + + return os.path.join(outdir, template % v) + class Program(object): """ @@ -79,6 +218,76 @@ class Program(object): return itable + def getTagList(self, metadata, i): + """ + Based on the metadata, get a gst.TagList for the given track. + + @param metadata: + @type metadata: L{DiscMetadata} + @param i: track number (0 for HTOA) + @type i: int + + @rtype: L{gst.TagList} + """ + artist = u'Unknown Artist' + disc = u'Unknown Disc' + title = u'Unknown Track' + + if metadata: + artist = metadata.artist + disc = metadata.title + if i > 0: + try: + artist = metadata.tracks[i - 1].artist + title = metadata.tracks[i - 1].title + except IndexError, e: + print 'ERROR: no track %d found, %r' % (i, e) + raise + else: + # htoa defaults to disc's artist + title = 'Hidden Track One Audio' + + ret = gst.TagList() + + # gst-python 0.10.15.1 does not handle unicode -> utf8 string conversion + # see http://bugzilla.gnome.org/show_bug.cgi?id=584445 + ret[gst.TAG_ARTIST] = artist.encode('utf-8') + ret[gst.TAG_TITLE] = title.encode('utf-8') + ret[gst.TAG_ALBUM] = disc.encode('utf-8') + + # gst-python 0.10.15.1 does not handle tags that are UINT + # see gst-python commit 26fa6dd184a8d6d103eaddf5f12bd7e5144413fb + # FIXME: no way to compare against 'master' version after 0.10.15 + if gst.pygst_version >= (0, 10, 15): + ret[gst.TAG_TRACK_NUMBER] = i + if metadata: + # works, but not sure we want this + # if gst.pygst_version >= (0, 10, 15): + # ret[gst.TAG_TRACK_COUNT] = len(metadata.tracks) + # hack to get a GstDate which we cannot instantiate directly in + # 0.10.15.1 + # FIXME: The dates are strings and must have the format 'YYYY', + # 'YYYY-MM' or 'YYYY-MM-DD'. + # GstDate expects a full date, so default to Jan and 1st if MM and DD + # are missing + date = metadata.release + if date: + log.debug('metadata', + 'Converting release date %r to structure', date) + if len(date) == 4: + date += '-01' + if len(date) == 7: + date += '-01' + + s = gst.structure_from_string('hi,date=(GstDate)%s' % + str(date)) + ret[gst.TAG_DATE] = s['date'] + + # FIXME: gst.TAG_ISRC + + return ret + + def writeCue(self, discName): assert self.result.table.canCue() @@ -95,7 +304,7 @@ class Program(object): def writeLog(self, discName, logger): logPath = '%s.log' % discName handle = open(logPath, 'w') - handle.write(logger.log(res).encode('utf-8')) + handle.write(logger.log(self.result).encode('utf-8')) handle.close() self.logPath = logPath diff --git a/morituri/result/logger.py b/morituri/result/logger.py index 715eb2f..8a6145b 100644 --- a/morituri/result/logger.py +++ b/morituri/result/logger.py @@ -64,7 +64,7 @@ class MorituriLogger(object): " Track | Start | Length") lines.append( " ------------------------------------------------") - table = ripResult.toctable + table = ripResult.table for t in table.tracks: diff --git a/morituri/rip/cd.py b/morituri/rip/cd.py index d443e09..8ce89de 100644 --- a/morituri/rip/cd.py +++ b/morituri/rip/cd.py @@ -21,231 +21,17 @@ # along with morituri. If not, see . import os -import sys import math import gobject gobject.threads_init() -import gst - from morituri.common import logcommand, task, checksum, common, accurip, log from morituri.common import drive, encode, program from morituri.result import result from morituri.image import image, cue, table from morituri.program import cdrdao, cdparanoia -class TrackMetadata(object): - artist = None - title = None - -class DiscMetadata(object): - """ - @param release: earliest release date, in YYYY-MM-DD - @type release: unicode - """ - artist = None - title = None - various = False - tracks = None - release = None - - def __init__(self): - self.tracks = [] - -def filterForPath(text): - return "-".join(text.split("/")) - -def musicbrainz(discid): - metadata = DiscMetadata() - - #import musicbrainz2.disc as mbdisc - import musicbrainz2.webservice as mbws - - - # Setup a Query object. - service = mbws.WebService() - query = mbws.Query(service) - - - # Query for all discs matching the given DiscID. - try: - rfilter = mbws.ReleaseFilter(discId=discid) - results = query.getReleases(rfilter) - except mbws.WebServiceError, e: - print "Error:", e - return - - - # No disc matching this DiscID has been found. - if len(results) == 0: - print "Disc is not yet in the MusicBrainz database." - print "Consider adding it." - return - - - # Display the returned results to the user. - print 'Matching releases:' - - for result in results: - release = result.release - print 'Artist :', release.artist.name - print 'Title :', release.title - print - - - # Select one of the returned releases. We just pick the first one. - selectedRelease = results[0].release - - - # The returned release object only contains title and artist, but no tracks. - # Query the web service once again to get all data we need. - try: - inc = mbws.ReleaseIncludes(artist=True, tracks=True, releaseEvents=True) - release = query.getReleaseById(selectedRelease.getId(), inc) - except mbws.WebServiceError, e: - print "Error:", e - sys.exit(2) - - - # convert to our objects - isSingleArtist = release.isSingleArtistRelease() - metadata.various = not isSingleArtist - metadata.title = release.title - # getUniqueName gets disambiguating names like Muse (UK rock band) - metadata.artist = release.artist.name - metadata.release = release.getEarliestReleaseDate() - - print "%s - %s" % (release.artist.name, release.title) - - for t in release.tracks: - track = TrackMetadata() - if isSingleArtist: - track.artist = metadata.artist - track.title = t.title - else: - track.artist = t.artist.name - track.title = t.title - metadata.tracks.append(track) - - return metadata - -def getPath(outdir, template, metadata, mbdiscid, i): - """ - Based on the template, get a complete path for the given track, - minus extension. - Also works for the disc name, using disc variables for the template. - - @param outdir: the directory where to write the files - @type outdir: str - @param template: the template for writing the file - @type template: str - @param metadata: - @type metadata: L{DiscMetadata} - @param i: track number (0 for HTOA) - @type i: int - """ - # returns without extension - - v = {} - - v['t'] = '%02d' % i - - # default values - v['A'] = 'Unknown Artist' - v['d'] = mbdiscid - - v['a'] = v['A'] - v['n'] = 'Unknown Track %d' % i - - if metadata: - v['A'] = filterForPath(metadata.artist) - v['d'] = filterForPath(metadata.title) - if i > 0: - try: - v['a'] = filterForPath(metadata.tracks[i - 1].artist) - v['n'] = filterForPath(metadata.tracks[i - 1].title) - except IndexError, e: - print 'ERROR: no track %d found, %r' % (i, e) - raise - else: - # htoa defaults to disc's artist - v['a'] = filterForPath(metadata.artist) - v['n'] = filterForPath('Hidden Track One Audio') - - import re - template = re.sub(r'%(\w)', r'%(\1)s', template) - - return os.path.join(outdir, template % v) - -def getTagList(metadata, i): - """ - Based on the metadata, get a gst.TagList for the given track. - - @param metadata: - @type metadata: L{DiscMetadata} - @param i: track number (0 for HTOA) - @type i: int - - @rtype: L{gst.TagList} - """ - artist = u'Unknown Artist' - disc = u'Unknown Disc' - title = u'Unknown Track' - - if metadata: - artist = metadata.artist - disc = metadata.title - if i > 0: - try: - artist = metadata.tracks[i - 1].artist - title = metadata.tracks[i - 1].title - except IndexError, e: - print 'ERROR: no track %d found, %r' % (i, e) - raise - else: - # htoa defaults to disc's artist - title = 'Hidden Track One Audio' - - ret = gst.TagList() - - # gst-python 0.10.15.1 does not handle unicode -> utf8 string conversion - # see http://bugzilla.gnome.org/show_bug.cgi?id=584445 - ret[gst.TAG_ARTIST] = artist.encode('utf-8') - ret[gst.TAG_TITLE] = title.encode('utf-8') - ret[gst.TAG_ALBUM] = disc.encode('utf-8') - - # gst-python 0.10.15.1 does not handle tags that are UINT - # see gst-python commit 26fa6dd184a8d6d103eaddf5f12bd7e5144413fb - # FIXME: no way to compare against 'master' version after 0.10.15 - if gst.pygst_version >= (0, 10, 15): - ret[gst.TAG_TRACK_NUMBER] = i - if metadata: - # works, but not sure we want this - # if gst.pygst_version >= (0, 10, 15): - # ret[gst.TAG_TRACK_COUNT] = len(metadata.tracks) - # hack to get a GstDate which we cannot instantiate directly in - # 0.10.15.1 - # FIXME: The dates are strings and must have the format 'YYYY', - # 'YYYY-MM' or 'YYYY-MM-DD'. - # GstDate expects a full date, so default to Jan and 1st if MM and DD - # are missing - date = metadata.release - if date: - log.debug('metadata', - 'Converting release date %r to structure', date) - if len(date) == 4: - date += '-01' - if len(date) == 7: - date += '-01' - - s = gst.structure_from_string('hi,date=(GstDate)%s' % - str(date)) - ret[gst.TAG_DATE] = s['date'] - - # FIXME: gst.TAG_ISRC - - return ret class Rip(logcommand.LogCommand): summary = "rip CD" @@ -311,10 +97,27 @@ class Rip(logcommand.LogCommand): mbdiscid = ittoc.getMusicBrainzDiscId() print "MusicBrainz disc id", mbdiscid - metadata = musicbrainz(mbdiscid) - if not metadata: + # look up disc on musicbrainz + try: + metadatas = program.musicbrainz(mbdiscid) + except program.MusicBrainzException, e: + print "Error:", e + return + + metadata = None + + if metadatas: + print 'Matching releases:' + for metadata in metadatas: + print 'Artist :', metadata.artist + print 'Title :', metadata.title + + # Select one of the returned releases. We just pick the first one. + metadata = metadatas[0] + else: print 'Submit this disc to MusicBrainz at:' print ittoc.getMusicBrainzSubmitURL() + print # now, read the complete index table, which is slower itable = prog.getTable(runner, ittoc.getCDDBDiscId(), device) @@ -334,18 +137,17 @@ class Rip(logcommand.LogCommand): extension = profile.extension # result - res = prog.result - res.offset = int(self.options.offset) - res.artist = metadata and metadata.artist or 'Unknown Artist' - res.title = metadata and metadata.title or 'Unknown Title' + prog.result.offset = int(self.options.offset) + prog.result.artist = metadata and metadata.artist or 'Unknown Artist' + prog.result.title = metadata and metadata.title or 'Unknown Title' # cdio is optional for now try: import cdio - _, res.vendor, res.model, __ = cdio.Device(device).get_hwinfo() + _, prog.result.vendor, prog.result.model, __ = cdio.Device(device).get_hwinfo() except ImportError: print 'WARNING: pycdio not installed, cannot identify drive' - res.vendor = 'Unknown' - res.model = 'Unknown' + prog.result.vendor = 'Unknown' + prog.result.model = 'Unknown' # check for hidden track one audio @@ -364,8 +166,8 @@ class Rip(logcommand.LogCommand): start, stop) # rip it - htoapath = getPath(outdir, self.options.track_template, metadata, - mbdiscid, 0) + '.' + extension + htoapath = program.getPath(outdir, self.options.track_template, + metadata, mbdiscid, 0) + '.' + extension dirname = os.path.dirname(htoapath) if not os.path.exists(dirname): os.makedirs(dirname) @@ -378,7 +180,7 @@ class Rip(logcommand.LogCommand): offset=int(self.options.offset), device=self.parentCommand.options.device, profile=profile, - taglist=getTagList(metadata, 0)) + taglist=prog.getTagList(metadata, 0)) function(runner, t) if t.checksum is not None: @@ -402,9 +204,9 @@ class Rip(logcommand.LogCommand): continue trackResult = result.TrackResult() - res.tracks.append(trackResult) + prog.result.tracks.append(trackResult) - path = getPath(outdir, self.options.track_template, metadata, + path = program.getPath(outdir, self.options.track_template, metadata, mbdiscid, i + 1) + '.' + extension trackResult.number = i + 1 trackResult.filename = path @@ -423,7 +225,7 @@ class Rip(logcommand.LogCommand): offset=int(self.options.offset), device=self.parentCommand.options.device, profile=profile, - taglist=getTagList(metadata, i + 1)) + taglist=prog.getTagList(metadata, i + 1)) t.description = 'Reading Track %d' % (i + 1) function(runner, t) if t.checksum: @@ -445,7 +247,7 @@ class Rip(logcommand.LogCommand): ### write disc files - discName = getPath(outdir, self.options.disc_template, metadata, + discName = program.getPath(outdir, self.options.disc_template, metadata, mbdiscid, 0) dirname = os.path.dirname(discName) if not os.path.exists(dirname): @@ -467,7 +269,7 @@ class Rip(logcommand.LogCommand): if not track.audio: continue - path = getPath(outdir, self.options.track_template, metadata, + path = program.getPath(outdir, self.options.track_template, metadata, mbdiscid, i + 1) + '.' + extension u = u'#EXTINF:%d,%s\n' % ( itable.getTrackLength(i + 1) / common.FRAMES_PER_SECOND, @@ -507,7 +309,7 @@ class Rip(logcommand.LogCommand): # loop over tracks for i, csum in enumerate(cuetask.checksums): - trackResult = res.tracks[i] + trackResult = prog.result.tracks[i] trackResult.accuripCRC = csum status = 'rip NOT accurate'