Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add SyncMetadataSourcePlugin #3393

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions beets/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,3 +764,190 @@ def track_distance(self, item, track_info):
return get_distance(
data_source=self.data_source, info=track_info, config=self.config
)


class SyncMetadataSourcePlugin(BeetsPlugin):
def __init__(self, command_name, metadata_source_class):
super(BeetsPlugin, self).__init__()
self.command_name = command_name
self.metadata_source_plugin = metadata_source_class()
self.metadata_source_plugin.setup()

def commands(self):
from beets import ui

cmd = ui.Subcommand(
self.command_name,
help=u'update metadata from {}'.format(
self.metadata_source_plugin.data_source
),
)
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.
"""
from beets import ui

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.
"""
from beets.autotag import apply_item_metadata

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_metadata_source_track(item):
self._log.info(
u'Skipping non-{} singleton: {}',
self.metadata_source_plugin.data_source,
item,
)
continue

# Apply.
trackinfo = self.metadata_source_plugin.track_for_id(
item.mb_trackid
)
with lib.transaction():
apply_item_metadata(item, trackinfo)
apply_item_changes(lib, item, move, pretend, write)

def is_metadata_source_track(self, item):
if item.get('data_source') != self.metadata_source_plugin.data_source:
return False
match = re.search(
self.metadata_source_plugin.id_regex['pattern'].format('track'),
item.mb_trackid,
)
return match and match.group(
self.metadata_source_plugin.id_regex['match_group']
)

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.metadata_source_plugin.data_source,
album,
)
return False
items = list(album.items())
if album.get('data_source') == self.metadata_source_plugin.data_source:
return items
if not all(self.is_metadata_source_track(item) for item in items):
self._log.info(
u'Skipping non-{} release: {}',
self.metadata_source_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.
"""
from beets import autotag, library, ui, util

# Process matching albums.
for album in lib.albums(query):
# Do we have a valid metadata source album?
items = self.get_album_tracks(album)
if not items:
continue

# Get the metadata source album information.
albuminfo = self.metadata_source_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

metadata_source_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: metadata_source_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()
9 changes: 7 additions & 2 deletions beetsplug/beatport.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ def __init__(self, data):
class BeatportPlugin(BeetsPlugin):
data_source = 'Beatport'

id_regex = {
'pattern': r'(^|beatport\.com/release/.+/)(\d+)$',
'match_group': 2,
}

def __init__(self):
super(BeatportPlugin, self).__init__()
self.config.add({
Expand Down Expand Up @@ -385,11 +390,11 @@ def album_for_id(self, release_id):
or None if the query is not a valid ID or release is not found.
"""
self._log.debug(u'Searching for release {0}', release_id)
match = re.search(r'(^|beatport\.com/release/.+/)(\d+)$', release_id)
match = re.search(self.id_regex['pattern'], release_id)
if not match:
self._log.debug(u'Not a valid Beatport release ID.')
return None
release = self.client.get_release(match.group(2))
release = self.client.get_release(match.group(self.id_regex['match_group']))
if release:
return self._get_album_info(release)
return None
Expand Down
167 changes: 3 additions & 164 deletions beetsplug/bpsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,172 +17,11 @@
"""
from __future__ import division, absolute_import, print_function

from beets.plugins import BeetsPlugin, apply_item_changes
from beets import autotag, library, ui, util
from beets.plugins import SyncMetadataSourcePlugin, BeetsPlugin

from .beatport import BeatportPlugin


class BPSyncPlugin(BeetsPlugin):
class BPSyncPlugin(SyncMetadataSourcePlugin, 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()
super(BPSyncPlugin, self).__init__('bpsync', BeatportPlugin)
Loading