batchmp 1.4__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.
- batchmp/__init__.py +0 -0
- batchmp/cli/__init__.py +0 -0
- batchmp/cli/base/__init__.py +0 -0
- batchmp/cli/base/bmp_dispatch.py +60 -0
- batchmp/cli/base/bmp_options.py +349 -0
- batchmp/cli/base/vchk.py +47 -0
- batchmp/cli/bmfp/__init__.py +0 -0
- batchmp/cli/bmfp/bmfp_dispatch.py +120 -0
- batchmp/cli/bmfp/bmfp_options.py +442 -0
- batchmp/cli/renamer/__init__.py +0 -0
- batchmp/cli/renamer/renamer_dispatch.py +135 -0
- batchmp/cli/renamer/renamer_options.py +355 -0
- batchmp/cli/tagger/__init__.py +0 -0
- batchmp/cli/tagger/tagger_dispatch.py +143 -0
- batchmp/cli/tagger/tagger_options.py +338 -0
- batchmp/commons/__init__.py +0 -0
- batchmp/commons/chainedhandler.py +102 -0
- batchmp/commons/descriptors.py +173 -0
- batchmp/commons/progressbar.py +154 -0
- batchmp/commons/taskprocessor.py +149 -0
- batchmp/commons/utils.py +194 -0
- batchmp/ffmptools/__init__.py +0 -0
- batchmp/ffmptools/ffcommands/__init__.py +0 -0
- batchmp/ffmptools/ffcommands/cmdopt.py +115 -0
- batchmp/ffmptools/ffcommands/convert.py +130 -0
- batchmp/ffmptools/ffcommands/cuesplit.py +223 -0
- batchmp/ffmptools/ffcommands/denoise.py +173 -0
- batchmp/ffmptools/ffcommands/fragment.py +121 -0
- batchmp/ffmptools/ffcommands/normalize_peak.py +135 -0
- batchmp/ffmptools/ffcommands/segment.py +157 -0
- batchmp/ffmptools/ffcommands/silencesplit.py +159 -0
- batchmp/ffmptools/ffrunner.py +189 -0
- batchmp/ffmptools/ffutils.py +300 -0
- batchmp/ffmptools/processors/__init__.py +0 -0
- batchmp/ffmptools/processors/basefp.py +92 -0
- batchmp/ffmptools/processors/ffentry.py +81 -0
- batchmp/ffmptools/utils/__init__.py +0 -0
- batchmp/ffmptools/utils/cueparse.py +227 -0
- batchmp/ffmptools/utils/cuesheet.py +239 -0
- batchmp/fstools/__init__.py +0 -0
- batchmp/fstools/builders/__init__.py +0 -0
- batchmp/fstools/builders/fsb.py +221 -0
- batchmp/fstools/builders/fsentry.py +60 -0
- batchmp/fstools/builders/fsprms.py +372 -0
- batchmp/fstools/dirtools.py +549 -0
- batchmp/fstools/fsutils.py +272 -0
- batchmp/fstools/rename.py +390 -0
- batchmp/fstools/walker.py +79 -0
- batchmp/tags/__init__.py +0 -0
- batchmp/tags/handlers/__init__.py +0 -0
- batchmp/tags/handlers/basehandler.py +99 -0
- batchmp/tags/handlers/ffmphandler.py +75 -0
- batchmp/tags/handlers/ffmphandlers/__init__.py +0 -0
- batchmp/tags/handlers/ffmphandlers/base.py +243 -0
- batchmp/tags/handlers/mtghandler.py +56 -0
- batchmp/tags/handlers/pmhandler.py +36 -0
- batchmp/tags/handlers/tagsholder.py +264 -0
- batchmp/tags/output/__init__.py +0 -0
- batchmp/tags/output/formatters.py +218 -0
- batchmp/tags/processors/__init__.py +0 -0
- batchmp/tags/processors/basetp.py +266 -0
- batchmp-1.4.dist-info/METADATA +422 -0
- batchmp-1.4.dist-info/RECORD +68 -0
- batchmp-1.4.dist-info/WHEEL +5 -0
- batchmp-1.4.dist-info/entry_points.txt +5 -0
- batchmp-1.4.dist-info/licenses/LICENSE +11 -0
- batchmp-1.4.dist-info/top_level.txt +1 -0
- batchmp-1.4.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# coding=utf8
|
|
2
|
+
## Copyright (c) 2014 Arseniy Kuznetsov
|
|
3
|
+
##
|
|
4
|
+
## This program is free software; you can redistribute it and/or
|
|
5
|
+
## modify it under the terms of the GNU General Public License
|
|
6
|
+
## as published by the Free Software Foundation; either version 2
|
|
7
|
+
## of the License, or (at your option) any later version.
|
|
8
|
+
##
|
|
9
|
+
## This program is distributed in the hope that it will be useful,
|
|
10
|
+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
## GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from string import Template
|
|
17
|
+
from batchmp.commons.utils import MiscHelpers
|
|
18
|
+
from batchmp.commons.descriptors import (
|
|
19
|
+
PropertyDescriptor,
|
|
20
|
+
LazyFunctionPropertyDescriptor,
|
|
21
|
+
FunctionPropertyDescriptor,
|
|
22
|
+
WeakMethodPropertyDescriptor)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Tag Field Descriptors
|
|
26
|
+
class TaggableMediaFieldDescriptor(PropertyDescriptor):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
class TaggableMediaTrackFieldDescriptor(TaggableMediaFieldDescriptor):
|
|
30
|
+
def __get__(self, instance, owner=None):
|
|
31
|
+
if instance is None:
|
|
32
|
+
return self
|
|
33
|
+
value = super().__get__(instance, owner = owner)
|
|
34
|
+
if value:
|
|
35
|
+
trt = getattr(instance, 'tracktotal', None)
|
|
36
|
+
if trt:
|
|
37
|
+
value = '{}'.format(value)
|
|
38
|
+
return value.zfill(MiscHelpers.int_num_digits(trt))
|
|
39
|
+
return value
|
|
40
|
+
else:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
class ExpandableMediaFieldDescriptor(TaggableMediaFieldDescriptor):
|
|
44
|
+
def __set__(self, instance, value):
|
|
45
|
+
if value:
|
|
46
|
+
value = instance._process_value(value)
|
|
47
|
+
super().__set__(instance, value)
|
|
48
|
+
|
|
49
|
+
class NullableMediaFieldDescriptor(PropertyDescriptor):
|
|
50
|
+
def __set__(self, instance, value):
|
|
51
|
+
if value is not None:
|
|
52
|
+
if not isinstance(value, list):
|
|
53
|
+
raise ValueError('{}: Nullable fields should be either None or a list of taggable fields'.format(value))
|
|
54
|
+
taggable_fields = [field for field in instance.taggable_fields()]
|
|
55
|
+
for field in value:
|
|
56
|
+
if not field in taggable_fields:
|
|
57
|
+
raise ValueError('Field is not supported: {}'.format(field))
|
|
58
|
+
super().__set__(instance, value)
|
|
59
|
+
|
|
60
|
+
class NonTaggableMediaFieldDescriptor(PropertyDescriptor):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# Art Field is a lazy property
|
|
64
|
+
class ArtFieldDescriptor(LazyFunctionPropertyDescriptor):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
class TagHolder:
|
|
68
|
+
''' Tag Holder
|
|
69
|
+
Defines supported tags & the protocol
|
|
70
|
+
Supports tag templates processing
|
|
71
|
+
'''
|
|
72
|
+
title = ExpandableMediaFieldDescriptor()
|
|
73
|
+
album = ExpandableMediaFieldDescriptor()
|
|
74
|
+
artist = ExpandableMediaFieldDescriptor()
|
|
75
|
+
albumartist = ExpandableMediaFieldDescriptor()
|
|
76
|
+
genre = ExpandableMediaFieldDescriptor()
|
|
77
|
+
composer = ExpandableMediaFieldDescriptor()
|
|
78
|
+
track = TaggableMediaTrackFieldDescriptor()
|
|
79
|
+
tracktotal = TaggableMediaFieldDescriptor()
|
|
80
|
+
disc = TaggableMediaFieldDescriptor()
|
|
81
|
+
disctotal = TaggableMediaFieldDescriptor()
|
|
82
|
+
year = TaggableMediaFieldDescriptor()
|
|
83
|
+
encoder = ExpandableMediaFieldDescriptor()
|
|
84
|
+
|
|
85
|
+
bpm = TaggableMediaFieldDescriptor()
|
|
86
|
+
comp = TaggableMediaFieldDescriptor()
|
|
87
|
+
grouping = ExpandableMediaFieldDescriptor()
|
|
88
|
+
comments = ExpandableMediaFieldDescriptor()
|
|
89
|
+
lyrics = ExpandableMediaFieldDescriptor()
|
|
90
|
+
|
|
91
|
+
# non-taggable fields
|
|
92
|
+
length = NonTaggableMediaFieldDescriptor()
|
|
93
|
+
bitrate = NonTaggableMediaFieldDescriptor()
|
|
94
|
+
samplerate = NonTaggableMediaFieldDescriptor()
|
|
95
|
+
channels = NonTaggableMediaFieldDescriptor()
|
|
96
|
+
bitdepth = NonTaggableMediaFieldDescriptor()
|
|
97
|
+
format = NonTaggableMediaFieldDescriptor()
|
|
98
|
+
|
|
99
|
+
# additional non-tag properties
|
|
100
|
+
deferred_art_method = WeakMethodPropertyDescriptor()
|
|
101
|
+
filepath = PropertyDescriptor()
|
|
102
|
+
template_processor_method = FunctionPropertyDescriptor()
|
|
103
|
+
nullable_fields = NullableMediaFieldDescriptor()
|
|
104
|
+
|
|
105
|
+
def __init__(self, copy_empty_vals = False, nullable_fields = None,
|
|
106
|
+
copy_non_taggable = False, process_templates = True):
|
|
107
|
+
self._copy_empty_vals = copy_empty_vals
|
|
108
|
+
self._copy_non_taggable = copy_non_taggable
|
|
109
|
+
self._process_templates = process_templates
|
|
110
|
+
self.nullable_fields = nullable_fields
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def copy_empty_vals(self):
|
|
114
|
+
return self._copy_empty_vals
|
|
115
|
+
@property
|
|
116
|
+
def copy_non_taggable(self):
|
|
117
|
+
return self._copy_non_taggable
|
|
118
|
+
@property
|
|
119
|
+
def process_templates(self):
|
|
120
|
+
return self._process_templates
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def has_artwork(self):
|
|
124
|
+
''' when art retrieval is deferred,
|
|
125
|
+
provides info on art presence whithout loading into memory
|
|
126
|
+
'''
|
|
127
|
+
if self.deferred_art_method:
|
|
128
|
+
return True
|
|
129
|
+
elif self.art:
|
|
130
|
+
return True
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
@ArtFieldDescriptor
|
|
134
|
+
def art(self):
|
|
135
|
+
''' provides access on the class level
|
|
136
|
+
when art is set on the instance level, should be ignored
|
|
137
|
+
'''
|
|
138
|
+
if self.deferred_art_method:
|
|
139
|
+
return self.deferred_art_method()
|
|
140
|
+
else:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def taggable_fields(cls):
|
|
145
|
+
''' generates names of all writable tag fields
|
|
146
|
+
'''
|
|
147
|
+
for c in cls.__mro__:
|
|
148
|
+
for field, descr in vars(c).items():
|
|
149
|
+
if isinstance(descr, TaggableMediaFieldDescriptor):
|
|
150
|
+
yield field
|
|
151
|
+
elif isinstance(descr, ArtFieldDescriptor):
|
|
152
|
+
yield field
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def non_taggable_fields(cls):
|
|
156
|
+
''' generates names of non-writable tag fields
|
|
157
|
+
'''
|
|
158
|
+
for c in cls.__mro__:
|
|
159
|
+
for field, descr in vars(c).items():
|
|
160
|
+
if isinstance(descr, NonTaggableMediaFieldDescriptor):
|
|
161
|
+
yield field
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def textual_fields(cls):
|
|
165
|
+
for c in cls.__mro__:
|
|
166
|
+
for field, descr in vars(c).items():
|
|
167
|
+
if isinstance(descr, ExpandableMediaFieldDescriptor):
|
|
168
|
+
yield field
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def fields(cls):
|
|
172
|
+
''' generates names of all tag fields
|
|
173
|
+
'''
|
|
174
|
+
yield from cls.taggable_fields
|
|
175
|
+
yield from cls.non_taggable_fields
|
|
176
|
+
|
|
177
|
+
def copy_tags(self, tag_holder = None):
|
|
178
|
+
''' Copies tags from passed tag_holder object
|
|
179
|
+
Supports tag templates processing
|
|
180
|
+
'''
|
|
181
|
+
if not tag_holder:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
fields = self.fields if tag_holder.copy_non_taggable else self.taggable_fields
|
|
185
|
+
|
|
186
|
+
if tag_holder.template_processor_method:
|
|
187
|
+
self.template_processor_method = tag_holder.template_processor_method
|
|
188
|
+
|
|
189
|
+
for field in fields():
|
|
190
|
+
value = getattr(tag_holder, field)
|
|
191
|
+
if (value is not None) or \
|
|
192
|
+
(tag_holder.copy_empty_vals) or \
|
|
193
|
+
(tag_holder.nullable_fields and (field in tag_holder.nullable_fields)):
|
|
194
|
+
setattr(self, field, value)
|
|
195
|
+
|
|
196
|
+
def clear_tags(self, reset_art = False):
|
|
197
|
+
''' clears writable tags values
|
|
198
|
+
'''
|
|
199
|
+
for field in self.taggable_fields():
|
|
200
|
+
setattr(self, field, None)
|
|
201
|
+
if reset_art and hasattr(self, 'art'):
|
|
202
|
+
del self.art
|
|
203
|
+
|
|
204
|
+
def reset_tags(self):
|
|
205
|
+
''' resets a tag holder to its initial state
|
|
206
|
+
'''
|
|
207
|
+
self.template_processor_method = None
|
|
208
|
+
self.filepath = None
|
|
209
|
+
self.clear_tags(reset_art = True)
|
|
210
|
+
|
|
211
|
+
# Internal Helpers
|
|
212
|
+
def _process_value(self, value):
|
|
213
|
+
''' templates processing
|
|
214
|
+
'''
|
|
215
|
+
if not self.process_templates:
|
|
216
|
+
return value
|
|
217
|
+
|
|
218
|
+
if self.template_processor_method:
|
|
219
|
+
return (self.template_processor_method(self._expand_templates(value)))
|
|
220
|
+
else:
|
|
221
|
+
return self._expand_templates(value)
|
|
222
|
+
|
|
223
|
+
def _expand_templates(self, value):
|
|
224
|
+
''' expands template values
|
|
225
|
+
'''
|
|
226
|
+
template = Template(value)
|
|
227
|
+
return template.safe_substitute(self._substitute_dictionary)
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def _substitute_dictionary(self):
|
|
231
|
+
''' internal property for template value substitution
|
|
232
|
+
'''
|
|
233
|
+
sd = {}
|
|
234
|
+
sd['title'] = self.title if self.title else ''
|
|
235
|
+
sd['album'] = self.album if self.album else ''
|
|
236
|
+
sd['artist'] = self.artist if self.artist else ''
|
|
237
|
+
sd['albumartist'] = self.albumartist if self.albumartist else ''
|
|
238
|
+
sd['genre'] = self.genre if self.genre else ''
|
|
239
|
+
sd['composer'] = self.composer if self.composer else ''
|
|
240
|
+
sd['track'] = self.track if self.track else ''
|
|
241
|
+
sd['tracktotal'] = self.tracktotal if self.tracktotal else ''
|
|
242
|
+
sd['disc'] = self.disc if self.disc else ''
|
|
243
|
+
sd['disctotal'] = self.disctotal if self.disctotal else ''
|
|
244
|
+
sd['year'] = self.year if self.year else ''
|
|
245
|
+
sd['encoder'] = self.encoder if self.encoder else ''
|
|
246
|
+
sd['bpm'] = self.bpm if self.bpm else ''
|
|
247
|
+
sd['compilaton'] = self.comp if self.comp else ''
|
|
248
|
+
sd['grouping'] = self.grouping if self.grouping else ''
|
|
249
|
+
sd['comments'] = self.comments if self.comments else ''
|
|
250
|
+
sd['lyrics'] = self.lyrics if self.lyrics else ''
|
|
251
|
+
sd['length'] = self.length if self.length else ''
|
|
252
|
+
sd['bitrate'] = self.bitrate if self.bitrate else ''
|
|
253
|
+
sd['samplerate'] = self.samplerate if self.samplerate else ''
|
|
254
|
+
sd['channels'] = self.channels if self.channels else ''
|
|
255
|
+
sd['bitdepth'] = self.bitdepth if self.bitdepth else ''
|
|
256
|
+
sd['format'] = self.format if self.format else ''
|
|
257
|
+
|
|
258
|
+
if self.filepath:
|
|
259
|
+
sd['filename'] = os.path.splitext(os.path.basename(self.filepath))[0]
|
|
260
|
+
full_dir_name = os.path.dirname(self.filepath)
|
|
261
|
+
sd['dirname'] = os.path.basename(full_dir_name)
|
|
262
|
+
sd['pardirname'] = os.path.basename(os.path.dirname(full_dir_name))
|
|
263
|
+
|
|
264
|
+
return sd
|
|
File without changes
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# coding=utf8
|
|
2
|
+
## Copyright (c) 2014 Arseniy Kuznetsov
|
|
3
|
+
##
|
|
4
|
+
## This program is free software; you can redistribute it and/or
|
|
5
|
+
## modify it under the terms of the GNU General Public License
|
|
6
|
+
## as published by the Free Software Foundation; either version 2
|
|
7
|
+
## of the License, or (at your option) any later version.
|
|
8
|
+
##
|
|
9
|
+
## This program is distributed in the hope that it will be useful,
|
|
10
|
+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
## GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import datetime, math
|
|
16
|
+
from enum import IntEnum
|
|
17
|
+
from batchmp.commons.utils import MiscHelpers
|
|
18
|
+
from batchmp.fstools.builders.fsentry import FSEntry, FSEntryType
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OutputFormatType(IntEnum):
|
|
22
|
+
COMPACT = 0
|
|
23
|
+
FULL = 1
|
|
24
|
+
STATS = 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TagOutputFormatter:
|
|
28
|
+
''' Generates output for printing tags
|
|
29
|
+
'''
|
|
30
|
+
DEFAULT_TAG_INDENT = ' '
|
|
31
|
+
|
|
32
|
+
COMPACT_FIELDS = ['title', 'album', 'artist', 'albumartist', 'genre', 'composer', 'year', 'track', 'tracktotal', 'disc', 'disctotal']
|
|
33
|
+
EXTENDED_FIELDS = ['encoder', 'bpm', 'comp', 'grouping', 'comments', 'lyrics', 'art']
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def tags_formatter(entry, *,
|
|
37
|
+
format = None, handler = None, show_stats = False,
|
|
38
|
+
tag_holder = None, tag_holder_builder = None,
|
|
39
|
+
show_tag_holder_values = False,
|
|
40
|
+
diff_tags_only = False):
|
|
41
|
+
|
|
42
|
+
''' Tag formatting entry point
|
|
43
|
+
'''
|
|
44
|
+
|
|
45
|
+
# check inputs
|
|
46
|
+
if entry.type == FSEntryType.DIR or entry.type == FSEntryType.ROOT:
|
|
47
|
+
return entry.basename
|
|
48
|
+
if not handler or not handler.can_handle(entry.realpath):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
if tag_holder_builder:
|
|
52
|
+
tag_holder = tag_holder_builder(entry)
|
|
53
|
+
|
|
54
|
+
if not format or (format not in OutputFormatType):
|
|
55
|
+
if tag_holder:
|
|
56
|
+
format = OutputFormatType.FULL
|
|
57
|
+
else:
|
|
58
|
+
format = OutputFormatType.COMPACT
|
|
59
|
+
|
|
60
|
+
diff_fields = None
|
|
61
|
+
if tag_holder:
|
|
62
|
+
# figure out relevant fields to show
|
|
63
|
+
diff_fields = []
|
|
64
|
+
for field in tag_holder.taggable_fields():
|
|
65
|
+
value = getattr(tag_holder, field)
|
|
66
|
+
if (value is not None) or \
|
|
67
|
+
(tag_holder.copy_empty_vals) or \
|
|
68
|
+
(tag_holder.nullable_fields and (field in tag_holder.nullable_fields)):
|
|
69
|
+
diff_fields.append(field)
|
|
70
|
+
|
|
71
|
+
if not diff_tags_only:
|
|
72
|
+
# if need to show the changes along with other tag fields,
|
|
73
|
+
# return a minimal set of compact fields + all changed fields (including extended)
|
|
74
|
+
diff_extended_fields = list(set(diff_fields).intersection(set(TagOutputFormatter.EXTENDED_FIELDS)))
|
|
75
|
+
diff_fields = TagOutputFormatter.COMPACT_FIELDS + diff_extended_fields
|
|
76
|
+
|
|
77
|
+
if tag_holder and show_tag_holder_values:
|
|
78
|
+
# if care for new values, copy tags / process templates
|
|
79
|
+
handler.tag_holder.copy_tags(tag_holder)
|
|
80
|
+
|
|
81
|
+
if format == OutputFormatType.COMPACT:
|
|
82
|
+
return TagOutputFormatter._formatter(entry, handler.tag_holder, show_stats = show_stats)
|
|
83
|
+
elif format == OutputFormatType.FULL:
|
|
84
|
+
return TagOutputFormatter._formatter(entry, handler.tag_holder, show_extended = True,
|
|
85
|
+
show_stats = show_stats, diff_fields = diff_fields)
|
|
86
|
+
elif format == OutputFormatType.STATS:
|
|
87
|
+
return TagOutputFormatter._formatter(entry, handler.tag_holder,
|
|
88
|
+
show_compact = False, show_stats = show_stats)
|
|
89
|
+
else:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
# Helpers
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _formatter(entry, tag_holder,
|
|
95
|
+
show_compact = True, show_extended = False, show_stats = False,
|
|
96
|
+
diff_fields = None):
|
|
97
|
+
indent = entry.indent[:-3] + TagOutputFormatter.DEFAULT_TAG_INDENT
|
|
98
|
+
media_str = ''
|
|
99
|
+
|
|
100
|
+
if diff_fields is not None:
|
|
101
|
+
compact_fields = [f for f in TagOutputFormatter.COMPACT_FIELDS if f in diff_fields]
|
|
102
|
+
extended_fields = [f for f in TagOutputFormatter.EXTENDED_FIELDS if f in diff_fields]
|
|
103
|
+
else:
|
|
104
|
+
compact_fields = TagOutputFormatter.COMPACT_FIELDS
|
|
105
|
+
extended_fields = TagOutputFormatter.EXTENDED_FIELDS
|
|
106
|
+
|
|
107
|
+
if show_compact:
|
|
108
|
+
track_set = disc_set = False
|
|
109
|
+
for field in compact_fields:
|
|
110
|
+
field_val = getattr(tag_holder, field)
|
|
111
|
+
if field_val:
|
|
112
|
+
if field in ('disc', 'disctotal'):
|
|
113
|
+
if not disc_set:
|
|
114
|
+
disc_set = True
|
|
115
|
+
if tag_holder.disc or tag_holder.disctotal:
|
|
116
|
+
media_str = TagOutputFormatter._disc_str(tag_holder, indent, media_str)
|
|
117
|
+
elif field in ('track', 'tracktotal'):
|
|
118
|
+
if not track_set:
|
|
119
|
+
track_set = True
|
|
120
|
+
if tag_holder.track or tag_holder.tracktotal:
|
|
121
|
+
media_str = TagOutputFormatter._track_str(tag_holder, indent, media_str)
|
|
122
|
+
else:
|
|
123
|
+
media_str = '{0}\n{1}{2}: {3}'.format(media_str, indent,
|
|
124
|
+
TagOutputFormatter._tag_display_name(field),
|
|
125
|
+
field_val)
|
|
126
|
+
if show_extended:
|
|
127
|
+
for field in extended_fields:
|
|
128
|
+
field_val = getattr(tag_holder, field)
|
|
129
|
+
if field_val:
|
|
130
|
+
if field == 'art':
|
|
131
|
+
if tag_holder.has_artwork:
|
|
132
|
+
media_str = '{0}\n{1}Artwork present'.format(media_str, indent)
|
|
133
|
+
else:
|
|
134
|
+
media_str = '{0}\n{1}{2}: {3}'.format(media_str, indent,
|
|
135
|
+
TagOutputFormatter._tag_display_name(field),
|
|
136
|
+
field_val)
|
|
137
|
+
# Stats
|
|
138
|
+
if show_stats:
|
|
139
|
+
media_str = TagOutputFormatter._stats_str(tag_holder, indent, media_str)
|
|
140
|
+
|
|
141
|
+
return '{0}{1}'.format(entry.basename, media_str)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _disc_str(tag_holder, indent, media_str):
|
|
145
|
+
disc = tag_holder.disc if tag_holder.disc else '_'
|
|
146
|
+
if tag_holder.disctotal:
|
|
147
|
+
disc_str = '{}/{}'.format(disc, tag_holder.disctotal)
|
|
148
|
+
else:
|
|
149
|
+
disc_str = tag_holder.disc
|
|
150
|
+
return '{0}\n{1}Disk: {2}'.format(media_str, indent, disc_str)
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def _track_str(tag_holder, indent, media_str, show_always = False):
|
|
154
|
+
track = tag_holder.track if tag_holder.track else '_'
|
|
155
|
+
if tag_holder.tracktotal:
|
|
156
|
+
track_str = '{}/{}'.format(track, tag_holder.tracktotal)
|
|
157
|
+
else:
|
|
158
|
+
track_str = track
|
|
159
|
+
return '{0}\n{1}Track: {2}'.format(media_str, indent, track_str)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def _stats_str(tag_holder, indent, media_str):
|
|
163
|
+
if tag_holder.format:
|
|
164
|
+
media_str = '{0}\n{1}Format: {2}'.format(media_str, indent, tag_holder.format)
|
|
165
|
+
|
|
166
|
+
duration = datetime.timedelta(seconds = math.ceil(tag_holder.length)) if tag_holder.length else None
|
|
167
|
+
duration = 'Duration: {}'.format(duration if duration else 'n/a')
|
|
168
|
+
|
|
169
|
+
bitrate = math.ceil(tag_holder.bitrate / 1000) if tag_holder.bitrate else None
|
|
170
|
+
bitrate = 'Bit rate: {}'.format('{}kb/s'.format(bitrate) if bitrate else 'n/a')
|
|
171
|
+
|
|
172
|
+
samplerate = tag_holder.samplerate if tag_holder.samplerate else None
|
|
173
|
+
samplerate = 'Sample rate: {}'.format('{}Hz'.format(samplerate) if samplerate else 'n/a')
|
|
174
|
+
|
|
175
|
+
bitdepth = tag_holder.bitdepth if tag_holder.bitdepth else None
|
|
176
|
+
bitdepth = 'Bit depth: {}'.format(bitdepth if bitdepth else 'n/a')
|
|
177
|
+
|
|
178
|
+
return '{0}\n{1}{2}, {3}, {4}, {5}'.format(media_str, indent, duration,
|
|
179
|
+
bitrate, samplerate, bitdepth)
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _tag_display_name(field):
|
|
183
|
+
if field == 'title':
|
|
184
|
+
return 'Title'
|
|
185
|
+
elif field == 'album':
|
|
186
|
+
return 'Album'
|
|
187
|
+
elif field == 'artist':
|
|
188
|
+
return 'Artist'
|
|
189
|
+
elif field == 'albumartist':
|
|
190
|
+
return 'Album Artist'
|
|
191
|
+
elif field == 'genre':
|
|
192
|
+
return 'Genre'
|
|
193
|
+
elif field == 'composer':
|
|
194
|
+
return 'Composer'
|
|
195
|
+
elif field == 'track':
|
|
196
|
+
return 'Track'
|
|
197
|
+
elif field == 'tracktotal':
|
|
198
|
+
return 'Track Total'
|
|
199
|
+
elif field == 'disc':
|
|
200
|
+
return 'Disc'
|
|
201
|
+
elif field == 'disctotal':
|
|
202
|
+
return 'Disc Total'
|
|
203
|
+
elif field == 'year':
|
|
204
|
+
return 'Year'
|
|
205
|
+
elif field == 'encoder':
|
|
206
|
+
return 'Encoder'
|
|
207
|
+
elif field == 'bpm':
|
|
208
|
+
return 'BPM'
|
|
209
|
+
elif field == 'comp':
|
|
210
|
+
return 'Compilation'
|
|
211
|
+
elif field == 'grouping':
|
|
212
|
+
return 'Grouping'
|
|
213
|
+
elif field == 'comments':
|
|
214
|
+
return 'Comments'
|
|
215
|
+
elif field == 'lyrics':
|
|
216
|
+
return 'Lyrics'
|
|
217
|
+
|
|
218
|
+
return None
|
|
File without changes
|