diff --git a/beets/autotag/__init__.py b/beets/autotag/__init__.py index 09564f49d..c4ee1300e 100644 --- a/beets/autotag/__init__.py +++ b/beets/autotag/__init__.py @@ -40,6 +40,7 @@ def apply_item_metadata(item, track_info): item.artist_credit = track_info.artist_credit item.title = track_info.title item.mb_trackid = track_info.track_id + item.mb_releasetrackid = track_info.release_track_id if track_info.artist_id: item.mb_artistid = track_info.artist_id if track_info.data_source: @@ -129,6 +130,7 @@ def apply_metadata(album_info, mapping): # MusicBrainz IDs. item.mb_trackid = track_info.track_id + item.mb_releasetrackid = track_info.release_track_id item.mb_albumid = album_info.album_id if track_info.artist_id: item.mb_artistid = track_info.artist_id diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 053d050c6..7df1f62f4 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -129,6 +129,8 @@ class TrackInfo(object): - ``title``: name of the track - ``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_id`` - ``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`` are all 1-based. """ - def __init__(self, title, track_id, artist=None, artist_id=None, - length=None, index=None, medium=None, medium_index=None, - medium_total=None, artist_sort=None, disctitle=None, - artist_credit=None, data_source=None, data_url=None, - media=None, lyricist=None, composer=None, composer_sort=None, - arranger=None, track_alt=None): + def __init__(self, title, track_id, release_track_id=None, artist=None, + artist_id=None, length=None, index=None, medium=None, + medium_index=None, medium_total=None, artist_sort=None, + disctitle=None, artist_credit=None, data_source=None, + data_url=None, media=None, lyricist=None, composer=None, + composer_sort=None, arranger=None, track_alt=None): self.title = title self.track_id = track_id + self.release_track_id = release_track_id self.artist = artist self.artist_id = artist_id self.length = length diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 9ce449a8b..2b28a5cc4 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -281,6 +281,10 @@ def album_info(release): continue 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: all_tracks.insert(0, medium['pregap']) @@ -302,8 +306,9 @@ def album_info(release): index, int(medium['position']), int(track['position']), - len(medium['track-list']), + track_count, ) + ti.release_track_id = track['id'] ti.disctitle = disctitle ti.media = format ti.track_alt = track['number'] diff --git a/beets/library.py b/beets/library.py index 64035e642..ba57407d0 100644 --- a/beets/library.py +++ b/beets/library.py @@ -455,6 +455,7 @@ class Item(LibModel): 'mb_albumid': types.STRING, 'mb_artistid': types.STRING, 'mb_albumartistid': types.STRING, + 'mb_releasetrackid': types.STRING, 'albumtype': types.STRING, 'label': types.STRING, 'acoustid_fingerprint': types.STRING, diff --git a/beets/mediafile.py b/beets/mediafile.py index 34ad49af7..32a32fe1d 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -1865,6 +1865,12 @@ class MediaFile(object): StorageStyle('MUSICBRAINZ_TRACKID'), 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( MP3DescStorageStyle(u'MusicBrainz Album Id'), MP4StorageStyle('----:com.apple.iTunes:MusicBrainz Album Id'), diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 610ae551e..46ae1d936 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -237,7 +237,7 @@ def show_change(cur_artist, cur_album, match): medium = track_info.disc mediums = track_info.disctotal if config['per_disc_numbering']: - if mediums > 1: + if mediums and mediums > 1: return u'{0}-{1}'.format(medium, medium_index) else: return six.text_type(medium_index or index) diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index a760eb619..eeb87d311 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -498,9 +498,10 @@ class DiscogsPlugin(BeetsPlugin): medium, medium_index, _ = self.get_track_index(track['position']) artist, artist_id = self.get_artist(track.get('artists', [])) length = self.get_track_length(track['duration']) - return TrackInfo(title, track_id, artist, artist_id, length, index, - medium, medium_index, artist_sort=None, - disctitle=None, artist_credit=None) + return TrackInfo(title, track_id, artist=artist, artist_id=artist_id, + length=length, index=index, + medium=medium, medium_index=medium_index, + artist_sort=None, disctitle=None, artist_credit=None) def get_track_index(self, position): """Returns the medium, medium index and subtrack index for a discogs diff --git a/beetsplug/keyfinder.py b/beetsplug/keyfinder.py index 34a4abca4..a3fbc8211 100644 --- a/beetsplug/keyfinder.py +++ b/beetsplug/keyfinder.py @@ -48,7 +48,7 @@ class KeyFinderPlugin(BeetsPlugin): self.find_key(lib.items(ui.decargs(args)), write=ui.should_write()) def imported(self, session, task): - self.find_key(task.items) + self.find_key(task.imported_items()) def find_key(self, items, write=False): overwrite = self.config['overwrite'].get(bool) diff --git a/docs/changelog.rst b/docs/changelog.rst index b4431b1f7..0cc8400e3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -29,6 +29,9 @@ New features: and tracklist positions. Track ids are stored in ``mb_trackid``. :bug:`#2336` Thanks to :user:`dbogdanov`. * :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: @@ -104,6 +107,11 @@ Fixes: to which a track belongs, not the total number of different mediums present on the release. :bug:`2887` 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: diff --git a/docs/reference/pathformat.rst b/docs/reference/pathformat.rst index 667be3150..72907b6df 100644 --- a/docs/reference/pathformat.rst +++ b/docs/reference/pathformat.rst @@ -239,6 +239,7 @@ Audio information: MusicBrainz and fingerprint information: * mb_trackid +* mb_releasetrackid * mb_albumid * mb_artistid * mb_albumartistid diff --git a/extra/_beet b/extra/_beet index 23155b8e5..5b715dcef 100644 --- a/extra/_beet +++ b/extra/_beet @@ -2,28 +2,29 @@ # 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) -# You can make it faster in future by creating a cached version: -# 1) perform a query completion with this file (_beet), e.g. do: beet list artist:" -# to create the completion function (takes a few seconds) -# 2) save a copy of the completion function: which _beet > _beet_cached -# 3) save a copy of the query completion function: which _beet_query > _beet_query_cached -# 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 -# 6) add the following line to the top of _beet_cached: #compdef beet -# 7) add the following line to the bottom of _beet_cached: _beet "$@" -# 8) save _beet_cached to your completions directory (e.g. /usr/share/zsh/functions/Completion) -# 9) add the following line to your .zshrc file: compdef _beet_cached beet -# You will need to repeat this proceedure each time you enable new plugins if you want them to complete properly. +# Cache will be updated if it is older than the beets database or binary. +# Need to set BEETS_LIBRARY to some preliminary value since it is used by the cache checking function. +typeset -g BEETS_LIBRARY=~/.config/beets/library.db +zstyle ":completion:${curcontext}:" cache-policy _beet_check_cache +_beet_check_cache () { + [[ ! -a "${1}" ]] || [[ ! -a ${~BEETS_LIBRARY} ]] || [[ "${1}" -ot ${~BEETS_LIBRARY} ]] || [[ "${1}" -ot =beet ]] +} +# Try to retrieve the cache, and find out if it needs to be updated +if ! _retrieve_cache beets || _cache_invalid beets; then + local updatecache=1 + # Location of database + typeset -g BEETS_LIBRARY="$(beet config|grep library|cut -f 2 -d ' ')" + # 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 local matchany=/$'[^\0]##\0'/ # Deal with completions for querying and modifying fields.. 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 matchquery=/"(${(j/|/)fields[@]})"$':[^\0]##\0'/ matchmodify=/"(${(j/|/)fields[@]})"$'(=[^\0]##|!)\0'/ @@ -43,14 +44,17 @@ function _join_lines() { function _beet_field_values() { local -a output fieldvals - local library="$(beet config|grep library|cut -f 2 -d ' ')" - output=$(sqlite3 ${~library} "select distinct $1 from items;") + local sqlcmd="select distinct $1 from items;" case $1 in lyrics) 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[@]}") ;; 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) modifyelem="_values -S = 'modify field (replace = with ! to remove field)' $(echo "'${^fields[@]}:: '")" # Create completion function for queries -_regex_arguments _beet_query "$matchany" \# \( "$matchquery" ":query:query string:$queryelem" \) \ - \( "$matchquery" ":query:query string:$queryelem" \) \# +_regex_arguments _beet_query "$matchany" \# \( "$matchquery" ":query:query string:$queryelem" \) \( "$matchquery" ":query:query string:$queryelem" \) \# # store regexps for completing lists of queries and modifications local -a query modify query=( \( "$matchquery" ":query:query string:{_beet_query}" \) \( "$matchquery" ":query:query string:{_beet_query}" \) \# ) @@ -108,7 +111,7 @@ retagopt='-L:retag items matching a query:${query[@]}' skipopt='-i:skip already-imported directories' noskipopt='-I:do not skip already-imported directories' flatopt='--flat:import an entire tree as a single album' -groupopt='-g:group tracks in a folder into separate albums' +groupopt='-g:group tracks in a folder into separate albums' editopt='-e:edit user configuration with $EDITOR' defaultopt='-d:include the default configuration' copynomoveopt='-c:copy instead of moving' @@ -174,73 +177,76 @@ function _beet_subcmd_options() } # Now build the arguments to _regex_arguments for each subcommand. -local -a options regex_words_subcmds regex_words_help -local subcmd cmddesc -for i in ${${(f)"$(beet help | _join_lines ' ' 3 'Commands:')"[@]}[@]} -do - subcmd="${i[(w)1]}" - # remove first word and parenthesised alias, replace : with -, [ with (, ] with ), and remove single quotes - cmddesc="${${${${${i[(w)2,-1]##\(*\) #}//:/-}//\[/(}//\]/)}//\'/}" - case $subcmd +if [[ -n $updatecache ]]; then + local -a options regex_words_subcmds regex_words_help + local subcmd cmddesc + for i in ${${(f)"$(beet help | _join_lines ' ' 3 'Commands:')"[@]}[@]} + do + subcmd="${i[(w)1]}" + # remove first word and parenthesised alias, replace : with -, [ with (, ] with ), and remove single quotes + cmddesc="${${${${${i[(w)2,-1]##\(*\) #}//:/-}//\[/(}//\]/)}//\'/}" + case $subcmd in - (config) - _regex_words options "config options" "$helpopt" "$pathopt" "$editopt" "$defaultopt" - options=("${reply[@]}") - ;; - (import) - _regex_words options "import options" "$helpopt" "$writeopt" "$nowriteopt" "$copyopt" "$nocopyopt"\ - "$inferopt" "$noinferopt" "$resumeopt" "$noresumeopt" "$nopromptopt" "$logopt" "$individualopt" "$confirmopt"\ - "$retagopt" "$skipopt" "$noskipopt" "$flatopt" "$groupopt" - options=( "${reply[@]}" \# "${files[@]}" \# ) - ;; - (list) - _regex_words options "list options" "$helpopt" "$pathopt" "$albumopt" "$formatopt" - options=( "$reply[@]" \# "${query[@]}" ) - ;; - (modify) - _regex_words options "modify options" "$helpopt" "$dontmoveopt" "$writeopt" "$nowriteopt" "$albumopt" \ - "$noconfirmopt" "$formatopt" - options=( "${reply[@]}" \# "${query[@]}" "${modify[@]}" ) - ;; - (move) - _regex_words options "move options" "$helpopt" "$albumopt" "$destopt" "$copynomoveopt" - options=( "${reply[@]}" \# "${query[@]}") - ;; - (remove) - _regex_words options "remove options" "$helpopt" "$albumopt" "$removeopt" - options=( "${reply[@]}" \# "${query[@]}" ) - ;; - (stats) - _regex_words options "stats options" "$helpopt" "$exactopt" - options=( "${reply[@]}" \# "${query[@]}" ) - ;; - (update) - _regex_words options "update options" "$helpopt" "$albumopt" "$dontmoveopt" "$pretendopt" "$formatopt" - options=( "${reply[@]}" \# "${query[@]}" ) - ;; - (write) - _regex_words options "write options" "$helpopt" "$pretendopt" - options=( "${reply[@]}" \# "${query[@]}" ) - ;; - (fields|migrate|version) - options=() - ;; - (help) - # The help subcommand is treated separately - continue - ;; - (*) # completions for plugin commands are generated using _beet_subcmd_options - _beet_subcmd_options "$subcmd" - options=( \( "${reply[@]}" \# "${query[@]}" \) ) - ;; - esac - # Create variable for holding option for this subcommand, and assign to it (needs to have a unique name). - typeset -a opts_for_$subcmd - set -A opts_for_$subcmd ${options[@]} # Assignment MUST be done using set (other methods fail). - regex_words_subcmds+=("$subcmd:$cmddesc:\${(@)opts_for_$subcmd}") - # Add to regex_words args for help subcommand - regex_words_help+=("$subcmd:$cmddesc") -done + (config) + _regex_words options "config options" "$helpopt" "$pathopt" "$editopt" "$defaultopt" + options=("${reply[@]}") + ;; + (import) + _regex_words options "import options" "$helpopt" "$writeopt" "$nowriteopt" "$copyopt" "$nocopyopt"\ + "$inferopt" "$noinferopt" "$resumeopt" "$noresumeopt" "$nopromptopt" "$logopt" "$individualopt" "$confirmopt"\ + "$retagopt" "$skipopt" "$noskipopt" "$flatopt" "$groupopt" + options=( "${reply[@]}" \# "${files[@]}" \# ) + ;; + (list) + _regex_words options "list options" "$helpopt" "$pathopt" "$albumopt" "$formatopt" + options=( "$reply[@]" \# "${query[@]}" ) + ;; + (modify) + _regex_words options "modify options" "$helpopt" "$dontmoveopt" "$writeopt" "$nowriteopt" "$albumopt" \ + "$noconfirmopt" "$formatopt" + options=( "${reply[@]}" \# "${query[@]}" "${modify[@]}" ) + ;; + (move) + _regex_words options "move options" "$helpopt" "$albumopt" "$destopt" "$copynomoveopt" + options=( "${reply[@]}" \# "${query[@]}") + ;; + (remove) + _regex_words options "remove options" "$helpopt" "$albumopt" "$removeopt" + options=( "${reply[@]}" \# "${query[@]}" ) + ;; + (stats) + _regex_words options "stats options" "$helpopt" "$exactopt" + options=( "${reply[@]}" \# "${query[@]}" ) + ;; + (update) + _regex_words options "update options" "$helpopt" "$albumopt" "$dontmoveopt" "$pretendopt" "$formatopt" + options=( "${reply[@]}" \# "${query[@]}" ) + ;; + (write) + _regex_words options "write options" "$helpopt" "$pretendopt" + options=( "${reply[@]}" \# "${query[@]}" ) + ;; + (fields|migrate|version) + options=() + ;; + (help) + # The help subcommand is treated separately + continue + ;; + (*) # completions for plugin commands are generated using _beet_subcmd_options + _beet_subcmd_options "$subcmd" + options=( \( "${reply[@]}" \# "${query[@]}" \) ) + ;; + esac + # Create variable for holding option for this subcommand, and assign to it (needs to have a unique name). + typeset -a opts_for_$subcmd + set -A opts_for_$subcmd ${options[@]} # Assignment MUST be done using set (other methods fail). + regex_words_subcmds+=("$subcmd:$cmddesc:\${(@)opts_for_$subcmd}") + # Add to regex_words args for help subcommand + regex_words_help+=("$subcmd:$cmddesc") + done + _store_cache beets regex_words_subcmds regex_words_help BEETS_LIBRARY fields +fi local -a opts_for_help _regex_words subcmds "subcommands" "${regex_words_help[@]}" @@ -253,7 +259,7 @@ _regex_words options "global options" "$configopt" "$debugopt" "$libopt" "$helpo globalopts=("${reply[@]}") # Create main completion function -#local -a subcmds +local -a subcmds _regex_words subcmds "subcommands" "${regex_words_subcmds[@]}" subcmds=("${reply[@]}") _regex_arguments _beet "$matchany" \( "${globalopts[@]}" \# \) "${subcmds[@]}" @@ -267,3 +273,4 @@ _beet "$@" # Local Variables: # mode:shell-script # End: + diff --git a/test/_common.py b/test/_common.py index fc7b650b3..f5e65ca76 100644 --- a/test/_common.py +++ b/test/_common.py @@ -89,6 +89,7 @@ def item(lib=None): mb_albumid='someID-2', mb_artistid='someID-3', mb_albumartistid='someID-4', + mb_releasetrackid='someID-5', album_id=None, mtime=12345, ) diff --git a/test/rsrc/full.aiff b/test/rsrc/full.aiff index 5d791b0b7..e9606f9e7 100644 Binary files a/test/rsrc/full.aiff and b/test/rsrc/full.aiff differ diff --git a/test/rsrc/full.alac.m4a b/test/rsrc/full.alac.m4a index 8ec7d377c..6bfcf317e 100644 Binary files a/test/rsrc/full.alac.m4a and b/test/rsrc/full.alac.m4a differ diff --git a/test/rsrc/full.ape b/test/rsrc/full.ape index 5bcea98e8..e1667505c 100644 Binary files a/test/rsrc/full.ape and b/test/rsrc/full.ape differ diff --git a/test/rsrc/full.dsf b/test/rsrc/full.dsf index a90e6946f..f13ea694b 100644 Binary files a/test/rsrc/full.dsf and b/test/rsrc/full.dsf differ diff --git a/test/rsrc/full.flac b/test/rsrc/full.flac index abc18ac30..e2d4f9bb4 100644 Binary files a/test/rsrc/full.flac and b/test/rsrc/full.flac differ diff --git a/test/rsrc/full.m4a b/test/rsrc/full.m4a index 6105250e6..2ec831e32 100644 Binary files a/test/rsrc/full.m4a and b/test/rsrc/full.m4a differ diff --git a/test/rsrc/full.mp3 b/test/rsrc/full.mp3 index 9aeca5fb3..d8c638f9c 100644 Binary files a/test/rsrc/full.mp3 and b/test/rsrc/full.mp3 differ diff --git a/test/rsrc/full.mpc b/test/rsrc/full.mpc index 9027ba862..007cfe494 100644 Binary files a/test/rsrc/full.mpc and b/test/rsrc/full.mpc differ diff --git a/test/rsrc/full.ogg b/test/rsrc/full.ogg index d2598d002..54e1d71eb 100644 Binary files a/test/rsrc/full.ogg and b/test/rsrc/full.ogg differ diff --git a/test/rsrc/full.opus b/test/rsrc/full.opus index 9a5534b22..2fb362c2f 100644 Binary files a/test/rsrc/full.opus and b/test/rsrc/full.opus differ diff --git a/test/rsrc/full.wma b/test/rsrc/full.wma index 741bc5f97..4a03f02f7 100644 Binary files a/test/rsrc/full.wma and b/test/rsrc/full.wma differ diff --git a/test/rsrc/full.wv b/test/rsrc/full.wv index d0fe83864..a892cb233 100644 Binary files a/test/rsrc/full.wv and b/test/rsrc/full.wv differ diff --git a/test/test_autotag.py b/test/test_autotag.py index 932616be1..244e48ecc 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -103,9 +103,9 @@ def _make_item(title, track, artist=u'some artist'): def _make_trackinfo(): return [ - TrackInfo(u'one', None, u'some artist', length=1, index=1), - TrackInfo(u'two', None, u'some artist', length=1, index=2), - TrackInfo(u'three', None, u'some artist', length=1, index=3), + TrackInfo(u'one', None, artist=u'some artist', length=1, index=1), + TrackInfo(u'two', None, artist=u'some artist', length=1, index=2), + TrackInfo(u'three', None, artist=u'some artist', length=1, index=3), ] @@ -827,15 +827,15 @@ class ApplyCompilationTest(_common.TestCase, ApplyTestUtil): trackinfo.append(TrackInfo( u'oneNew', u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c', - u'artistOneNew', - u'a05686fc-9db2-4c23-b99e-77f5db3e5282', + artist=u'artistOneNew', + artist_id=u'a05686fc-9db2-4c23-b99e-77f5db3e5282', index=1, )) trackinfo.append(TrackInfo( u'twoNew', u'40130ed1-a27c-42fd-a328-1ebefb6caef4', - u'artistTwoNew', - u'80b3cf5e-18fe-4c59-98c7-e5bb87210710', + artist=u'artistTwoNew', + artist_id=u'80b3cf5e-18fe-4c59-98c7-e5bb87210710', index=2, )) self.info = AlbumInfo( diff --git a/test/test_importer.py b/test/test_importer.py index e30f5609c..6721f0dc2 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -1819,6 +1819,7 @@ def mocked_get_release_by_id(id_, includes=[], release_status=[], 'id': id_, 'medium-list': [{ 'track-list': [{ + 'id': 'baz', 'recording': { 'title': 'foo', 'id': 'bar', diff --git a/test/test_mb.py b/test/test_mb.py index 644e7f5de..b61ebe59a 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -27,7 +27,8 @@ import mock class MBAlbumInfoTest(_common.TestCase): 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 = { 'title': 'ALBUM TITLE', 'id': 'ALBUM ID', @@ -62,12 +63,15 @@ class MBAlbumInfoTest(_common.TestCase): 'country': 'COUNTRY', 'status': 'STATUS', } + i = 0 + track_list = [] if tracks: - track_list = [] - for i, recording in enumerate(tracks): + for recording in tracks: + i += 1 track = { + 'id': 'RELEASE TRACK ID %d' % i, 'recording': recording, - 'position': i + 1, + 'position': i, 'number': 'A1', } if track_length: @@ -87,12 +91,24 @@ class MBAlbumInfoTest(_common.TestCase): } ] track_list.append(track) - release['medium-list'].append({ - 'position': '1', - 'track-list': track_list, - 'format': medium_format, - 'title': 'MEDIUM TITLE', - }) + data_track_list = [] + if data_tracks: + for recording in data_tracks: + i += 1 + 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 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)] release = self._make_release(tracks=[tracks[0]]) second_track_list = [{ + 'id': 'RELEASE TRACK ID 2', 'recording': tracks[1], 'position': '1', 'number': 'A1', @@ -354,6 +371,18 @@ class MBAlbumInfoTest(_common.TestCase): self.assertEqual(d.tracks[0].title, 'TITLE ONE') 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): tracks = [self._make_track('TITLE ONE', 'ID ONE', 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[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): config['match']['ignore_video_tracks'] = False 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[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): def test_parse_id_correct(self): @@ -504,6 +557,7 @@ class MBLibraryTest(unittest.TestCase): 'id': mbid, 'medium-list': [{ 'track-list': [{ + 'id': 'baz', 'recording': { 'title': 'foo', 'id': 'bar', diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 5a004b7e8..36a2c53ac 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -318,29 +318,30 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, """ full_initial_tags = { - 'title': u'full', - 'artist': u'the artist', - 'album': u'the album', - 'genre': u'the genre', - 'composer': u'the composer', - 'grouping': u'the grouping', - 'year': 2001, - 'month': None, - 'day': None, - 'date': datetime.date(2001, 1, 1), - 'track': 2, - 'tracktotal': 3, - 'disc': 4, - 'disctotal': 5, - 'lyrics': u'the lyrics', - 'comments': u'the comments', - 'bpm': 6, - 'comp': True, - 'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e', - 'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628', - 'mb_artistid': '7cf0ea9d-86b9-4dad-ba9e-2355a64899ea', - 'art': None, - 'label': u'the label', + 'title': u'full', + 'artist': u'the artist', + 'album': u'the album', + 'genre': u'the genre', + 'composer': u'the composer', + 'grouping': u'the grouping', + 'year': 2001, + 'month': None, + 'day': None, + 'date': datetime.date(2001, 1, 1), + 'track': 2, + 'tracktotal': 3, + 'disc': 4, + 'disctotal': 5, + 'lyrics': u'the lyrics', + 'comments': u'the comments', + 'bpm': 6, + 'comp': True, + 'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e', + 'mb_releasetrackid': 'c29f3a57-b439-46fd-a2e2-93776b1371e0', + 'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628', + 'mb_artistid': '7cf0ea9d-86b9-4dad-ba9e-2355a64899ea', + 'art': None, + 'label': u'the label', } tag_fields = [ @@ -366,6 +367,7 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, 'bpm', 'comp', 'mb_trackid', + 'mb_releasetrackid', 'mb_albumid', 'mb_artistid', 'art', @@ -773,7 +775,7 @@ class MusepackTest(ReadWriteTestBase, unittest.TestCase): extension = 'mpc' audio_properties = { 'length': 1.0, - 'bitrate': 23458, + 'bitrate': 24023, 'format': u'Musepack', 'samplerate': 44100, 'bitdepth': 0, @@ -871,7 +873,7 @@ class ApeTest(ReadWriteTestBase, ExtendedImageStructureTestMixin, extension = 'ape' audio_properties = { 'length': 1.0, - 'bitrate': 112040, + 'bitrate': 112608, 'format': u'APE', 'samplerate': 44100, 'bitdepth': 16, @@ -883,7 +885,7 @@ class WavpackTest(ReadWriteTestBase, unittest.TestCase): extension = 'wv' audio_properties = { 'length': 1.0, - 'bitrate': 108744, + 'bitrate': 109312, 'format': u'WavPack', 'samplerate': 44100, 'bitdepth': 0, @@ -895,7 +897,7 @@ class OpusTest(ReadWriteTestBase, unittest.TestCase): extension = 'opus' audio_properties = { 'length': 1.0, - 'bitrate': 57984, + 'bitrate': 66792, 'format': u'Opus', 'samplerate': 48000, 'bitdepth': 0,