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,442 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf8
3
+ ## Copyright (c) 2014 Arseniy Kuznetsov
4
+ ##
5
+ ## This program is free software; you can redistribute it and/or
6
+ ## modify it under the terms of the GNU General Public License
7
+ ## as published by the Free Software Foundation; either version 2
8
+ ## of the License, or (at your option) any later version.
9
+ ##
10
+ ## This program is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+
15
+
16
+ """ Batch processing of media files
17
+ . Uses multiprocessing to utilize available CPU cores
18
+ . supports source directory / source file modes
19
+ . supports recursion to specified end_level
20
+ . allows for include / exclude patterns (Unix style)
21
+ . action commands:
22
+ .. print Prints media files
23
+ .. convert Converts media to specified format
24
+ For example, to convert all files in current directory
25
+ $ bmfp convert -la -tf FLAC
26
+ .. normalize Nomalizes sound volume in media files
27
+ Peak normalization supported, RMS normalizations TBD
28
+ .. fragment Extract a media file fragment
29
+ .. segment Splits media files into segments
30
+ For example, to split media files in segments of 45 mins:
31
+ $ bmfp segment -d 45:00
32
+ .. silencesplit Splits media files into segments via detecting specified silence
33
+ $ bmfp silencesplit
34
+
35
+ .. cuesplit Splits media files into parts with specified output format,
36
+ according to their respective cue sheets
37
+ For example, to split all cue files in the current directory
38
+ $ bmfp cuesplit -tf mp3
39
+
40
+ .. denoise Reduces background audio noise in media files
41
+
42
+ .. adjust volume TDB: Adjust audio volume
43
+ .. speed up TDB: Uses Time Stretching to increase audio / video speed
44
+ .. slow down TDB: Uses Time Stretching to increase audio / video speed
45
+
46
+ Usage: bmfp [-h] [-d DIR] [-f FILE] [GLobal Options] {Commands}[Commands Options]
47
+ Input source mode:
48
+ [-d, --dir] Source directory (default is the current directory)
49
+ [-f, --file] File to process
50
+
51
+ Recursion mode:
52
+ [-r, --recursive] Recurse into nested folders
53
+ [-el, --end-level] End level for recursion into nested folders
54
+
55
+ Filter files or folders:
56
+ [-in, --include] Include: Unix-style name patterns separated by ';'
57
+ [-sh, --show-hidden] Shows hidden files
58
+ [-ex, --exclude] Exclude: Unix-style name patterns separated by ';'
59
+ (excludes hidden files by default)
60
+ [-fd, --filter-dirs] Enable Include/Exclude patterns on directories
61
+ [-af, --all-files] Disable Include/Exclude patterns on files
62
+ (shows hidden files excluded by default)
63
+
64
+ Target output Directory Target output directory. When omitted, will be
65
+ [-td, --target-dir] automatically created at the parent level of
66
+ the input source. For recursive processing,
67
+ the processed files directory structure there
68
+ will be the same as for the original files.
69
+ FFmpeg General Output Options:
70
+ [-ma, --map-all] Force including all streams from the input file
71
+ [-cc, --copy-codecs] Copy streams codecs without re-encoding
72
+ [-vn, --no-video] Exclude video streams from the output
73
+ [-an, --no-audio] Exclude audio streams from the output
74
+ [-sn, --no-subs] Exclude subtitles streams from the output
75
+ [-fo, --ffmpeg-options] Additional FFmpeg options
76
+
77
+ FFmpeg Commands Execution:
78
+ [-q, --quiet] Do not visualise changes / show messages during processing
79
+ [-se, --serial-exec] Run all task's commands in a single process
80
+
81
+ Commands:
82
+ {print, convert, normalize, fragment, segment, silencesplit, cuesplit, denoise, version, info}
83
+ $ bmfp {command} -h #run this for detailed help on individual commands
84
+ """
85
+ import os, sys, argparse
86
+ from datetime import timedelta
87
+ from batchmp.cli.base.bmp_options import BatchMPArgParser, BatchMPHelpFormatter, BatchMPBaseCommands
88
+ from batchmp.ffmptools.ffrunner import LogLevel
89
+ from batchmp.ffmptools.ffcommands.silencesplit import SilenceSplitter
90
+ from batchmp.ffmptools.ffcommands.denoise import Denoiser
91
+ from batchmp.ffmptools.ffutils import FFH, FFmpegNotInstalled, FFHDefaults
92
+ from batchmp.ffmptools.ffcommands.cmdopt import FFmpegCommands, FFmpegBitMaskOptions
93
+ from batchmp.fstools.builders.fsentry import FSEntryDefaults
94
+
95
+ class BMFPCommands(BatchMPBaseCommands):
96
+ CONVERT = 'convert'
97
+ NORMALIZE = 'normalize'
98
+ FRAGMENT = 'fragment'
99
+ SEGMENT = 'segment'
100
+ SILENCESPLIT = 'silencesplit'
101
+ CUESPLIT = 'cuesplit'
102
+ DENOISE = 'denoise'
103
+
104
+ @classmethod
105
+ def commands_meta(cls):
106
+ return ''.join(('{',
107
+ '{}, '.format(cls.PRINT),
108
+ '{}, '.format(cls.CONVERT),
109
+ '{}, '.format(cls.NORMALIZE),
110
+ '{}, '.format(cls.FRAGMENT),
111
+ '{}, '.format(cls.SEGMENT),
112
+ '{}, '.format(cls.SILENCESPLIT),
113
+ '{}, '.format(cls.CUESPLIT),
114
+ '{}, '.format(cls.DENOISE),
115
+ '{}, '.format(cls.INFO),
116
+ '{}'.format(cls.VERSION),
117
+ '}'))
118
+
119
+
120
+ class BMFPArgParser(BatchMPArgParser):
121
+ ''' BMFP commands parsing
122
+ '''
123
+ def __init__(self):
124
+ self._script_name = 'BMFP'
125
+ self._description = \
126
+ '''
127
+ BMFP is a batch audio/video media processor for
128
+ efficient media content transformations across
129
+ selected media files. BMFP supports operations
130
+ such as batch conversion between various formats,
131
+ normalization of audio volume,
132
+ segmenting / fragmenting media, denoising audio,
133
+ detaching individual audio / video streams, etc.
134
+
135
+ BMFP is built on top of FFmpeg (http://ffmpeg.org/),
136
+ which needs to be installed and available
137
+ in the command line.
138
+ '''
139
+
140
+ # Args Parsing
141
+ def parse_commands(self, parser):
142
+ ''' BMFP commands parsing
143
+ '''
144
+
145
+ # BFMP Global options
146
+ target_output_group = parser.add_argument_group('Target Output Directory')
147
+ target_output_group.add_argument("-td", "--target-dir", dest = "target_dir",
148
+ type = lambda d: self._is_valid_dir_path(parser, d),
149
+ default = '.',
150
+ help = "Target output directory. When omitted, will be automatically "
151
+ "created inside the parent level of the input source. "
152
+ "For recursive processing, the processed files directory structure there "
153
+ "will be the same as for the original files.")
154
+
155
+ ffmpeg_group = parser.add_argument_group('FFmpeg General Output Options')
156
+ ffmpeg_group.add_argument("-ma", "--map-all", dest='all_streams',
157
+ help = "Force including all streams from the input file",
158
+ action='store_true')
159
+ ffmpeg_group.add_argument("-cc", "--copy-codecs", dest='copy_codecs',
160
+ help = "Copy streams codecs without re-encoding",
161
+ action='store_true')
162
+ ffmpeg_group.add_argument("-vn", "--no-video", dest='exclude_video',
163
+ help = "Exclude video streams from the output",
164
+ action='store_true')
165
+ ffmpeg_group.add_argument("-an", "--no-audio", dest='exclude_audio',
166
+ help = "Exclude audio streams from the output",
167
+ action='store_true')
168
+ ffmpeg_group.add_argument("-sn", "--no-subs", dest='exclude_subtitles',
169
+ help = "Exclude subtitles streams from the output",
170
+ action='store_true')
171
+ ffmpeg_group.add_argument('-fo', '--ffmpeg-options', dest='ffmpeg_options',
172
+ help = 'Additional options for running FFmpeg',
173
+ type = str,
174
+ default = FFmpegCommands.CONVERT_COPY_VBR_QUALITY)
175
+
176
+ misc_group = parser.add_argument_group('FFmpeg Commands Execution')
177
+ #misc_group.add_argument("-pm", "--preserve-meta", dest='preserve_metadata',
178
+ # help = "Preserve metadata of processed files",
179
+ # action='store_true')
180
+ misc_group.add_argument("-se", "--serial-exec", dest='serial_exec',
181
+ help = "Run all task's commands in a single process",
182
+ action='store_true')
183
+ misc_group.add_argument("-q", "--quiet", dest = 'quiet',
184
+ help = "Do not display info messages during processing",
185
+ action = 'store_true')
186
+
187
+ # Commands
188
+ subparsers = parser.add_subparsers(dest='sub_cmd',
189
+ title = 'BMFP Commands',
190
+ metavar = BMFPCommands.commands_meta())
191
+ self._add_version(subparsers)
192
+ self._add_info(subparsers)
193
+
194
+ # Print
195
+ print_parser = subparsers.add_parser(BMFPCommands.PRINT, description = 'Print source directory',
196
+ formatter_class = BatchMPHelpFormatter)
197
+ print_parser.add_argument('-sl', '--startlevel', dest='start_level',
198
+ help = 'Initial nested level for printing (0, i.e. root source directory by default)',
199
+ type = int,
200
+ default = 0)
201
+ print_parser.add_argument('-ss', '--show-size', dest='show_size',
202
+ help ='Shows files size',
203
+ action = 'store_true')
204
+ print_parser.add_argument('-st', '--show-tags', dest='show_tags',
205
+ help ='Shows media tags',
206
+ action = 'store_true')
207
+ print_parser.add_argument('-sv', '--show-volume', dest='show_volume',
208
+ help ='Shows volume statistics',
209
+ action = 'store_true')
210
+ print_parser.add_argument('-se', '--show-silence', dest='show_silence',
211
+ help ='Shows silence',
212
+ action = 'store_true')
213
+
214
+ # Convert
215
+ convert_parser = subparsers.add_parser(BMFPCommands.CONVERT,
216
+ description = 'Converts media to specified format',
217
+ formatter_class = BatchMPHelpFormatter)
218
+ convert_parser.add_argument('-tf', '--target-format', dest='target_format',
219
+ help = 'Target format file extension, e.g. mp3 / m4a / mp4 / mov /...',
220
+ type = str,
221
+ required = True)
222
+ group = convert_parser.add_argument_group('Conversion Options')
223
+ group.add_argument('-cc', '--change-container', dest='change_container',
224
+ help = 'Changes media container without actual re-encoding of contained streams. When specified, ' \
225
+ 'takes priority over all other option switches except for those explicitly specified via "-fo/ --ffmpeg-options"',
226
+ action='store_true')
227
+ group.add_argument('-la', '--lossless-audio', dest='lossless_audio',
228
+ help = 'For media formats with support for lossless audio, tries a lossless conversion',
229
+ action='store_true')
230
+
231
+ # Nomalize
232
+ norm_parser = subparsers.add_parser(BMFPCommands.NORMALIZE,
233
+ description = 'Nomalizes media files. ' \
234
+ 'Both Peak and RMS normalizations are supported, ' \
235
+ 'Peak normalization is the default',
236
+ formatter_class = BatchMPHelpFormatter)
237
+ group = norm_parser.add_argument_group('RMS Normalization')
238
+ group.add_argument('-rm', '--rms', dest='rms_norm',
239
+ help ='(TBD) Leverages RMS-based normalization to set average loudness across selected media files',
240
+ action = 'store_true')
241
+ group.add_argument('-ac', '--allow-clipping', dest = 'allow_clipping',
242
+ help ='(TBD) Allows clipping, via turning off automatic limiting of the gain applied (use with caution)',
243
+ action = 'store_true')
244
+
245
+ # Fragment
246
+ fragment_parser = subparsers.add_parser(BMFPCommands.FRAGMENT,
247
+ description = 'Extracts a fragment via specified start time & duration',
248
+ formatter_class = BatchMPHelpFormatter)
249
+ group = fragment_parser.add_argument_group('Fragment parameters')
250
+ group.add_argument('-fs', '--start', dest='fragment_starttime',
251
+ help = 'Fragment start time, in seconds or in the "hh:mm:ss[.xxx]" format',
252
+ type = lambda f: self._is_timedelta(parser, f),
253
+ required = True)
254
+ group.add_argument('-fd', '--duration', dest='fragment_duration',
255
+ help = 'Fragment duration (default is full media length), in seconds or in the "hh:mm:ss[.xxx]" format',
256
+ type = lambda f: self._is_timedelta(parser, f),
257
+ default = timedelta(days = 380))
258
+ group.add_argument('-ft', '--trim', dest='fragment_trim',
259
+ help = 'Fragment trimming at the end (optional), in seconds or in the "hh:mm:ss[.xxx]" format',
260
+ type = lambda f: self._is_timedelta(parser, f),
261
+ default = timedelta(0))
262
+
263
+ # Segment
264
+ segment_parser = subparsers.add_parser(BMFPCommands.SEGMENT,
265
+ description = 'Segments media by specified maximum duration or file size',
266
+ formatter_class = BatchMPHelpFormatter)
267
+ segment_group = segment_parser.add_mutually_exclusive_group()
268
+ segment_group.add_argument('-fs', '--filesize', dest='segment_filesize',
269
+ help = 'Maximum media file size in MB',
270
+ type = float,
271
+ default = 0.0)
272
+ segment_group.add_argument('-sd', '--duration', dest='segment_duration',
273
+ help = 'Maximum media duration, in seconds or in the "hh:mm:ss[.xxx]" format',
274
+ type = lambda f: self._is_timedelta(parser, f),
275
+ default = timedelta(0))
276
+ segment_parser.add_argument("-rt", "--reset-timestamps", dest='reset_timestamps',
277
+ help = "Reset timestamps at the begin of each segment, so that it "
278
+ "starts with near-zero timestamps and therefore there are minimum pauses "
279
+ "betweeen segments when played one after another. "
280
+ "May not work well for some formats / combinations of muxers/codecs",
281
+ action='store_true')
282
+
283
+ # Silence Split
284
+ silencesplit_parser = subparsers.add_parser(BMFPCommands.SILENCESPLIT,
285
+ description = 'Splits media files into segments via detecting specified silence',
286
+ formatter_class = BatchMPHelpFormatter)
287
+ silencesplit_group = silencesplit_parser.add_argument_group('Silence detection parameters')
288
+ silencesplit_group.add_argument('-md', '--min-duration', dest='min_duration',
289
+ help = 'Minimal silence duration, in seconds or in the "hh:mm:ss[.xxx]" format (default is {} seconds).' \
290
+ .format(FFHDefaults.DEFAULT_SILENCE_MIN_DURATION),
291
+ type = lambda md: self._is_timedelta(parser, md),
292
+ default = timedelta(seconds = FFHDefaults.DEFAULT_SILENCE_MIN_DURATION))
293
+ silencesplit_group.add_argument('-nt', '--noise-tolerance', dest='noise_tolerance',
294
+ help = 'Silence noise tolerance, specified as amplitude ratio (default is {})' \
295
+ .format(FFHDefaults.DEFAULT_SILENCE_NOISE_TOLERANCE),
296
+ type = float,
297
+ default = FFHDefaults.DEFAULT_SILENCE_NOISE_TOLERANCE)
298
+ silencesplit_group.add_argument('-ad', '--auto-duration', dest='auto_duration',
299
+ help = 'Automatically selects representative silence duration',
300
+ action='store_true')
301
+ silencesplit_group.add_argument('-td', '--trim-duration', dest='trimmed_duration',
302
+ help = 'Trims target silence start durations to a given value, in seconds or in the "hh:mm:ss[.xxx]" format (default is {} seconds).' \
303
+ .format(FFHDefaults.DEFAULT_SILENCE_TARGET_TRIMMED_DURATION),
304
+ type = lambda md: self._is_timedelta(parser, md),
305
+ default = timedelta(seconds = FFHDefaults.DEFAULT_SILENCE_TARGET_TRIMMED_DURATION))
306
+
307
+ silencesplit_parser.add_argument("-rt", "--reset-timestamps", dest='reset_timestamps',
308
+ help = "Reset timestamps at the begin of each segment, so that it "
309
+ "starts with near-zero timestamps and therefore there are minimum pauses "
310
+ "betweeen segments when played one after another. "
311
+ "May not work well for some formats / combinations of muxers/codecs",
312
+ action='store_true')
313
+
314
+ # Cue Split
315
+ cuesplit_parser = subparsers.add_parser(BMFPCommands.CUESPLIT,
316
+ description = 'Splits media files according to their respective cue sheets',
317
+ formatter_class = BatchMPHelpFormatter)
318
+
319
+ cuesplit_parser.add_argument('-en', '--encoding', dest='encoding',
320
+ help = 'Cue file encoding, utf-8 by default',
321
+ type = str,
322
+ default = 'utf-8')
323
+
324
+ group = cuesplit_parser.add_argument_group('Conversion Options')
325
+ group.add_argument('-tf', '--target-format', dest='target_format',
326
+ help = 'Target format file extension, e.g. mp3 / m4a / mp4 ...',
327
+ type = str,
328
+ required = True)
329
+ group.add_argument('-la', '--lossless-audio', dest='lossless_audio',
330
+ help = 'For media formats with support for lossless audio, tries a lossless conversion',
331
+ action='store_true')
332
+
333
+
334
+ # Denoise
335
+ denoise_parser = subparsers.add_parser(BMFPCommands.DENOISE,
336
+ description = 'Reduces background audio noise in media files via filtering out highpass / low-pass frequencies',
337
+ formatter_class = BatchMPHelpFormatter)
338
+ denoise_parser.add_argument('-np', '--numpasses', dest='num_passes',
339
+ help = 'Applies filters in multiple passes',
340
+ type = int,
341
+ default = Denoiser.DEFAULT_NUM_PASSES)
342
+ group = denoise_parser.add_argument_group('Pass Filters')
343
+ group.add_argument("-hp", "--highpass", dest='highpass',
344
+ help = "Cutoff boundary for lower frequencies",
345
+ type = int,
346
+ default = Denoiser.DEFAULT_HIGHPASS)
347
+ group.add_argument("-lp", "--lowpass", dest='lowpass',
348
+ help = "Cutoff boundary for higher frequencies",
349
+ type = int,
350
+ default = Denoiser.DEFAULT_LOWPASS)
351
+
352
+ # Troubleshooting
353
+ parser.add_argument('-.ll', dest='log_level',
354
+ help=argparse.SUPPRESS,
355
+ type = int,
356
+ choices = [LogLevel.QUIET, LogLevel.FFMPEG, LogLevel.VERBOSE],
357
+ default = LogLevel.QUIET)
358
+
359
+ # Args Checking
360
+ def default_command(self, args, parser):
361
+ args['sub_cmd'] = BMFPCommands.PRINT
362
+ args['start_level'] = 0
363
+ args['show_size'] = False
364
+ args['show_tags'] = False
365
+ args['show_volume'] = False
366
+ args['show_silence'] = False
367
+
368
+ def check_args(self, args, parser):
369
+ ''' Validation of supplied BMFP CLI arguments
370
+ '''
371
+ # Global options check
372
+ super().check_args(args, parser)
373
+
374
+ # if input source is a file, adjust the target directory
375
+ if args['file']:
376
+ if not args['target_dir']:
377
+ args['target_dir'] = os.path.dirname(args['file'])
378
+
379
+ # only consider playable media files by default
380
+ if args['file_type'] == FSEntryDefaults.DEFAULT_FILE_TYPE and args['sub_cmd'] != BMFPCommands.CUESPLIT:
381
+ args['file_type'] = FSEntryDefaults.DEFAULT_MEDIA_TYPE
382
+
383
+ # Compile FF global options
384
+ ff_general_options = 0
385
+ if args['all_streams']:
386
+ ff_general_options |= FFmpegBitMaskOptions.MAP_ALL_STREAMS
387
+ if args['copy_codecs']:
388
+ ff_general_options |= FFmpegBitMaskOptions.COPY_CODECS
389
+ if args['exclude_video']:
390
+ ff_general_options |= FFmpegBitMaskOptions.DISABLE_VIDEO
391
+ if args['exclude_audio']:
392
+ ff_general_options |= FFmpegBitMaskOptions.DISABLE_AUDIO
393
+ if args['exclude_subtitles']:
394
+ ff_general_options |= FFmpegBitMaskOptions.DISABLE_SUBTITLES
395
+
396
+ args['ff_general_options'] = ff_general_options
397
+
398
+ # Always preserve metadata (experimental)
399
+ args['preserve_metadata'] = True
400
+
401
+ # If advanced media options requested,
402
+ # check ffmpeg presence
403
+ if args['sub_cmd'] == 'print':
404
+ if args['show_volume'] or args['show_silence']:
405
+ if not FFH.ffmpeg_installed():
406
+ print('Advanced media content operations require FFmpeg')
407
+ print(FFmpegNotInstalled().default_message)
408
+ sys.exit(0)
409
+
410
+ # Segment attributes check
411
+ elif args['sub_cmd'] == BMFPCommands.SEGMENT:
412
+ if not args['segment_filesize'] and not args['segment_duration'].total_seconds():
413
+ parser.error('bmfp segment:\n\t'
414
+ 'One of the command parameters needs to be specified: <filesize | duration>')
415
+
416
+ elif args['sub_cmd'] in (BMFPCommands.CONVERT, BMFPCommands.CUESPLIT):
417
+ # Convert attributes check
418
+ args['target_format'] = args['target_format'].lower()
419
+ if not args['target_format'].startswith('.'):
420
+ args['target_format'] = '.{}'.format(args['target_format'])
421
+
422
+ if args['ffmpeg_options'] == FFmpegCommands.CONVERT_COPY_VBR_QUALITY: #default
423
+ if args['lossless_audio']:
424
+ # takes priority over default settings
425
+ args['ffmpeg_options'] = FFmpegCommands.CONVERT_LOSSLESS
426
+
427
+ if args['sub_cmd'] == BMFPCommands.CONVERT and args['change_container']:
428
+ # takes priority over default settings or lossless
429
+ args['ffmpeg_options'] = FFmpegCommands.CONVERT_CHANGE_CONTAINER
430
+
431
+ if not args['ffmpeg_options'].startswith(' '):
432
+ # add a space if needed
433
+ args['ffmpeg_options'] = ' {}'.format(args['ffmpeg_options'])
434
+
435
+
436
+ # Internal Helpers
437
+ @staticmethod
438
+ def _add_arg_misc_group(parser):
439
+ pass
440
+
441
+
442
+
File without changes
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf8
3
+ ## Copyright (c) 2014 Arseniy Kuznetsov
4
+ ##
5
+ ## This program is free software; you can redistribute it and/or
6
+ ## modify it under the terms of the GNU General Public License
7
+ ## as published by the Free Software Foundation; either version 2
8
+ ## of the License, or (at your option) any later version.
9
+ ##
10
+ ## This program is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+
15
+ from batchmp.cli.base.bmp_dispatch import BatchMPDispatcher
16
+ from batchmp.cli.renamer.renamer_options import RenameArgParser, RenamerCommands
17
+ from batchmp.fstools.dirtools import DHandler
18
+ from batchmp.fstools.rename import Renamer
19
+ from batchmp.fstools.builders.fsprms import FSEntryParamsBase, FSEntryParamsExt, FSEntryParamsFlatten, FSEntryParamsOrganize
20
+ from batchmp.fstools.builders.fsb import FSEntryBuilderBase
21
+
22
+ class RenameDispatcher(BatchMPDispatcher):
23
+ ''' Renamer Commands Dispatcher
24
+ '''
25
+ def __init__(self):
26
+ self.option_parser = RenameArgParser()
27
+
28
+ # Dispatcher
29
+ def dispatch(self):
30
+ ''' Dispatches Renamer commands
31
+ '''
32
+ if not super().dispatch():
33
+ args = self.option_parser.parse_options()
34
+ if args['sub_cmd'] == RenamerCommands.PRINT:
35
+ self.print_dir(args)
36
+
37
+ elif args['sub_cmd'] == RenamerCommands.FLATTEN:
38
+ self.flatten(args)
39
+
40
+ elif args['sub_cmd'] == RenamerCommands.INDEX:
41
+ self.add_index(args)
42
+
43
+ elif args['sub_cmd'] == RenamerCommands.ADD_DATE:
44
+ self.add_date(args)
45
+
46
+ elif args['sub_cmd'] == RenamerCommands.ADD_TEXT:
47
+ self.add_text(args)
48
+
49
+ elif args['sub_cmd'] == RenamerCommands.REMOVE:
50
+ self.remove(args)
51
+
52
+ elif args['sub_cmd'] == RenamerCommands.REPLACE:
53
+ self.replace(args)
54
+
55
+ elif args['sub_cmd'] == RenamerCommands.CAPITALIZE:
56
+ self.capitalize(args)
57
+
58
+ elif args['sub_cmd'] == RenamerCommands.DELETE:
59
+ self.delete(args)
60
+
61
+ elif args['sub_cmd'] == RenamerCommands.STATS:
62
+ self.stats(args)
63
+
64
+ elif args['sub_cmd'] == RenamerCommands.ORGANIZE:
65
+ self.organize(args)
66
+
67
+ else:
68
+ print('Nothing to dispatch')
69
+ return False
70
+
71
+ return True
72
+
73
+ # Dispatched Methods
74
+ def print_dir(self, args):
75
+ # Check if organize view is requested
76
+ if args.get('by'):
77
+ fs_entry_params = FSEntryParamsOrganize(args)
78
+ DHandler.print_organized_view(fs_entry_params)
79
+ else:
80
+ fs_entry_params = FSEntryParamsBase(args)
81
+ DHandler.print_dir(fs_entry_params)
82
+
83
+ def stats(self, args):
84
+ fs_entry_params = FSEntryParamsBase(args)
85
+ DHandler.stats(fs_entry_params)
86
+
87
+ def flatten(self, args):
88
+ fs_entry_params = FSEntryParamsFlatten(args)
89
+ DHandler.flatten_folders(fs_entry_params)
90
+
91
+ def add_index(self, args):
92
+ fs_entry_params = FSEntryParamsExt(args)
93
+ Renamer.add_index(fs_entry_params,
94
+ as_prefix = not args['as_suffix'], join_str = args['join_str'],
95
+ start_from = args['start_from'], min_digits = args['min_digits'],
96
+ sequential = args['sequential'], by_directory = args['by_directory'])
97
+
98
+ def add_date(self, args):
99
+ fs_entry_params = FSEntryParamsExt(args)
100
+ Renamer.add_date(fs_entry_params,
101
+ as_prefix = args['as_prefix'], join_str = args['join_str'], format = args['format'])
102
+
103
+ def add_text(self, args):
104
+ fs_entry_params = FSEntryParamsExt(args)
105
+ Renamer.add_text(fs_entry_params,
106
+ text = args['text'], as_prefix = args['as_prefix'], join_str = args['join_str'])
107
+
108
+ def remove(self, args):
109
+ fs_entry_params = FSEntryParamsExt(args)
110
+ Renamer.remove_n_characters(fs_entry_params, num_chars = args['num_chars'], from_head = not args['from_tail'])
111
+
112
+ def replace(self, args):
113
+ fs_entry_params = FSEntryParamsExt(args)
114
+ Renamer.replace(fs_entry_params,
115
+ find_str = args['find_str'],
116
+ replace_str = args['replace_str'] if 'replace_str' in args else None,
117
+ case_insensitive = args['ignore_case'],
118
+ include_extension = args['include_extension'])
119
+
120
+ def capitalize(self, args):
121
+ fs_entry_params = FSEntryParamsExt(args)
122
+ Renamer.capitalize(fs_entry_params)
123
+
124
+ def delete(self, args):
125
+ fs_entry_params = FSEntryParamsExt(args)
126
+ Renamer.delete(fs_entry_params)
127
+
128
+ def organize(self, args):
129
+ fs_entry_params = FSEntryParamsOrganize(args)
130
+ DHandler.organize(fs_entry_params)
131
+
132
+ def main():
133
+ ''' Renamer entry point
134
+ '''
135
+ RenameDispatcher().dispatch()