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.
Files changed (68) hide show
  1. batchmp/__init__.py +0 -0
  2. batchmp/cli/__init__.py +0 -0
  3. batchmp/cli/base/__init__.py +0 -0
  4. batchmp/cli/base/bmp_dispatch.py +60 -0
  5. batchmp/cli/base/bmp_options.py +349 -0
  6. batchmp/cli/base/vchk.py +47 -0
  7. batchmp/cli/bmfp/__init__.py +0 -0
  8. batchmp/cli/bmfp/bmfp_dispatch.py +120 -0
  9. batchmp/cli/bmfp/bmfp_options.py +442 -0
  10. batchmp/cli/renamer/__init__.py +0 -0
  11. batchmp/cli/renamer/renamer_dispatch.py +135 -0
  12. batchmp/cli/renamer/renamer_options.py +355 -0
  13. batchmp/cli/tagger/__init__.py +0 -0
  14. batchmp/cli/tagger/tagger_dispatch.py +143 -0
  15. batchmp/cli/tagger/tagger_options.py +338 -0
  16. batchmp/commons/__init__.py +0 -0
  17. batchmp/commons/chainedhandler.py +102 -0
  18. batchmp/commons/descriptors.py +173 -0
  19. batchmp/commons/progressbar.py +154 -0
  20. batchmp/commons/taskprocessor.py +149 -0
  21. batchmp/commons/utils.py +194 -0
  22. batchmp/ffmptools/__init__.py +0 -0
  23. batchmp/ffmptools/ffcommands/__init__.py +0 -0
  24. batchmp/ffmptools/ffcommands/cmdopt.py +115 -0
  25. batchmp/ffmptools/ffcommands/convert.py +130 -0
  26. batchmp/ffmptools/ffcommands/cuesplit.py +223 -0
  27. batchmp/ffmptools/ffcommands/denoise.py +173 -0
  28. batchmp/ffmptools/ffcommands/fragment.py +121 -0
  29. batchmp/ffmptools/ffcommands/normalize_peak.py +135 -0
  30. batchmp/ffmptools/ffcommands/segment.py +157 -0
  31. batchmp/ffmptools/ffcommands/silencesplit.py +159 -0
  32. batchmp/ffmptools/ffrunner.py +189 -0
  33. batchmp/ffmptools/ffutils.py +300 -0
  34. batchmp/ffmptools/processors/__init__.py +0 -0
  35. batchmp/ffmptools/processors/basefp.py +92 -0
  36. batchmp/ffmptools/processors/ffentry.py +81 -0
  37. batchmp/ffmptools/utils/__init__.py +0 -0
  38. batchmp/ffmptools/utils/cueparse.py +227 -0
  39. batchmp/ffmptools/utils/cuesheet.py +239 -0
  40. batchmp/fstools/__init__.py +0 -0
  41. batchmp/fstools/builders/__init__.py +0 -0
  42. batchmp/fstools/builders/fsb.py +221 -0
  43. batchmp/fstools/builders/fsentry.py +60 -0
  44. batchmp/fstools/builders/fsprms.py +372 -0
  45. batchmp/fstools/dirtools.py +549 -0
  46. batchmp/fstools/fsutils.py +272 -0
  47. batchmp/fstools/rename.py +390 -0
  48. batchmp/fstools/walker.py +79 -0
  49. batchmp/tags/__init__.py +0 -0
  50. batchmp/tags/handlers/__init__.py +0 -0
  51. batchmp/tags/handlers/basehandler.py +99 -0
  52. batchmp/tags/handlers/ffmphandler.py +75 -0
  53. batchmp/tags/handlers/ffmphandlers/__init__.py +0 -0
  54. batchmp/tags/handlers/ffmphandlers/base.py +243 -0
  55. batchmp/tags/handlers/mtghandler.py +56 -0
  56. batchmp/tags/handlers/pmhandler.py +36 -0
  57. batchmp/tags/handlers/tagsholder.py +264 -0
  58. batchmp/tags/output/__init__.py +0 -0
  59. batchmp/tags/output/formatters.py +218 -0
  60. batchmp/tags/processors/__init__.py +0 -0
  61. batchmp/tags/processors/basetp.py +266 -0
  62. batchmp-1.4.dist-info/METADATA +422 -0
  63. batchmp-1.4.dist-info/RECORD +68 -0
  64. batchmp-1.4.dist-info/WHEEL +5 -0
  65. batchmp-1.4.dist-info/entry_points.txt +5 -0
  66. batchmp-1.4.dist-info/licenses/LICENSE +11 -0
  67. batchmp-1.4.dist-info/top_level.txt +1 -0
  68. 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