mirror of
https://github.com/beetbox/beets.git
synced 2026-02-17 12:56:05 +01:00
Add attach-import command and detect globs
This commit is contained in:
parent
c463c6ed84
commit
f98d8619ae
4 changed files with 86 additions and 33 deletions
|
|
@ -18,6 +18,7 @@ import os.path
|
|||
import collections
|
||||
import logging
|
||||
from argparse import ArgumentParser
|
||||
from fnmatch import fnmatch
|
||||
|
||||
from beets import dbcore
|
||||
from beets.dbcore.query import Query, AndQuery, MatchQuery, OrQuery, FalseQuery
|
||||
|
|
@ -457,7 +458,7 @@ class AttachmentFactory(object):
|
|||
return self.find(queries[Attachment], queries[Album], queries[Item])
|
||||
|
||||
def discover(self, entity_or_prefix, local=None):
|
||||
"""Return a list of non-audio files whose path start with the
|
||||
"""Return a list of non-audio file paths that start with the
|
||||
entity prefix.
|
||||
|
||||
For albums the entity prefix is the album directory. For items it
|
||||
|
|
@ -469,6 +470,9 @@ class AttachmentFactory(object):
|
|||
type. For albums the only separator is the directory separator.
|
||||
For items the separtors are configured by `attachments.item_sep`
|
||||
"""
|
||||
# TODO return attachments with create()
|
||||
# FIXME we need to handle paths as `entity_prefix` because of
|
||||
# the importer.
|
||||
prefix, dir = self.path_prefix(entity_or_prefix)
|
||||
if local is None:
|
||||
return self._discover_full(prefix, dir)
|
||||
|
|
@ -507,7 +511,11 @@ class AttachmentFactory(object):
|
|||
a list of types for `path`. For each type it yields an attachment
|
||||
through `create`.
|
||||
"""
|
||||
for type in self._detect_types(path):
|
||||
# TODO entity should not be optional
|
||||
# TODO update doc
|
||||
types = self._detect_plugin_types(path)
|
||||
types.update(self._detect_config_types(path))
|
||||
for type in types:
|
||||
yield self.create(path, type, entity)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -599,27 +607,33 @@ class AttachmentFactory(object):
|
|||
if hasattr(plugin, 'attachment_collector'):
|
||||
self.register_collector(plugin.attachment_collector)
|
||||
|
||||
def _detect_types(self, path):
|
||||
"""Yield a list of types registered for the path.
|
||||
|
||||
Uses the functions from `register_detector` and the
|
||||
`attachments.types` configuration.
|
||||
"""
|
||||
def _detect_plugin_types(self, path):
|
||||
types = set()
|
||||
# TODO Make list unique
|
||||
for detector in self._detectors:
|
||||
try:
|
||||
type = detector(path)
|
||||
if type:
|
||||
yield type
|
||||
types.add(type)
|
||||
except:
|
||||
# TODO logging?
|
||||
pass
|
||||
return types
|
||||
|
||||
def _detect_config_types(self, path):
|
||||
types = set()
|
||||
types_config = config('types')
|
||||
if types_config.exists():
|
||||
for matcher, type in types_config.get(dict).items():
|
||||
if re.match(matcher, path):
|
||||
yield type
|
||||
if not types_config.exists():
|
||||
return types
|
||||
|
||||
basename = os.path.basename(path)
|
||||
for pattern, type in types_config.get(dict).items():
|
||||
if ((pattern[0] == '/' and pattern[-1] == '/'
|
||||
and re.match(pattern[1:-1] + '$', basename))
|
||||
or fnmatch(basename, pattern)):
|
||||
types.add(type)
|
||||
|
||||
return types
|
||||
|
||||
def _collect_meta(self, type, path):
|
||||
all_meta = {}
|
||||
|
|
|
|||
|
|
@ -80,8 +80,6 @@ def _do_query(lib, query, album, also_items=True):
|
|||
|
||||
|
||||
class AttachCommand(ui.Subcommand):
|
||||
"""Duck type for ui.Subcommand
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(AttachCommand, self).__init__(
|
||||
|
|
@ -172,12 +170,37 @@ class AttachListCommand(ui.Subcommand):
|
|||
args = decargs(args)
|
||||
factory = AttachmentFactory(lib)
|
||||
for a in factory.parse_and_find(*args):
|
||||
print('{0}: {1}'.format(a.type, displayable_path(a.path)))
|
||||
print(u'{0}: {1}'.format(a.type, displayable_path(a.path)))
|
||||
|
||||
|
||||
default_commands.append(AttachListCommand())
|
||||
|
||||
|
||||
class AttachImportCommand(ui.Subcommand):
|
||||
"""Search files in album directories and create attachments for them.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(AttachImportCommand, self).__init__(
|
||||
'attach-import',
|
||||
help='create attachments for albums already in the library'
|
||||
)
|
||||
|
||||
def func(self, lib, opts, args):
|
||||
args = decargs(args)
|
||||
factory = AttachmentFactory(lib)
|
||||
for album in lib.albums(decargs(args)):
|
||||
for path in factory.discover(album):
|
||||
for attachment in factory.detect(path, album):
|
||||
print(u"add {0} attachment {1} to '{2} - {3}'"
|
||||
.format(attachment.type, path,
|
||||
album.albumartist, album.album))
|
||||
attachment.add()
|
||||
|
||||
|
||||
default_commands.append(AttachImportCommand())
|
||||
|
||||
|
||||
# fields: Shows a list of available fields for queries and format strings.
|
||||
|
||||
def fields_func(lib, opts, args):
|
||||
|
|
|
|||
|
|
@ -81,13 +81,13 @@ def capture_stdout():
|
|||
'spam'
|
||||
"""
|
||||
org = sys.stdout
|
||||
sys.stdout = StringIO()
|
||||
sys.stdout = captured = StringIO()
|
||||
sys.stdout.encoding = 'utf8'
|
||||
try:
|
||||
yield sys.stdout
|
||||
yield captured
|
||||
finally:
|
||||
sys.stdout = org
|
||||
print(org.getvalue())
|
||||
print(captured.getvalue())
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
|
|||
|
|
@ -197,7 +197,24 @@ class AttachmentDocTest(unittest.TestCase, AttachmentTestHelper):
|
|||
self.assertIn("add booklet attachment {0} to 'Artist - Album 0'"
|
||||
.format(booklet_path), output)
|
||||
|
||||
# TODO attach-import
|
||||
def test_attach_import(self):
|
||||
self.config['attachments']['types'] = {'cover.jpg': 'cover'}
|
||||
|
||||
album1 = self.add_album(name='Revolver', artist='The Beatles')
|
||||
album2 = self.add_album(name='Abbey Road', artist='The Beatles')
|
||||
cover_path1 = self.touch(os.path.join(album1.item_dir(), 'cover.jpg'))
|
||||
cover_path2 = self.touch(os.path.join(album2.item_dir(), 'cover.jpg'))
|
||||
|
||||
output = self.cli_output('attach-import')
|
||||
self.assertIn("add cover attachment {0} to 'The Beatles - Revolver'"
|
||||
.format(cover_path1), output)
|
||||
self.assertIn("add cover attachment {0} to 'The Beatles - Abbey Road'"
|
||||
.format(cover_path2), output)
|
||||
|
||||
album1.load()
|
||||
self.assertEqual(len(album1.attachments()), 1)
|
||||
album2.load()
|
||||
self.assertEqual(len(album2.attachments()), 1)
|
||||
|
||||
|
||||
class AttachmentDestinationTest(unittest.TestCase, AttachmentTestHelper):
|
||||
|
|
@ -484,13 +501,10 @@ class AttachmentFactoryTest(unittest.TestCase, AttachmentTestHelper):
|
|||
self.assertEqual(len(attachments), 1)
|
||||
self.assertEqual(attachments[0].type, 'image')
|
||||
|
||||
# TODO Glob and RegExp.
|
||||
# * Globs dont match files starting with a dot
|
||||
# * Add extended bash globs.
|
||||
# * Regexp must match full basename
|
||||
def test_detect_config_types(self):
|
||||
# TODO add extended bash globs
|
||||
def test_detect_config_glob_types(self):
|
||||
self.config['attachments']['types'] = {
|
||||
'.*\.jpg': 'image'
|
||||
'*.jpg': 'image'
|
||||
}
|
||||
|
||||
attachments = list(self.factory.detect('/path/to/cover.jpg'))
|
||||
|
|
@ -500,15 +514,17 @@ class AttachmentFactoryTest(unittest.TestCase, AttachmentTestHelper):
|
|||
attachments = list(self.factory.detect('/path/to/cover.png'))
|
||||
self.assertEqual(len(attachments), 0)
|
||||
|
||||
def test_detect_multiple_types(self):
|
||||
self.factory.register_detector(lambda _: 'a')
|
||||
self.factory.register_detector(lambda _: 'b')
|
||||
def test_detect_config_regexp_types(self):
|
||||
self.config['attachments']['types'] = {
|
||||
'.*\.jpg$': 'c',
|
||||
'.*/cover.jpg': 'd'
|
||||
'/[abc]+.*\.(txt|md)/': 'xxx'
|
||||
}
|
||||
attachments = list(self.factory.detect('/path/to/cover.jpg'))
|
||||
self.assertItemsEqual(map(lambda a: a.type, attachments), 'abcd')
|
||||
|
||||
attachments = list(self.factory.detect('/path/to/aabbcc.md'))
|
||||
self.assertEqual(len(attachments), 1)
|
||||
self.assertEqual(attachments[0].type, 'xxx')
|
||||
|
||||
attachments = list(self.factory.detect('/path/to/aabbcc.mdx'))
|
||||
self.assertEqual(len(attachments), 0)
|
||||
|
||||
# factory.discover(album)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue