Attachment doc and tests

This commit is contained in:
Thomas Scholtes 2014-08-18 16:06:21 +02:00
parent addac17b2c
commit c463c6ed84
4 changed files with 267 additions and 3 deletions

View file

@ -605,6 +605,7 @@ class AttachmentFactory(object):
Uses the functions from `register_detector` and the
`attachments.types` configuration.
"""
# TODO Make list unique
for detector in self._detectors:
try:
type = detector(path)

194
docs/guides/attachments.rst Normal file
View file

@ -0,0 +1,194 @@
Attachments
===========
Beets also gives you tools to organize the non-audio files that are
part of your music collection, e.g. cover images, digital booklets, rip
logs, etc. These are called *attachments*. Each attachment has at least
a path and a type attribute, and a track or album (an *entity*) it is
attached to. The type attribute provides a basic taxonomy for your
attachments and allows plugins to provide additional functionality for
specific types.
Getting Started
---------------
TODO: Introduction
Attaching Single Files
^^^^^^^^^^^^^^^^^^^^^^
Suppose you have downloaded the cover of the Beatles “Revolver” album
and the file is called `cover.jpg`. You can attach it to the album with
the following command: ::
$ beet attach /path/to/cover.jpg --type cover album:Revolver
add cover attachment /path/to/cover.jpg to 'The Beatles - Revolver'
The file at ``/path/to/cover.jpg`` has now been moved to the album
directory and you can query the attachment with ::
$ beet attachls type:cover e:album:Revolver
cover: /music/Revolver/cover.jpg
The query arguments for that `attachls` command work like the arguments
for the usual :ref:`ls <list-cmd>` command, with one addition: You can match against
the album or track (the *entity*) the file is attached to using the
``e:`` prefix. For more on the `attachls` command see :ref:`the
reference <attachls-cmd>`.
Maybe you want your cover images to have a different name, say
`front.jpg`. You can change the default paths for you attachments
through the configuration file: ::
attachments:
paths:
- type: cover
path: front.$ext
This moves all attachments of type cover are to `front.ext` in the
corresponding album directory, where `ext` is the extension of the
source file. ::
$ beet attach /path/to/cover.jpg --type cover album:Revolver
add cover attachment /path/to/cover.jpg to 'The Beatles - Revolver'
$ beet attachls type:cover e:album:Revolver
cover: /music/Revolver/front.jpg
Beets can also be configured to automatically detect the type of an
attachment from its filename. ::
attachments:
types:
cover.*: cover
The :ref:`types configuration <conf-attachments-types>` is a map from
glob patterns or regular expressions to type names. You can now omit
the ``--type`` option and beet will detect the type automatically ::
$ beet attach /path/to/cover.jpg album:Revolver
add cover attachment /path/to/cover.jpg to 'The Beatles - Revolver'
$ beet attachls type:cover e:album:Revolver
cover: /music/Revolver/cover.jpg
Of course you can still specify another type on the command line.
Importing Attachments
^^^^^^^^^^^^^^^^^^^^^
Beets will automatically create attachments when you import new music.
Since you already have “Revolver” in your library, suppose you want to
also add the “Abbey Road” album. You have ripped the album and moved it
to the ``/import`` directory. The directory also contains the files
``cover.jpg`` and ``booklet.pdf`` that you want to create attachments
for. To automatically detect the types, we add the following to our
configuration: ::
attachments:
types:
cover.*: cover
booklet.pdf: booklet
In addition to adding the album to your library, the ``beet import
/import`` command will now print the lines ::
add cover attachment /import/cover.jpg to 'The Beatles - Revolver'
add booklet attachment /import/booklet.pdf to 'The Beatles - Revolver'
and you can confirm it with ::
$ beet attachls "e:Abbey Road"
/music/Abbey Road/cover.jpg
/music/Abbey Road/booklet.pdf
For each album that is about to be imported, beets looks at all the
non-music files contained in the albums source directory. Beets then
tries to determine the type of each file and, if successful, creates an
attachment of this type. Files with no type are ignored. The file
manipulations for attachments mirror that of the music files and can be
configured through the ``import.move`` and ``import.copy`` options.
Import Attachments Only
^^^^^^^^^^^^^^^^^^^^^^^
If you have used beets before, you may already have some files in your
library that you want to attach with beets. Instead of repeating the
`attach` command for each of those files, there is a :ref:`attach-import
command <attach-import-cmd>`. This command is similar to a reimport
with ``beet import``, but it just creates attachments and skip all
audio files.
As an example, suppose you have a ``cover.jpg`` file in some of your
album directories and you want them to be added as a ``cover``
attachment to their corresponding album. First make sure the type of
the file is recognised by beets. ::
attachments:
types:
cover.jpg: cover
Then run ::
$ beet attach-import
add cover attachment /music/Revolver/cover.jpg to 'The Beatles - Revolver'
add cover attachment /music/Abbey Road/cover.jpg to 'The Beatles - Abbey Road'
...
and all cover images will be attached to their albums.
.. _attachment-plugins:
Attachment Plugins
------------------
TODO
Reference
=========
Command-Line
------------
``attach``
^^^^^^^^^^
``attachls``
^^^^^^^^^^^^
``attach-import``
^^^^^^^^^^^^^^^^^
Configuration
-------------
.. _conf-attachments-types:
types
^^^^^
paths
^^^^^
To Do
=====
* Fallback type for discover and import
* Ignore dot files
* Interactive type input on import (create issue)
* Documentation for multiple types (do we need them)
* Document track attachments
* Move attachments with same path
* Automatically determine query from path for `attach`
* Remove warning for unknown files
* Additional template variables overwritten by flex attrs

View file

@ -11,4 +11,5 @@ guide.
main
tagger
advanced
attachments
migration

View file

@ -39,6 +39,7 @@ class AttachmentTestHelper(TestHelper):
return self._factory
def touch(self, path, dir=None, content=''):
# TODO move into TestHelper
if dir:
path = os.path.join(dir, path)
@ -133,7 +134,7 @@ class AttachmentTestHelper(TestHelper):
def cli_output(self, *args):
with capture_stdout() as output:
self.runcli(*args)
return output.getvalue().split('\n')
return [l for l in output.getvalue().split('\n') if l]
def libpath(self, *components):
components = \
@ -141,6 +142,64 @@ class AttachmentTestHelper(TestHelper):
return os.path.join(self.libdir, *components)
class AttachmentDocTest(unittest.TestCase, AttachmentTestHelper):
"""Tests for the guide of the attachment documentation.
"""
def setUp(self):
self.setup_beets()
self.config['path_formats'] = {'default': '$album/$track $title'}
def tearDown(self):
self.teardown_beets()
@unittest.skip
def test_attache_single_file_with_type(self):
self.add_album(name='Revolver')
attachment_path = self.touch('cover.jpg')
output = self.cli_output('attach', attachment_path, '--type',
'cover', 'album:Revolver')
self.assertIn("add cover attachment {0} to 'The Beatles - Revolver'"
.format(attachment_path), output)
output = self.cli_output('attachls', 'type:cover', 'e:album:Revolver')
self.assertIn('cover: {0}/Revolver/cover.jpg'
.format(self.libdir), output)
def test_attache_single_file_with_type_and_path_config(self):
self.config['attachments']['paths'] = [{
'type': 'cover',
'path': 'front.$ext',
}]
self.add_album(name='Revolver')
attachment_path = self.touch('cover.jpg')
self.runcli('attach', attachment_path, '--type',
'cover', 'album:Revolver')
output = self.cli_output('attachls', 'type:cover', 'e:album:Revolver')
self.assertIn('cover: {0}/Revolver/front.jpg'
.format(self.libdir), output)
@unittest.skip
def test_import_cover_and_booklet(self):
importer = self.create_importer()
album_dir = os.path.join(self.importer.paths[0], 'album 0')
cover_path = self.touch(album_dir, 'cover.jpg')
booklet_path = self.touch(album_dir, 'booklet.pdf')
with capture_stdout() as output:
importer.run()
output = output.getValue().split('\n')
self.assertIn("add cover attachment {0} to 'Artist - Album 0'"
.format(cover_path), output)
self.assertIn("add booklet attachment {0} to 'Artist - Album 0'"
.format(booklet_path), output)
# TODO attach-import
class AttachmentDestinationTest(unittest.TestCase, AttachmentTestHelper):
"""Test the `attachment.destination` property.
"""
@ -263,6 +322,7 @@ class AttachmentTest(unittest.TestCase, AttachmentTestHelper):
self.teardown_beets()
# attachment.move()
# TODO move attachments with same path
def test_move(self):
attachment = self.create_album_attachment(self.touch('a'))
@ -424,7 +484,10 @@ class AttachmentFactoryTest(unittest.TestCase, AttachmentTestHelper):
self.assertEqual(len(attachments), 1)
self.assertEqual(attachments[0].type, 'image')
# TODO Glob and RegExp
# 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):
self.config['attachments']['types'] = {
'.*\.jpg': 'image'
@ -601,7 +664,10 @@ class EntityAttachmentsTest(unittest.TestCase, AttachmentTestHelper):
class AttachmentImportTest(unittest.TestCase, AttachmentTestHelper):
"""Attachments should be created in the importer.
"""Import process should discover and add attachments.
Since the importer uses the `AttachmentFactory.discover()` method more
comprehensive tests can be found in that test case.
"""
def setUp(self):
@ -651,6 +717,8 @@ class AttachmentImportTest(unittest.TestCase, AttachmentTestHelper):
os.path.splitext(item.path)[0] + ' - cover.jpg'
)
# TODO interactive type input
class AttachCommandTest(unittest.TestCase, AttachmentTestHelper):
"""Tests the `beet attach FILE QUERY...` command