Merge branch 'master' of https://github.com/beetbox/beets into fix-bs1170gain

This commit is contained in:
Yann Leprince 2018-01-02 10:54:40 +01:00
commit 079e167b6b
22 changed files with 270 additions and 97 deletions

View file

@ -19,7 +19,7 @@ import os
from beets.util import confit
__version__ = u'1.4.6'
__version__ = u'1.4.7'
__author__ = u'Adrian Sampson <adrian@radbox.org>'

View file

@ -36,6 +36,11 @@ if util.SNI_SUPPORTED:
else:
BASE_URL = 'http://musicbrainz.org/'
NON_AUDIO_FORMATS = ['Data CD', 'DVD', 'DVD-Video', 'Blu-ray', 'HD-DVD', 'VCD',
'SVCD', 'UMD', 'VHS']
SKIPPED_TRACKS = ['[data track]']
musicbrainzngs.set_useragent('beets', beets.__version__,
'http://beets.io/')
@ -275,11 +280,24 @@ def album_info(release):
disctitle = medium.get('title')
format = medium.get('format')
if format in NON_AUDIO_FORMATS:
continue
all_tracks = medium['track-list']
if 'pregap' in medium:
all_tracks.insert(0, medium['pregap'])
for track in all_tracks:
if ('title' in track['recording'] and
track['recording']['title'] in SKIPPED_TRACKS):
continue
if ('video' in track['recording'] and
track['recording']['video'] == 'true' and
config['match']['ignore_video_tracks']):
continue
# Basic information from the recording.
index += 1
ti = track_info(

View file

@ -10,6 +10,7 @@ import:
delete: no
resume: ask
incremental: no
from_scratch: no
quiet_fallback: skip
none_rec_action: ask
timid: no
@ -125,5 +126,6 @@ match:
original_year: no
ignored: []
required: []
ignore_video_tracks: yes
track_length_grace: 10
track_length_max: 30

View file

@ -534,6 +534,10 @@ class ImportTask(BaseImportTask):
def apply_metadata(self):
"""Copy metadata from match info to the items.
"""
if config['import']['from_scratch']:
for item in self.match.mapping:
item.clear()
autotag.apply_metadata(self.match.info, self.match.mapping)
def duplicate_items(self, lib):

View file

@ -561,6 +561,11 @@ class Item(LibModel):
if self.mtime == 0 and 'mtime' in values:
self.mtime = values['mtime']
def clear(self):
"""Set all key/value pairs to None."""
for key in self._media_fields:
setattr(self, key, None)
def get_album(self):
"""Get the Album object that this item belongs to, if any, or
None if the item is a singleton or is not associated with a

View file

@ -1004,6 +1004,10 @@ import_cmd.parser.add_option(
u'-I', u'--noincremental', dest='incremental', action='store_false',
help=u'do not skip already-imported directories'
)
import_cmd.parser.add_option(
u'--from-scratch', dest='from_scratch', action='store_true',
help=u'erase existing metadata before applying new metadata'
)
import_cmd.parser.add_option(
u'--flat', dest='flat', action='store_true',
help=u'import an entire tree as a single album'

View file

@ -88,14 +88,13 @@ def im_resize(maxwidth, path_in, path_out=None):
log.debug(u'artresizer: ImageMagick resizing {0} to {1}',
util.displayable_path(path_in), util.displayable_path(path_out))
# "-resize widthxheight>" shrinks images with dimension(s) larger
# than the corresponding width and/or height dimension(s). The >
# "only shrink" flag is prefixed by ^ escape char for Windows
# compatibility.
# "-resize WIDTHx>" shrinks images with the width larger
# than the given width while maintaining the aspect ratio
# with regards to the height.
try:
util.command_output([
'convert', util.syspath(path_in, prefix=False),
'-resize', '{0}x^>'.format(maxwidth),
'-resize', '{0}x>'.format(maxwidth),
util.syspath(path_out, prefix=False),
])
except subprocess.CalledProcessError:

View file

@ -110,6 +110,7 @@ class AcousticPlugin(plugins.BeetsPlugin):
self.config.add({
'auto': True,
'force': False,
'tags': []
})
if self.config['auto']:
@ -164,6 +165,7 @@ class AcousticPlugin(plugins.BeetsPlugin):
def _fetch_info(self, items, write, force):
"""Fetch additional information from AcousticBrainz for the `item`s.
"""
tags = self.config['tags'].as_str_seq()
for item in items:
# If we're not forcing re-downloading for all tracks, check
# whether the data is already present. We use one
@ -183,11 +185,18 @@ class AcousticPlugin(plugins.BeetsPlugin):
data = self._get_data(item.mb_trackid)
if data:
for attr, val in self._map_data_to_scheme(data, ABSCHEME):
self._log.debug(u'attribute {} of {} set to {}',
attr,
item,
val)
setattr(item, attr, val)
if not tags or attr in tags:
self._log.debug(u'attribute {} of {} set to {}',
attr,
item,
val)
setattr(item, attr, val)
else:
self._log.debug(u'skipping attribute {} of {}'
u' (value {}) due to config',
attr,
item,
val)
item.store()
if write:
item.try_write()

View file

@ -253,20 +253,19 @@ class DuplicatesPlugin(BeetsPlugin):
"completeness" (objects with more non-null fields come first)
and Albums are ordered by their track count.
"""
if tiebreak:
kind = 'items' if all(isinstance(o, Item)
for o in objs) else 'albums'
kind = 'items' if all(isinstance(o, Item) for o in objs) else 'albums'
if tiebreak and kind in tiebreak.keys():
key = lambda x: tuple(getattr(x, k) for k in tiebreak[kind])
else:
kind = Item if all(isinstance(o, Item) for o in objs) else Album
if kind is Item:
if kind == 'items':
def truthy(v):
# Avoid a Unicode warning by avoiding comparison
# between a bytes object and the empty Unicode
# string ''.
return v is not None and \
(v != '' if isinstance(v, six.text_type) else True)
fields = kind.all_keys()
fields = Item.all_keys()
key = lambda x: sum(1 for f in fields if truthy(getattr(x, f)))
else:
key = lambda x: len(x.items())

View file

@ -27,30 +27,21 @@ import six
# Filename field extraction patterns.
PATTERNS = [
# "01 - Track 01" and "01": do nothing
r'^(\d+)\s*-\s*track\s*\d$',
r'^\d+$',
# Useful patterns.
r'^(?P<artist>.+)-(?P<title>.+)-(?P<tag>.*)$',
r'^(?P<track>\d+)\s*-(?P<artist>.+)-(?P<title>.+)-(?P<tag>.*)$',
r'^(?P<track>\d+)\s(?P<artist>.+)-(?P<title>.+)-(?P<tag>.*)$',
r'^(?P<artist>.+)-(?P<title>.+)$',
r'^(?P<track>\d+)\.\s*(?P<artist>.+)-(?P<title>.+)$',
r'^(?P<track>\d+)\s*-\s*(?P<artist>.+)-(?P<title>.+)$',
r'^(?P<track>\d+)\s*-(?P<artist>.+)-(?P<title>.+)$',
r'^(?P<track>\d+)\s(?P<artist>.+)-(?P<title>.+)$',
r'^(?P<title>.+)$',
r'^(?P<track>\d+)\.\s*(?P<title>.+)$',
r'^(?P<track>\d+)\s*-\s*(?P<title>.+)$',
r'^(?P<track>\d+)\s(?P<title>.+)$',
r'^(?P<title>.+) by (?P<artist>.+)$',
# Useful patterns.
r'^(?P<artist>.+)[\-_](?P<title>.+)[\-_](?P<tag>.*)$',
r'^(?P<track>\d+)[\s.\-_]+(?P<artist>.+)[\-_](?P<title>.+)[\-_](?P<tag>.*)$',
r'^(?P<artist>.+)[\-_](?P<title>.+)$',
r'^(?P<track>\d+)[\s.\-_]+(?P<artist>.+)[\-_](?P<title>.+)$',
r'^(?P<title>.+)$',
r'^(?P<track>\d+)[\s.\-_]+(?P<title>.+)$',
r'^(?P<track>\d+)\s+(?P<title>.+)$',
r'^(?P<title>.+) by (?P<artist>.+)$',
r'^(?P<track>\d+).*$',
]
# Titles considered "empty" and in need of replacement.
BAD_TITLE_PATTERNS = [
r'^$',
r'\d+?\s?-?\s*track\s*\d+',
]

View file

@ -1,66 +1,97 @@
Changelog
=========
1.4.6 (in development)
1.4.7 (in development)
----------------------
New features:
Changelog goes here!
* When the importer finds duplicate albums, you can now merge all the tracks
together and try importing them as a single album.
Fixes:
* Non-audio media (DVD-Video, etc.) are now skipped by the autotagger. :bug:`2688`
* Non-audio tracks (data tracks, video tracks, etc.) are now skipped by the
autotagger. :bug:`1210`
1.4.6 (December 21, 2017)
-------------------------
The highlight of this release is "album merging," an oft-requested option in
the importer to add new tracks to an existing album you already have in your
library. This way, you no longer need to resort to removing the partial album
from your library, combining the files manually, and importing again.
Here are the larger new features in this release:
* When the importer finds duplicate albums, you can now merge all the
tracks---old and new---together and try importing them as a single, combined
album.
Thanks to :user:`udiboy1209`.
:bug:`112` :bug:`2725`
* :doc:`/plugins/lyrics`: The plugin can now produce reStructuredText files
for beautiful, readable books of lyrics. Thanks to :user:`anarcat`.
:bug:`2628`
* :doc:`/plugins/convert`: Adds ``no_convert`` option which ignores transcoding
items matching provided query string. Thanks to :user:`Stunner`.
* A new :ref:`from_scratch` configuration option makes the importer remove old
metadata before applying new metadata. This new feature complements the
:doc:`zero </plugins/zero>` and :doc:`scrub </plugins/scrub>` plugins but is
slightly different: beets clears out all the old tags it knows about and
only keeps the new data it gets from the remote metadata source.
Thanks to :user:`tummychow`.
:bug:`934` :bug:`2755`
There are also somewhat littler, but still great, new features:
* :doc:`/plugins/convert`: A new ``no_convert`` option lets you skip
transcoding items matching a query. Instead, the files are just copied
as-is. Thanks to :user:`Stunner`.
:bug:`2732` :bug:`2751`
* :doc:`/plugins/fetchart`: The plugin has now a quiet switch that will only
display missing album arts. Thanks to :user:`euri10`.
* :doc:`/plugins/fetchart`: A new quiet switch that only prints out messages
when album art is missing.
Thanks to :user:`euri10`.
:bug:`2683`
* :doc:`/plugins/mbcollection`: The plugin now supports a custom MusicBrainz
collection via the ``collection`` configuration option.
* :doc:`/plugins/mbcollection`: You can configure a custom MusicBrainz
collection via the new ``collection`` configuration option.
:bug:`2685`
* :doc:`/plugins/mbcollection`: The plugin now supports removing albums
from collections that are longer in the beets library.
* :doc:`/plugins/mpdstats`: The plugin now updates song stats when MPD switches
from a song to a stream and when it plays the same song consecutively.
:bug:`2707`
* :doc:`/plugins/mbcollection`: The collection update command can now remove
albums from collections that are longer in the beets library.
* :doc:`/plugins/fetchart`: The ``clearart`` command now asks for confirmation
before touching your files.
Thanks to :user:`konman2`.
:bug:`2708` :bug:`2427`
* :doc:`/plugins/lyrics`: The Genius backend should work again.
Thanks to :user:`lmagno`.
:bug:`2709`
* :doc:`/plugins/mpdstats`: The plugin now correctly updates song statistics
when MPD switches from a song to a stream and when it plays the same song
multiple times consecutively.
:bug:`2707`
* :doc:`/plugins/acousticbrainz`: The plugin can now be configured to write only
a specific list of tags.
Thanks to :user:`woparry`.
Fixes:
There are lots and lots of bug fixes:
* :doc:`/plugins/hook`: Fixed a problem whereby accessing non-string properties of
objects such as item or album (e.g. item.track) would cause a crash.
* :doc:`/plugins/hook`: Fixed a problem where accessing non-string properties
of ``item`` or ``album`` (e.g., ``item.track``) would cause a crash.
Thanks to :user:`broddo`.
:bug:`2740`
* :doc:`/plugins/play`: When ``relative_to`` is set, correctly emit relative paths
even when querying for albums rather than tracks.
* :doc:`/plugins/play`: When ``relative_to`` is set, the plugin correctly
emits relative paths even when querying for albums rather than tracks.
Thanks to :user:`j000`.
:bug:`2702`
* Prevent Python from warning about a ``BrokenPipeError`` being ignored even
though we do take it into account. This was an issue when using beets in
simple shell scripts.
* We suppress a spurious Python warning about a ``BrokenPipeError`` being
ignored. This was an issue when using beets in simple shell scripts.
Thanks to :user:`Azphreal`.
:bug:`2622` :bug:`2631`
* :doc:`/plugins/replaygain`: Fix a regression in the previous release related
to the new R128 tags. :bug:`2615` :bug:`2623`
* :doc:`/plugins/lyrics`: The MusixMatch backend now detect and warns
the user when blocked on the server. Thanks to
:user:`anarcat`. :bug:`2634` :bug:`2632`
* :doc:`/plugins/lyrics`: The MusixMatch backend now detects and warns
when the server has blocked the client.
Thanks to :user:`anarcat`. :bug:`2634` :bug:`2632`
* :doc:`/plugins/importfeeds`: Fix an error on Python 3 in certain
configurations. Thanks to :user:`djl`. :bug:`2467` :bug:`2658`
* :doc:`/plugins/edit`: Fix a bug when editing items during a ``-L``
re-import. Previously, diffs against against unrelated items could be
shown or beets could crash with a traceback. :bug:`2659`
* :doc:`/plugins/kodiupdate`: Fix server URL and add better error reporting.
* :doc:`/plugins/edit`: Fix a bug when editing items during a re-import with
the ``-L`` flag. Previously, diffs against against unrelated items could be
shown or beets could crash. :bug:`2659`
* :doc:`/plugins/kodiupdate`: Fix the server URL and add better error
reporting.
:bug:`2662`
* Fixed a problem where "no-op" modifications would reset files' mtimes,
resulting in unnecessary writes. This most prominently affected the
@ -70,26 +101,43 @@ Fixes:
Python 3 on Windows with non-ASCII filenames. :bug:`2671`
* :doc:`/plugins/absubmit`: Fix an occasional crash on Python 3 when the AB
analysis tool produced non-ASCII metadata. :bug:`2673`
* :doc:`/plugins/duplicates`: Fix the `--key` command line option, which was
* :doc:`/plugins/duplicates`: Use the default tiebreak for items or albums
when the configuration only specifies a tiebreak for the other kind of
entity.
Thanks to :user:`cgevans`.
:bug:`2758`
* :doc:`/plugins/duplicates`: Fix the ``--key`` command line option, which was
ignored.
* :doc:`/plugins/replaygain`: Fix album replaygain calculation with the
gstreamer backend. :bug:`2636`
* :doc:`/plugins/replaygain`: Fix album ReplayGain calculation with the
GStreamer backend. :bug:`2636`
* :doc:`/plugins/scrub`: Handle errors when manipulating files using newer
versions of Mutagen. :bug:`2716`
* :doc:`/plugins/fetchart`: Fix: don't skip running the fetchart plugin during import, when the
"Edit Candidates" option is used. :bug:`2734`
* :doc:`/plugins/fetchart`: The plugin no longer gets skipped during import
when the "Edit Candidates" option is used from the :doc:`/plugins/edit`.
:bug:`2734`
* Fix a crash when numeric metadata fields contain just a minus or plus sign
with no following numbers. Thanks to :user:`eigengrau`. :bug:`2741`
* :doc:`/plugins/fromfilename`: Recognize file names that contain *only* a
track number, such as `01.mp3`. Also, the plugin now allows underscores as a
separator between fields.
Thanks to :user:`Vrihub`.
:bug:`2738` :bug:`2759`
* Fixed an issue where images would be resized according to their longest
edge, instead of their width, when using the ``maxwidth`` config option in
the :doc:`/plugins/fetchart` and :doc:`/plugins/embedart`. Thanks to
:user:`sekjun9878`. :bug:`2729`
For developers:
There are some changes for developers:
* Fixed fields in Album and Item objects are now more strict about translating
* "Fixed fields" in Album and Item objects are now more strict about translating
missing values into type-specific null-like values. This should help in
cases where a string field is unexpectedly `None` sometimes instead of just
showing up as an empty string. :bug:`2605`
* Refactored the move functions in library.py and the `manipulate_files` function
in importer.py to use a single parameter describing the file operation instead
of multiple boolean flags. :bug:`2682`
* Refactored the move functions the `beets.library` module and the
`manipulate_files` function in `beets.importer` to use a single parameter
describing the file operation instead of multiple Boolean flags.
There is a new numerated type describing how to move, copy, or link files.
:bug:`2682`
1.4.5 (June 20, 2017)

View file

@ -16,7 +16,7 @@ project = u'beets'
copyright = u'2016, Adrian Sampson'
version = '1.4'
release = '1.4.6'
release = '1.4.7'
pygments_style = 'sphinx'

7
docs/modd.conf Normal file
View file

@ -0,0 +1,7 @@
**/*.rst {
prep: make html
}
_build/html/** {
daemon: devd -m _build/html
}

View file

@ -54,10 +54,12 @@ Configuration
-------------
To configure the plugin, make a ``acousticbrainz:`` section in your
configuration file. There is one option:
configuration file. There are three options:
- **auto**: Enable AcousticBrainz during ``beet import``.
Default: ``yes``.
- **force**: Download AcousticBrainz data even for tracks that already have
it.
Default: ``no``.
- **tags**: Which tags from the list above to set on your files.
Default: [] (all)

View file

@ -1,7 +1,7 @@
Thumbnails Plugin
==================
The ``thumbnails`` plugin creates thumbnails your for album folders with the
The ``thumbnails`` plugin creates thumbnails for your album folders with the
album cover. This works on freedesktop.org-compliant file managers such as
Nautilus or Thunar, and is therefore POSIX-only.

View file

@ -111,6 +111,12 @@ Optional command flags:
time, when no subdirectories will be skipped. So consider enabling the
``incremental`` configuration option.
* When beets applies metadata to your music, it will retain the value of any
existing tags that weren't overwritten, and import them into the database. You
may prefer to only use existing metadata for finding matches, and to erase it
completely when new metadata is applied. You can enforce this behavior with
the ``--from-scratch`` option, or the ``from_scratch`` configuration option.
* By default, beets will proceed without asking if it finds a very close
metadata match. To disable this and have the importer ask you every time,
use the ``-t`` (for *timid*) option.

View file

@ -475,6 +475,15 @@ Either ``yes`` or ``no``, controlling whether imported directories are
recorded and whether these recorded directories are skipped. This
corresponds to the ``-i`` flag to ``beet import``.
.. _from_scratch:
from_scratch
~~~~~~~~~~~~
Either ``yes`` or ``no`` (default), controlling whether existing metadata is
discarded when a match is applied. This corresponds to the ``--from_scratch``
flag to ``beet import``.
quiet_fallback
~~~~~~~~~~~~~~
@ -765,6 +774,17 @@ want to enforce to the ``required`` setting::
No tags are required by default.
.. _ignore_video_tracks:
ignore_video_tracks
~~~~~~~~~~~~~~~~~~~
By default, video tracks within a release will be ignored. If you want them to
be included (for example if you would like to track the audio-only versions of
the video tracks), set it to ``no``.
Default: ``yes``.
.. _path-format-config:
Path Format Configuration

View file

@ -1,10 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function
from livereload import Server, shell
server = Server()
server.watch('*.rst', shell('make html'))
server.serve(root='_build/html')

View file

@ -166,7 +166,7 @@ def rst2md(text):
"""Use Pandoc to convert text from ReST to Markdown.
"""
pandoc = subprocess.Popen(
['pandoc', '--from=rst', '--to=markdown', '--no-wrap'],
['pandoc', '--from=rst', '--to=markdown', '--wrap=none'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
stdout, _ = pandoc.communicate(text.encode('utf-8'))

View file

@ -56,7 +56,7 @@ if 'sdist' in sys.argv:
setup(
name='beets',
version='1.4.6',
version='1.4.7',
description='music tagger and library organizer',
author='Adrian Sampson',
author_email='adrian@radbox.org',

View file

@ -633,6 +633,17 @@ class ImportTest(_common.TestCase, ImportHelper):
self.assert_file_in_lib(
b'Applied Artist', b'Applied Album', b'Applied Title 1.mp3')
def test_apply_from_scratch_removes_other_metadata(self):
config['import']['from_scratch'] = True
for mediafile in self.import_media:
mediafile.genre = u'Tag Genre'
mediafile.save()
self.importer.add_choice(importer.action.APPLY)
self.importer.run()
self.assertEqual(self.lib.items().get().genre, u'')
def test_apply_with_move_deletes_import(self):
config['import']['move'] = True

View file

@ -27,7 +27,7 @@ import mock
class MBAlbumInfoTest(_common.TestCase):
def _make_release(self, date_str='2009', tracks=None, track_length=None,
track_artist=False):
track_artist=False, medium_format='FORMAT'):
release = {
'title': 'ALBUM TITLE',
'id': 'ALBUM ID',
@ -90,12 +90,12 @@ class MBAlbumInfoTest(_common.TestCase):
release['medium-list'].append({
'position': '1',
'track-list': track_list,
'format': 'FORMAT',
'format': medium_format,
'title': 'MEDIUM TITLE',
})
return release
def _make_track(self, title, tr_id, duration, artist=False):
def _make_track(self, title, tr_id, duration, artist=False, video=False):
track = {
'title': title,
'id': tr_id,
@ -113,6 +113,8 @@ class MBAlbumInfoTest(_common.TestCase):
'name': 'RECORDING ARTIST CREDIT',
}
]
if video:
track['video'] = 'true'
return track
def test_parse_release_with_year(self):
@ -324,6 +326,62 @@ class MBAlbumInfoTest(_common.TestCase):
d = mb.album_info(release)
self.assertEqual(d.data_source, 'MusicBrainz')
def test_skip_non_audio_dvd(self):
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
release = self._make_release(tracks=tracks, medium_format="DVD")
d = mb.album_info(release)
self.assertEqual(len(d.tracks), 0)
def test_skip_non_audio_dvd_video(self):
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
release = self._make_release(tracks=tracks, medium_format="DVD-Video")
d = mb.album_info(release)
self.assertEqual(len(d.tracks), 0)
def test_no_skip_dvd_audio(self):
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
release = self._make_release(tracks=tracks, medium_format="DVD-Audio")
d = mb.album_info(release)
self.assertEqual(len(d.tracks), 2)
def test_skip_data_track(self):
tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0),
self._make_track('[data track]', 'ID DATA TRACK',
100.0 * 1000.0),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
release = self._make_release(tracks=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_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,
False, True),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
release = self._make_release(tracks=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),
self._make_track('TITLE VIDEO', 'ID VIDEO', 100.0 * 1000.0,
False, True),
self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)]
release = self._make_release(tracks=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 VIDEO')
self.assertEqual(d.tracks[2].title, 'TITLE TWO')
class ParseIDTest(_common.TestCase):
def test_parse_id_correct(self):