diff --git a/beetsplug/ihate.py b/beetsplug/ihate.py new file mode 100644 index 000000000..e589765a8 --- /dev/null +++ b/beetsplug/ihate.py @@ -0,0 +1,142 @@ +# This file is part of beets. +# Copyright 2012, Blemjhoo Tezoulbr . +# +# 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. + +"""Warns you about things you hate (or even blocks import).""" + +import re +import logging +from beets.plugins import BeetsPlugin +from beets import ui +from beets.importer import action + + +__author__ = 'baobab@heresiarch.info' +__version__ = '1.0' + + +class IHatePlugin(BeetsPlugin): + + _instance = None + _log = logging.getLogger('beets') + + warn_genre = [] + warn_artist = [] + warn_album = [] + warn_whitelist = [] + skip_genre = [] + skip_artist = [] + skip_album = [] + skip_whitelist = [] + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super(IHatePlugin, + cls).__new__(cls, *args, **kwargs) + return cls._instance + + def __str__(self): + return ('(\n warn_genre = {0}\n' + ' warn_artist = {1}\n' + ' warn_album = {2}\n' + ' warn_whitelist = {3}\n' + ' skip_genre = {4}\n' + ' skip_artist = {5}\n' + ' skip_album = {6}\n' + ' skip_whitelist = {7} )\n' + .format(self.warn_genre, self.warn_artist, self.warn_album, + self.warn_whitelist, self.skip_genre, self.skip_artist, + self.skip_album, self.skip_whitelist)) + + def configure(self, config): + if not config.has_section('ihate'): + self._log.warn('[ihate] plugin is not configured') + return + self.warn_genre = ui.config_val(config, 'ihate', 'warn_genre', + '').split() + self.warn_artist = ui.config_val(config, 'ihate', 'warn_artist', + '').split() + self.warn_album = ui.config_val(config, 'ihate', 'warn_album', + '').split() + self.warn_whitelist = ui.config_val(config, 'ihate', 'warn_whitelist', + '').split() + self.skip_genre = ui.config_val(config, 'ihate', 'skip_genre', + '').split() + self.skip_artist = ui.config_val(config, 'ihate', 'skip_artist', + '').split() + self.skip_album = ui.config_val(config, 'ihate', 'skip_album', + '').split() + self.skip_whitelist = ui.config_val(config, 'ihate', 'skip_whitelist', + '').split() + + @classmethod + def match_patterns(cls, s, patterns): + """Check if string is matching any of the patterns in the list.""" + for p in patterns: + if re.findall(p, s, flags=re.IGNORECASE): + return True + return False + + @classmethod + def do_i_hate_this(cls, task, genre_patterns, artist_patterns, + album_patterns, whitelist_patterns): + """Process group of patterns (warn or skip) and returns True if + task is hated and not whitelisted. + """ + hate = False + try: + genre = task.items[0].genre + except: + genre = u'' + if genre and genre_patterns: + if IHatePlugin.match_patterns(genre, genre_patterns): + hate = True + if not hate and task.cur_album and album_patterns: + if IHatePlugin.match_patterns(task.cur_album, album_patterns): + hate = True + if not hate and task.cur_artist and artist_patterns: + if IHatePlugin.match_patterns(task.cur_artist, artist_patterns): + hate = True + if hate and whitelist_patterns: + if IHatePlugin.match_patterns(task.cur_artist, whitelist_patterns): + hate = False + return hate + + def job_to_do(self): + """Return True if at least one pattern is defined.""" + return any([self.warn_genre, self.warn_artist, self.warn_album, + self.skip_genre, self.skip_artist, self.skip_album]) + + def import_task_choice_event(self, task, config): + if task.choice_flag == action.APPLY: + if self.job_to_do: + self._log.debug('[ihate] processing your hate') + if self.do_i_hate_this(task, self.skip_genre, self.skip_artist, + self.skip_album, self.skip_whitelist): + task.choice_flag = action.SKIP + self._log.info(u'[ihate] skipped: {0} - {1}' + .format(task.cur_artist, task.cur_album)) + return + if self.do_i_hate_this(task, self.warn_genre, self.warn_artist, + self.warn_album, self.warn_whitelist): + self._log.info(u'[ihate] you maybe hate this: {0} - {1}' + .format(task.cur_artist, task.cur_album)) + else: + self._log.debug('[ihate] nothing to do') + else: + self._log.debug('[ihate] user make a decision, nothing to do') + + +@IHatePlugin.listen('import_task_choice') +def ihate_import_task_choice(task, config): + IHatePlugin().import_task_choice_event(task, config) diff --git a/docs/plugins/ihate.rst b/docs/plugins/ihate.rst new file mode 100644 index 000000000..24e46fd14 --- /dev/null +++ b/docs/plugins/ihate.rst @@ -0,0 +1,35 @@ +IHate Plugin +============ + +The ``ihate`` plugin allows you to automatically skip things you hate during +import or warn you about them. It supports album, artist and genre patterns. +Also there is whitelist to avoid skipping bands you still like. There are two +groups: warn and skip. Skip group is checked first. Whitelist overrides any +other patterns. + +To use plugin, enable it by including ``ihate`` into ``plugins`` line of +your beets config:: + + [beets] + plugins = ihate + +You need to configure plugin before use, so add following section into config +file and adjust it to your needs:: + + [ihate] + # you will be warned about these suspicious genres/artists (regexps): + warn_genre=rnb soul power\smetal + warn_artist=bad\band another\sbad\sband + warn_album=tribute\sto + # if you don't like genre in general, but accept some band playing it, + # add exceptions here: + warn_whitelist=hate\sexception + # never import any of this: + skip_genre=russian\srock polka + skip_artist=manowar + skip_album=christmas + # but import this: + skip_whitelist= + +Note: plugin will trust you decision in 'as-is' mode. + \ No newline at end of file diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 757d8a3ac..b9a7e64f5 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -53,6 +53,7 @@ disabled by default, but you can turn them on as described above. the fuzzy_search zero + ihate Autotagger Extensions '''''''''''''''''''''' @@ -92,6 +93,7 @@ Miscellaneous * :doc:`rdm`: Randomly choose albums and tracks from your library. * :doc:`fuzzy_search`: Search albums and tracks with fuzzy string matching. * :doc:`mbcollection`: Maintain your MusicBrainz collection list. +* :doc:`ihate`: Skip by defined patterns things you hate during import process. * :doc:`bpd`: A music player for your beets library that emulates `MPD`_ and is compatible with `MPD clients`_. diff --git a/test/test_ihate.py b/test/test_ihate.py new file mode 100644 index 000000000..3ff6dcd8d --- /dev/null +++ b/test/test_ihate.py @@ -0,0 +1,52 @@ +"""Tests for the 'ihate' plugin""" + +from _common import unittest +from beets.importer import ImportTask +from beets.library import Item +from beetsplug.ihate import IHatePlugin + + +class IHatePluginTest(unittest.TestCase): + + def test_hate(self): + genre_p = [] + artist_p = [] + album_p = [] + white_p = [] + task = ImportTask() + task.cur_artist = u'Test Artist' + task.cur_album = u'Test Album' + task.items = [Item({'genre': 'Test Genre'})] + self.assertFalse(IHatePlugin.do_i_hate_this(task, genre_p, artist_p, + album_p, white_p)) + genre_p = 'some_genre test\sgenre'.split() + self.assertTrue(IHatePlugin.do_i_hate_this(task, genre_p, artist_p, + album_p, white_p)) + genre_p = [] + artist_p = 'bad_artist test\sartist' + self.assertTrue(IHatePlugin.do_i_hate_this(task, genre_p, artist_p, + album_p, white_p)) + artist_p = [] + album_p = 'tribute christmas test'.split() + self.assertTrue(IHatePlugin.do_i_hate_this(task, genre_p, artist_p, + album_p, white_p)) + album_p = [] + white_p = 'goodband test\sartist another_band'.split() + genre_p = 'some_genre test\sgenre'.split() + self.assertFalse(IHatePlugin.do_i_hate_this(task, genre_p, artist_p, + album_p, white_p)) + genre_p = [] + artist_p = 'bad_artist test\sartist' + self.assertFalse(IHatePlugin.do_i_hate_this(task, genre_p, artist_p, + album_p, white_p)) + artist_p = [] + album_p = 'tribute christmas test'.split() + self.assertFalse(IHatePlugin.do_i_hate_this(task, genre_p, artist_p, + album_p, white_p)) + + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + +if __name__ == '__main__': + unittest.main(defaultTest='suite')