mirror of
https://github.com/beetbox/beets.git
synced 2025-12-14 20:43:41 +01:00
Merge pull request #3389 from rhlahuja/add-bpsync-plugin
Add BPSyncPlugin
This commit is contained in:
commit
61a56f1b51
11 changed files with 320 additions and 65 deletions
|
|
@ -186,7 +186,7 @@ def apply_metadata(album_info, mapping):
|
|||
'mb_workid',
|
||||
'work_disambig',
|
||||
'bpm',
|
||||
'musical_key',
|
||||
'initial_key',
|
||||
'genre'
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ class TrackInfo(object):
|
|||
data_url=None, media=None, lyricist=None, composer=None,
|
||||
composer_sort=None, arranger=None, track_alt=None,
|
||||
work=None, mb_workid=None, work_disambig=None, bpm=None,
|
||||
musical_key=None, genre=None):
|
||||
initial_key=None, genre=None):
|
||||
self.title = title
|
||||
self.track_id = track_id
|
||||
self.release_track_id = release_track_id
|
||||
|
|
@ -206,7 +206,7 @@ class TrackInfo(object):
|
|||
self.mb_workid = mb_workid
|
||||
self.work_disambig = work_disambig
|
||||
self.bpm = bpm
|
||||
self.musical_key = musical_key
|
||||
self.initial_key = initial_key
|
||||
self.genre = genre
|
||||
|
||||
# As above, work around a bug in python-musicbrainz-ngs.
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ class BeetsPlugin(object):
|
|||
|
||||
``descriptor`` must be an instance of ``mediafile.MediaField``.
|
||||
"""
|
||||
# Defer impor to prevent circular dependency
|
||||
# Defer import to prevent circular dependency
|
||||
from beets import library
|
||||
mediafile.MediaFile.add_field(name, descriptor)
|
||||
library.Item._media_fields.add(name)
|
||||
|
|
@ -590,6 +590,36 @@ def get_distance(config, data_source, info):
|
|||
return dist
|
||||
|
||||
|
||||
def apply_item_changes(lib, item, move, pretend, write):
|
||||
"""Store, move, and write the item according to the arguments.
|
||||
|
||||
:param lib: beets library.
|
||||
:type lib: beets.library.Library
|
||||
:param item: Item whose changes to apply.
|
||||
:type item: beets.library.Item
|
||||
:param move: Move the item if it's in the library.
|
||||
:type move: bool
|
||||
:param pretend: Return without moving, writing, or storing the item's
|
||||
metadata.
|
||||
:type pretend: bool
|
||||
:param write: Write the item's metadata to its media file.
|
||||
:type write: bool
|
||||
"""
|
||||
if pretend:
|
||||
return
|
||||
|
||||
from beets import util
|
||||
|
||||
# Move the item if it's in the library.
|
||||
if move and lib.directory in util.ancestry(item.path):
|
||||
item.move(with_album=False)
|
||||
|
||||
if write:
|
||||
item.try_write()
|
||||
|
||||
item.store()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class MetadataSourcePlugin(object):
|
||||
def __init__(self):
|
||||
|
|
@ -633,13 +663,20 @@ class MetadataSourcePlugin(object):
|
|||
"""Returns an artist string (all artists) and an artist_id (the main
|
||||
artist) for a list of artist object dicts.
|
||||
|
||||
:param artists: Iterable of artist dicts returned by API.
|
||||
:type artists: list[dict]
|
||||
:param id_key: Key corresponding to ``artist_id`` value.
|
||||
:type id_key: str
|
||||
:param name_key: Keys corresponding to values to concatenate
|
||||
for ``artist``.
|
||||
:type name_key: str
|
||||
For each artist, this function moves articles (such as 'a', 'an',
|
||||
and 'the') to the front and strips trailing disambiguation numbers. It
|
||||
returns a tuple containing the comma-separated string of all
|
||||
normalized artists and the ``id`` of the main/first artist.
|
||||
|
||||
:param artists: Iterable of artist dicts or lists returned by API.
|
||||
:type artists: list[dict] or list[list]
|
||||
:param id_key: Key or index corresponding to the value of ``id`` for
|
||||
the main/first artist. Defaults to 'id'.
|
||||
:type id_key: str or int
|
||||
:param name_key: Key or index corresponding to values of names
|
||||
to concatenate for the artist string (containing all artists).
|
||||
Defaults to 'name'.
|
||||
:type name_key: str or int
|
||||
:return: Normalized artist string.
|
||||
:rtype: str
|
||||
"""
|
||||
|
|
@ -649,6 +686,8 @@ class MetadataSourcePlugin(object):
|
|||
if not artist_id:
|
||||
artist_id = artist[id_key]
|
||||
name = artist[name_key]
|
||||
# Strip disambiguation number.
|
||||
name = re.sub(r' \(\d+\)$', '', name)
|
||||
# Move articles to the front.
|
||||
name = re.sub(r'^(.*?), (a|an|the)$', r'\2 \1', name, flags=re.I)
|
||||
artist_names.append(name)
|
||||
|
|
@ -694,8 +733,9 @@ class MetadataSourcePlugin(object):
|
|||
query_filters = {'album': album}
|
||||
if not va_likely:
|
||||
query_filters['artist'] = artist
|
||||
albums = self._search_api(query_type='album', filters=query_filters)
|
||||
return [self.album_for_id(album_id=a['id']) for a in albums]
|
||||
results = self._search_api(query_type='album', filters=query_filters)
|
||||
albums = [self.album_for_id(album_id=r['id']) for r in results]
|
||||
return [a for a in albums if a is not None]
|
||||
|
||||
def item_candidates(self, item, artist, title):
|
||||
"""Returns a list of TrackInfo objects for Search API results
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ from requests_oauthlib.oauth1_session import (TokenRequestDenied, TokenMissing,
|
|||
|
||||
import beets
|
||||
import beets.ui
|
||||
from beets.autotag.hooks import AlbumInfo, TrackInfo, Distance
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets.autotag.hooks import AlbumInfo, TrackInfo
|
||||
from beets.plugins import BeetsPlugin, MetadataSourcePlugin, get_distance
|
||||
import confuse
|
||||
|
||||
|
||||
|
|
@ -228,6 +228,7 @@ class BeatportRelease(BeatportObject):
|
|||
if 'slug' in data:
|
||||
self.url = "https://beatport.com/release/{0}/{1}".format(
|
||||
data['slug'], data['id'])
|
||||
self.genre = data.get('genre')
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
|
|
@ -258,7 +259,7 @@ class BeatportTrack(BeatportObject):
|
|||
.format(data['slug'], data['id'])
|
||||
self.track_number = data.get('trackNumber')
|
||||
self.bpm = data.get('bpm')
|
||||
self.musical_key = six.text_type(
|
||||
self.initial_key = six.text_type(
|
||||
(data.get('key') or {}).get('shortName')
|
||||
)
|
||||
|
||||
|
|
@ -270,6 +271,8 @@ class BeatportTrack(BeatportObject):
|
|||
|
||||
|
||||
class BeatportPlugin(BeetsPlugin):
|
||||
data_source = 'Beatport'
|
||||
|
||||
def __init__(self):
|
||||
super(BeatportPlugin, self).__init__()
|
||||
self.config.add({
|
||||
|
|
@ -333,22 +336,24 @@ class BeatportPlugin(BeetsPlugin):
|
|||
return self.config['tokenfile'].get(confuse.Filename(in_app_dir=True))
|
||||
|
||||
def album_distance(self, items, album_info, mapping):
|
||||
"""Returns the beatport source weight and the maximum source weight
|
||||
"""Returns the Beatport source weight and the maximum source weight
|
||||
for albums.
|
||||
"""
|
||||
dist = Distance()
|
||||
if album_info.data_source == 'Beatport':
|
||||
dist.add('source', self.config['source_weight'].as_number())
|
||||
return dist
|
||||
return get_distance(
|
||||
data_source=self.data_source,
|
||||
info=album_info,
|
||||
config=self.config
|
||||
)
|
||||
|
||||
def track_distance(self, item, track_info):
|
||||
"""Returns the beatport source weight and the maximum source weight
|
||||
"""Returns the Beatport source weight and the maximum source weight
|
||||
for individual tracks.
|
||||
"""
|
||||
dist = Distance()
|
||||
if track_info.data_source == 'Beatport':
|
||||
dist.add('source', self.config['source_weight'].as_number())
|
||||
return dist
|
||||
return get_distance(
|
||||
data_source=self.data_source,
|
||||
info=track_info,
|
||||
config=self.config
|
||||
)
|
||||
|
||||
def candidates(self, items, artist, release, va_likely):
|
||||
"""Returns a list of AlbumInfo objects for beatport search results
|
||||
|
|
@ -435,7 +440,8 @@ class BeatportPlugin(BeetsPlugin):
|
|||
day=release.release_date.day,
|
||||
label=release.label_name,
|
||||
catalognum=release.catalog_number, media=u'Digital',
|
||||
data_source=u'Beatport', data_url=release.url)
|
||||
data_source=self.data_source, data_url=release.url,
|
||||
genre=release.genre)
|
||||
|
||||
def _get_track_info(self, track):
|
||||
"""Returns a TrackInfo object for a Beatport Track object.
|
||||
|
|
@ -449,26 +455,17 @@ class BeatportPlugin(BeetsPlugin):
|
|||
artist=artist, artist_id=artist_id,
|
||||
length=length, index=track.track_number,
|
||||
medium_index=track.track_number,
|
||||
data_source=u'Beatport', data_url=track.url,
|
||||
bpm=track.bpm, musical_key=track.musical_key)
|
||||
data_source=self.data_source, data_url=track.url,
|
||||
bpm=track.bpm, initial_key=track.initial_key,
|
||||
genre=track.genre)
|
||||
|
||||
def _get_artist(self, artists):
|
||||
"""Returns an artist string (all artists) and an artist_id (the main
|
||||
artist) for a list of Beatport release or track artists.
|
||||
"""
|
||||
artist_id = None
|
||||
bits = []
|
||||
for artist in artists:
|
||||
if not artist_id:
|
||||
artist_id = artist[0]
|
||||
name = artist[1]
|
||||
# Strip disambiguation number.
|
||||
name = re.sub(r' \(\d+\)$', '', name)
|
||||
# Move articles to the front.
|
||||
name = re.sub(r'^(.*?), (a|an|the)$', r'\2 \1', name, flags=re.I)
|
||||
bits.append(name)
|
||||
artist = ', '.join(bits).replace(' ,', ',') or None
|
||||
return artist, artist_id
|
||||
return MetadataSourcePlugin.get_artist(
|
||||
artists=artists, id_key=0, name_key=1
|
||||
)
|
||||
|
||||
def _get_tracks(self, query):
|
||||
"""Returns a list of TrackInfo objects for a Beatport query.
|
||||
|
|
|
|||
188
beetsplug/bpsync.py
Normal file
188
beetsplug/bpsync.py
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2019, Rahul Ahuja.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Update library's tags using Beatport.
|
||||
"""
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
from beets.plugins import BeetsPlugin, apply_item_changes
|
||||
from beets import autotag, library, ui, util
|
||||
|
||||
from .beatport import BeatportPlugin
|
||||
|
||||
|
||||
class BPSyncPlugin(BeetsPlugin):
|
||||
def __init__(self):
|
||||
super(BPSyncPlugin, self).__init__()
|
||||
self.beatport_plugin = BeatportPlugin()
|
||||
self.beatport_plugin.setup()
|
||||
|
||||
def commands(self):
|
||||
cmd = ui.Subcommand('bpsync', help=u'update metadata from Beatport')
|
||||
cmd.parser.add_option(
|
||||
u'-p',
|
||||
u'--pretend',
|
||||
action='store_true',
|
||||
help=u'show all changes but do nothing',
|
||||
)
|
||||
cmd.parser.add_option(
|
||||
u'-m',
|
||||
u'--move',
|
||||
action='store_true',
|
||||
dest='move',
|
||||
help=u"move files in the library directory",
|
||||
)
|
||||
cmd.parser.add_option(
|
||||
u'-M',
|
||||
u'--nomove',
|
||||
action='store_false',
|
||||
dest='move',
|
||||
help=u"don't move files in library",
|
||||
)
|
||||
cmd.parser.add_option(
|
||||
u'-W',
|
||||
u'--nowrite',
|
||||
action='store_false',
|
||||
default=None,
|
||||
dest='write',
|
||||
help=u"don't write updated metadata to files",
|
||||
)
|
||||
cmd.parser.add_format_option()
|
||||
cmd.func = self.func
|
||||
return [cmd]
|
||||
|
||||
def func(self, lib, opts, args):
|
||||
"""Command handler for the bpsync function.
|
||||
"""
|
||||
move = ui.should_move(opts.move)
|
||||
pretend = opts.pretend
|
||||
write = ui.should_write(opts.write)
|
||||
query = ui.decargs(args)
|
||||
|
||||
self.singletons(lib, query, move, pretend, write)
|
||||
self.albums(lib, query, move, pretend, write)
|
||||
|
||||
def singletons(self, lib, query, move, pretend, write):
|
||||
"""Retrieve and apply info from the autotagger for items matched by
|
||||
query.
|
||||
"""
|
||||
for item in lib.items(query + [u'singleton:true']):
|
||||
if not item.mb_trackid:
|
||||
self._log.info(
|
||||
u'Skipping singleton with no mb_trackid: {}', item
|
||||
)
|
||||
continue
|
||||
|
||||
if not self.is_beatport_track(item):
|
||||
self._log.info(
|
||||
u'Skipping non-{} singleton: {}',
|
||||
self.beatport_plugin.data_source,
|
||||
item,
|
||||
)
|
||||
continue
|
||||
|
||||
# Apply.
|
||||
trackinfo = self.beatport_plugin.track_for_id(item.mb_trackid)
|
||||
with lib.transaction():
|
||||
autotag.apply_item_metadata(item, trackinfo)
|
||||
apply_item_changes(lib, item, move, pretend, write)
|
||||
|
||||
@staticmethod
|
||||
def is_beatport_track(item):
|
||||
return (
|
||||
item.get('data_source') == BeatportPlugin.data_source
|
||||
and item.mb_trackid.isnumeric()
|
||||
)
|
||||
|
||||
def get_album_tracks(self, album):
|
||||
if not album.mb_albumid:
|
||||
self._log.info(u'Skipping album with no mb_albumid: {}', album)
|
||||
return False
|
||||
if not album.mb_albumid.isnumeric():
|
||||
self._log.info(
|
||||
u'Skipping album with invalid {} ID: {}',
|
||||
self.beatport_plugin.data_source,
|
||||
album,
|
||||
)
|
||||
return False
|
||||
items = list(album.items())
|
||||
if album.get('data_source') == self.beatport_plugin.data_source:
|
||||
return items
|
||||
if not all(self.is_beatport_track(item) for item in items):
|
||||
self._log.info(
|
||||
u'Skipping non-{} release: {}',
|
||||
self.beatport_plugin.data_source,
|
||||
album,
|
||||
)
|
||||
return False
|
||||
return items
|
||||
|
||||
def albums(self, lib, query, move, pretend, write):
|
||||
"""Retrieve and apply info from the autotagger for albums matched by
|
||||
query and their items.
|
||||
"""
|
||||
# Process matching albums.
|
||||
for album in lib.albums(query):
|
||||
# Do we have a valid Beatport album?
|
||||
items = self.get_album_tracks(album)
|
||||
if not items:
|
||||
continue
|
||||
|
||||
# Get the Beatport album information.
|
||||
albuminfo = self.beatport_plugin.album_for_id(album.mb_albumid)
|
||||
if not albuminfo:
|
||||
self._log.info(
|
||||
u'Release ID {} not found for album {}',
|
||||
album.mb_albumid,
|
||||
album,
|
||||
)
|
||||
continue
|
||||
|
||||
beatport_trackid_to_trackinfo = {
|
||||
track.track_id: track for track in albuminfo.tracks
|
||||
}
|
||||
library_trackid_to_item = {
|
||||
int(item.mb_trackid): item for item in items
|
||||
}
|
||||
item_to_trackinfo = {
|
||||
item: beatport_trackid_to_trackinfo[track_id]
|
||||
for track_id, item in library_trackid_to_item.items()
|
||||
}
|
||||
|
||||
self._log.info(u'applying changes to {}', album)
|
||||
with lib.transaction():
|
||||
autotag.apply_metadata(albuminfo, item_to_trackinfo)
|
||||
changed = False
|
||||
# Find any changed item to apply Beatport changes to album.
|
||||
any_changed_item = items[0]
|
||||
for item in items:
|
||||
item_changed = ui.show_model_changes(item)
|
||||
changed |= item_changed
|
||||
if item_changed:
|
||||
any_changed_item = item
|
||||
apply_item_changes(lib, item, move, pretend, write)
|
||||
|
||||
if pretend or not changed:
|
||||
continue
|
||||
|
||||
# Update album structure to reflect an item in it.
|
||||
for key in library.Album.item_keys:
|
||||
album[key] = any_changed_item[key]
|
||||
album.store()
|
||||
|
||||
# Move album art (and any inconsistent items).
|
||||
if move and lib.directory in util.ancestry(items[0].path):
|
||||
self._log.debug(u'moving album {}', album)
|
||||
album.move()
|
||||
|
|
@ -83,6 +83,8 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
tracks_data = requests.get(
|
||||
self.album_url + deezer_id + '/tracks'
|
||||
).json()['data']
|
||||
if not tracks_data:
|
||||
return None
|
||||
tracks = []
|
||||
medium_totals = collections.defaultdict(int)
|
||||
for i, track_data in enumerate(tracks_data, start=1):
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
"""
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets.plugins import BeetsPlugin, apply_item_changes
|
||||
from beets import autotag, library, ui, util
|
||||
from beets.autotag import hooks
|
||||
from collections import defaultdict
|
||||
|
|
@ -27,19 +27,6 @@ import re
|
|||
MBID_REGEX = r"(\d|\w){8}-(\d|\w){4}-(\d|\w){4}-(\d|\w){4}-(\d|\w){12}"
|
||||
|
||||
|
||||
def apply_item_changes(lib, item, move, pretend, write):
|
||||
"""Store, move and write the item according to the arguments.
|
||||
"""
|
||||
if not pretend:
|
||||
# Move the item if it's in the library.
|
||||
if move and lib.directory in util.ancestry(item.path):
|
||||
item.move(with_album=False)
|
||||
|
||||
if write:
|
||||
item.try_write()
|
||||
item.store()
|
||||
|
||||
|
||||
class MBSyncPlugin(BeetsPlugin):
|
||||
def __init__(self):
|
||||
super(MBSyncPlugin, self).__init__()
|
||||
|
|
|
|||
|
|
@ -75,6 +75,11 @@ New features:
|
|||
:bug:`2080`
|
||||
* :doc:`/plugins/beatport`: Fix default assignment of the musical key.
|
||||
:bug:`3377`
|
||||
* :doc:`/plugins/bpsync`: Add `bpsync` plugin to sync metadata changes
|
||||
from the Beatport database.
|
||||
* :doc:`/plugins/beatport`: Fix assignment of `genre` and rename `musical_key`
|
||||
to `initial_key`.
|
||||
:bug:`3387`
|
||||
|
||||
Fixes:
|
||||
|
||||
|
|
|
|||
34
docs/plugins/bpsync.rst
Normal file
34
docs/plugins/bpsync.rst
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
BPSync Plugin
|
||||
=============
|
||||
|
||||
This plugin provides the ``bpsync`` command, which lets you fetch metadata
|
||||
from Beatport for albums and tracks that already have Beatport IDs.
|
||||
This plugin works similarly to :doc:`/plugins/mbsync`.
|
||||
|
||||
If you have downloaded music from Beatport, this can speed
|
||||
up the initial import if you just import "as-is" and then use ``bpsync`` to
|
||||
get up-to-date tags that are written to the files according to your beets
|
||||
configuration.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Enable the ``bpsync`` plugin in your configuration (see :ref:`using-plugins`)
|
||||
and then run ``beet bpsync QUERY`` to fetch updated metadata for a part of your
|
||||
collection (or omit the query to run over your whole library).
|
||||
|
||||
This plugin treats albums and singletons (non-album tracks) separately. It
|
||||
first processes all matching singletons and then proceeds on to full albums.
|
||||
The same query is used to search for both kinds of entities.
|
||||
|
||||
The command has a few command-line options:
|
||||
|
||||
* To preview the changes that would be made without applying them, use the
|
||||
``-p`` (``--pretend``) flag.
|
||||
* By default, files will be moved (renamed) according to their metadata if
|
||||
they are inside your beets library directory. To disable this, use the
|
||||
``-M`` (``--nomove``) command-line option.
|
||||
* If you have the ``import.write`` configuration option enabled, then this
|
||||
plugin will write new metadata to files' tags. To disable this, use the
|
||||
``-W`` (``--nowrite``) option.
|
||||
|
|
@ -65,6 +65,7 @@ following to your configuration::
|
|||
beatport
|
||||
bpd
|
||||
bpm
|
||||
bpsync
|
||||
bucket
|
||||
chroma
|
||||
convert
|
||||
|
|
@ -142,6 +143,7 @@ Metadata
|
|||
* :doc:`absubmit`: Analyse audio with the `streaming_extractor_music`_ program and submit the metadata to the AcousticBrainz server
|
||||
* :doc:`acousticbrainz`: Fetch various AcousticBrainz metadata
|
||||
* :doc:`bpm`: Measure tempo using keystrokes.
|
||||
* :doc:`bpsync`: Fetch updated metadata from Beatport.
|
||||
* :doc:`edit`: Edit metadata from a text editor.
|
||||
* :doc:`embedart`: Embed album art images into files' metadata.
|
||||
* :doc:`fetchart`: Fetch album cover art from various sources.
|
||||
|
|
@ -154,7 +156,7 @@ Metadata
|
|||
* :doc:`lastgenre`: Fetch genres based on Last.fm tags.
|
||||
* :doc:`lastimport`: Collect play counts from Last.fm.
|
||||
* :doc:`lyrics`: Automatically fetch song lyrics.
|
||||
* :doc:`mbsync`: Fetch updated metadata from MusicBrainz
|
||||
* :doc:`mbsync`: Fetch updated metadata from MusicBrainz.
|
||||
* :doc:`metasync`: Fetch metadata from local or remote sources
|
||||
* :doc:`mpdstats`: Connect to `MPD`_ and update the beets library with play
|
||||
statistics (last_played, play_count, skip_count, rating).
|
||||
|
|
|
|||
|
|
@ -482,12 +482,12 @@ class BeatportTest(_common.TestCase, TestHelper):
|
|||
items[4].bpm = 123
|
||||
items[5].bpm = 123
|
||||
|
||||
items[0].musical_key = 'Gmin'
|
||||
items[1].musical_key = 'Gmaj'
|
||||
items[2].musical_key = 'Fmaj'
|
||||
items[3].musical_key = 'Amin'
|
||||
items[4].musical_key = 'E♭maj'
|
||||
items[5].musical_key = 'Amaj'
|
||||
items[0].initial_key = 'Gmin'
|
||||
items[1].initial_key = 'Gmaj'
|
||||
items[2].initial_key = 'Fmaj'
|
||||
items[3].initial_key = 'Amin'
|
||||
items[4].initial_key = 'E♭maj'
|
||||
items[5].initial_key = 'Amaj'
|
||||
|
||||
for item in items:
|
||||
self.lib.add(item)
|
||||
|
|
@ -549,9 +549,9 @@ class BeatportTest(_common.TestCase, TestHelper):
|
|||
for track, test_track in zip(self.tracks, self.test_tracks):
|
||||
self.assertEqual(track.bpm, test_track.bpm)
|
||||
|
||||
def test_musical_key_applied(self):
|
||||
def test_initial_key_applied(self):
|
||||
for track, test_track in zip(self.tracks, self.test_tracks):
|
||||
self.assertEqual(track.musical_key, test_track.musical_key)
|
||||
self.assertEqual(track.initial_key, test_track.initial_key)
|
||||
|
||||
def test_genre_applied(self):
|
||||
for track, test_track in zip(self.tracks, self.test_tracks):
|
||||
|
|
|
|||
Loading…
Reference in a new issue