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,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()
|