Skip to content

Commit e1d41c2

Browse files
authored
Merge pull request #4048 from edgars-supe/albumtypes-plugin
Add plugin for formatting albumtypes
2 parents 5176110 + d40f0c8 commit e1d41c2

File tree

5 files changed

+243
-0
lines changed

5 files changed

+243
-0
lines changed

beetsplug/albumtypes.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# This file is part of beets.
4+
# Copyright 2021, Edgars Supe.
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining
7+
# a copy of this software and associated documentation files (the
8+
# "Software"), to deal in the Software without restriction, including
9+
# without limitation the rights to use, copy, modify, merge, publish,
10+
# distribute, sublicense, and/or sell copies of the Software, and to
11+
# permit persons to whom the Software is furnished to do so, subject to
12+
# the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be
15+
# included in all copies or substantial portions of the Software.
16+
17+
"""Adds an album template field for formatted album types."""
18+
19+
from __future__ import division, absolute_import, print_function
20+
21+
from beets.autotag.mb import VARIOUS_ARTISTS_ID
22+
from beets.library import Album
23+
from beets.plugins import BeetsPlugin
24+
25+
26+
class AlbumTypesPlugin(BeetsPlugin):
27+
"""Adds an album template field for formatted album types."""
28+
29+
def __init__(self):
30+
"""Init AlbumTypesPlugin."""
31+
super(AlbumTypesPlugin, self).__init__()
32+
self.album_template_fields['atypes'] = self._atypes
33+
self.config.add({
34+
'types': [
35+
('ep', 'EP'),
36+
('single', 'Single'),
37+
('soundtrack', 'OST'),
38+
('live', 'Live'),
39+
('compilation', 'Anthology'),
40+
('remix', 'Remix')
41+
],
42+
'ignore_va': ['compilation'],
43+
'bracket': '[]'
44+
})
45+
46+
def _atypes(self, item: Album):
47+
"""Returns a formatted string based on album's types."""
48+
types = self.config['types'].as_pairs()
49+
ignore_va = self.config['ignore_va'].as_str_seq()
50+
bracket = self.config['bracket'].as_str()
51+
52+
# Assign a left and right bracket or leave blank if argument is empty.
53+
if len(bracket) == 2:
54+
bracket_l = bracket[0]
55+
bracket_r = bracket[1]
56+
else:
57+
bracket_l = u''
58+
bracket_r = u''
59+
60+
res = ''
61+
albumtypes = item.albumtypes.split('; ')
62+
is_va = item.mb_albumartistid == VARIOUS_ARTISTS_ID
63+
for type in types:
64+
if type[0] in albumtypes and type[1]:
65+
if not is_va or (type[0] not in ignore_va and is_va):
66+
res += f'{bracket_l}{type[1]}{bracket_r}'
67+
68+
return res

docs/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Major new features:
2424
``albumtypes`` field. Thanks to :user:`edgars-supe`.
2525
:bug:`2200`
2626

27+
* :doc:`/plugins/albumtypes`: An accompanying plugin for formatting
28+
``albumtypes``. Thanks to :user:`edgars-supe`.
29+
2730

2831
1.5.0 (August 19, 2021)
2932
-----------------------

docs/plugins/albumtypes.rst

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
AlbumTypes Plugin
2+
=================
3+
4+
The ``albumtypes`` plugin adds the ability to format and output album types,
5+
such as "Album", "EP", "Single", etc. For the list of available album types,
6+
see the `MusicBrainz documentation`_.
7+
8+
To use the ``albumtypes`` plugin, enable it in your configuration
9+
(see :ref:`using-plugins`). The plugin defines a new field ``$atypes``, which
10+
you can use in your path formats or elsewhere.
11+
12+
.. _MusicBrainz documentation: https://musicbrainz.org/doc/Release_Group/Type
13+
14+
Configuration
15+
-------------
16+
17+
To configure the plugin, make a ``albumtypes:`` section in your configuration
18+
file. The available options are:
19+
20+
- **types**: An ordered list of album type to format mappings. The order of the
21+
mappings determines their order in the output. If a mapping is missing or
22+
blank, it will not be in the output.
23+
- **ignore_va**: A list of types that should not be output for Various Artists
24+
albums. Useful for not adding redundant information - various artist albums
25+
are often compilations.
26+
- **bracket**: Defines the brackets to enclose each album type in the output.
27+
28+
The default configuration looks like this::
29+
30+
albumtypes:
31+
types:
32+
- ep: 'EP'
33+
- single: 'Single'
34+
- soundtrack: 'OST'
35+
- live: 'Live'
36+
- compilation: 'Anthology'
37+
- remix: 'Remix'
38+
ignore_va: compilation
39+
bracket: '[]'
40+
41+
Examples
42+
--------
43+
With path formats configured like::
44+
45+
paths:
46+
default: $albumartist/[$year]$atypes $album/...
47+
albumtype:soundtrack Various Artists/$album [$year]$atypes)/...
48+
comp: Various Artists/$album [$year]$atypes/...
49+
50+
51+
The default plugin configuration generates paths that look like this, for example::
52+
53+
Aphex Twin/[1993][EP][Remix] On Remixes
54+
Pink Flow/[1995][Live] p·u·l·s·e
55+
Various Artists/20th Century Lullabies [1999]
56+
Various Artists/Ocean's Eleven [2001][OST]
57+

