Merge branch 'master' into discogs_original_year

This commit is contained in:
Dmitry Bogdanov 2018-05-02 17:41:07 +02:00 committed by GitHub
commit a840bc700b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 236 additions and 144 deletions

View file

@ -40,6 +40,7 @@ def apply_item_metadata(item, track_info):
item.artist_credit = track_info.artist_credit item.artist_credit = track_info.artist_credit
item.title = track_info.title item.title = track_info.title
item.mb_trackid = track_info.track_id item.mb_trackid = track_info.track_id
item.mb_releasetrackid = track_info.release_track_id
if track_info.artist_id: if track_info.artist_id:
item.mb_artistid = track_info.artist_id item.mb_artistid = track_info.artist_id
if track_info.data_source: if track_info.data_source:
@ -129,6 +130,7 @@ def apply_metadata(album_info, mapping):
# MusicBrainz IDs. # MusicBrainz IDs.
item.mb_trackid = track_info.track_id item.mb_trackid = track_info.track_id
item.mb_releasetrackid = track_info.release_track_id
item.mb_albumid = album_info.album_id item.mb_albumid = album_info.album_id
if track_info.artist_id: if track_info.artist_id:
item.mb_artistid = track_info.artist_id item.mb_artistid = track_info.artist_id

View file

@ -129,6 +129,8 @@ class TrackInfo(object):
- ``title``: name of the track - ``title``: name of the track
- ``track_id``: MusicBrainz ID; UUID fragment only - ``track_id``: MusicBrainz ID; UUID fragment only
- ``release_track_id``: MusicBrainz ID respective to a track on a
particular release; UUID fragment only
- ``artist``: individual track artist name - ``artist``: individual track artist name
- ``artist_id`` - ``artist_id``
- ``length``: float: duration of the track in seconds - ``length``: float: duration of the track in seconds
@ -152,14 +154,15 @@ class TrackInfo(object):
may be None. The indices ``index``, ``medium``, and ``medium_index`` may be None. The indices ``index``, ``medium``, and ``medium_index``
are all 1-based. are all 1-based.
""" """
def __init__(self, title, track_id, artist=None, artist_id=None, def __init__(self, title, track_id, release_track_id=None, artist=None,
length=None, index=None, medium=None, medium_index=None, artist_id=None, length=None, index=None, medium=None,
medium_total=None, artist_sort=None, disctitle=None, medium_index=None, medium_total=None, artist_sort=None,
artist_credit=None, data_source=None, data_url=None, disctitle=None, artist_credit=None, data_source=None,
media=None, lyricist=None, composer=None, composer_sort=None, data_url=None, media=None, lyricist=None, composer=None,
arranger=None, track_alt=None): composer_sort=None, arranger=None, track_alt=None):
self.title = title self.title = title
self.track_id = track_id self.track_id = track_id
self.release_track_id = release_track_id
self.artist = artist self.artist = artist
self.artist_id = artist_id self.artist_id = artist_id
self.length = length self.length = length

View file

@ -281,6 +281,10 @@ def album_info(release):
continue continue
all_tracks = medium['track-list'] all_tracks = medium['track-list']
if 'data-track-list' in medium:
all_tracks += medium['data-track-list']
track_count = len(all_tracks)
if 'pregap' in medium: if 'pregap' in medium:
all_tracks.insert(0, medium['pregap']) all_tracks.insert(0, medium['pregap'])
@ -302,8 +306,9 @@ def album_info(release):
index, index,
int(medium['position']), int(medium['position']),
int(track['position']), int(track['position']),
len(medium['track-list']), track_count,
) )
ti.release_track_id = track['id']
ti.disctitle = disctitle ti.disctitle = disctitle
ti.media = format ti.media = format
ti.track_alt = track['number'] ti.track_alt = track['number']

View file

@ -455,6 +455,7 @@ class Item(LibModel):
'mb_albumid': types.STRING, 'mb_albumid': types.STRING,
'mb_artistid': types.STRING, 'mb_artistid': types.STRING,
'mb_albumartistid': types.STRING, 'mb_albumartistid': types.STRING,
'mb_releasetrackid': types.STRING,
'albumtype': types.STRING, 'albumtype': types.STRING,
'label': types.STRING, 'label': types.STRING,
'acoustid_fingerprint': types.STRING, 'acoustid_fingerprint': types.STRING,

