Completed
Push — dev ( 232b11...6092be )
by Konstantinos
04:15 queued 01:32
created

MetadataDealer.set_album_metadata()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 9
dl 0
loc 3
ccs 3
cts 3
cp 1
crap 1
rs 10
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
10 1
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
15 1
logger = logging.getLogger(__name__)
16
17
18 1
class MetadataDealerType(type):
19
20 1
    @staticmethod
21
    def __parse_year(year):
22 1
        if year == '':
23
            return ''
24 1
        c = re.match(r'0*(\d+)', year)
25 1
        if not c:
26
            raise InvalidInputYearError("Input year tag '{}' is invalid".format(year))
27 1
        return c.group(1)
28
29 1
    def __new__(mcs, name, bases, attributes):
30 1
        x = super().__new__(mcs, name, bases, attributes)
31 1
        x._filters = defaultdict(lambda: lambda y: y, track_number=lambda y: mcs.__parse_year(y))
32 1
        return x
33
34
35 1
class MetadataDealer(metaclass=MetadataDealerType):
36
37
    #############
38
    # simply add keys and constructor pairs to enrich the support of the API for writting tags/frames to audio files
39
    # you can use the cls._filters to add a new post processing filter as shown in MetadataDealerType constructor above
40 1
    _d = {'artist': TPE1,  # 4.2.1   TPE1    [#TPE1 Lead performer(s)/Soloist(s)]  ; taken from http://id3.org/id3v2.3.0
41
          #  in clementine temrs, it affects the 'Artist' tab but not the 'Album artist'
42
          'album_artist': TPE2,  # 4.2.1   TPE2    [#TPE2 Band/orchestra/accompaniment]
43
          # in clementine terms, it affects the 'Artist' tab but not the 'Album artist'
44
          'album': TALB,  # 4.2.1   TALB    [#TALB Album/Movie/Show title]
45
          'year': TDRC  # TDRC (recording time) consolidates TDAT (date), TIME (time), TRDA (recording dates), and TYER (year).
46
          }
47
48
    # supported metadata to try and infer automatically
49 1
    _auto_data = [('track_number', TRCK),  # 4.2.1   TRCK    [#TRCK Track number/Position in set]
50
                  ('track_name', TIT2)]   # 4.2.1   TIT2    [#TIT2 Title/songname/content description]
51
52 1
    _all = dict(_d, **dict(_auto_data))
53
54
    # reg = re.compile(r'(?:(\d{1,2})(?:[ \t]*[\-\.][ \t]*|[ \t]+)|^)?([\w\'\(\) ’]*[\w)])\.mp3$')  # use to parse track file names like "1. Loyal to the Pack.mp3"
55
56 1
    @classmethod
57 1
    def set_album_metadata(cls, album_directory, track_number=True, track_name=True, artist='', album_artist='', album='', year='', verbose=False):
58 1
        cls._write_metadata(album_directory, track_number=track_number, track_name=track_name, artist=artist, album_artist=album_artist, album=album, year=str(year))
59
60 1
    @classmethod
61
    def _write_metadata(cls, album_directory, **kwargs):
62 1
        files = glob.glob('{}/*.mp3'.format(album_directory))
63 1
        logger.info("Files selected: [{}]".format(', '.join(map(os.path.basename, files))))
64 1
        for file in files:
65 1
            cls.write_metadata(file, **dict(cls._filter_auto_inferred(StringParser.parse_track_number_n_name(file), **kwargs),
66
                                            **{k: kwargs.get(k, '') for k in cls._d.keys()}))
67
68 1
    @classmethod
69
    def write_metadata(cls, file, **kwargs):
70 1
        if not all(map(lambda x: x[0] in cls._all.keys(), kwargs.items())):
71
            raise RuntimeError("Some of the input keys [{}] used to request the addition of metadata, do not correspond"
72
                               " to a tag/frame of the supported [{}]".format(', '.join(kwargs.keys()), ' '.join(cls._d)))
73 1
        audio = ID3(file)
74 1
        for k, v in kwargs.items():
75 1
            if bool(v):
76 1
                audio.add(cls._all[k](encoding=3, text=u'{}'.format(cls._filters[k](v))))
77 1
                logger.info("Track '{}'; set {}: {}={}".format(file, k, cls._all[k].__name__, cls._filters[k](v)))
78 1
        audio.save()
79
80 1
    @classmethod
81
    def _filter_auto_inferred(cls, d, **kwargs):
82
        """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"""
83 1
        for k in cls._auto_data:
84 1
            if not kwargs.get(k, False) and k in d:
85
                del d[k]
86 1
        return d
87
88
89
class InvalidInputYearError(Exception): pass
90
91
92 1
@click.command()
93 1
@click.option('--album-dir', required=True, help="The directory where a music album resides. Currently only mp3 "
94
                                                 "files are supported as contents of the directory. Namely only "
95
                                                 "such files will be apprehended as tracks of the album.")
96 1
@click.option('--track_name/--no-track_name', default=True, show_default=True, help='Whether to extract the track names from the mp3 files and write them as metadata correspondingly.')
97 1
@click.option('--track_number/--no-track_number', default=True, show_default=True, help='Whether to extract the track numbers from the mp3 files and write them as metadata correspondingly.')
98 1
@click.option('--artist', '-a', 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.")
99 1
@click.option('--album_artist', '-aa', 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.")
100 1
@click.option('--album', '-al', 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.")
101 1
@click.option('--year', 'y', 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.")
102
def main(album_dir, track_name, track_number, artist, album_artist, album, year):
103
    md = MetadataDealer()
104
    md.set_album_metadata(album_dir, track_number=track_number, track_name=track_name, artist=artist, album_artist=album_artist, album=album, year=year, verbose=True)
105
106
107 1
if __name__ == '__main__':
108
    main()
109