diff --git a/man/whipper-cd-rip.rst b/man/whipper-cd-rip.rst index 2b8447b0..01319e2a 100644 --- a/man/whipper-cd-rip.rst +++ b/man/whipper-cd-rip.rst @@ -94,7 +94,15 @@ Template schemes | - %A: release artist | - %S: release sort name -| - %d: disc title +| - %B: release barcode +| - %C: release catalog number +| - %c: release disambiguation comment +| - %d: release title (with disambiguation) +| - %D: disc title (without disambiguation) +| - %I: MusicBrainz Disc ID +| - %M: total number of discs in the chosen release +| - %N: number of current disc +| - %T: medium title | - %y: release year | - %r: release type, lowercase | - %R: release type, normal case diff --git a/whipper/command/cd.py b/whipper/command/cd.py index 205e6504..68b6d421 100644 --- a/whipper/command/cd.py +++ b/whipper/command/cd.py @@ -54,7 +54,15 @@ disc and track template are: - %A: release artist - %S: release sort name - - %d: disc title + - %B: release barcode + - %C: release catalog number + - %c: release disambiguation comment + - %d: release title (with disambiguation) + - %D: disc title (without disambiguation) + - %I: MusicBrainz Disc ID + - %M: total number of discs in the chosen release + - %N: number of current disc + - %T: medium title - %y: release year - %r: release type, lowercase - %R: release type, normal case @@ -185,7 +193,7 @@ def do(self): and self.program.metadata.artist \ or 'Unknown Artist' self.program.result.title = self.program.metadata \ - and self.program.metadata.title \ + and self.program.metadata.releaseTitle \ or 'Unknown Title' _, self.program.result.vendor, self.program.result.model, \ self.program.result.release = \ diff --git a/whipper/command/mblookup.py b/whipper/command/mblookup.py index 967a6991..89d24730 100644 --- a/whipper/command/mblookup.py +++ b/whipper/command/mblookup.py @@ -26,7 +26,7 @@ def _printMetadata(self, md): :type md: `DiscMetadata` """ print(' Artist: %s' % md.artist.encode('utf-8')) - print(' Title: %s' % md.title.encode('utf-8')) + print(' Title: %s' % md.releaseTitle.encode('utf-8')) print(' Type: %s' % str(md.releaseType).encode('utf-8')) print(' URL: %s' % md.url) print(' Tracks: %d' % len(md.tracks)) diff --git a/whipper/common/common.py b/whipper/common/common.py index 9a5f5235..bdbb9ac6 100644 --- a/whipper/common/common.py +++ b/whipper/common/common.py @@ -277,9 +277,9 @@ def getRelativePath(targetPath, collectionPath): def validate_template(template, kind): """Raise exception if disc/track template includes invalid variables.""" if kind == 'disc': - matches = re.findall(r'%[^ARSXdrxy]', template) + matches = re.findall(r'%[^ABCDIMNRSTXcdrxy]', template) elif kind == 'track': - matches = re.findall(r'%[^ARSXadnrstxy]', template) + matches = re.findall(r'%[^ABCDIMNRSTXacdnrstxy]', template) if '%' in template and matches: raise ValueError(kind + ' template string contains invalid ' 'variable(s): {}'.format(', '.join(matches))) diff --git a/whipper/common/mbngs.py b/whipper/common/mbngs.py index a36f5367..c6a2aaa3 100644 --- a/whipper/common/mbngs.py +++ b/whipper/common/mbngs.py @@ -66,11 +66,25 @@ class DiscMetadata: :cvar sortName: release artist sort name :cvar release: earliest release date, in YYYY-MM-DD :vartype release: str - :cvar title: title of the disc (with disambiguation) - :cvar releaseTitle: title of the release (without disambiguation) + :cvar title: title of the disc (without disambiguation) + :vartype title: str or None + :cvar releaseTitle: title of the release (with disambiguation) + :vartype releasetitle: str or None + :cvar releaseDisambCmt: release disambiguation comment + :vartype releaseDisambCmt: str or None + :cvar mediumTitle: title of the medium + :vartype mediumTitle: str or None :vartype tracks: list of :any:`TrackMetadata` :cvar countries: MusicBrainz release countries :vartype countries: list or None + :cvar discNumber: number of current disc + :vartype discNumber: int or None + :cvar discTotal: total number of discs in the chosen release + :vartype discTotal: int or None + :cvar catalogNumber: release catalog number + :vartype catalogNumber: str or None + :cvar barcode: release barcode + :vartype barcode: str or None """ artist = None @@ -81,6 +95,7 @@ class DiscMetadata: release = None releaseTitle = None + releaseDisambCmt = None releaseType = None mbid = None @@ -91,6 +106,9 @@ class DiscMetadata: catalogNumber = None barcode = None countries = None + discNumber = None + discTotal = None + mediumTitle = None def __init__(self): self.tracks = [] @@ -281,17 +299,20 @@ def _getMetadata(release, discid=None, country=None): for medium in release['medium-list']: for disc in medium['disc-list']: if discid is None or disc['id'] == discid: - title = release['title'] - discMD.releaseTitle = title + discMD.title = release['title'] + discMD.releaseTitle = releaseTitle = discMD.title if 'disambiguation' in release: - title += " (%s)" % release['disambiguation'] - count = len(release['medium-list']) - if count > 1: - title += ' (Disc %d of %d)' % ( - int(medium['position']), count) + discMD.releaseDisambCmt = release['disambiguation'] + releaseTitle += " (%s)" % release['disambiguation'] + discMD.discNumber = int(medium['position']) + discMD.discTotal = len(release['medium-list']) + if discMD.discTotal > 1: + releaseTitle += ' (Disc %d of %d)' % ( + discMD.discNumber, discMD.discTotal) if 'title' in medium: - title += ": %s" % medium['title'] - discMD.title = title + discMD.mediumTitle = medium['title'] + releaseTitle += ": %s" % medium['title'] + discMD.releaseTitle = releaseTitle for t in medium['track-list']: track = TrackMetadata() trackCredit = _Credit( diff --git a/whipper/common/program.py b/whipper/common/program.py index c9532873..2ee4146d 100644 --- a/whipper/common/program.py +++ b/whipper/common/program.py @@ -176,7 +176,15 @@ def getPath(self, outdir, template, mbdiscid, metadata, track_number=None): * ``%A``: release artist * ``%S``: release artist sort name - * ``%d``: disc title + * ``%B``: release barcode + * ``%C``: release catalog number + * ``%c``: release disambiguation comment + * ``%d``: release title (with disambiguation) + * ``%D``: disc title (without disambiguation) + * ``%I``: MusicBrainz Disc ID + * ``%M``: total number of discs in the chosen release + * ``%N``: number of current disc + * ``%T``: medium title * ``%y``: release year * ``%r``: release type, lowercase * ``%R``: release type, normal case @@ -187,7 +195,7 @@ def getPath(self, outdir, template, mbdiscid, metadata, track_number=None): assert isinstance(template, str), "%r is not str" % template v = {} v['A'] = 'Unknown Artist' - v['d'] = mbdiscid # fallback for title + v['I'] = v['d'] = v['D'] = mbdiscid # fallback for title v['r'] = 'unknown' v['R'] = 'Unknown' v['B'] = '' # barcode @@ -208,9 +216,14 @@ def getPath(self, outdir, template, mbdiscid, metadata, track_number=None): v['y'] = release[:4] v['A'] = metadata.artist v['S'] = metadata.sortName - v['d'] = metadata.title + v['d'] = metadata.releaseTitle + v['D'] = metadata.title v['B'] = metadata.barcode v['C'] = metadata.catalogNumber + v['c'] = metadata.releaseDisambCmt + v['M'] = metadata.discTotal + v['N'] = metadata.discNumber + v['T'] = metadata.mediumTitle if metadata.releaseType: v['R'] = metadata.releaseType v['r'] = metadata.releaseType.lower() @@ -316,7 +329,7 @@ def getMusicBrainz(self, ittoc, mbdiscid, release=None, country=None, for metadata in metadatas: print('\nArtist : %s' % metadata.artist) - print('Title : %s' % metadata.title) + print('Title : %s' % metadata.releaseTitle) print('Duration: %s' % common.formatTime( metadata.duration / 1000.0)) print('URL : %s' % metadata.url) @@ -356,7 +369,7 @@ def getMusicBrainz(self, ittoc, mbdiscid, release=None, country=None, if len(metadatas) == 1: logger.info('picked requested release id %s', release) print('Artist: %s' % metadatas[0].artist) - print('Title : %s' % metadatas[0].title) + print('Title : %s' % metadatas[0].releaseTitle) elif not metadatas: logger.warning("requested release id '%s', but none of " "the found releases match", release) @@ -368,16 +381,16 @@ def getMusicBrainz(self, ittoc, mbdiscid, release=None, country=None, # If we have multiple, make sure they match if len(metadatas) > 1: artist = metadatas[0].artist - releaseTitle = metadatas[0].releaseTitle + discTitle = metadatas[0].title for i, metadata in enumerate(metadatas): if not artist == metadata.artist: logger.warning("artist 0: %r and artist %d: %r are " "not the same", artist, i, metadata.artist) - if not releaseTitle == metadata.releaseTitle: + if not discTitle == metadata.title: logger.warning("title 0: %r and title %d: %r are " - "not the same", releaseTitle, i, - metadata.releaseTitle) + "not the same", discTitle, i, + metadata.title) if not release and len(list(deltas)) > 1: logger.warning('picked closest match in duration. ' @@ -407,13 +420,13 @@ def getTagList(self, number, mbdiscid): """ trackArtist = 'Unknown Artist' releaseArtist = 'Unknown Artist' - disc = 'Unknown Disc' + album = 'Unknown Album' title = 'Unknown Track' if self.metadata: trackArtist = self.metadata.artist releaseArtist = self.metadata.artist - disc = self.metadata.title + album = self.metadata.title # No disambiguation is proper here mbidRelease = self.metadata.mbid mbidReleaseGroup = self.metadata.mbidReleaseGroup mbidReleaseArtist = self.metadata.mbidArtist @@ -445,13 +458,19 @@ def getTagList(self, number, mbdiscid): tags['ALBUMARTIST'] = releaseArtist tags['ARTIST'] = trackArtist tags['TITLE'] = title - tags['ALBUM'] = disc + tags['ALBUM'] = album tags['TRACKNUMBER'] = '%s' % number if self.metadata: if self.metadata.release is not None: tags['DATE'] = self.metadata.release + if self.metadata.tracks: + tags['TRACKTOTAL'] = str(len(self.metadata.tracks)) + if self.metadata.discTotal is not None: + tags['DISCTOTAL'] = str(self.metadata.discTotal) + if self.metadata.discNumber is not None: + tags['DISCNUMBER'] = str(self.metadata.discNumber) if number > 0: tags['MUSICBRAINZ_RELEASETRACKID'] = mbidTrack diff --git a/whipper/test/test_common_program.py b/whipper/test/test_common_program.py index 7856f55e..1bc07a28 100644 --- a/whipper/test/test_common_program.py +++ b/whipper/test/test_common_program.py @@ -25,7 +25,7 @@ def testStandardTemplateFilled(self): prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = md.sortName = 'Jeff Buckley' - md.title = 'Grace' + md.releaseTitle = 'Grace' path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE, 'mbdiscid', md, 0) @@ -36,7 +36,7 @@ def testIssue66TemplateFilled(self): prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = md.sortName = 'Jeff Buckley' - md.title = 'Grace' + md.releaseTitle = 'Grace' path = prog.getPath('/tmp', '%A/%d', 'mbdiscid', md, 0) self.assertEqual(path,