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,300 @@
|
|
|
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, subprocess, shlex, sys
|
|
16
|
+
import time, datetime, json, re
|
|
17
|
+
from collections import namedtuple
|
|
18
|
+
from batchmp.fstools.builders.fsentry import FSMediaEntryType
|
|
19
|
+
from batchmp.commons.utils import (
|
|
20
|
+
run_cmd,
|
|
21
|
+
CmdProcessingError,
|
|
22
|
+
MiscHelpers
|
|
23
|
+
)
|
|
24
|
+
from batchmp.fstools.fsutils import FSH
|
|
25
|
+
|
|
26
|
+
class FFmpegNotInstalled(Exception):
|
|
27
|
+
def __init__(self, message = None):
|
|
28
|
+
super().__init__(message if message is not None else self.default_message)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def default_message(self):
|
|
32
|
+
windows_instructions = \
|
|
33
|
+
'''
|
|
34
|
+
Installing on Windows:
|
|
35
|
+
http://www.wikihow.com/Install-FFmpeg-on-Windows
|
|
36
|
+
http://www.renevolution.com/how-to-get-ffmpeg-for-windows/
|
|
37
|
+
'''
|
|
38
|
+
macos_instructions = \
|
|
39
|
+
'''
|
|
40
|
+
Installing on Mac OS X:
|
|
41
|
+
http://www.renevolution.com/how-to-install-ffmpeg-on-mac-os-x/
|
|
42
|
+
'''
|
|
43
|
+
linux_instructions = \
|
|
44
|
+
'''
|
|
45
|
+
Installing on Ubuntu and Debian:
|
|
46
|
+
$ sudo apt-get update
|
|
47
|
+
$ sudo apt-get install ffmpeg
|
|
48
|
+
|
|
49
|
+
Installing on CentOS/RHEL and Fedora:
|
|
50
|
+
enable atrpms repository, then:
|
|
51
|
+
# yum install ffmpeg
|
|
52
|
+
'''
|
|
53
|
+
|
|
54
|
+
platforms_install_instructions = ''
|
|
55
|
+
|
|
56
|
+
if sys.platform == 'linux':
|
|
57
|
+
platforms_install_instructions = linux_instructions
|
|
58
|
+
elif sys.platform == 'darwin':
|
|
59
|
+
platforms_install_instructions = macos_instructions
|
|
60
|
+
elif sys.platform == 'win32':
|
|
61
|
+
platforms_install_instructions = windows_instructions
|
|
62
|
+
|
|
63
|
+
return \
|
|
64
|
+
'''
|
|
65
|
+
|
|
66
|
+
Looks like FFmpeg is not installed
|
|
67
|
+
|
|
68
|
+
For full Batch Media Tools Processing functionalty,
|
|
69
|
+
|
|
70
|
+
please install FFmpeg and enable it in the command line
|
|
71
|
+
|
|
72
|
+
You can download FFmpeg from here:
|
|
73
|
+
http://www.ffmpeg.org/download.html
|
|
74
|
+
|
|
75
|
+
Installing FFmpeg
|
|
76
|
+
Manual Install (Universal):
|
|
77
|
+
. download FFmpeg (select a static build)
|
|
78
|
+
. put the FFmpeg executable in your $PATH
|
|
79
|
+
{0}
|
|
80
|
+
'''.format(platforms_install_instructions)
|
|
81
|
+
|
|
82
|
+
class FFHDefaults:
|
|
83
|
+
DEFAULT_SILENCE_MIN_DURATION = 2
|
|
84
|
+
DEFAULT_SILENCE_NOISE_TOLERANCE = 0.005
|
|
85
|
+
DEFAULT_SILENCE_TARGET_TRIMMED_DURATION = 2
|
|
86
|
+
|
|
87
|
+
class FFH:
|
|
88
|
+
''' FFmpeg-related utilities
|
|
89
|
+
'''
|
|
90
|
+
FFEntry = namedtuple('FFEntry', ['path', 'format', 'audio', 'artwork', 'video'])
|
|
91
|
+
FFFullEntry = namedtuple('FFFullEntry', ['path', 'format', 'audio_streams',
|
|
92
|
+
'video_streams', 'artwork_streams'])
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def ffmpeg_installed():
|
|
96
|
+
""" Checks if ffmpeg is installed and in system PATH
|
|
97
|
+
"""
|
|
98
|
+
ffmpeg_app_name = 'ffmpeg' if os.name != 'nt' else 'ffmpeg.exe'
|
|
99
|
+
for path in os.environ['PATH'].split(os.pathsep):
|
|
100
|
+
app_path = os.path.join(path, ffmpeg_app_name)
|
|
101
|
+
if os.path.isfile(app_path) and os.access(app_path, os.X_OK):
|
|
102
|
+
return True
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def media_file_info(fpath):
|
|
107
|
+
''' Compact media file info
|
|
108
|
+
Extracts main audio / artwork streams
|
|
109
|
+
'''
|
|
110
|
+
full_entry = FFH.media_file_info_full(fpath)
|
|
111
|
+
if full_entry:
|
|
112
|
+
audio_stream = artwork_stream = video_stream = None
|
|
113
|
+
if full_entry.audio_streams and len(full_entry.audio_streams) > 0:
|
|
114
|
+
# if there are multiple audio streams, take the first
|
|
115
|
+
audio_stream = full_entry.audio_streams[0]
|
|
116
|
+
|
|
117
|
+
if full_entry.video_streams and len(full_entry.video_streams) > 0:
|
|
118
|
+
# in case there are multiple art images, take the first
|
|
119
|
+
video_stream = full_entry.video_streams[0]
|
|
120
|
+
|
|
121
|
+
if full_entry.artwork_streams and len(full_entry.artwork_streams) > 0:
|
|
122
|
+
# in case there are multiple art images, take the first
|
|
123
|
+
artwork_stream = full_entry.artwork_streams[0]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
return FFH.FFEntry(fpath, full_entry.format, audio_stream, artwork_stream, video_stream)
|
|
127
|
+
else:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def media_file_info_full(fpath):
|
|
132
|
+
''' Gathers full info about a media file
|
|
133
|
+
'''
|
|
134
|
+
if not FFH.ffmpeg_installed():
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
cmd = ''.join(('ffprobe ',
|
|
138
|
+
' -v quiet',
|
|
139
|
+
' -show_streams',
|
|
140
|
+
#' -select_streams a',
|
|
141
|
+
' -show_format',
|
|
142
|
+
' -print_format json',
|
|
143
|
+
' {}'.format(shlex.quote(fpath))))
|
|
144
|
+
try:
|
|
145
|
+
output, _ = run_cmd(cmd)
|
|
146
|
+
except CmdProcessingError as e:
|
|
147
|
+
return None
|
|
148
|
+
else:
|
|
149
|
+
out = json.loads(output)
|
|
150
|
+
if not out:
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
streams = out.get('streams')
|
|
154
|
+
format = out.get('format')
|
|
155
|
+
audio_streams = video_streams = artwork_streams = None
|
|
156
|
+
if streams:
|
|
157
|
+
is_audio_stream = lambda stream: True if stream.get('codec_type') == 'audio' else False
|
|
158
|
+
is_video_stream = lambda stream: True if (stream.get('codec_type') == 'video' and format.get('format_name') != 'tty') else False
|
|
159
|
+
|
|
160
|
+
is_image_stream = lambda stream: True if \
|
|
161
|
+
stream['codec_name'].lower() in FFH.common_media_extensions(FSMediaEntryType.IMAGE) else False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
audio_streams = [stream for stream in streams if is_audio_stream(stream)]
|
|
165
|
+
video_streams = [stream for stream in streams if \
|
|
166
|
+
is_video_stream(stream) and not is_image_stream(stream)]
|
|
167
|
+
artwork_streams = [stream for stream in streams if \
|
|
168
|
+
is_video_stream(stream) and is_image_stream(stream)]
|
|
169
|
+
|
|
170
|
+
format = out.get('format')
|
|
171
|
+
return FFH.FFFullEntry(fpath, format, audio_streams, video_streams, artwork_streams)
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def ffmpeg_supported_media(fpath = None, ffentry = None):
|
|
175
|
+
''' Determines if a file can be processed with FFmpeg
|
|
176
|
+
'''
|
|
177
|
+
media_type = FFH.media_type(fpath = fpath, ffentry = ffentry)
|
|
178
|
+
return media_type in (FSMediaEntryType.VIDEO, FSMediaEntryType.AUDIO)
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def media_type(fpath = None, ffentry = None, fast_scan = False):
|
|
182
|
+
''' Determines file media type
|
|
183
|
+
'''
|
|
184
|
+
if fast_scan and fpath and not ffentry:
|
|
185
|
+
fpath_ext = FSH.path_extension(fpath)
|
|
186
|
+
if fpath_ext in FFH.common_media_extensions(FSMediaEntryType.IMAGE):
|
|
187
|
+
return FSMediaEntryType.IMAGE
|
|
188
|
+
elif fpath_ext in FFH.common_media_extensions(FSMediaEntryType.AUDIO):
|
|
189
|
+
return FSMediaEntryType.AUDIO
|
|
190
|
+
elif fpath_ext in FFH.common_media_extensions(FSMediaEntryType.VIDEO):
|
|
191
|
+
return FSMediaEntryType.VIDEO
|
|
192
|
+
else:
|
|
193
|
+
return FSMediaEntryType.NONMEDIA
|
|
194
|
+
|
|
195
|
+
ffentry = ffentry if ffentry else FFH.media_file_info(fpath)
|
|
196
|
+
if ffentry:
|
|
197
|
+
if hasattr(ffentry, "video")and ffentry.video:
|
|
198
|
+
return FSMediaEntryType.VIDEO
|
|
199
|
+
elif hasattr(ffentry, "audio") and ffentry.audio:
|
|
200
|
+
return FSMediaEntryType.AUDIO
|
|
201
|
+
elif hasattr(ffentry, "artwork") and ffentry.artwork:
|
|
202
|
+
return FSMediaEntryType.IMAGE
|
|
203
|
+
|
|
204
|
+
return FSMediaEntryType.NONMEDIA
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def common_media_extensions(media_type):
|
|
209
|
+
media_types_ext_map = {
|
|
210
|
+
FSMediaEntryType.IMAGE: ('jpeg', 'png', 'gif', 'tiff', 'bmp', 'mjpeg', 'jpg'),
|
|
211
|
+
FSMediaEntryType.VIDEO: ('264', 'avi', 'mp4', 'mp4v', 'mpeg', 'mpg', 'mov ', 'mkv', 'webm', 'wmv', 'flv', 'm4v', 'mov'),
|
|
212
|
+
FSMediaEntryType.AUDIO: ('wav', 'mp3', 'ogg', 'flac', 'aiff', 'wma', 'aac', 'mid', 'm4a', 'mka', 'm4b'),
|
|
213
|
+
}
|
|
214
|
+
return media_types_ext_map.get(media_type, None)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def silence_detector(fpath, *,
|
|
219
|
+
min_duration = FFHDefaults.DEFAULT_SILENCE_MIN_DURATION,
|
|
220
|
+
noise_tolerance_amplitude_ratio = FFHDefaults.DEFAULT_SILENCE_NOISE_TOLERANCE):
|
|
221
|
+
''' Detects silence
|
|
222
|
+
If successful, returns a list of SilenceEntry tuples
|
|
223
|
+
'''
|
|
224
|
+
if not FFH.ffmpeg_installed():
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
cmd = ''.join(('ffmpeg',
|
|
228
|
+
' -i {}'.format(shlex.quote(fpath)),
|
|
229
|
+
' -af silencedetect=',
|
|
230
|
+
'n={}'.format(noise_tolerance_amplitude_ratio),
|
|
231
|
+
':d={}'.format(min_duration),
|
|
232
|
+
' -vn',
|
|
233
|
+
' -sn',
|
|
234
|
+
' -f null - '))
|
|
235
|
+
|
|
236
|
+
# print(cmd)
|
|
237
|
+
try:
|
|
238
|
+
output, _ = run_cmd(cmd)
|
|
239
|
+
except CmdProcessingError as e:
|
|
240
|
+
return None
|
|
241
|
+
else:
|
|
242
|
+
silence_starts = re.findall(r'(?<=silence_start:)(?:\D*)(\d*\.?\d+)', output)
|
|
243
|
+
silence_ends = re.findall(r'(?<=silence_end:)(?:\D*)(\d*\.?\d+)', output)
|
|
244
|
+
|
|
245
|
+
SilenceEntry = namedtuple('SilenceEntry', ['silence_start', 'silence_end'])
|
|
246
|
+
silence_entries = []
|
|
247
|
+
for ss, se in zip(silence_starts, silence_ends):
|
|
248
|
+
silence_entries.append(SilenceEntry(float(ss), float(se)))
|
|
249
|
+
|
|
250
|
+
if len(silence_entries) < len(silence_starts):
|
|
251
|
+
# matched non-balanced silence at the end
|
|
252
|
+
# try to parse output audio duration and use it as the silence_end value
|
|
253
|
+
found = re.findall(r'(?<=Duration:)(?:\D*)([\d:\.]*)', output)
|
|
254
|
+
if found:
|
|
255
|
+
duration = MiscHelpers.time_delta(found[0]).total_seconds()
|
|
256
|
+
else:
|
|
257
|
+
duration = float(sys.maxsize)
|
|
258
|
+
silence_entries.append(SilenceEntry(float(silence_starts[-1]), duration))
|
|
259
|
+
|
|
260
|
+
return silence_entries
|
|
261
|
+
|
|
262
|
+
@staticmethod
|
|
263
|
+
def volume_detector(fpath):
|
|
264
|
+
''' Detect the volume of input media file
|
|
265
|
+
Returns Mean Volume and Max Volume in decibels, relative to max PCM value
|
|
266
|
+
'''
|
|
267
|
+
if not FFH.ffmpeg_installed():
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
cmd = ''.join(('ffmpeg',
|
|
271
|
+
' -i {}'.format(shlex.quote(fpath)),
|
|
272
|
+
' -filter:a "volumedetect"',
|
|
273
|
+
' -vn',
|
|
274
|
+
' -sn',
|
|
275
|
+
' -f null - '))
|
|
276
|
+
#print(cmd)
|
|
277
|
+
try:
|
|
278
|
+
output, _ = run_cmd(cmd)
|
|
279
|
+
except CmdProcessingError as e:
|
|
280
|
+
return None
|
|
281
|
+
else:
|
|
282
|
+
mean_volume = max_volume = 0
|
|
283
|
+
|
|
284
|
+
# mean volume
|
|
285
|
+
found = re.findall(r'(?<=mean_volume:)(?:\D*)(\d*\.?\d+)', output)
|
|
286
|
+
if found:
|
|
287
|
+
mean_volume = float(found[0])
|
|
288
|
+
|
|
289
|
+
# max volume
|
|
290
|
+
found = re.findall(r'(?<=max_volume:)(?:\D*)(\d*\.?\d+)', output)
|
|
291
|
+
if found:
|
|
292
|
+
max_volume = float(found[0])
|
|
293
|
+
|
|
294
|
+
VolumeEntry = namedtuple('VolumeEntry', ['mean_volume', 'max_volume'])
|
|
295
|
+
return VolumeEntry(mean_volume, max_volume)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# Quick dev test
|
|
299
|
+
if __name__ == '__main__':
|
|
300
|
+
print(FFmpegNotInstalled().default_message)
|
|
File without changes
|
|
@@ -0,0 +1,92 @@
|
|
|
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 sys, math, datetime
|
|
16
|
+
from batchmp.commons.utils import MiscHelpers
|
|
17
|
+
from batchmp.fstools.dirtools import DHandler
|
|
18
|
+
from batchmp.fstools.builders.fsentry import FSEntry, FSEntryType
|
|
19
|
+
from batchmp.ffmptools.ffutils import FFH, FFmpegNotInstalled
|
|
20
|
+
from batchmp.tags.output.formatters import TagOutputFormatter, OutputFormatType
|
|
21
|
+
from batchmp.tags.handlers.mtghandler import MutagenTagHandler
|
|
22
|
+
from batchmp.tags.handlers.ffmphandler import FFmpegTagHandler
|
|
23
|
+
from functools import partial
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseFFProcessor:
|
|
27
|
+
''' Base Tag Processing
|
|
28
|
+
'''
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self._handler = MutagenTagHandler() + FFmpegTagHandler()
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def handler(self):
|
|
34
|
+
return self._handler
|
|
35
|
+
|
|
36
|
+
def print_dir(self, ff_entry_params,
|
|
37
|
+
show_size = False, format = None, show_stats = False,
|
|
38
|
+
show_volume = False, show_silence = False):
|
|
39
|
+
|
|
40
|
+
''' Prints tags in selected media files
|
|
41
|
+
'''
|
|
42
|
+
|
|
43
|
+
if show_volume or show_silence:
|
|
44
|
+
if not FFH.ffmpeg_installed():
|
|
45
|
+
print(FFmpegNotInstalled().default_message)
|
|
46
|
+
sys.exit(0)
|
|
47
|
+
|
|
48
|
+
base_formatter = partial(TagOutputFormatter.tags_formatter,
|
|
49
|
+
format = format if format else OutputFormatType.COMPACT,
|
|
50
|
+
handler = self.handler,
|
|
51
|
+
show_stats = show_stats)
|
|
52
|
+
|
|
53
|
+
def volume_formatter(entry):
|
|
54
|
+
volume_str = ''
|
|
55
|
+
if show_volume:
|
|
56
|
+
if entry.type == FSEntryType.FILE:
|
|
57
|
+
if self.handler.can_handle(entry.realpath):
|
|
58
|
+
volume_entry = FFH.volume_detector(entry.realpath)
|
|
59
|
+
indent = entry.indent[:-3] + TagOutputFormatter.DEFAULT_TAG_INDENT
|
|
60
|
+
if not volume_entry:
|
|
61
|
+
volume_str = '\n{}No volume detected'.format(indent)
|
|
62
|
+
else:
|
|
63
|
+
volume_str = '\n{0}{1}: -{2}dB, {3}: -{4}dB'.format(indent,
|
|
64
|
+
'Max Volume', volume_entry.max_volume,
|
|
65
|
+
'Mean Volume', volume_entry.mean_volume)
|
|
66
|
+
return volume_str
|
|
67
|
+
|
|
68
|
+
def silence_formatter(entry):
|
|
69
|
+
silence_str = ''
|
|
70
|
+
if show_silence:
|
|
71
|
+
if entry.type == FSEntryType.FILE:
|
|
72
|
+
if self.handler.can_handle(entry.realpath):
|
|
73
|
+
indent = entry.indent[:-3] + TagOutputFormatter.DEFAULT_TAG_INDENT
|
|
74
|
+
silence_entries = FFH.silence_detector(entry.realpath)
|
|
75
|
+
if not silence_entries:
|
|
76
|
+
silence_str = '\n{}No silence detected'.format(indent)
|
|
77
|
+
else:
|
|
78
|
+
silence_str = '\n{}Detected Silences:'.format(indent)
|
|
79
|
+
indent = '{} '.format(indent)
|
|
80
|
+
for silence_entry in silence_entries:
|
|
81
|
+
silence_str = '{0}\n{1}{2}: {3}, {4}: {5}, {6}: {7}'.format(
|
|
82
|
+
silence_str, indent,
|
|
83
|
+
'Start', MiscHelpers.time_delta_str(silence_entry.silence_start),
|
|
84
|
+
'End', MiscHelpers.time_delta_str(silence_entry.silence_end),
|
|
85
|
+
'Duration', MiscHelpers.time_delta_str(silence_entry.silence_end - silence_entry.silence_start))
|
|
86
|
+
return silence_str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
DHandler.print_dir(ff_entry_params,
|
|
90
|
+
formatter = [base_formatter, volume_formatter, silence_formatter],
|
|
91
|
+
selected_files_description = 'media file')
|
|
92
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
from batchmp.fstools.builders.fsprms import FSEntryParamsExt
|
|
15
|
+
from batchmp.ffmptools.ffutils import FFHDefaults
|
|
16
|
+
from batchmp.ffmptools.ffrunner import LogLevel
|
|
17
|
+
from batchmp.ffmptools.ffcommands.cmdopt import FFmpegCommands
|
|
18
|
+
from batchmp.commons.descriptors import (
|
|
19
|
+
PropertyDescriptor,
|
|
20
|
+
BooleanPropertyDescriptor)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FFEntryParams(FSEntryParamsExt):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FFEntryParamsExt(FFEntryParams):
|
|
29
|
+
target_dir = PropertyDescriptor()
|
|
30
|
+
target_dir_prefix = PropertyDescriptor()
|
|
31
|
+
log_level = PropertyDescriptor()
|
|
32
|
+
serial_exec = BooleanPropertyDescriptor()
|
|
33
|
+
preserve_metadata = BooleanPropertyDescriptor()
|
|
34
|
+
|
|
35
|
+
target_format = PropertyDescriptor()
|
|
36
|
+
ff_general_options = PropertyDescriptor()
|
|
37
|
+
ff_other_options = PropertyDescriptor()
|
|
38
|
+
|
|
39
|
+
def __init__(self, args = {}):
|
|
40
|
+
super().__init__(args)
|
|
41
|
+
self.target_dir = args.get('target_dir')
|
|
42
|
+
self.log_level = args.get('log_level', LogLevel.QUIET)
|
|
43
|
+
self.serial_exec = args.get('serial_exec', False)
|
|
44
|
+
|
|
45
|
+
self.target_format = args.get('target_format')
|
|
46
|
+
self.ff_general_options = args.get('ff_general_options', 0)
|
|
47
|
+
self.ff_other_options = args.get('ffmpeg_options', FFmpegCommands.CONVERT_COPY_VBR_QUALITY)
|
|
48
|
+
self.preserve_metadata = args.get('preserve_metadata', True)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FFEntryParamsSilenceSplit(FFEntryParamsExt):
|
|
53
|
+
reset_timestamps = BooleanPropertyDescriptor()
|
|
54
|
+
silence_auto_duration = BooleanPropertyDescriptor()
|
|
55
|
+
|
|
56
|
+
silence_min_duration = PropertyDescriptor()
|
|
57
|
+
silence_noise_tolerance_amplitude_ratio = PropertyDescriptor()
|
|
58
|
+
silence_target_trimmed_duration = PropertyDescriptor()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def __init__(self, args = {}):
|
|
62
|
+
super().__init__(args)
|
|
63
|
+
self.reset_timestamps = args.get('reset_timestamps', False)
|
|
64
|
+
self.silence_auto_duration = args.get('auto_duration', False)
|
|
65
|
+
|
|
66
|
+
self.silence_noise_tolerance_amplitude_ratio = args.get('noise_tolerance', FFHDefaults.DEFAULT_SILENCE_NOISE_TOLERANCE)
|
|
67
|
+
|
|
68
|
+
min_duration = args.get('min_duration')
|
|
69
|
+
self.silence_min_duration = min_duration.total_seconds() if min_duration else FFHDefaults.DEFAULT_SILENCE_MIN_DURATION
|
|
70
|
+
|
|
71
|
+
trimmed_duration = args.get('trimmed_duration')
|
|
72
|
+
self.silence_target_trimmed_duration = trimmed_duration.total_seconds() if trimmed_duration else FFHDefaults.DEFAULT_SILENCE_TARGET_TRIMMED_DURATION
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
File without changes
|