MetadataDealer.set_album_metadata()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 19
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1.0003

Importance

Changes 0
Metric Value
cc 1
eloc 18
nop 8
dl 0
loc 19
ccs 13
cts 14
cp 0.9286
crap 1.0003
rs 9.5
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1 1
import glob
2 1
import logging
3 1
import os
4 1
import re
5 1
from collections import defaultdict
6
7 1
import click
8 1
from mutagen.id3 import ID3, TALB, TDRC, TIT2, TPE1, TPE2, TRCK
9 1
10
from music_album_creation.tracks_parsing import StringParser
11
12
# The main notable classes in mutagen are FileType, StreamInfo, Tags, Metadata and for error handling the MutagenError exception.
13
14 1
15
logger = logging.getLogger(__name__)
16
17 1
18
class MetadataDealerType(object):
19 1
    _filters = defaultdict(
20
        lambda: lambda y: y, track_number=lambda y: MetadataDealerType._parse_year(y)
21 1
    )
22
23 1
    @classmethod
24
    def _parse_year(cls, year):
25 1
        if year == '':
26 1
            return ''
27
        c = re.match(r'0*(\d+)', year)
28 1
        if not c:
29
            raise InvalidInputYearError("Input year tag '{}' is invalid".format(year))
30
        return c.group(1)
31 1
32
33
class MetadataDealer(MetadataDealerType):
34
    #############
35
    # simply add keys and constructor pairs to enrich the support of the API for writting tags/frames to audio files
36 1
    # you can use the cls._filters to add a new post processing filter as shown in MetadataDealerType constructor above
37
    _d = {
38
        'artist': TPE1,  # 4.2.1   TPE1    [#TPE1 Lead performer(s)/Soloist(s)]  ; taken from http://id3.org/id3v2.3.0
39
        #  in clementine temrs, it affects the 'Artist' tab but not the 'Album artist'
40
        'album_artist': TPE2,  # 4.2.1   TPE2    [#TPE2 Band/orchestra/accompaniment]
41
        # in clementine terms, it affects the 'Artist' tab but not the 'Album artist'
42
        'album': TALB,  # 4.2.1   TALB    [#TALB Album/Movie/Show title]
43
        'year': TDRC,  # TDRC (recording time) consolidates TDAT (date), TIME (time), TRDA (recording dates), and TYER (year).
44
    }
45 1
46
    # supported metadata to try and infer automatically
47
    _auto_data = [
48 1
        ('track_number', TRCK),  # 4.2.1   TRCK    [#TRCK Track number/Position in set]
49
        ('track_name', TIT2),
50 1
    ]  # 4.2.1   TIT2    [#TIT2 Title/songname/content description]
51 1
52 1
    _all = dict(_d, **dict(_auto_data))
53
54 1
    @classmethod
55
    def set_album_metadata(
56 1
        cls,
57 1
        album_directory,
58 1
        track_number=True,
59 1
        track_name=True,
60 1
        artist='',
61
        album_artist='',
62
        album='',
63 1
        year='',
64
    ):
65 1
        cls._write_metadata(
66
            album_directory,
67
            track_number=track_number,
68 1
            track_name=track_name,
69 1
            artist=artist,
70 1
            album_artist=album_artist,
71 1
            album=album,
72 1
            year=str(year),
73
        )
74 1
75 1
    @classmethod
76
    def _write_metadata(cls, album_directory, **kwargs):
77 1
        files = glob.glob('{}/*.mp3'.format(album_directory))
78
        logger.info("Album directory: {}".format(album_directory))
79
        for file in files:
80 1
            logger.info("File: {}".format(os.path.basename(file)))
81 1
            cls.write_metadata(
82
                file,
83 1
                **dict(
84
                    cls._filter_auto_inferred(
85
                        StringParser().parse_track_number_n_name(file), **kwargs
86
                    ),
87
                    **{k: kwargs.get(k, '') for k in cls._d.keys()}
88
                )
89 1
            )
90 1
91
    @classmethod
92
    def write_metadata(cls, file, **kwargs):
93 1
        if not all(map(lambda x: x[0] in cls._all.keys(), kwargs.items())):
94 1
            raise RuntimeError(
95 1
                "Some of the input keys [{}] used to request the addition of metadata, do not correspond"
96 1
                " to a tag/frame of the supported [{}]".format(
97 1
                    ', '.join(kwargs.keys()), ' '.join(cls._d)
98 1
                )
99
            )
100
        audio = ID3(file)
101
        for metadata_name, v in kwargs.items():
102
            if bool(v):
103
                audio.add(
104 1
                    cls._all[metadata_name](
105
                        encoding=3, text=u'{}'.format(cls._filters[metadata_name](v))
106
                    )
107
                )
108
                logger.info(
109
                    " {}: {}={}".format(
110
                        metadata_name,
111
                        cls._all[metadata_name].__name__,
112
                        cls._filters[metadata_name](v),
113
                    )
114
                )
115
            else:
116
                logger.warning(
117
                    "Skipping metadata '{}::'{}' because bool({}) == False".format(
118
                        metadata_name, cls._all[metadata_name].__name__, v
119
                    )
120
                )
121
        audio.save()
122
123
    @classmethod
124
    def _filter_auto_inferred(cls, d, **kwargs):
125
        """Given a dictionary (like the one outputted by _infer_track_number_n_name), deletes entries unless it finds them declared in kwargs as key_name=True"""
126
        for k in cls._auto_data:
127
            if not kwargs.get(k, False) and k in d:
128
                del d[k]
129
        return d
130
131
132
class InvalidInputYearError(Exception):
133
    pass
134
135
136
@click.command()
137
@click.option(
138
    '--album-dir',
139
    required=True,
140
    help="The directory where a music album resides. Currently only mp3 "
141
    "files are supported as contents of the directory. Namely only "
142
    "such files will be apprehended as tracks of the album.",
143
)
144
@click.option(
145
    '--track_name/--no-track_name',
146
    default=True,
147
    show_default=True,
148
    help='Whether to extract the track names from the mp3 files and write them as metadata correspondingly.',
149
)
150
@click.option(
151
    '--track_number/--no-track_number',
152
    default=True,
153
    show_default=True,
154
    help='Whether to extract the track numbers from the mp3 files and write them as metadata correspondingly.',
155
)
156
@click.option(
157
    '--artist',
158
    '-a',
159
    help="If given, then value shall be used as the TPE1 tag: 'Lead performer(s)/Soloist(s)'.  In the music player 'clementine' it corresponds to the 'Artist' column.",
160
)
161
@click.option(
162
    '--album_artist',
163
    '-aa',
164
    help="If given, then value shall be used as the TPE2 tag: 'Band/orchestra/accompaniment'.  In the music player 'clementine' it corresponds to the 'Album artist' column.",
165
)
166
@click.option(
167
    '--album',
168
    '-al',
169
    help="If given, then value shall be used as the TALB tag: 'Album/Movie/Show title'.  In the music player 'clementine' it corresponds to the 'Album' column.",
170
)
171
@click.option(
172
    '--year',
173
    'y',
174
    help="If given, then value shall be used as the TDRC tag: 'Recoring time'.  In the music player 'clementine' it corresponds to the 'Year' column.",
175
)
176
def main(album_dir, track_name, track_number, artist, album_artist, album, year):
177
    md = MetadataDealer()
178
    md.set_album_metadata(
179
        album_dir,
180
        track_number=track_number,
181
        track_name=track_name,
182
        artist=artist,
183
        album_artist=album_artist,
184
        album=album,
185
        year=year,
186
    )
187
188
189
if __name__ == '__main__':
190
    main()
191