eyeD3 0.9.8a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,48 @@
1
+ import math
2
+ from eyed3 import id3
3
+ from eyed3.plugins import Plugin
4
+
5
+
6
+ class GenreListPlugin(Plugin):
7
+ SUMMARY = "Display the full list of standard ID3 genres."
8
+ DESCRIPTION = "ID3 v1 defined a list of genres and mapped them to "\
9
+ "to numeric values so they can be stored as a single "\
10
+ "byte.\nIt is *recommended* that these genres are used "\
11
+ "although most newer software (including eyeD3) does not "\
12
+ "care."
13
+ NAMES = ["genres"]
14
+
15
+ def __init__(self, arg_parser):
16
+ super(GenreListPlugin, self).__init__(arg_parser)
17
+ self.arg_group.add_argument("-1", "--single-column", action="store_true",
18
+ help="List on genre per line.")
19
+
20
+ def start(self, args, config):
21
+ self._printGenres(args)
22
+
23
+ @staticmethod
24
+ def _printGenres(args):
25
+ # Filter out 'Unknown'
26
+ genre_ids = [i for i in id3.genres
27
+ if type(i) is int and id3.genres[i] is not None]
28
+ genre_ids.sort()
29
+
30
+ if args.single_column:
31
+ for gid in genre_ids:
32
+ print("%3d: %s" % (gid, id3.genres[gid]))
33
+ else:
34
+ offset = int(math.ceil(float(len(genre_ids)) / 2))
35
+ for i in range(offset):
36
+ if i < len(genre_ids):
37
+ c1 = "%3d: %s" % (i, id3.genres[i])
38
+ else:
39
+ c1 = ""
40
+ if (i * 2) < len(genre_ids):
41
+ try:
42
+ c2 = "%3d: %s" % (i + offset, id3.genres[i + offset])
43
+ except IndexError:
44
+ break
45
+ else:
46
+ c2 = ""
47
+ print(c1 + (" " * (40 - len(c1))) + c2)
48
+ print("")
@@ -0,0 +1,64 @@
1
+ from eyed3.plugins import LoaderPlugin
2
+ from eyed3.id3.apple import PCST, PCST_FID, WFED, WFED_FID
3
+
4
+
5
+ class Podcast(LoaderPlugin):
6
+ NAMES = ['itunes-podcast']
7
+ SUMMARY = "Adds (or removes) the tags necessary for Apple iTunes to "\
8
+ "identify the file as a podcast."
9
+
10
+ def __init__(self, arg_parser):
11
+ super(Podcast, self).__init__(arg_parser)
12
+ g = self.arg_group
13
+ g.add_argument("--add", action="store_true",
14
+ help="Add the podcast frames.")
15
+ g.add_argument("--remove", action="store_true",
16
+ help="Remove the podcast frames.")
17
+
18
+ def _add(self, tag):
19
+ save = False
20
+ if PCST_FID not in tag.frame_set:
21
+ tag.frame_set[PCST_FID] = PCST()
22
+ save = True
23
+ if WFED_FID not in tag.frame_set:
24
+ tag.frame_set[WFED_FID] = WFED("http://eyeD3.nicfit.net/")
25
+ save = True
26
+
27
+ if save:
28
+ print("\tAdding...")
29
+ tag.save(backup=self.args.backup)
30
+ self._printStatus(tag)
31
+
32
+ def _remove(self, tag):
33
+ save = False
34
+ for fid in [PCST_FID, WFED_FID]:
35
+ try:
36
+ del tag.frame_set[fid]
37
+ save = True
38
+ except KeyError:
39
+ continue
40
+
41
+ if save:
42
+ print("\tRemoving...")
43
+ tag.save(backup=self.args.backup)
44
+ self._printStatus(tag)
45
+
46
+ def _printStatus(self, tag):
47
+ status = ":-("
48
+ if PCST_FID in tag.frame_set:
49
+ status = ":-/"
50
+ if WFED_FID in tag.frame_set:
51
+ status = ":-)"
52
+ print("\tiTunes podcast? %s" % status)
53
+
54
+ def handleFile(self, f):
55
+ super(Podcast, self).handleFile(f)
56
+
57
+ if self.audio_file and self.audio_file.tag:
58
+ print(f)
59
+ tag = self.audio_file.tag
60
+ self._printStatus(tag)
61
+ if self.args.remove:
62
+ self._remove(self.audio_file.tag)
63
+ elif self.args.add:
64
+ self._add(self.audio_file.tag)
@@ -0,0 +1,133 @@
1
+ import base64
2
+ import inspect
3
+ import dataclasses
4
+ from json import dumps
5
+
6
+ import eyed3.core
7
+ import eyed3.plugins
8
+ import eyed3.id3.tag
9
+ import eyed3.id3.headers
10
+
11
+ from eyed3.utils.log import getLogger
12
+
13
+ log = getLogger(__name__)
14
+
15
+
16
+ class JsonTagPlugin(eyed3.plugins.LoaderPlugin):
17
+ NAMES = ["json"]
18
+ SUMMARY = "Outputs all tags as JSON."
19
+
20
+ def __init__(self, arg_parser):
21
+ super().__init__(arg_parser, cache_files=True, track_images=False)
22
+ g = self.arg_group
23
+ g.add_argument("-c", "--compact", action="store_true",
24
+ help="Output in compact form, wound new lines or indentation.")
25
+ g.add_argument("-s", "--sort", action="store_true", help="Output JSON in sorted by key.")
26
+
27
+ def handleFile(self, f, *args, **kwargs):
28
+ super().handleFile(f)
29
+ if self.audio_file and self.audio_file.info and self.audio_file.tag:
30
+ json = audioFileToJson(self.audio_file)
31
+ print(dumps(json, indent=2 if not self.args.compact else None,
32
+ sort_keys=self.args.sort))
33
+
34
+
35
+ def audioFileToJson(audio_file):
36
+ tag = audio_file.tag
37
+
38
+ tdict = dict(path=audio_file.path, info=dataclasses.asdict(audio_file.info))
39
+
40
+ # Tag fields
41
+ for name in [m for m in sorted(dir(tag)) if not m.startswith("_") and m not in _tag_exclusions]:
42
+ member = getattr(tag, name)
43
+
44
+ if name not in _tag_map:
45
+ if not inspect.ismethod(member) and not inspect.isfunction(member):
46
+ log.warning(f"Unhandled Tag member: {name}")
47
+ continue
48
+ elif member is None:
49
+ continue
50
+ elif member.__class__ is not _tag_map[name]:
51
+ log.warning(f"Unexpected type for member {name}: {member.__class__}")
52
+ continue
53
+
54
+ if isinstance(member, (str, int, bool)):
55
+ tdict[name] = member
56
+ elif isinstance(member, eyed3.core.Date):
57
+ tdict[name] = str(member)
58
+ elif isinstance(member, eyed3.id3.Genre):
59
+ tdict[name] = member.name
60
+ elif isinstance(member, bytes):
61
+ tdict[name] = base64.b64encode(member).decode("ascii")
62
+ elif isinstance(member, eyed3.id3.tag.ArtistOrigin):
63
+ ... # TODO
64
+ elif isinstance(member, (eyed3.core.CountAndTotalTuple,)):
65
+ if any(member):
66
+ tdict[name] = member._asdict()
67
+ elif isinstance(member, (list, tuple)):
68
+ ... # TODO
69
+ elif isinstance(member, eyed3.id3.tag.AccessorBase):
70
+ ... # TODO
71
+ elif isinstance(member, (eyed3.id3.tag.TagHeader, eyed3.id3.tag.ExtendedTagHeader,
72
+ eyed3.id3.tag.FileInfo, eyed3.id3.frames.FrameSet)):
73
+ ... # TODO
74
+ else:
75
+ log.warning(f"Unhandled tag member {name}, type {member.__class__.__name__})")
76
+
77
+ tdict["_eyeD3"] = eyed3.__about__.__version__
78
+ return tdict
79
+
80
+
81
+ _tag_map = {
82
+ 'album': str,
83
+ 'album_artist': str,
84
+ 'album_type': str,
85
+ 'artist': str,
86
+ 'original_artist': str,
87
+ 'artist_origin': list,
88
+ 'artist_url': str,
89
+ 'audio_file_url': str,
90
+ 'audio_source_url': str,
91
+ 'best_release_date': eyed3.core.Date,
92
+ 'bpm': int,
93
+ 'cd_id': bytes,
94
+ 'chapters': eyed3.id3.tag.ChaptersAccessor,
95
+ 'comments': eyed3.id3.tag.CommentsAccessor,
96
+ 'commercial_url': str,
97
+ 'composer': str,
98
+ 'copyright_url': str,
99
+ 'disc_num': eyed3.core.CountAndTotalTuple,
100
+ 'encoding_date': eyed3.core.Date,
101
+ 'extended_header': eyed3.id3.headers.ExtendedTagHeader,
102
+ 'file_info': eyed3.id3.tag.FileInfo,
103
+ 'frame_set': eyed3.id3.frames.FrameSet,
104
+ 'genre': eyed3.id3.Genre,
105
+ 'header': eyed3.id3.headers.TagHeader,
106
+ 'images': eyed3.id3.tag.ImagesAccessor,
107
+ 'internet_radio_url': str,
108
+ 'lyrics': eyed3.id3.tag.LyricsAccessor,
109
+ 'non_std_genre': eyed3.id3.Genre,
110
+ 'objects': eyed3.id3.tag.ObjectsAccessor,
111
+ 'original_release_date': eyed3.core.Date,
112
+ 'payment_url': str,
113
+ 'play_count': int,
114
+ 'popularities': eyed3.id3.tag.PopularitiesAccessor,
115
+ 'privates': eyed3.id3.tag.PrivatesAccessor,
116
+ 'publisher': str,
117
+ 'publisher_url': str,
118
+ 'recording_date': eyed3.core.Date,
119
+ 'release_date': eyed3.core.Date,
120
+ 'table_of_contents': eyed3.id3.tag.TocAccessor,
121
+ 'tagging_date': eyed3.core.Date,
122
+ 'terms_of_use': str,
123
+ 'title': str,
124
+ 'track_num': eyed3.core.CountAndTotalTuple,
125
+ 'unique_file_ids': eyed3.id3.tag.UniqueFileIdAccessor,
126
+ 'user_text_frames': eyed3.id3.tag.UserTextsAccessor,
127
+ 'user_url_frames': eyed3.id3.tag.UserUrlsAccessor,
128
+ 'version': tuple,
129
+ }
130
+
131
+ _tag_exclusions = {
132
+ "read_only": bool,
133
+ }
@@ -0,0 +1,86 @@
1
+ import math
2
+ from eyed3.utils import formatSize
3
+ from eyed3.utils.console import printMsg, getTtySize
4
+ from eyed3.plugins import LoaderPlugin
5
+
6
+
7
+ class LameInfoPlugin(LoaderPlugin):
8
+ NAMES = ["lameinfo", "xing"]
9
+ SUMMARY = "Outputs lame header (if one exists) for file."
10
+ DESCRIPTION = (
11
+ "The 'lame' (or xing) header provides extra information about the mp3 "
12
+ "that is useful to players and encoders but not officially part of "
13
+ "the mp3 specification. Variable bit rate mp3s, for example, use this "
14
+ "header.\n\n"
15
+ "For more details see `here <http://gabriel.mp3-tech.org/mp3infotag.html>`_"
16
+ )
17
+
18
+ def printHeader(self, file_path):
19
+ w = getTtySize()[1]
20
+ printMsg(self._getFileHeader(file_path, w))
21
+ printMsg(self._getHardRule(w))
22
+
23
+ def handleFile(self, f, *_, **__):
24
+ super().handleFile(f)
25
+ if self.audio_file is None:
26
+ return
27
+
28
+ self.printHeader(f)
29
+ if (self.audio_file.info is None
30
+ or not self.audio_file.info.lame_tag):
31
+ printMsg("No LAME Tag")
32
+ return
33
+
34
+ lt = self.audio_file.info.lame_tag
35
+ if "infotag_crc" not in lt:
36
+ try:
37
+ printMsg(f"Encoder Version: {lt['encoder_version']}")
38
+ except KeyError:
39
+ pass
40
+ return
41
+
42
+ values = [("Encoder Version", lt['encoder_version']),
43
+ ("LAME Tag Revision", lt['tag_revision']),
44
+ ("VBR Method", lt['vbr_method']),
45
+ ("Lowpass Filter", lt['lowpass_filter']),
46
+ ]
47
+
48
+ if "replaygain" in lt:
49
+ try:
50
+ peak = lt["replaygain"]["peak_amplitude"]
51
+ db = 20 * math.log10(peak)
52
+ val = "%.8f (%+.1f dB)" % (peak, db)
53
+ values.append(("Peak Amplitude", val))
54
+ except KeyError:
55
+ pass
56
+ for type_ in ["radio", "audiofile"]:
57
+ try:
58
+ gain = lt["replaygain"][type_]
59
+ name = "%s Replay Gain" % gain['name'].capitalize()
60
+ val = "%s dB (%s)" % (gain['adjustment'],
61
+ gain['originator'])
62
+ values.append((name, val))
63
+ except KeyError:
64
+ pass
65
+
66
+ values.append(("Encoding Flags", " ".join((lt["encoding_flags"]))))
67
+ if lt["nogap"]:
68
+ values.append(("No Gap", " and ".join(lt["nogap"])))
69
+ values.append(("ATH Type", lt["ath_type"]))
70
+ values.append(("Bitrate (%s)" % lt["bitrate"][1], lt["bitrate"][0]))
71
+ values.append(("Encoder Delay", "%s samples" % lt["encoder_delay"]))
72
+ values.append(("Encoder Padding", "%s samples" % lt["encoder_padding"]))
73
+ values.append(("Noise Shaping", lt["noise_shaping"]))
74
+ values.append(("Stereo Mode", lt["stereo_mode"]))
75
+ values.append(("Unwise Settings", lt["unwise_settings"]))
76
+ values.append(("Sample Frequency", lt["sample_freq"]))
77
+ values.append(("MP3 Gain", "%s (%+.1f dB)" % (lt["mp3_gain"],
78
+ lt["mp3_gain"] * 1.5)))
79
+ values.append(("Preset", lt["preset"]))
80
+ values.append(("Surround Info", lt["surround_info"]))
81
+ values.append(("Music Length", "%s" % formatSize(lt["music_length"])))
82
+ values.append(("Music CRC-16", "%04X" % lt["music_crc"]))
83
+ values.append(("LAME Tag CRC-16", "%04X" % lt["infotag_crc"]))
84
+
85
+ for v in values:
86
+ printMsg(f"{v[0]:<20}: {v[1]}")
@@ -0,0 +1,50 @@
1
+ from pylast import SIZE_EXTRA_LARGE, SIZE_LARGE, SIZE_MEDIUM, SIZE_MEGA, SIZE_SMALL
2
+ from pylast import LastFMNetwork, WSError
3
+
4
+ api_k = "a5f0ac61e7db2481b054ba52ff9a654f"
5
+ api_s = "0c4a52ae5dcdbba1f9e782833a50b623"
6
+ _network = None
7
+
8
+
9
+ def Client():
10
+ global _network
11
+ if not _network:
12
+ _network = LastFMNetwork(api_key=api_k, api_secret=api_s)
13
+ _network.enable_rate_limit()
14
+ return _network
15
+
16
+
17
+ def getArtist(artist):
18
+ return Client().get_artist(artist)
19
+
20
+
21
+ def getAlbum(artist, title):
22
+ return Client().get_album(artist, title)
23
+
24
+
25
+ def getAlbumArt(artist, title, size=SIZE_EXTRA_LARGE):
26
+ return _getArt(getAlbum(artist, title), size=size)
27
+
28
+
29
+ def getArtistArt(artist, size=SIZE_EXTRA_LARGE):
30
+ return _getArt(getArtist(artist), size=size)
31
+
32
+
33
+ def _getArt(obj, size=SIZE_EXTRA_LARGE):
34
+ try:
35
+ return obj.get_cover_image(size)
36
+ except WSError:
37
+ raise ValueError("{} not found.".format(obj.__class__.__name__))
38
+
39
+
40
+ if __name__ == "__main__":
41
+ album = getAlbum("Melvins", "Houdini")
42
+ for sz in (SIZE_SMALL, SIZE_MEGA, SIZE_MEDIUM, SIZE_LARGE,
43
+ SIZE_EXTRA_LARGE):
44
+ print(album.get_cover_image(sz))
45
+
46
+ melvins = getArtist("Melvins")
47
+ print(melvins)
48
+ for sz in (SIZE_SMALL, SIZE_MEGA, SIZE_MEDIUM, SIZE_LARGE,
49
+ SIZE_EXTRA_LARGE):
50
+ print(melvins.get_cover_image(sz))
@@ -0,0 +1,93 @@
1
+ import time
2
+ import pprint
3
+ import eyed3
4
+ import eyed3.utils
5
+ from pathlib import Path
6
+ from collections import Counter
7
+ from eyed3.mimetype import guessMimetype
8
+ from eyed3.utils.log import getLogger
9
+
10
+ log = getLogger(__name__)
11
+
12
+ # python-magic
13
+ try:
14
+ import magic
15
+
16
+ class MagicTypes(magic.Magic):
17
+ def __init__(self):
18
+ magic.Magic.__init__(self, mime=True, mime_encoding=False, keep_going=True)
19
+
20
+ def guess_type(self, filename, all_types=False):
21
+ try:
22
+ types = self.from_file(filename)
23
+ except UnicodeEncodeError:
24
+ # https://github.com/ahupp/python-magic/pull/144
25
+ types = self.from_file(filename.encode("utf-8", 'surrogateescape'))
26
+
27
+ delim = r"\012- "
28
+ if all_types:
29
+ return types.split(delim)
30
+ else:
31
+ return types.split(delim)[0]
32
+
33
+ _python_magic = MagicTypes()
34
+
35
+ except ImportError:
36
+ _python_magic = None
37
+
38
+
39
+ class MimetypesPlugin(eyed3.plugins.LoaderPlugin):
40
+ NAMES = ["mimetypes"]
41
+
42
+ def __init__(self, arg_parser):
43
+ self._num_visited = 0
44
+ super().__init__(arg_parser, cache_files=False, track_images=False)
45
+
46
+ g = self.arg_group
47
+ g.add_argument("--status", action="store_true", help="Print dot status.")
48
+ g.add_argument("--parse-files", action="store_true", help="Parse each file.")
49
+ g.add_argument("--hide-notfound", action="store_true")
50
+ if _python_magic:
51
+ g.add_argument("-M", "--use-pymagic", action="store_true",
52
+ help="Use python-magic to determine mimetype.")
53
+ self.magic = None
54
+ self.start_t = None
55
+ self.mime_types = Counter()
56
+
57
+ def start(self, args, config):
58
+ super().start(args, config)
59
+ self.magic = "pymagic" if self.args.use_pymagic else "filetype"
60
+ self.start_t = time.time()
61
+
62
+ def handleFile(self, f, *args, **kwargs):
63
+
64
+ self._num_visited += 1
65
+ if self.args.parse_files:
66
+ try:
67
+ super().handleFile(f)
68
+ except Exception as ex:
69
+ log.critical(ex, exc_info=ex)
70
+ else:
71
+ self._num_loaded += 1
72
+
73
+ if self.magic == "pymagic":
74
+ mtype = _python_magic.guess_type(f)
75
+ else:
76
+ mtype = guessMimetype(f)
77
+
78
+ self.mime_types[mtype] += 1
79
+ if not self.args.hide_notfound:
80
+ if mtype is None and Path(f).suffix.lower() in (".mp3",):
81
+ print("None mimetype:", f)
82
+
83
+ if self.args.status:
84
+ print(".", end="", flush=True)
85
+
86
+ def handleDone(self):
87
+ t = time.time() - self.start_t
88
+ print(f"\nVisited {self._num_visited} files")
89
+ print(f"Processed {self._num_loaded} files")
90
+ print(f"magic: {self.magic}")
91
+ print(f"time: {eyed3.utils.formatTime(t)} seconds")
92
+ if self.mime_types:
93
+ pprint.pprint(self.mime_types)
eyed3/plugins/nfo.py ADDED
@@ -0,0 +1,123 @@
1
+ import time
2
+ import eyed3
3
+ from eyed3.utils.console import printMsg
4
+ from eyed3.utils import formatSize, formatTime
5
+ from eyed3.id3 import versionToString
6
+ from eyed3.plugins import LoaderPlugin
7
+
8
+
9
+ class NfoPlugin(LoaderPlugin):
10
+ NAMES = ["nfo"]
11
+ SUMMARY = "Create NFO files for each directory scanned."
12
+ DESCRIPTION = "Each directory scanned is treated as an album and a "\
13
+ "`NFO <http://en.wikipedia.org/wiki/.nfo>`_ file is "\
14
+ "written to standard out.\n\n"\
15
+ "NFO files are often found in music archives."
16
+
17
+ def __init__(self, arg_parser):
18
+ super(NfoPlugin, self).__init__(arg_parser)
19
+ self.albums = {}
20
+
21
+ def handleFile(self, f):
22
+ super(NfoPlugin, self).handleFile(f)
23
+
24
+ if self.audio_file and self.audio_file.tag:
25
+ tag = self.audio_file.tag
26
+ album = tag.album
27
+ if album and album not in self.albums:
28
+ self.albums[album] = []
29
+ self.albums[album].append(self.audio_file)
30
+ elif album:
31
+ self.albums[album].append(self.audio_file)
32
+
33
+ def handleDone(self):
34
+ if not self.albums:
35
+ printMsg("No albums found.")
36
+ return
37
+
38
+ for album in self.albums:
39
+ audio_files = self.albums[album]
40
+ if not audio_files:
41
+ continue
42
+ audio_files.sort(key=lambda af: (af.tag.track_num[0] or 999,
43
+ af.tag.track_num[1] or 999))
44
+
45
+ max_title_len = 0
46
+ avg_bitrate = 0
47
+ encoder_info = ''
48
+ for audio_file in audio_files:
49
+ tag = audio_file.tag
50
+ # Compute maximum title length
51
+ title_len = len(tag.title or "")
52
+ if title_len > max_title_len:
53
+ max_title_len = title_len
54
+ # Compute average bitrate
55
+ avg_bitrate += audio_file.info.bit_rate[1]
56
+ # Grab the last lame version in case not all files have one
57
+ if "encoder_version" in audio_file.info.lame_tag:
58
+ version = audio_file.info.lame_tag['encoder_version']
59
+ encoder_info = (version or encoder_info)
60
+ avg_bitrate = avg_bitrate / len(audio_files)
61
+
62
+ printMsg("")
63
+ printMsg("Artist : %s" % audio_files[0].tag.artist)
64
+ printMsg("Album : %s" % album)
65
+ printMsg("Released : %s" %
66
+ (audio_files[0].tag.original_release_date or
67
+ audio_files[0].tag.release_date))
68
+ printMsg("Recorded : %s" % audio_files[0].tag.recording_date)
69
+ genre = audio_files[0].tag.genre
70
+ if genre:
71
+ genre = genre.name
72
+ else:
73
+ genre = ""
74
+ printMsg("Genre : %s" % genre)
75
+
76
+ printMsg("")
77
+ printMsg("Source : ")
78
+ printMsg("Encoder : %s" % encoder_info)
79
+ printMsg("Codec : mp3")
80
+ printMsg("Bitrate : ~%s K/s @ %s Hz, %s" %
81
+ (avg_bitrate, audio_files[0].info.sample_freq,
82
+ audio_files[0].info.mode))
83
+ printMsg("Tag : ID3 %s" %
84
+ versionToString(audio_files[0].tag.version))
85
+
86
+ printMsg("")
87
+ printMsg("Ripped By: ")
88
+
89
+ printMsg("")
90
+ printMsg("Track Listing")
91
+ printMsg("-------------")
92
+ count = 0
93
+ total_time = 0
94
+ total_size = 0
95
+ for audio_file in audio_files:
96
+ tag = audio_file.tag
97
+ count += 1
98
+
99
+ title = tag.title or ""
100
+ title_len = len(title)
101
+ padding = " " * ((max_title_len - title_len) + 3)
102
+ time_secs = audio_file.info.time_secs
103
+ total_time += time_secs
104
+ total_size += audio_file.info.size_bytes
105
+
106
+ zero_pad = "0" * (len(str(len(audio_files))) - len(str(count)))
107
+ printMsg(" %s%d. %s%s(%s)" %
108
+ (zero_pad, count, title, padding,
109
+ formatTime(time_secs)))
110
+
111
+ printMsg("")
112
+ printMsg("Total play time : %s" %
113
+ formatTime(total_time))
114
+ printMsg("Total size : %s" %
115
+ formatSize(total_size))
116
+
117
+ printMsg("")
118
+ printMsg("=" * 78)
119
+ printMsg(".NFO file created with eyeD3 %s on %s" %
120
+ (eyed3.version, time.asctime()))
121
+ printMsg("For more information about eyeD3 go to %s" %
122
+ "http://eyeD3.nicfit.net/")
123
+ printMsg("=" * 78)
eyed3/plugins/pymod.py ADDED
@@ -0,0 +1,72 @@
1
+ import os
2
+ import importlib.machinery
3
+ from eyed3.plugins import LoaderPlugin
4
+
5
+ _DEFAULT_MOD = "eyeD3mod.py"
6
+
7
+
8
+ class PyModulePlugin(LoaderPlugin):
9
+ SUMMARY = "Imports a Python module file and calls its functions for the "\
10
+ "the various plugin events."
11
+ DESCRIPTION = f'''
12
+ If no module if provided a file named {_DEFAULT_MOD} in the current working directory is
13
+ imported. If any of the following methods exist they still be invoked:
14
+
15
+ def audioFile(audio_file):
16
+ """Invoked for every audio file that is encountered. The ``audio_file``
17
+ is of type ``eyed3.core.AudioFile``; currently this is the concrete type
18
+ ``eyed3.mp3.Mp3AudioFile``."""
19
+ pass
20
+
21
+ def audioDir(d, audio_files, images):
22
+ """This function is invoked for any directory (``d``) that contains audio
23
+ (``audio_files``) or image (``images``) media."""
24
+ pass
25
+
26
+ def done():
27
+ """This method is invoke before successful exit."""
28
+ pass
29
+ '''
30
+ NAMES = ["pymod"]
31
+
32
+ def __init__(self, arg_parser):
33
+ super(PyModulePlugin, self).__init__(arg_parser, cache_files=True,
34
+ track_images=True)
35
+ self._mod = None
36
+ self.arg_group.add_argument("-m", "--module", dest="module",
37
+ help="The Python module module to invoke. "
38
+ f"The default is ./{_DEFAULT_MOD}")
39
+
40
+ def start(self, args, config):
41
+ mod_file = args.module or _DEFAULT_MOD
42
+ try:
43
+ mod_name = os.path.splitext(os.path.basename(mod_file))[0]
44
+ loader = importlib.machinery.SourceFileLoader(mod_name, mod_file)
45
+ mod = loader.load_module()
46
+ self._mod = mod
47
+ except IOError:
48
+ raise IOError("Module file not found: %s" % mod_file)
49
+ except (NameError, ImportError, SyntaxError) as ex:
50
+ raise IOError("Module load error: %s" % str(ex))
51
+
52
+ def handleFile(self, f):
53
+ super(PyModulePlugin, self).handleFile(f)
54
+ if not self.audio_file:
55
+ return
56
+
57
+ if "audioFile" in dir(self._mod):
58
+ self._mod.audioFile(self.audio_file)
59
+
60
+ def handleDirectory(self, d, _):
61
+ if not self._file_cache and not self._dir_images:
62
+ return
63
+
64
+ if "audioDir" in dir(self._mod):
65
+ self._mod.audioDir(d, self._file_cache, self._dir_images)
66
+
67
+ super(PyModulePlugin, self).handleDirectory(d, _)
68
+
69
+ def handleDone(self):
70
+ super(PyModulePlugin, self).handleDone()
71
+ if "done" in dir(self._mod):
72
+ self._mod.done()