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,60 @@
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
+ from enum import IntEnum
16
+
17
+ class FSEntry:
18
+ ''' File System entry representation
19
+ '''
20
+ def __init__(self, type, basename, realpath, indent,
21
+ isEnclosingEntry = False, isEnclosingFilesContainterEntry = False,
22
+ isScopeSwitchingEntry = False):
23
+ self.type = type
24
+ self.basename = basename
25
+ self.realpath = realpath
26
+ self.indent = indent
27
+ self.isEnclosingEntry = isEnclosingEntry
28
+ self.isEnclosingFilesContainterEntry = isEnclosingFilesContainterEntry
29
+ self.scopeSwitchingEntry = isScopeSwitchingEntry
30
+
31
+
32
+ class FSEntryType(IntEnum):
33
+ ROOT = 0x00000
34
+ DIR = 0x00001
35
+ FILE = 0x00002
36
+
37
+
38
+ class FSMediaEntryType(IntEnum):
39
+ IMAGE = 0x00010
40
+ AUDIO = 0x00011
41
+ VIDEO = 0x00012
42
+ NONMEDIA = 0x00013
43
+
44
+
45
+ class FSMediaEntryGroupType(IntEnum):
46
+ MEDIA = 0x00100
47
+ PLAYABLE = 0x00101
48
+ NONPLAYABLE = 0x00102
49
+ ANY = 0x00103
50
+
51
+
52
+ class FSEntryDefaults:
53
+ DEFAULT_NESTED_INDENT = ' '
54
+ DEFAULT_INCLUDE = '*'
55
+ DEFAULT_EXCLUDE = '.*' #exclude hidden files
56
+ DEFAULT_SORT = 'na'
57
+ DEFAULT_FILE_TYPE = 'any'
58
+ DEFAULT_MEDIA_TYPE = 'playable'
59
+
60
+
@@ -0,0 +1,372 @@
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, sys, fnmatch, collections, pygtrie, heapq
16
+ from enum import IntEnum
17
+ from abc import ABCMeta, abstractmethod
18
+ from batchmp.fstools.fsutils import FSH
19
+ from batchmp.ffmptools.ffutils import FFH
20
+ from batchmp.fstools.builders.fsentry import FSEntryDefaults, FSMediaEntryType, FSMediaEntryGroupType
21
+ from batchmp.commons.descriptors import (
22
+ PropertyDescriptor,
23
+ LazyFunctionPropertyDescriptor,
24
+ LazyClassPropertyDescriptor,
25
+ FunctionPropertyDescriptor,
26
+ BooleanPropertyDescriptor)
27
+
28
+ # FSEntry Attributes with default values
29
+ class FSEntryDefaultValueDescriptor(LazyFunctionPropertyDescriptor):
30
+ pass
31
+
32
+ # FSEntry Attributes with default values
33
+ class FSEntryRuntimeAttributeDescriptor(PropertyDescriptor):
34
+ pass
35
+
36
+
37
+ class FSEntryFilteredFilesValueDescriptor(FSEntryRuntimeAttributeDescriptor):
38
+ ''' Files property descriptor
39
+ '''
40
+ def __set__(self, instance, value):
41
+ if isinstance(instance, FSEntryParamsBase):
42
+ # filtering
43
+ if instance.filter_files:
44
+ fnames = [fname for fname in value if instance.passed_filters(fname)]
45
+ else:
46
+ fnames = [fname for fname in value]
47
+
48
+ # file types
49
+ if instance.file_type != FSMediaEntryGroupType.ANY:
50
+ fnames = [fname for fname in fnames if instance.is_of_required_type(os.path.join(instance.rpath, fname))]
51
+
52
+ # sorting
53
+ if instance.by_size:
54
+ sort_key = lambda fname: os.path.getsize(os.path.join(instance.rpath, fname))
55
+ else:
56
+ sort_key = lambda fname: fname.lower()
57
+ fnames.sort(key = sort_key, reverse = instance.descending)
58
+ # set value
59
+ super().__set__(instance, fnames)
60
+ else:
61
+ raise TypeError("Not a FSEntryParamsBase Type: {}".format(instance.__class__))
62
+
63
+
64
+ DNames = collections.namedtuple('DNames', ['passed', 'enclosing'])
65
+ class FSEntryFilteredDirsValueDescriptor(FSEntryRuntimeAttributeDescriptor):
66
+ ''' Directories property descriptor
67
+ '''
68
+ def __set__(self, instance, value):
69
+ if isinstance(instance, FSEntryParamsBase):
70
+ passed_dnames, enclosing_dnames = [], []
71
+ # filtering
72
+ if instance.filter_dirs:
73
+ for dname in value:
74
+ if instance.file_type == FSMediaEntryGroupType.ANY and instance.passed_filters(dname):
75
+ passed_dnames.append(dname)
76
+ elif instance.scan_for_enclosing_directories:
77
+ en_dname = os.path.join(instance.rpath, dname)
78
+ if instance._enclosing_dnames.has_node(en_dname):
79
+ enclosing_dnames.append(dname)
80
+ else:
81
+ passed_dnames = [dname for dname in value]
82
+ # sorting
83
+ if instance.by_size:
84
+ dirs_sort_key = lambda dname: FSH.dir_size(os.path.join(instance.rpath, dname))
85
+ else:
86
+ dirs_sort_key = lambda dname: dname.lower()
87
+ passed_dnames.sort(key = dirs_sort_key, reverse = instance.descending)
88
+ enclosing_dnames.sort(key = dirs_sort_key, reverse = instance.descending)
89
+ super().__set__(instance, DNames(passed_dnames, enclosing_dnames))
90
+ else:
91
+ raise TypeError("Not a FSEntryParamsBase Type: {}".format(instance.__class__))
92
+
93
+
94
+
95
+ class FSEntryRPathDescriptor(FSEntryRuntimeAttributeDescriptor):
96
+ ''' RPath property descriptor
97
+ '''
98
+ def __set__(self, instance, value):
99
+ if isinstance(instance, FSEntryParamsBase):
100
+ super().__set__(instance, FSH.full_path(value))
101
+ else:
102
+ raise TypeError("Not a FSEntryParamsBase Type: {}".format(instance.__class__))
103
+
104
+
105
+ class FSEntryFileTypeDescriptor(PropertyDescriptor):
106
+ ''' RPath property descriptor
107
+ '''
108
+ def __set__(self, instance, value):
109
+ if isinstance(instance, FSEntryParamsBase):
110
+ file_types_map = {
111
+ 'image': FSMediaEntryType.IMAGE,
112
+ 'video': FSMediaEntryType.VIDEO,
113
+ 'audio': FSMediaEntryType.AUDIO,
114
+ 'nonmedia': FSMediaEntryType.NONMEDIA,
115
+ 'playable': FSMediaEntryGroupType.PLAYABLE,
116
+ 'nonplayable': FSMediaEntryGroupType.NONPLAYABLE,
117
+ 'media': FSMediaEntryGroupType.MEDIA,
118
+ 'any': FSMediaEntryGroupType.ANY
119
+ }
120
+ super().__set__(instance, file_types_map.get(value, FSMediaEntryGroupType.ANY))
121
+ else:
122
+ raise TypeError("Not a FSEntryParamsBase Type: {}".format(instance.__class__))
123
+
124
+
125
+
126
+ class FSEntryParamsBase():
127
+ ''' Base Entry attributes
128
+ '''
129
+ src_dir = PropertyDescriptor()
130
+
131
+ start_level = PropertyDescriptor()
132
+ end_level = PropertyDescriptor()
133
+
134
+ filter_dirs = BooleanPropertyDescriptor()
135
+ filter_files = BooleanPropertyDescriptor()
136
+ show_size = BooleanPropertyDescriptor()
137
+
138
+ include = PropertyDescriptor()
139
+ exclude = PropertyDescriptor()
140
+ nested_indent = PropertyDescriptor()
141
+ sort = PropertyDescriptor()
142
+ file_type = FSEntryFileTypeDescriptor()
143
+ media_scan = BooleanPropertyDescriptor()
144
+
145
+ fs_entry_builder = LazyClassPropertyDescriptor('batchmp.fstools.builders.fsb.FSEntryBuilderBase')
146
+ '''Runtime attrbutes
147
+ '''
148
+ rpath = FSEntryRPathDescriptor()
149
+ fnames = FSEntryFilteredFilesValueDescriptor()
150
+ dnames = FSEntryFilteredDirsValueDescriptor()
151
+
152
+ def __init__(self, args = {}):
153
+ self.src_dir = args.get('dir')
154
+ self.start_level = args.get('start_level', 0)
155
+ self.end_level = args.get('end_level', sys.maxsize)
156
+ self.nested_indent = args.get('nested_indent', FSEntryDefaults.DEFAULT_NESTED_INDENT)
157
+ self.include = args.get('include', FSEntryDefaults.DEFAULT_INCLUDE)
158
+ self.exclude = args.get('exclude', FSEntryDefaults.DEFAULT_EXCLUDE)
159
+ self.file_type = args.get('file_type', FSEntryDefaults.DEFAULT_FILE_TYPE)
160
+ self.sort = args.get('sort', FSEntryDefaults.DEFAULT_SORT)
161
+ self.filter_dirs = not args.get('all_dirs', False)
162
+ self.filter_files = not args.get('all_files', False)
163
+ self.show_size = args.get('show_size', False)
164
+ self.fast_scan = not args.get('media_scan', False)
165
+
166
+ #self._media_extensions_cache = set()
167
+
168
+ # enclosing directores
169
+ self._enclosing_dnames = pygtrie.StringTrie(separator=os.path.sep)
170
+ self._enclosing_files_containters = set()
171
+ if self.scan_for_enclosing_directories:
172
+ for rpath, dirs, files in os.walk(self.src_dir):
173
+ if FSH.level_from_root(self.src_dir, rpath) < self.end_level:
174
+ marked_enclosing = False
175
+ for dir_name in dirs:
176
+ if self.file_type == FSMediaEntryGroupType.ANY and self.passed_filters(dir_name):
177
+ self._enclosing_dnames[rpath] = rpath
178
+ marked_enclosing = True
179
+ break # no need to check this root further
180
+ for file_name in files:
181
+ if self.passed_filters(file_name) and self.is_of_required_type(os.path.join(rpath,file_name)):
182
+ if not marked_enclosing:
183
+ self._enclosing_dnames[rpath] = rpath
184
+ self._enclosing_files_containters.add(rpath)
185
+ break # no need to check this root further
186
+ #print(self.file_type)
187
+ #print('Enclosing: {}'.format(self._enclosing_dnames))
188
+ #print('Enclosing File Containers: {}'.format(self._enclosing_files_containters))
189
+
190
+ # Current level
191
+ @property
192
+ def current_level(self):
193
+ return FSH.level_from_root(self.src_dir, self.rpath)
194
+
195
+ # Sorting
196
+ @property
197
+ def descending(self):
198
+ return True if self.sort.endswith('d') else False
199
+
200
+ @property
201
+ def by_size(self):
202
+ return True if self.sort.startswith('s') else False
203
+
204
+ # Filtering
205
+ @property
206
+ def passed_filters(self):
207
+ def include_match(fsname):
208
+ for include_pattern in self.include.split(';'):
209
+ if fnmatch.fnmatch(fsname, include_pattern):
210
+ return True
211
+ return False
212
+
213
+ def exclude_match(fsname):
214
+ for exclude_pattern in self.exclude.split(';'):
215
+ if fnmatch.fnmatch(fsname, exclude_pattern):
216
+ return True
217
+ return False
218
+
219
+ return lambda fs_name: include_match(fs_name) and (not exclude_match(fs_name))
220
+
221
+ def is_of_entry_type(self, media_type):
222
+ return {
223
+ FSMediaEntryGroupType.ANY: True,
224
+ FSMediaEntryType.IMAGE: media_type == FSMediaEntryType.IMAGE,
225
+ FSMediaEntryType.VIDEO: media_type == FSMediaEntryType.VIDEO,
226
+ FSMediaEntryType.AUDIO: media_type == FSMediaEntryType.AUDIO,
227
+ FSMediaEntryGroupType.PLAYABLE: media_type in (FSMediaEntryType.VIDEO, FSMediaEntryType.AUDIO),
228
+ FSMediaEntryGroupType.NONPLAYABLE: media_type not in (FSMediaEntryType.VIDEO, FSMediaEntryType.AUDIO),
229
+ FSMediaEntryGroupType.MEDIA: media_type in (FSMediaEntryType.IMAGE, FSMediaEntryType.VIDEO, FSMediaEntryType.AUDIO),
230
+ FSMediaEntryType.NONMEDIA: media_type not in (FSMediaEntryType.IMAGE, FSMediaEntryType.VIDEO, FSMediaEntryType.AUDIO)
231
+ }[self.file_type]
232
+
233
+
234
+ def is_of_required_type(self, fpath):
235
+ media_type = FFH.media_type(fpath = fpath, fast_scan = self.fast_scan)
236
+ return self.is_of_entry_type(media_type)
237
+
238
+ @property
239
+ def scan_for_enclosing_directories(self):
240
+ return self. filter_dirs and (self.file_type != FSMediaEntryGroupType.ANY or self.include != FSEntryDefaults.DEFAULT_INCLUDE) and self.end_level > 0
241
+
242
+ @property
243
+ def skip_iteration(self):
244
+ return True if (self.current_level < self.start_level) or (self.current_level > self.end_level) else False
245
+
246
+ @property
247
+ def end_iteration(self):
248
+ return True if self.current_level > self.end_level else False
249
+
250
+ @property
251
+ def current_indent(self):
252
+ return '{0}{1}'.format(self.nested_indent * (self.current_level), '|-> ' if not (self.isEnclosingEntry) else '|.. ')
253
+
254
+ @property
255
+ def siblings_indent(self):
256
+ return '{0}{1}'.format(self.nested_indent * (self.current_level + 1), '|- ')
257
+
258
+ @property
259
+ def merged_dnames(self):
260
+ return list(heapq.merge(self.dnames.passed, self.dnames.enclosing, reverse = self.descending))
261
+
262
+ @property
263
+ def isEnclosingEntry(self):
264
+ return self._enclosing_dnames.has_node(self.rpath) and not self.isMatchingDirEntry
265
+
266
+ @property
267
+ def isMatchingDirEntry(self):
268
+ dir_name = os.path.basename(self.rpath)
269
+ return self.file_type == FSMediaEntryGroupType.ANY and self.passed_filters(dir_name)
270
+
271
+ @property
272
+ def isEnclosingFilesContainterEntry(self):
273
+ return self.rpath in self._enclosing_files_containters
274
+
275
+ @property
276
+ def args(self):
277
+ return self._args
278
+
279
+ @classmethod
280
+ def writable_fields(cls):
281
+ ''' generates names of all writable tag fields
282
+ '''
283
+ for c in cls.__mro__:
284
+ for field, descr in vars(c).items():
285
+ if isinstance(descr, LazyClassPropertyDescriptor):
286
+ continue
287
+ if isinstance(descr, BooleanPropertyDescriptor):
288
+ yield field
289
+ elif isinstance(descr, PropertyDescriptor):
290
+ yield field
291
+
292
+ @classmethod
293
+ def runtime_attributes(cls):
294
+ ''' generates names of all runtime fields
295
+ '''
296
+ for c in cls.__mro__:
297
+ for field, descr in vars(c).items():
298
+ if isinstance(descr, FSEntryRuntimeAttributeDescriptor):
299
+ yield field
300
+
301
+ # Copy attributes from another entry
302
+ def copy_params(self, fs_entry_params):
303
+ self._enclosing_dnames = fs_entry_params._enclosing_dnames
304
+ self._enclosing_files_containters = fs_entry_params._enclosing_files_containters
305
+ for field in self.writable_fields():
306
+ value = getattr(fs_entry_params, field)
307
+ if value is not None:
308
+ setattr(self, field, value)
309
+
310
+ def reset_runtime(self):
311
+ for field in self.runtime_attributes():
312
+ setattr(self, field, [])
313
+
314
+ def __str__(self):
315
+ return ('Entry of type: {}\n'.format(self.__class__.__name__) + \
316
+ '\n '.join('{}: {}'.format(key, value) for key, value in vars(self).items()))
317
+
318
+
319
+
320
+ class FSEntryParamsExt(FSEntryParamsBase):
321
+ display_current = BooleanPropertyDescriptor()
322
+ include_dirs = BooleanPropertyDescriptor()
323
+ include_files = BooleanPropertyDescriptor()
324
+ quiet = BooleanPropertyDescriptor()
325
+
326
+ def __init__(self, args = {}):
327
+ super().__init__(args)
328
+ self.display_current = args.get('display_current', False)
329
+ self.include_dirs = args.get('include_dirs', False)
330
+ self.include_files = not args.get('exclude_files', False)
331
+ self.quiet = args.get('quiet', False)
332
+
333
+
334
+ class FSEntryParamsFlatten(FSEntryParamsExt):
335
+ ''' Flatten Entry attributes
336
+ '''
337
+ fs_entry_builder = LazyClassPropertyDescriptor('batchmp.fstools.builders.fsb.FSEntryBuilderFlatten')
338
+
339
+ target_level = PropertyDescriptor()
340
+ remove_folders = BooleanPropertyDescriptor()
341
+ remove_non_empty_folders = BooleanPropertyDescriptor()
342
+ unique_fnames = FunctionPropertyDescriptor()
343
+ non_empty_folders_mgs = PropertyDescriptor
344
+
345
+ def __init__(self, args = {}):
346
+ super().__init__(args)
347
+ self.remove_folders = False if args.get('discard_flattened') == 'le' else True
348
+ self.remove_non_empty_folders = True if args.get('discard_flattened') == 'da' else False
349
+ self.unique_fnames = args.get('unique_fnames', FSH.unique_fnames)
350
+
351
+ self.non_empty_folders_mgs = 'Use --discard-flattened parameter to remove non empty folders'
352
+
353
+ self.target_level = args.get('target_level', 0)
354
+ if self.end_level < self.target_level:
355
+ self.end_level = self.target_level
356
+
357
+
358
+
359
+ class FSEntryParamsOrganize(FSEntryParamsExt):
360
+ ''' Organize Entry attributes
361
+ '''
362
+ fs_entry_builder = LazyClassPropertyDescriptor('batchmp.fstools.builders.fsb.FSEntryBuilderOrganizeWorker')
363
+
364
+ by = PropertyDescriptor()
365
+ date_format = PropertyDescriptor()
366
+ target_dir = PropertyDescriptor()
367
+
368
+ def __init__(self, args = {}):
369
+ super().__init__(args)
370
+ self.by = args.get('by')
371
+ self.date_format = args.get('date_format')
372
+ self.target_dir = args.get('target_dir')