Merge pull request #536 from whipper-team/misc-changes-template
This pull request extends the supported template variables. Additions: - `%B`: release barcode (already included but is now allowed) - `%C`: release catalog number (already included but is now allowed) - `%c`: release disambiguation comment - `%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 When the relative metadata is available, whipper now adds the `TRACKTOTAL`, `DISCTOTAL` and `DISCNUMBER` metadata tags to the audio tracks. I've also taken the inspiration from pull request #476. Resolves #401, resolves #440, resolves #448.
This commit is contained in:
@@ -94,7 +94,15 @@ Template schemes
|
|||||||
|
|
||||||
| - %A: release artist
|
| - %A: release artist
|
||||||
| - %S: release sort name
|
| - %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
|
| - %y: release year
|
||||||
| - %r: release type, lowercase
|
| - %r: release type, lowercase
|
||||||
| - %R: release type, normal case
|
| - %R: release type, normal case
|
||||||
|
|||||||
@@ -54,7 +54,15 @@ filling in the variables and adding the file extension. Variables for both
|
|||||||
disc and track template are:
|
disc and track template are:
|
||||||
- %A: release artist
|
- %A: release artist
|
||||||
- %S: release sort name
|
- %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
|
- %y: release year
|
||||||
- %r: release type, lowercase
|
- %r: release type, lowercase
|
||||||
- %R: release type, normal case
|
- %R: release type, normal case
|
||||||
@@ -185,7 +193,7 @@ class _CD(BaseCommand):
|
|||||||
and self.program.metadata.artist \
|
and self.program.metadata.artist \
|
||||||
or 'Unknown Artist'
|
or 'Unknown Artist'
|
||||||
self.program.result.title = self.program.metadata \
|
self.program.result.title = self.program.metadata \
|
||||||
and self.program.metadata.title \
|
and self.program.metadata.releaseTitle \
|
||||||
or 'Unknown Title'
|
or 'Unknown Title'
|
||||||
_, self.program.result.vendor, self.program.result.model, \
|
_, self.program.result.vendor, self.program.result.model, \
|
||||||
self.program.result.release = \
|
self.program.result.release = \
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Example Disc ID: KnpGsLhvH.lPrNc1PBL21lb9Bg4-"""
|
|||||||
:type md: `DiscMetadata`
|
:type md: `DiscMetadata`
|
||||||
"""
|
"""
|
||||||
print(' Artist: %s' % md.artist.encode('utf-8'))
|
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(' Type: %s' % str(md.releaseType).encode('utf-8'))
|
||||||
print(' URL: %s' % md.url)
|
print(' URL: %s' % md.url)
|
||||||
print(' Tracks: %d' % len(md.tracks))
|
print(' Tracks: %d' % len(md.tracks))
|
||||||
|
|||||||
@@ -277,9 +277,9 @@ def getRelativePath(targetPath, collectionPath):
|
|||||||
def validate_template(template, kind):
|
def validate_template(template, kind):
|
||||||
"""Raise exception if disc/track template includes invalid variables."""
|
"""Raise exception if disc/track template includes invalid variables."""
|
||||||
if kind == 'disc':
|
if kind == 'disc':
|
||||||
matches = re.findall(r'%[^ARSXdrxy]', template)
|
matches = re.findall(r'%[^ABCDIMNRSTXcdrxy]', template)
|
||||||
elif kind == 'track':
|
elif kind == 'track':
|
||||||
matches = re.findall(r'%[^ARSXadnrstxy]', template)
|
matches = re.findall(r'%[^ABCDIMNRSTXacdnrstxy]', template)
|
||||||
if '%' in template and matches:
|
if '%' in template and matches:
|
||||||
raise ValueError(kind + ' template string contains invalid '
|
raise ValueError(kind + ' template string contains invalid '
|
||||||
'variable(s): {}'.format(', '.join(matches)))
|
'variable(s): {}'.format(', '.join(matches)))
|
||||||
|
|||||||
@@ -66,11 +66,25 @@ class DiscMetadata:
|
|||||||
:cvar sortName: release artist sort name
|
:cvar sortName: release artist sort name
|
||||||
:cvar release: earliest release date, in YYYY-MM-DD
|
:cvar release: earliest release date, in YYYY-MM-DD
|
||||||
:vartype release: str
|
:vartype release: str
|
||||||
:cvar title: title of the disc (with disambiguation)
|
:cvar title: title of the disc (without disambiguation)
|
||||||
:cvar releaseTitle: title of the release (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`
|
:vartype tracks: list of :any:`TrackMetadata`
|
||||||
:cvar countries: MusicBrainz release countries
|
:cvar countries: MusicBrainz release countries
|
||||||
:vartype countries: list or None
|
: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
|
artist = None
|
||||||
@@ -81,6 +95,7 @@ class DiscMetadata:
|
|||||||
release = None
|
release = None
|
||||||
|
|
||||||
releaseTitle = None
|
releaseTitle = None
|
||||||
|
releaseDisambCmt = None
|
||||||
releaseType = None
|
releaseType = None
|
||||||
|
|
||||||
mbid = None
|
mbid = None
|
||||||
@@ -91,6 +106,9 @@ class DiscMetadata:
|
|||||||
catalogNumber = None
|
catalogNumber = None
|
||||||
barcode = None
|
barcode = None
|
||||||
countries = None
|
countries = None
|
||||||
|
discNumber = None
|
||||||
|
discTotal = None
|
||||||
|
mediumTitle = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.tracks = []
|
self.tracks = []
|
||||||
@@ -281,17 +299,20 @@ def _getMetadata(release, discid=None, country=None):
|
|||||||
for medium in release['medium-list']:
|
for medium in release['medium-list']:
|
||||||
for disc in medium['disc-list']:
|
for disc in medium['disc-list']:
|
||||||
if discid is None or disc['id'] == discid:
|
if discid is None or disc['id'] == discid:
|
||||||
title = release['title']
|
discMD.title = release['title']
|
||||||
discMD.releaseTitle = title
|
discMD.releaseTitle = releaseTitle = discMD.title
|
||||||
if 'disambiguation' in release:
|
if 'disambiguation' in release:
|
||||||
title += " (%s)" % release['disambiguation']
|
discMD.releaseDisambCmt = release['disambiguation']
|
||||||
count = len(release['medium-list'])
|
releaseTitle += " (%s)" % release['disambiguation']
|
||||||
if count > 1:
|
discMD.discNumber = int(medium['position'])
|
||||||
title += ' (Disc %d of %d)' % (
|
discMD.discTotal = len(release['medium-list'])
|
||||||
int(medium['position']), count)
|
if discMD.discTotal > 1:
|
||||||
|
releaseTitle += ' (Disc %d of %d)' % (
|
||||||
|
discMD.discNumber, discMD.discTotal)
|
||||||
if 'title' in medium:
|
if 'title' in medium:
|
||||||
title += ": %s" % medium['title']
|
discMD.mediumTitle = medium['title']
|
||||||
discMD.title = title
|
releaseTitle += ": %s" % medium['title']
|
||||||
|
discMD.releaseTitle = releaseTitle
|
||||||
for t in medium['track-list']:
|
for t in medium['track-list']:
|
||||||
track = TrackMetadata()
|
track = TrackMetadata()
|
||||||
trackCredit = _Credit(
|
trackCredit = _Credit(
|
||||||
|
|||||||
@@ -176,7 +176,15 @@ class Program:
|
|||||||
|
|
||||||
* ``%A``: release artist
|
* ``%A``: release artist
|
||||||
* ``%S``: release artist sort name
|
* ``%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
|
* ``%y``: release year
|
||||||
* ``%r``: release type, lowercase
|
* ``%r``: release type, lowercase
|
||||||
* ``%R``: release type, normal case
|
* ``%R``: release type, normal case
|
||||||
@@ -187,7 +195,7 @@ class Program:
|
|||||||
assert isinstance(template, str), "%r is not str" % template
|
assert isinstance(template, str), "%r is not str" % template
|
||||||
v = {}
|
v = {}
|
||||||
v['A'] = 'Unknown Artist'
|
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['R'] = 'Unknown'
|
v['R'] = 'Unknown'
|
||||||
v['B'] = '' # barcode
|
v['B'] = '' # barcode
|
||||||
@@ -208,9 +216,14 @@ class Program:
|
|||||||
v['y'] = release[:4]
|
v['y'] = release[:4]
|
||||||
v['A'] = metadata.artist
|
v['A'] = metadata.artist
|
||||||
v['S'] = metadata.sortName
|
v['S'] = metadata.sortName
|
||||||
v['d'] = metadata.title
|
v['d'] = metadata.releaseTitle
|
||||||
|
v['D'] = metadata.title
|
||||||
v['B'] = metadata.barcode
|
v['B'] = metadata.barcode
|
||||||
v['C'] = metadata.catalogNumber
|
v['C'] = metadata.catalogNumber
|
||||||
|
v['c'] = metadata.releaseDisambCmt
|
||||||
|
v['M'] = metadata.discTotal
|
||||||
|
v['N'] = metadata.discNumber
|
||||||
|
v['T'] = metadata.mediumTitle
|
||||||
if metadata.releaseType:
|
if metadata.releaseType:
|
||||||
v['R'] = metadata.releaseType
|
v['R'] = metadata.releaseType
|
||||||
v['r'] = metadata.releaseType.lower()
|
v['r'] = metadata.releaseType.lower()
|
||||||
@@ -316,7 +329,7 @@ class Program:
|
|||||||
|
|
||||||
for metadata in metadatas:
|
for metadata in metadatas:
|
||||||
print('\nArtist : %s' % metadata.artist)
|
print('\nArtist : %s' % metadata.artist)
|
||||||
print('Title : %s' % metadata.title)
|
print('Title : %s' % metadata.releaseTitle)
|
||||||
print('Duration: %s' % common.formatTime(
|
print('Duration: %s' % common.formatTime(
|
||||||
metadata.duration / 1000.0))
|
metadata.duration / 1000.0))
|
||||||
print('URL : %s' % metadata.url)
|
print('URL : %s' % metadata.url)
|
||||||
@@ -356,7 +369,7 @@ class Program:
|
|||||||
if len(metadatas) == 1:
|
if len(metadatas) == 1:
|
||||||
logger.info('picked requested release id %s', release)
|
logger.info('picked requested release id %s', release)
|
||||||
print('Artist: %s' % metadatas[0].artist)
|
print('Artist: %s' % metadatas[0].artist)
|
||||||
print('Title : %s' % metadatas[0].title)
|
print('Title : %s' % metadatas[0].releaseTitle)
|
||||||
elif not metadatas:
|
elif not metadatas:
|
||||||
logger.warning("requested release id '%s', but none of "
|
logger.warning("requested release id '%s', but none of "
|
||||||
"the found releases match", release)
|
"the found releases match", release)
|
||||||
@@ -368,16 +381,16 @@ class Program:
|
|||||||
# If we have multiple, make sure they match
|
# If we have multiple, make sure they match
|
||||||
if len(metadatas) > 1:
|
if len(metadatas) > 1:
|
||||||
artist = metadatas[0].artist
|
artist = metadatas[0].artist
|
||||||
releaseTitle = metadatas[0].releaseTitle
|
discTitle = metadatas[0].title
|
||||||
for i, metadata in enumerate(metadatas):
|
for i, metadata in enumerate(metadatas):
|
||||||
if not artist == metadata.artist:
|
if not artist == metadata.artist:
|
||||||
logger.warning("artist 0: %r and artist %d: %r are "
|
logger.warning("artist 0: %r and artist %d: %r are "
|
||||||
"not the same", artist, i,
|
"not the same", artist, i,
|
||||||
metadata.artist)
|
metadata.artist)
|
||||||
if not releaseTitle == metadata.releaseTitle:
|
if not discTitle == metadata.title:
|
||||||
logger.warning("title 0: %r and title %d: %r are "
|
logger.warning("title 0: %r and title %d: %r are "
|
||||||
"not the same", releaseTitle, i,
|
"not the same", discTitle, i,
|
||||||
metadata.releaseTitle)
|
metadata.title)
|
||||||
|
|
||||||
if not release and len(list(deltas)) > 1:
|
if not release and len(list(deltas)) > 1:
|
||||||
logger.warning('picked closest match in duration. '
|
logger.warning('picked closest match in duration. '
|
||||||
@@ -407,13 +420,13 @@ class Program:
|
|||||||
"""
|
"""
|
||||||
trackArtist = 'Unknown Artist'
|
trackArtist = 'Unknown Artist'
|
||||||
releaseArtist = 'Unknown Artist'
|
releaseArtist = 'Unknown Artist'
|
||||||
disc = 'Unknown Disc'
|
album = 'Unknown Album'
|
||||||
title = 'Unknown Track'
|
title = 'Unknown Track'
|
||||||
|
|
||||||
if self.metadata:
|
if self.metadata:
|
||||||
trackArtist = self.metadata.artist
|
trackArtist = self.metadata.artist
|
||||||
releaseArtist = self.metadata.artist
|
releaseArtist = self.metadata.artist
|
||||||
disc = self.metadata.title
|
album = self.metadata.title # No disambiguation is proper here
|
||||||
mbidRelease = self.metadata.mbid
|
mbidRelease = self.metadata.mbid
|
||||||
mbidReleaseGroup = self.metadata.mbidReleaseGroup
|
mbidReleaseGroup = self.metadata.mbidReleaseGroup
|
||||||
mbidReleaseArtist = self.metadata.mbidArtist
|
mbidReleaseArtist = self.metadata.mbidArtist
|
||||||
@@ -445,13 +458,19 @@ class Program:
|
|||||||
tags['ALBUMARTIST'] = releaseArtist
|
tags['ALBUMARTIST'] = releaseArtist
|
||||||
tags['ARTIST'] = trackArtist
|
tags['ARTIST'] = trackArtist
|
||||||
tags['TITLE'] = title
|
tags['TITLE'] = title
|
||||||
tags['ALBUM'] = disc
|
tags['ALBUM'] = album
|
||||||
|
|
||||||
tags['TRACKNUMBER'] = '%s' % number
|
tags['TRACKNUMBER'] = '%s' % number
|
||||||
|
|
||||||
if self.metadata:
|
if self.metadata:
|
||||||
if self.metadata.release is not None:
|
if self.metadata.release is not None:
|
||||||
tags['DATE'] = self.metadata.release
|
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:
|
if number > 0:
|
||||||
tags['MUSICBRAINZ_RELEASETRACKID'] = mbidTrack
|
tags['MUSICBRAINZ_RELEASETRACKID'] = mbidTrack
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class PathTestCase(unittest.TestCase):
|
|||||||
prog = program.Program(config.Config())
|
prog = program.Program(config.Config())
|
||||||
md = mbngs.DiscMetadata()
|
md = mbngs.DiscMetadata()
|
||||||
md.artist = md.sortName = 'Jeff Buckley'
|
md.artist = md.sortName = 'Jeff Buckley'
|
||||||
md.title = 'Grace'
|
md.releaseTitle = 'Grace'
|
||||||
|
|
||||||
path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE,
|
path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE,
|
||||||
'mbdiscid', md, 0)
|
'mbdiscid', md, 0)
|
||||||
@@ -36,7 +36,7 @@ class PathTestCase(unittest.TestCase):
|
|||||||
prog = program.Program(config.Config())
|
prog = program.Program(config.Config())
|
||||||
md = mbngs.DiscMetadata()
|
md = mbngs.DiscMetadata()
|
||||||
md.artist = md.sortName = 'Jeff Buckley'
|
md.artist = md.sortName = 'Jeff Buckley'
|
||||||
md.title = 'Grace'
|
md.releaseTitle = 'Grace'
|
||||||
|
|
||||||
path = prog.getPath('/tmp', '%A/%d', 'mbdiscid', md, 0)
|
path = prog.getPath('/tmp', '%A/%d', 'mbdiscid', md, 0)
|
||||||
self.assertEqual(path,
|
self.assertEqual(path,
|
||||||
|
|||||||
Reference in New Issue
Block a user