mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
zero plugin, version 0.9
This commit is contained in:
parent
e092af2b2f
commit
aff36fa694
3 changed files with 204 additions and 0 deletions
127
beetsplug/zero.py
Normal file
127
beetsplug/zero.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# This file is part of beets.
|
||||
# Copyright 2012, Blemjhoo Tezoulbr <baobab@heresiarch.info>.
|
||||
#
|
||||
# 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.
|
||||
|
||||
""" Clears tag fields in media files."""
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import re
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets import ui
|
||||
from beets.library import ITEM_KEYS
|
||||
from beets.importer import action
|
||||
|
||||
|
||||
__author__ = 'baobab@heresiarch.info'
|
||||
__version__ = '0.9'
|
||||
|
||||
|
||||
class ZeroPlugin(BeetsPlugin):
|
||||
|
||||
_instance = None
|
||||
|
||||
debug = False
|
||||
fields = []
|
||||
patterns = {}
|
||||
warned = False
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(ZeroPlugin,
|
||||
cls).__new__(cls, *args, **kwargs)
|
||||
return cls._instance
|
||||
|
||||
def __str__(self):
|
||||
return ('[zero]\n debug = {}\n fields = {}\n patterns = {}\n'
|
||||
' warned = {}'.format(self.debug, self.fields, self.patterns,
|
||||
self.warned))
|
||||
|
||||
def dbg(self, *args):
|
||||
"""Prints message to stderr."""
|
||||
if self.debug:
|
||||
print('[zero]', *args, file=sys.stderr)
|
||||
|
||||
def configure(self, config):
|
||||
if not config.has_section('zero'):
|
||||
self.dbg('plugin is not configured')
|
||||
return
|
||||
self.debug = ui.config_val(config, 'zero', 'debug', True, bool)
|
||||
for f in ui.config_val(config, 'zero', 'fields', '').split():
|
||||
if f not in ITEM_KEYS:
|
||||
self.dbg('invalid field \"{}\" (try \'beet fields\')')
|
||||
else:
|
||||
self.fields.append(f)
|
||||
p = ui.config_val(config, 'zero', f, '').split()
|
||||
if p:
|
||||
self.patterns[f] = p
|
||||
else:
|
||||
self.patterns[f] = ['.']
|
||||
if self.debug:
|
||||
print(self, file=sys.stderr)
|
||||
|
||||
def import_task_choice_event(self, task, config):
|
||||
"""Listen for import_task_choice event."""
|
||||
if self.debug:
|
||||
self.dbg('listen: import_task_choice')
|
||||
if task.choice_flag == action.ASIS and not self.warned:
|
||||
self.dbg('cannot zero in \"as-is\" mode')
|
||||
self.warned = True
|
||||
# TODO request write in as-is mode
|
||||
|
||||
@classmethod
|
||||
def match_patterns(cls, field, patterns):
|
||||
"""Check if field (as string) is matching any of the patterns in
|
||||
the list.
|
||||
"""
|
||||
for p in patterns:
|
||||
if re.findall(p, unicode(field), flags=re.IGNORECASE):
|
||||
return True
|
||||
return False
|
||||
|
||||
def write_event(self, item):
|
||||
"""Listen for write event."""
|
||||
if self.debug:
|
||||
self.dbg('listen: write')
|
||||
if not self.fields:
|
||||
self.dbg('no fields, nothing to do')
|
||||
return
|
||||
for fn in self.fields:
|
||||
try:
|
||||
fval = getattr(item, fn)
|
||||
except AttributeError:
|
||||
self.dbg('? no such field: {}'.format(fn))
|
||||
else:
|
||||
if not self.match_patterns(fval, self.patterns[fn]):
|
||||
self.dbg('\"{}\" ({}) is not match any of: {}'
|
||||
.format(fval, fn, ' '.join(self.patterns[fn])))
|
||||
continue
|
||||
self.dbg('\"{}\" ({}) match: {}'
|
||||
.format(fval, fn, ' '.join(self.patterns[fn])))
|
||||
setattr(item, fn, type(fval)())
|
||||
self.dbg('{}={}'.format(fn, getattr(item, fn)))
|
||||
|
||||
|
||||
@ZeroPlugin.listen('import_task_choice')
|
||||
def zero_choice(task, config):
|
||||
ZeroPlugin().import_task_choice_event(task, config)
|
||||
|
||||
@ZeroPlugin.listen('write')
|
||||
def zero_write(item):
|
||||
ZeroPlugin().write_event(item)
|
||||
|
||||
|
||||
# simple test
|
||||
if __name__ == '__main__':
|
||||
print(ZeroPlugin().match_patterns('test', ['[0-9]']))
|
||||
print(ZeroPlugin().match_patterns('test', ['.']))
|
||||
27
docs/plugins/zero.rst
Normal file
27
docs/plugins/zero.rst
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Zero Plugin
|
||||
===========
|
||||
|
||||
The ``zero`` plugin allows you to null fields before writing tags to files.
|
||||
Fields can be nulled unconditionally or by pattern match. For example, it can
|
||||
be used to strip useless comments like "ripped by" etc or any other stuff you
|
||||
hate. Library is not modified.
|
||||
|
||||
To use plugin, enable it by including ``zero`` into ``plugins`` line of
|
||||
your beets config::
|
||||
|
||||
[beets]
|
||||
plugins = zero
|
||||
|
||||
You need to configure plugin before use, so add following section into config
|
||||
file and adjust it to your needs::
|
||||
|
||||
[zero]
|
||||
# list of fields to null, you can get full list by running 'beet fields'
|
||||
fields=month day genre comments
|
||||
# custom regexp patterns for each field, separated by space
|
||||
# if custom pattern is not defined, field will be nulled unconditionally
|
||||
comments=EAC LAME from.+collection ripped\sby
|
||||
genre=rnb power\smetal
|
||||
|
||||
Note: for now plugin will not zero fields in 'as-is' mode.
|
||||
|
||||
50
test/test_zero.py
Normal file
50
test/test_zero.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""Tests for the 'zero' plugin"""
|
||||
|
||||
from _common import unittest
|
||||
from beets.library import Item
|
||||
from beetsplug.zero import ZeroPlugin
|
||||
|
||||
|
||||
class ThePluginTest(unittest.TestCase):
|
||||
|
||||
def test_singleton(self):
|
||||
z1 = ZeroPlugin()
|
||||
z2 = ZeroPlugin()
|
||||
self.assertTrue(z1 is z2)
|
||||
|
||||
def test_no_patterns(self):
|
||||
v = {'comments' : 'test comment',
|
||||
'day' : 13,
|
||||
'month' : 3,
|
||||
'year' : 2012}
|
||||
i=Item(v)
|
||||
z = ZeroPlugin()
|
||||
z.debug = False
|
||||
z.fields = ['comments', 'month', 'day']
|
||||
z.patterns = {'comments': ['.'],
|
||||
'month': ['.'],
|
||||
'day': ['.']}
|
||||
z.write_event(i)
|
||||
self.assertEqual(i.comments, '')
|
||||
self.assertEqual(i.day, 0)
|
||||
self.assertEqual(i.month, 0)
|
||||
self.assertEqual(i.year, 2012)
|
||||
|
||||
def test_patterns(self):
|
||||
v = {'comments' : 'from lame collection, ripped by eac',
|
||||
'year' : 2012}
|
||||
i=Item(v)
|
||||
z = ZeroPlugin()
|
||||
z.debug = False
|
||||
z.fields = ['comments', 'year']
|
||||
z.patterns = {'comments': 'eac lame'.split(),
|
||||
'year': '2098 2099'.split()}
|
||||
z.write_event(i)
|
||||
self.assertEqual(i.comments, '')
|
||||
self.assertEqual(i.year, 2012)
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
Loading…
Reference in a new issue