docs/plugins/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ following to your configuration::
6161

6262
absubmit
6363
acousticbrainz
64+
albumtypes
6465
aura
6566
badfiles
6667
bareasc
@@ -176,6 +177,7 @@ Metadata
176177
Path Formats
177178
------------
178179

180+
* :doc:`albumtypes`: Format album type in path formats.
179181
* :doc:`bucket`: Group your files into bucket directories that cover different
180182
field values ranges.
181183
* :doc:`inline`: Use Python snippets to customize path format strings.

test/test_albumtypes.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
# This file is part of beets.
3+
# Copyright 2021, Edgars Supe.
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining
6+
# a copy of this software and associated documentation files (the
7+
# "Software"), to deal in the Software without restriction, including
8+
# without limitation the rights to use, copy, modify, merge, publish,
9+
# distribute, sublicense, and/or sell copies of the Software, and to
10+
# permit persons to whom the Software is furnished to do so, subject to
11+
# the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be
14+
# included in all copies or substantial portions of the Software.
15+
16+
"""Tests for the 'albumtypes' plugin."""
17+
18+
from __future__ import division, absolute_import, print_function
19+
20+
import unittest
21+
22+
from beets.autotag.mb import VARIOUS_ARTISTS_ID
23+
from beetsplug.albumtypes import AlbumTypesPlugin
24+
from test.helper import TestHelper
25+
26+
27+
class AlbumTypesPluginTest(unittest.TestCase, TestHelper):
28+
"""Tests for albumtypes plugin."""
29+
30+
def setUp(self):
31+
"""Set up tests."""
32+
self.setup_beets()
33+
self.load_plugins('albumtypes')
34+
35+
def tearDown(self):
36+
"""Tear down tests."""
37+
self.unload_plugins()
38+
self.teardown_beets()
39+
40+
def test_renames_types(self):
41+
"""Tests if the plugin correctly renames the specified types."""
42+
self._set_config(
43+
types=[('ep', 'EP'), ('remix', 'Remix')],
44+
ignore_va=[],
45+
bracket='()'
46+
)
47+
album = self._create_album(album_types=['ep', 'remix'])
48+
subject = AlbumTypesPlugin()
49+
result = subject._atypes(album)
50+
self.assertEqual('(EP)(Remix)', result)
51+
return
52+
53+
def test_returns_only_specified_types(self):
54+
"""Tests if the plugin returns only non-blank types given in config."""
55+
self._set_config(
56+
types=[('ep', 'EP'), ('soundtrack', '')],
57+
ignore_va=[],
58+
bracket='()'
59+
)
60+
album = self._create_album(album_types=['ep', 'remix', 'soundtrack'])
61+
subject = AlbumTypesPlugin()
62+
result = subject._atypes(album)
63+
self.assertEqual('(EP)', result)
64+
65+
def test_respects_type_order(self):
66+
"""Tests if the types are returned in the same order as config."""
67+
self._set_config(
68+
types=[('remix', 'Remix'), ('ep', 'EP')],
69+
ignore_va=[],
70+
bracket='()'
71+
)
72+
album = self._create_album(album_types=['ep', 'remix'])
73+
subject = AlbumTypesPlugin()
74+
result = subject._atypes(album)
75+
self.assertEqual('(Remix)(EP)', result)
76+
return
77+
78+
def test_ignores_va(self):
79+
"""Tests if the specified type is ignored for VA albums."""
80+
self._set_config(
81+
types=[('ep', 'EP'), ('soundtrack', 'OST')],
82+
ignore_va=['ep'],
83+
bracket='()'
84+
)
85+
album = self._create_album(
86+
album_types=['ep', 'soundtrack'],
87+
artist_id=VARIOUS_ARTISTS_ID
88+
)
89+
subject = AlbumTypesPlugin()
90+
result = subject._atypes(album)
91+
self.assertEqual('(OST)', result)
92+
93+
def test_respects_defaults(self):
94+
"""Tests if the plugin uses the default values if config not given."""
95+
album = self._create_album(
96+
album_types=['ep', 'single', 'soundtrack', 'live', 'compilation',
97+
'remix'],
98+
artist_id=VARIOUS_ARTISTS_ID
99+
)
100+
subject = AlbumTypesPlugin()
101+
result = subject._atypes(album)
102+
self.assertEqual('[EP][Single][OST][Live][Remix]', result)
103+
104+
def _set_config(self, types: [(str, str)], ignore_va: [str], bracket: str):
105+
self.config['albumtypes']['types'] = types
106+
self.config['albumtypes']['ignore_va'] = ignore_va
107+
self.config['albumtypes']['bracket'] = bracket
108+
109+
def _create_album(self, album_types: [str], artist_id: str = 0):
110+
return self.add_album(
111+
albumtypes='; '.join(album_types),
112+
mb_albumartistid=artist_id
113+
)

0 commit comments

Comments
 (0)