View file

@ -1865,6 +1865,12 @@ class MediaFile(object):
StorageStyle('MUSICBRAINZ_TRACKID'), StorageStyle('MUSICBRAINZ_TRACKID'),
ASFStorageStyle('MusicBrainz/Track Id'), ASFStorageStyle('MusicBrainz/Track Id'),
) )
mb_releasetrackid = MediaField(
MP3DescStorageStyle(u'MusicBrainz Release Track Id'),
MP4StorageStyle('----:com.apple.iTunes:MusicBrainz Release Track Id'),
StorageStyle('MUSICBRAINZ_RELEASETRACKID'),
ASFStorageStyle('MusicBrainz/Release Track Id'),
)
mb_albumid = MediaField( mb_albumid = MediaField(
MP3DescStorageStyle(u'MusicBrainz Album Id'), MP3DescStorageStyle(u'MusicBrainz Album Id'),
MP4StorageStyle('----:com.apple.iTunes:MusicBrainz Album Id'), MP4StorageStyle('----:com.apple.iTunes:MusicBrainz Album Id'),

View file

@ -237,7 +237,7 @@ def show_change(cur_artist, cur_album, match):
medium = track_info.disc medium = track_info.disc
mediums = track_info.disctotal mediums = track_info.disctotal
if config['per_disc_numbering']: if config['per_disc_numbering']:
if mediums > 1: if mediums and mediums > 1:
return u'{0}-{1}'.format(medium, medium_index) return u'{0}-{1}'.format(medium, medium_index)
else: else:
return six.text_type(medium_index or index) return six.text_type(medium_index or index)

View file

@ -498,9 +498,10 @@ class DiscogsPlugin(BeetsPlugin):
medium, medium_index, _ = self.get_track_index(track['position']) medium, medium_index, _ = self.get_track_index(track['position'])
artist, artist_id = self.get_artist(track.get('artists', [])) artist, artist_id = self.get_artist(track.get('artists', []))
length = self.get_track_length(track['duration']) length = self.get_track_length(track['duration'])
return TrackInfo(title, track_id, artist, artist_id, length, index, return TrackInfo(title, track_id, artist=artist, artist_id=artist_id,
medium, medium_index, artist_sort=None, length=length, index=index,
disctitle=None, artist_credit=None) medium=medium, medium_index=medium_index,
artist_sort=None, disctitle=None, artist_credit=None)
def get_track_index(self, position): def get_track_index(self, position):
"""Returns the medium, medium index and subtrack index for a discogs """Returns the medium, medium index and subtrack index for a discogs

View file

@ -48,7 +48,7 @@ class KeyFinderPlugin(BeetsPlugin):
self.find_key(lib.items(ui.decargs(args)), write=ui.should_write()) self.find_key(lib.items(ui.decargs(args)), write=ui.should_write())
def imported(self, session, task): def imported(self, session, task):
self.find_key(task.items) self.find_key(task.imported_items())
def find_key(self, items, write=False): def find_key(self, items, write=False):
overwrite = self.config['overwrite'].get(bool) overwrite = self.config['overwrite'].get(bool)

View file

@ -29,6 +29,9 @@ New features:
and tracklist positions. Track ids are stored in ``mb_trackid``. :bug:`#2336` and tracklist positions. Track ids are stored in ``mb_trackid``. :bug:`#2336`
Thanks to :user:`dbogdanov`. Thanks to :user:`dbogdanov`.
* :doc:`/plugins/discogs`: Fetch original year from master releases. :bug:`#1122` * :doc:`/plugins/discogs`: Fetch original year from master releases. :bug:`#1122`
* As a first step to get :bug:`#406` implemented, beets now imports the
``musicbrainz_releasetrackid`` field into the library and tags media files
accordingly. Thanks to :user:`Rawrmonkeys`.
Fixes: Fixes:
@ -104,6 +107,11 @@ Fixes:
to which a track belongs, not the total number of different mediums present to which a track belongs, not the total number of different mediums present
on the release. :bug:`2887` on the release. :bug:`2887`
Thanks to :user:`dbogdanov`. Thanks to :user:`dbogdanov`.
* The importer now supports audio files contained in data tracks when they are
listed in MusicBrainz: the corresponding audio tracks are now merged into the
main track list. Thanks to :user:`jdetrey`. :bug:`1638`
* :doc:`/plugins/keyfinder`: Avoid a crash when trying to process unmatched
tracks. :bug:`2537`
For developers: For developers:

View file

@ -239,6 +239,7 @@ Audio information:
MusicBrainz and fingerprint information: MusicBrainz and fingerprint information:
* mb_trackid * mb_trackid
* mb_releasetrackid
* mb_albumid * mb_albumid
* mb_artistid * mb_artistid
* mb_albumartistid * mb_albumartistid

View file

@ -2,28 +2,29 @@
# zsh completion for beets music library manager and MusicBrainz tagger: http://beets.radbox.org/ # zsh completion for beets music library manager and MusicBrainz tagger: http://beets.radbox.org/
# NOTE: it will be very slow the first time you try to complete in a zsh shell (especially if you've enable many plugins) # Cache will be updated if it is older than the beets database or binary.
# You can make it faster in future by creating a cached version: # Need to set BEETS_LIBRARY to some preliminary value since it is used by the cache checking function.
# 1) perform a query completion with this file (_beet), e.g. do: beet list artist:"<TAB> typeset -g BEETS_LIBRARY=~/.config/beets/library.db
# to create the completion function (takes a few seconds) zstyle ":completion:${curcontext}:" cache-policy _beet_check_cache
# 2) save a copy of the completion function: which _beet > _beet_cached _beet_check_cache () {
# 3) save a copy of the query completion function: which _beet_query > _beet_query_cached [[ ! -a "${1}" ]] || [[ ! -a ${~BEETS_LIBRARY} ]] || [[ "${1}" -ot ${~BEETS_LIBRARY} ]] || [[ "${1}" -ot =beet ]]
# 4) copy the contents of _beet_query_cached to the top of _beet_cached }
# 5) copy and paste the _beet_field_values function from _beet to the top of _beet_cached # Try to retrieve the cache, and find out if it needs to be updated
# 6) add the following line to the top of _beet_cached: #compdef beet if ! _retrieve_cache beets || _cache_invalid beets; then
# 7) add the following line to the bottom of _beet_cached: _beet "$@" local updatecache=1
# 8) save _beet_cached to your completions directory (e.g. /usr/share/zsh/functions/Completion) # Location of database
# 9) add the following line to your .zshrc file: compdef _beet_cached beet typeset -g BEETS_LIBRARY="$(beet config|grep library|cut -f 2 -d ' ')"
# You will need to repeat this proceedure each time you enable new plugins if you want them to complete properly. # List of all fields
local -a fields
fields=(`beet fields | grep -G '^ ' | sort -u | colrm 1 2`)
fi
# useful: argument to _regex_arguments for matching any word # useful: argument to _regex_arguments for matching any word
local matchany=/$'[^\0]##\0'/ local matchany=/$'[^\0]##\0'/
# Deal with completions for querying and modifying fields.. # Deal with completions for querying and modifying fields..
local fieldargs matchquery matchmodify local fieldargs matchquery matchmodify
local -a fields
# get list of all fields
fields=(`beet fields | grep -G '^ ' | sort -u | colrm 1 2`)
# regexps for matching query and modify terms on the command line # regexps for matching query and modify terms on the command line
matchquery=/"(${(j/|/)fields[@]})"$':[^\0]##\0'/ matchquery=/"(${(j/|/)fields[@]})"$':[^\0]##\0'/
matchmodify=/"(${(j/|/)fields[@]})"$'(=[^\0]##|!)\0'/ matchmodify=/"(${(j/|/)fields[@]})"$'(=[^\0]##|!)\0'/
@ -43,14 +44,17 @@ function _join_lines() {
function _beet_field_values() function _beet_field_values()
{ {
local -a output fieldvals local -a output fieldvals
local library="$(beet config|grep library|cut -f 2 -d ' ')" local sqlcmd="select distinct $1 from items;"
output=$(sqlite3 ${~library} "select distinct $1 from items;")
case $1 case $1
in in
lyrics) lyrics)
fieldvals= fieldvals=
;; ;;
*) *)
if [[ "$(sqlite3 ${~BEETS_LIBRARY} ${sqlcmd} 2>&1)" =~ "no such column" ]]; then
sqlcmd="select distinct value from item_attributes where key=='$1' and value!='';"
fi
output="$(sqlite3 ${~BEETS_LIBRARY} ${sqlcmd} 2>/dev/null | sed -rn '/^-+$/,${{/^[- ]+$/n};p}')"
fieldvals=("${(f)output[@]}") fieldvals=("${(f)output[@]}")
;; ;;
esac esac
@ -68,8 +72,7 @@ queryelem="_values -S : 'query field (add an extra : to match by regexp)' '::' $
# store call to _values function for completing modify terms (no need to complete field values) # store call to _values function for completing modify terms (no need to complete field values)
modifyelem="_values -S = 'modify field (replace = with ! to remove field)' $(echo "'${^fields[@]}:: '")" modifyelem="_values -S = 'modify field (replace = with ! to remove field)' $(echo "'${^fields[@]}:: '")"
# Create completion function for queries # Create completion function for queries
_regex_arguments _beet_query "$matchany" \# \( "$matchquery" ":query:query string:$queryelem" \) \ _regex_arguments _beet_query "$matchany" \# \( "$matchquery" ":query:query string:$queryelem" \) \( "$matchquery" ":query:query string:$queryelem" \) \#
\( "$matchquery" ":query:query string:$queryelem" \) \#
# store regexps for completing lists of queries and modifications # store regexps for completing lists of queries and modifications
local -a query modify local -a query modify
query=( \( "$matchquery" ":query:query string:{_beet_query}" \) \( "$matchquery" ":query:query string:{_beet_query}" \) \# ) query=( \( "$matchquery" ":query:query string:{_beet_query}" \) \( "$matchquery" ":query:query string:{_beet_query}" \) \# )
@ -174,73 +177,76 @@ function _beet_subcmd_options()
} }
# Now build the arguments to _regex_arguments for each subcommand. # Now build the arguments to _regex_arguments for each subcommand.
local -a options regex_words_subcmds regex_words_help if [[ -n $updatecache ]]; then
local subcmd cmddesc local -a options regex_words_subcmds regex_words_help
for i in ${${(f)"$(beet help | _join_lines ' ' 3 'Commands:')"[@]}[@]} local subcmd cmddesc
do for i in ${${(f)"$(beet help | _join_lines ' ' 3 'Commands:')"[@]}[@]}
subcmd="${i[(w)1]}" do
# remove first word and parenthesised alias, replace : with -, [ with (, ] with ), and remove single quotes subcmd="${i[(w)1]}"
cmddesc="${${${${${i[(w)2,-1]##\(*\) #}//:/-}//\[/(}//\]/)}//\'/}" # remove first word and parenthesised alias, replace : with -, [ with (, ] with ), and remove single quotes
case $subcmd cmddesc="${${${${${i[(w)2,-1]##\(*\) #}//:/-}//\[/(}//\]/)}//\'/}"
case $subcmd
in in
(config) (config)
_regex_words options "config options" "$helpopt" "$pathopt" "$editopt" "$defaultopt" _regex_words options "config options" "$helpopt" "$pathopt" "$editopt" "$defaultopt"
options=("${reply[@]}") options=("${reply[@]}")
;; ;;
(import) (import)
_regex_words options "import options" "$helpopt" "$writeopt" "$nowriteopt" "$copyopt" "$nocopyopt"\ _regex_words options "import options" "$helpopt" "$writeopt" "$nowriteopt" "$copyopt" "$nocopyopt"\
"$inferopt" "$noinferopt" "$resumeopt" "$noresumeopt" "$nopromptopt" "$logopt" "$individualopt" "$confirmopt"\ "$inferopt" "$noinferopt" "$resumeopt" "$noresumeopt" "$nopromptopt" "$logopt" "$individualopt" "$confirmopt"\
"$retagopt" "$skipopt" "$noskipopt" "$flatopt" "$groupopt" "$retagopt" "$skipopt" "$noskipopt" "$flatopt" "$groupopt"
options=( "${reply[@]}" \# "${files[@]}" \# ) options=( "${reply[@]}" \# "${files[@]}" \# )
;; ;;
(list) (list)
_regex_words options "list options" "$helpopt" "$pathopt" "$albumopt" "$formatopt" _regex_words options "list options" "$helpopt" "$pathopt" "$albumopt" "$formatopt"
options=( "$reply[@]" \# "${query[@]}" ) options=( "$reply[@]" \# "${query[@]}" )
;; ;;
(modify) (modify)
_regex_words options "modify options" "$helpopt" "$dontmoveopt" "$writeopt" "$nowriteopt" "$albumopt" \ _regex_words options "modify options" "$helpopt" "$dontmoveopt" "$writeopt" "$nowriteopt" "$albumopt" \
"$noconfirmopt" "$formatopt" "$noconfirmopt" "$formatopt"
options=( "${reply[@]}" \# "${query[@]}" "${modify[@]}" ) options=( "${reply[@]}" \# "${query[@]}" "${modify[@]}" )
;; ;;
(move) (move)
_regex_words options "move options" "$helpopt" "$albumopt" "$destopt" "$copynomoveopt" _regex_words options "move options" "$helpopt" "$albumopt" "$destopt" "$copynomoveopt"
options=( "${reply[@]}" \# "${query[@]}") options=( "${reply[@]}" \# "${query[@]}")
;; ;;
(remove) (remove)
_regex_words options "remove options" "$helpopt" "$albumopt" "$removeopt" _regex_words options "remove options" "$helpopt" "$albumopt" "$removeopt"
options=( "${reply[@]}" \# "${query[@]}" ) options=( "${reply[@]}" \# "${query[@]}" )
;; ;;
(stats) (stats)
_regex_words options "stats options" "$helpopt" "$exactopt" _regex_words options "stats options" "$helpopt" "$exactopt"
options=( "${reply[@]}" \# "${query[@]}" ) options=( "${reply[@]}" \# "${query[@]}" )
;; ;;
(update) (update)
_regex_words options "update options" "$helpopt" "$albumopt" "$dontmoveopt" "$pretendopt" "$formatopt" _regex_words options "update options" "$helpopt" "$albumopt" "$dontmoveopt" "$pretendopt" "$formatopt"
options=( "${reply[@]}" \# "${query[@]}" ) options=( "${reply[@]}" \# "${query[@]}" )
;; ;;
(write) (write)
_regex_words options "write options" "$helpopt" "$pretendopt" _regex_words options "write options" "$helpopt" "$pretendopt"
options=( "${reply[@]}" \# "${query[@]}" ) options=( "${reply[@]}" \# "${query[@]}" )
;; ;;
(fields|migrate|version) (fields|migrate|version)
options=() options=()
;; ;;
(help) (help)
# The help subcommand is treated separately # The help subcommand is treated separately
continue continue
;; ;;
(*) # completions for plugin commands are generated using _beet_subcmd_options (*) # completions for plugin commands are generated using _beet_subcmd_options
_beet_subcmd_options "$subcmd" _beet_subcmd_options "$subcmd"
options=( \( "${reply[@]}" \# "${query[@]}" \) ) options=( \( "${reply[@]}" \# "${query[@]}" \) )
;; ;;
esac esac
# Create variable for holding option for this subcommand, and assign to it (needs to have a unique name). # Create variable for holding option for this subcommand, and assign to it (needs to have a unique name).
typeset -a opts_for_$subcmd typeset -a opts_for_$subcmd
set -A opts_for_$subcmd ${options[@]} # Assignment MUST be done using set (other methods fail). set -A opts_for_$subcmd ${options[@]} # Assignment MUST be done using set (other methods fail).
regex_words_subcmds+=("$subcmd:$cmddesc:\${(@)opts_for_$subcmd}") regex_words_subcmds+=("$subcmd:$cmddesc:\${(@)opts_for_$subcmd}")
# Add to regex_words args for help subcommand # Add to regex_words args for help subcommand
regex_words_help+=("$subcmd:$cmddesc") regex_words_help+=("$subcmd:$cmddesc")
done done
_store_cache beets regex_words_subcmds regex_words_help BEETS_LIBRARY fields
fi
local -a opts_for_help local -a opts_for_help
_regex_words subcmds "subcommands" "${regex_words_help[@]}" _regex_words subcmds "subcommands" "${regex_words_help[@]}"
@ -253,7 +259,7 @@ _regex_words options "global options" "$configopt" "$debugopt" "$libopt" "$helpo
globalopts=("${reply[@]}") globalopts=("${reply[@]}")
# Create main completion function # Create main completion function
#local -a subcmds local -a subcmds
_regex_words subcmds "subcommands" "${regex_words_subcmds[@]}" _regex_words subcmds "subcommands" "${regex_words_subcmds[@]}"
subcmds=("${reply[@]}") subcmds=("${reply[@]}")
_regex_arguments _beet "$matchany" \( "${globalopts[@]}" \# \) "${subcmds[@]}" _regex_arguments _beet "$matchany" \( "${globalopts[@]}" \# \) "${subcmds[@]}"
@ -267,3 +273,4 @@ _beet "$@"
# Local Variables: # Local Variables:
# mode:shell-script # mode:shell-script
# End: # End:

View file

@ -89,6 +89,7 @@ def item(lib=None):
mb_albumid='someID-2', mb_albumid='someID-2',
mb_artistid='someID-3', mb_artistid='someID-3',
mb_albumartistid='someID-4', mb_albumartistid='someID-4',
mb_releasetrackid='someID-5',
album_id=None, album_id=None,
mtime=12345, mtime=12345,
) )

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -103,9 +103,9 @@ def _make_item(title, track, artist=u'some artist'):
def _make_trackinfo(): def _make_trackinfo():
return [ return [
TrackInfo(u'one', None, u'some artist', length=1, index=1), TrackInfo(u'one', None, artist=u'some artist', length=1, index=1),
TrackInfo(u'two', None, u'some artist', length=1, index=2), TrackInfo(u'two', None, artist=u'some artist', length=1, index=2),
TrackInfo(u'three', None, u'some artist', length=1, index=3), TrackInfo(u'three', None, artist=u'some artist', length=1, index=3),
] ]
@ -827,15 +827,15 @@ class ApplyCompilationTest(_common.TestCase, ApplyTestUtil):
trackinfo.append(TrackInfo( trackinfo.append(TrackInfo(
u'oneNew', u'oneNew',
u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c', u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c',
u'artistOneNew', artist=u'artistOneNew',
u'a05686fc-9db2-4c23-b99e-77f5db3e5282', artist_id=u'a05686fc-9db2-4c23-b99e-77f5db3e5282',
index=1, index=1,
)) ))
trackinfo.append(TrackInfo( trackinfo.append(TrackInfo(
u'twoNew', u'twoNew',
u'40130ed1-a27c-42fd-a328-1ebefb6caef4', u'40130ed1-a27c-42fd-a328-1ebefb6caef4',
u'artistTwoNew', artist=u'artistTwoNew',
u'80b3cf5e-18fe-4c59-98c7-e5bb87210710', artist_id=u'80b3cf5e-18fe-4c59-98c7-e5bb87210710',
index=2, index=2,
)) ))
self.info = AlbumInfo( self.info = AlbumInfo(

View file

@ -1819,6 +1819,7 @@ def mocked_get_release_by_id(id_, includes=[], release_status=[],
'id': id_, 'id': id_,
'medium-list': [{ 'medium-list': [{
'track-list': [{ 'track-list': [{
'id': 'baz',
'recording': { 'recording': {
'title': 'foo', 'title': 'foo',
'id': 'bar', 'id': 'bar',

View file

@ -27,7 +27,8 @@ import mock
class MBAlbumInfoTest(_common.TestCase): class MBAlbumInfoTest(_common.TestCase):
def _make_release(self, date_str='2009', tracks=None, track_length=None, def _make_release(self, date_str='2009', tracks=None, track_length=None,
track_artist=False, medium_format='FORMAT'): track_artist=False, data_tracks=None,
medium_format='FORMAT'):
release = { release = {
'title': 'ALBUM TITLE', 'title': 'ALBUM TITLE',
'id': 'ALBUM ID', 'id': 'ALBUM ID',
@ -62,12 +63,15 @@ class MBAlbumInfoTest(_common.TestCase):
'country': 'COUNTRY', 'country': 'COUNTRY',
'status': 'STATUS', 'status': 'STATUS',
} }
i = 0
track_list = []
if tracks: if tracks:
track_list = [] for recording in tracks:
for i, recording in enumerate(tracks): i += 1
track = { track = {
'id': 'RELEASE TRACK ID %d' % i,
'recording': recording, 'recording': recording,
'position': i + 1, 'position': i,
'number': 'A1', 'number': 'A1',
} }
if track_length: if track_length:
@ -87,12 +91,24 @@ class MBAlbumInfoTest(_common.TestCase):
} }
] ]
track_list.append(track) track_list.append(track)
release['medium-list'].append({ data_track_list = []
'position': '1', if data_tracks:
'track-list': track_list, for recording in data_tracks:
'format': medium_format, i += 1
'title': 'MEDIUM TITLE', data_track = {
}) 'id': 'RELEASE TRACK ID %d' % i,
'recording': recording,
'position': i,
'number': 'A1',
}
data_track_list.append(data_track)
release['medium-list'].append({
'position': '1',
'track-list': track_list,
'data-track-list': data_track_list,
'format': medium_format,
'title': 'MEDIUM TITLE',
})
return release return release
def _make_track(self, title, tr_id, duration, artist=False, video=False): def _make_track(self, title, tr_id, duration, artist=False, video=False):
@ -183,6 +199,7 @@ class MBAlbumInfoTest(_common.TestCase):
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)] self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
release = self._make_release(tracks=[tracks[0]]) release = self._make_release(tracks=[tracks[0]])
second_track_list = [{ second_track_list = [{
'id': 'RELEASE TRACK ID 2',
'recording': tracks[1], 'recording': tracks[1],
'position': '1', 'position': '1',
'number': 'A1', 'number': 'A1',
@ -354,6 +371,18 @@ class MBAlbumInfoTest(_common.TestCase):
self.assertEqual(d.tracks[0].title, 'TITLE ONE') self.assertEqual(d.tracks[0].title, 'TITLE ONE')
self.assertEqual(d.tracks[1].title, 'TITLE TWO') self.assertEqual(d.tracks[1].title, 'TITLE TWO')
def test_no_skip_audio_data_tracks(self):
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
data_tracks = [self._make_track('TITLE AUDIO DATA', 'ID DATA TRACK',
100.0 * 1000.0)]
release = self._make_release(tracks=tracks, data_tracks=data_tracks)
d = mb.album_info(release)
self.assertEqual(len(d.tracks), 3)
self.assertEqual(d.tracks[0].title, 'TITLE ONE')
self.assertEqual(d.tracks[1].title, 'TITLE TWO')
self.assertEqual(d.tracks[2].title, 'TITLE AUDIO DATA')
def test_skip_video_tracks_by_default(self): def test_skip_video_tracks_by_default(self):
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0), tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
self._make_track('TITLE VIDEO', 'ID VIDEO', 100.0 * 1000.0, self._make_track('TITLE VIDEO', 'ID VIDEO', 100.0 * 1000.0,
@ -365,6 +394,17 @@ class MBAlbumInfoTest(_common.TestCase):
self.assertEqual(d.tracks[0].title, 'TITLE ONE') self.assertEqual(d.tracks[0].title, 'TITLE ONE')
self.assertEqual(d.tracks[1].title, 'TITLE TWO') self.assertEqual(d.tracks[1].title, 'TITLE TWO')
def test_skip_video_data_tracks_by_default(self):
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
data_tracks = [self._make_track('TITLE VIDEO', 'ID VIDEO',
100.0 * 1000.0, False, True)]
release = self._make_release(tracks=tracks, data_tracks=data_tracks)
d = mb.album_info(release)
self.assertEqual(len(d.tracks), 2)
self.assertEqual(d.tracks[0].title, 'TITLE ONE')
self.assertEqual(d.tracks[1].title, 'TITLE TWO')
def test_no_skip_video_tracks_if_configured(self): def test_no_skip_video_tracks_if_configured(self):
config['match']['ignore_video_tracks'] = False config['match']['ignore_video_tracks'] = False
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0), tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
@ -378,6 +418,19 @@ class MBAlbumInfoTest(_common.TestCase):
self.assertEqual(d.tracks[1].title, 'TITLE VIDEO') self.assertEqual(d.tracks[1].title, 'TITLE VIDEO')
self.assertEqual(d.tracks[2].title, 'TITLE TWO') self.assertEqual(d.tracks[2].title, 'TITLE TWO')
def test_no_skip_video_data_tracks_if_configured(self):
config['match']['ignore_video_tracks'] = False
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
data_tracks = [self._make_track('TITLE VIDEO', 'ID VIDEO',
100.0 * 1000.0, False, True)]
release = self._make_release(tracks=tracks, data_tracks=data_tracks)
d = mb.album_info(release)
self.assertEqual(len(d.tracks), 3)
self.assertEqual(d.tracks[0].title, 'TITLE ONE')
self.assertEqual(d.tracks[1].title, 'TITLE TWO')
self.assertEqual(d.tracks[2].title, 'TITLE VIDEO')
class ParseIDTest(_common.TestCase): class ParseIDTest(_common.TestCase):
def test_parse_id_correct(self): def test_parse_id_correct(self):
@ -504,6 +557,7 @@ class MBLibraryTest(unittest.TestCase):
'id': mbid, 'id': mbid,
'medium-list': [{ 'medium-list': [{
'track-list': [{ 'track-list': [{
'id': 'baz',
'recording': { 'recording': {
'title': 'foo', 'title': 'foo',
'id': 'bar', 'id': 'bar',

View file

@ -318,29 +318,30 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
""" """
full_initial_tags = { full_initial_tags = {
'title': u'full', 'title': u'full',
'artist': u'the artist', 'artist': u'the artist',
'album': u'the album', 'album': u'the album',
'genre': u'the genre', 'genre': u'the genre',
'composer': u'the composer', 'composer': u'the composer',
'grouping': u'the grouping', 'grouping': u'the grouping',
'year': 2001, 'year': 2001,
'month': None, 'month': None,
'day': None, 'day': None,
'date': datetime.date(2001, 1, 1), 'date': datetime.date(2001, 1, 1),
'track': 2, 'track': 2,
'tracktotal': 3, 'tracktotal': 3,
'disc': 4, 'disc': 4,
'disctotal': 5, 'disctotal': 5,
'lyrics': u'the lyrics', 'lyrics': u'the lyrics',
'comments': u'the comments', 'comments': u'the comments',
'bpm': 6, 'bpm': 6,
'comp': True, 'comp': True,
'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e', 'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e',
'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628', 'mb_releasetrackid': 'c29f3a57-b439-46fd-a2e2-93776b1371e0',
'mb_artistid': '7cf0ea9d-86b9-4dad-ba9e-2355a64899ea', 'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628',
'art': None, 'mb_artistid': '7cf0ea9d-86b9-4dad-ba9e-2355a64899ea',
'label': u'the label', 'art': None,
'label': u'the label',
} }
tag_fields = [ tag_fields = [
@ -366,6 +367,7 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
'bpm', 'bpm',
'comp', 'comp',
'mb_trackid', 'mb_trackid',
'mb_releasetrackid',
'mb_albumid', 'mb_albumid',
'mb_artistid', 'mb_artistid',
'art', 'art',
@ -773,7 +775,7 @@ class MusepackTest(ReadWriteTestBase, unittest.TestCase):
extension = 'mpc' extension = 'mpc'
audio_properties = { audio_properties = {
'length': 1.0, 'length': 1.0,
'bitrate': 23458, 'bitrate': 24023,
'format': u'Musepack', 'format': u'Musepack',
'samplerate': 44100, 'samplerate': 44100,
'bitdepth': 0, 'bitdepth': 0,
@ -871,7 +873,7 @@ class ApeTest(ReadWriteTestBase, ExtendedImageStructureTestMixin,
extension = 'ape' extension = 'ape'
audio_properties = { audio_properties = {
'length': 1.0, 'length': 1.0,
'bitrate': 112040, 'bitrate': 112608,
'format': u'APE', 'format': u'APE',
'samplerate': 44100, 'samplerate': 44100,
'bitdepth': 16, 'bitdepth': 16,
@ -883,7 +885,7 @@ class WavpackTest(ReadWriteTestBase, unittest.TestCase):
extension = 'wv' extension = 'wv'
audio_properties = { audio_properties = {
'length': 1.0, 'length': 1.0,
'bitrate': 108744, 'bitrate': 109312,
'format': u'WavPack', 'format': u'WavPack',
'samplerate': 44100, 'samplerate': 44100,
'bitdepth': 0, 'bitdepth': 0,
@ -895,7 +897,7 @@ class OpusTest(ReadWriteTestBase, unittest.TestCase):
extension = 'opus' extension = 'opus'
audio_properties = { audio_properties = {
'length': 1.0, 'length': 1.0,
'bitrate': 57984, 'bitrate': 66792,
'format': u'Opus', 'format': u'Opus',
'samplerate': 48000, 'samplerate': 48000,
'bitdepth': 0, 'bitdepth': 0,