diff --git a/beets/importer.py b/beets/importer.py index aac21d77f0..4e4084eec0 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -313,6 +313,8 @@ def run(self): stages += [import_asis(self)] # Plugin stages. + for stage_func in plugins.early_import_stages(): + stages.append(plugin_stage(self, stage_func)) for stage_func in plugins.import_stages(): stages.append(plugin_stage(self, stage_func)) diff --git a/beets/plugins.py b/beets/plugins.py index d62f3c011e..4a2475f503 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -81,6 +81,7 @@ def __init__(self, name=None): self.template_fields = {} if not self.album_template_fields: self.album_template_fields = {} + self.early_import_stages = [] self.import_stages = [] self._log = log.getChild(self.name) @@ -94,6 +95,22 @@ def commands(self): """ return () + def _set_stage_log_level(self, stages): + """Adjust all the stages in `stages` to WARNING logging level. + """ + return [self._set_log_level_and_params(logging.WARNING, stage) + for stage in stages] + + def get_early_import_stages(self): + """Return a list of functions that should be called as importer + pipelines stages early in the pipeline. + + The callables are wrapped versions of the functions in + `self.early_import_stages`. Wrapping provides some bookkeeping for the + plugin: specifically, the logging level is adjusted to WARNING. + """ + return self._set_stage_log_level(self.early_import_stages) + def get_import_stages(self): """Return a list of functions that should be called as importer pipelines stages. @@ -102,8 +119,7 @@ def get_import_stages(self): `self.import_stages`. Wrapping provides some bookkeeping for the plugin: specifically, the logging level is adjusted to WARNING. """ - return [self._set_log_level_and_params(logging.WARNING, import_stage) - for import_stage in self.import_stages] + return self._set_stage_log_level(self.import_stages) def _set_log_level_and_params(self, base_log_level, func): """Wrap `func` to temporarily set this plugin's logger level to @@ -393,6 +409,14 @@ def template_funcs(): return funcs +def early_import_stages(): + """Get a list of early import stage functions defined by plugins.""" + stages = [] + for plugin in find_plugins(): + stages += plugin.get_early_import_stages() + return stages + + def import_stages(): """Get a list of import stage functions defined by plugins.""" stages = [] diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 3800612482..d1223596fd 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -146,7 +146,7 @@ def __init__(self): u'copy_album_art': False, u'album_art_maxwidth': 0, }) - self.import_stages = [self.auto_convert] + self.early_import_stages = [self.auto_convert] self.register_listener('import_task_files', self._cleanup) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4381edeeb1..44558675b1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -46,6 +46,11 @@ Fixes: * Avoid a crash when importing a non-ASCII filename when using an ASCII locale on Unix under Python 3. :bug:`2793` :bug:`2803` +* Convert plugin now runs before all others in the pipeline to solve an issue + with generating ReplayGain data incompatible between the source and target + file formats. This option to request (part of) your plugin to run early in the + pipeline has been exposed in the plugin API as well (```early_import_stages```). + Thanks to :user:`autrimpo`. 1.4.6 (December 21, 2017) diff --git a/docs/dev/plugins.rst b/docs/dev/plugins.rst index 4d41c89718..5b0e4d08ba 100644 --- a/docs/dev/plugins.rst +++ b/docs/dev/plugins.rst @@ -432,6 +432,11 @@ to register it:: def stage(self, session, task): print('Importing something!') +It is also possible to request your function to run early in the pipeline by +adding the function to the plugin's ``early_import_stages`` field instead.:: + + self.early_import_stages = [self.stage] + .. _extend-query: Extend the Query Syntax