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,154 @@
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
+ ''' A simple single-line console progress bar
16
+ Displays progress by fractions of 10%
17
+ Supports premature stops & info messages during execution
18
+ '''
19
+ import sys, time
20
+ from bisect import bisect as bs
21
+ import threading, queue
22
+ from contextlib import contextmanager
23
+
24
+
25
+ class CmdProgressBarRefreshRate:
26
+ SLOW = 0.2
27
+ MODERATE = 0.1
28
+ FAST = 0.02
29
+
30
+
31
+ @contextmanager
32
+ def progress_bar(starts_from = 0, refresh_rate = CmdProgressBarRefreshRate.MODERATE):
33
+ ''' Enables usage via a runtime context
34
+ '''
35
+ p_bar = CmdProgressBar(starts_from, refresh_rate)
36
+ p_bar.start()
37
+ try:
38
+ yield p_bar
39
+ finally:
40
+ p_bar.stop()
41
+
42
+
43
+ class CmdProgressBarUpdateTypes:
44
+ UPDATE_PROGRESS = 0
45
+ UPDATE_MSG = 1
46
+
47
+
48
+ class CmdProgressBar(object):
49
+ def __init__(self, start_from=0, refresh_rate = CmdProgressBarRefreshRate.MODERATE):
50
+ self._queue = queue.Queue(1) # used to communicate with the worker thread
51
+ self._end_event = threading.Event() # used to exit
52
+
53
+ self.progress = start_from # access via property, for validation $ enqueuing
54
+ self._info_msg = None
55
+
56
+ self._bar_thread = threading.Thread(target=self._show_progress,
57
+ args=(start_from, refresh_rate, self._end_event, self._queue,))
58
+ self._bar_thread.daemon = True
59
+
60
+ @property
61
+ def info_msg(self):
62
+ return self._info_msg
63
+ @info_msg.setter
64
+ def info_msg(self, value):
65
+ self._queue.put((CmdProgressBarUpdateTypes.UPDATE_MSG, value))
66
+ self._info_msg = value
67
+
68
+ @property
69
+ def progress(self):
70
+ return self._progress
71
+ @progress.setter
72
+ def progress(self, value):
73
+ # unless in stopping mode, set & enqueue
74
+ if not self._end_event.is_set():
75
+ if value < 0: value = 0
76
+ if value > 100: value = 100
77
+ self._queue.put((CmdProgressBarUpdateTypes.UPDATE_PROGRESS, value))
78
+ self._progress = value
79
+
80
+ # the worker thread method
81
+ @staticmethod
82
+ def _show_progress(last_known_progress, refresh_rate, end_event, queue):
83
+ progress_values = [i for i in range(0, 110, 10)] # [0, 10, ..., 100]
84
+ chars = '|/-\\'
85
+ msg = None
86
+ while True:
87
+ if not queue.full():
88
+ # nothing in the queue yet, keep showing the last known progres
89
+ progress = last_known_progress
90
+ else:
91
+ update = queue.get()
92
+ # figure out what kind of update is being requested
93
+ if update[0] == CmdProgressBarUpdateTypes.UPDATE_PROGRESS:
94
+ progress = update[1]
95
+ last_known_progress = progress
96
+ else:
97
+ msg = update[1]
98
+ # signal that the value has been consumed
99
+ queue.task_done()
100
+
101
+ num_progress_vals = bs(progress_values, progress)
102
+ progress_info = '..'.join([''.join((str(i), '%')) for i in progress_values[:num_progress_vals]])
103
+ progress_info = ''.join((progress_info, '.' * (53 - len(progress_info))))
104
+
105
+ # for info msg updates, display the message
106
+ if msg != None:
107
+ sys.stdout.write(''.join(('\r', ' ' * 70, '\r')))
108
+ sys.stdout.write(''.join((msg, '\n')))
109
+ msg = None
110
+
111
+ # show pogress
112
+ for c in chars:
113
+ sys.stdout.write('\r[ {0} ..{1}.. ]'.format(c, progress_info))
114
+ sys.stdout.flush()
115
+ time.sleep(refresh_rate)
116
+
117
+ if end_event.is_set():
118
+ break
119
+
120
+ # starts the worker thread
121
+ def start(self):
122
+ self._bar_thread.start()
123
+
124
+ # stops the worker thread
125
+ # handles premature exits (e.g., when the method is called at 70% progress)
126
+ def stop(self):
127
+ # check if a graceful stop is needed
128
+ if self._progress < 100:
129
+ # looks like a premature exit,
130
+ # set progress to max
131
+ self.progress = 100
132
+
133
+ # wait till the queue is processed
134
+ self._queue.join()
135
+
136
+ # OK to stop now
137
+ self._end_event.set()
138
+ self._bar_thread.join()
139
+ sys.stdout.write(''.join(('\r', ' ' * 70, '\r')))
140
+ sys.stdout.flush()
141
+
142
+ # Quick Dev Test
143
+ if __name__ == '__main__':
144
+ start_from, msg_target, progress_target = 30, 40, 60
145
+ with progress_bar(start_from) as p_bar:
146
+ while True:
147
+ if p_bar.progress == msg_target:
148
+ p_bar.info_msg = 'At {}%, and doing well'.format(msg_target)
149
+ if p_bar.progress == progress_target:
150
+ p_bar.info_msg = 'At {}%, and feel like finishing early'.format(progress_target)
151
+ break
152
+ p_bar.progress += 10
153
+ print('All Done')
154
+
@@ -0,0 +1,149 @@
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 copyreg, types, multiprocessing
16
+ from abc import ABCMeta, abstractmethod
17
+ from batchmp.commons.progressbar import progress_bar, CmdProgressBarRefreshRate
18
+ from batchmp.commons.utils import timed, MiscHelpers
19
+
20
+
21
+ class Task(metaclass = ABCMeta):
22
+ ''' Abstract TasksProcessor task
23
+ '''
24
+ @abstractmethod
25
+ def execute(self):
26
+ return TaskResult()
27
+
28
+
29
+ class TaskResult:
30
+ ''' TasksProcessor Task result
31
+ '''
32
+ def __init__(self):
33
+ self._task_steps_info_msgs = []
34
+ self._task_steps_durations = []
35
+ self._succeeded = False
36
+
37
+ def add_task_step_duration(self, step_duration):
38
+ self._task_steps_durations.append(step_duration)
39
+
40
+ def add_task_step_info_msg(self, step_info_msg):
41
+ self._task_steps_info_msgs.append(step_info_msg)
42
+
43
+ def add_report_msg(self, processed_fpath):
44
+ task_duration_str = MiscHelpers.time_delta_str(self.task_duration)
45
+ self.add_task_step_info_msg('Done processing\n {0}\n in {1}'.format(
46
+ processed_fpath, task_duration_str))
47
+ @property
48
+ def succeeded(self):
49
+ return self._succeeded
50
+ @succeeded.setter
51
+ def succeeded(self, value):
52
+ self._succeeded = value
53
+
54
+ @property
55
+ def task_output(self):
56
+ task_output = None
57
+ for info_msg in self._task_steps_info_msgs:
58
+ task_output = '{0}{1}{2}'.format(task_output if task_output else '',
59
+ '\n' if task_output else '',
60
+ info_msg)
61
+ return task_output
62
+
63
+ @property
64
+ def task_duration(self):
65
+ task_duration = 0.0
66
+ for step_duration in self._task_steps_durations:
67
+ task_duration += step_duration
68
+ return task_duration
69
+
70
+
71
+ class TasksProcessor:
72
+ ''' Runs cmd-line Tasks, sequentially or in a pool of processes
73
+ Displays progress / tasks done
74
+ '''
75
+ def _process_task(self, task):
76
+ result = task.execute()
77
+ return result
78
+
79
+ @timed
80
+ def process_tasks(self, tasks_queue, serial_exec = False, num_workers = None, quiet = False):
81
+ tasks_results = []
82
+ cpu_core_time = 0.0
83
+
84
+ num_tasks = len(tasks_queue)
85
+ serial_exec = serial_exec or num_tasks == 1
86
+ if num_tasks > 0:
87
+ # Pre-processing msgs
88
+ if serial_exec:
89
+ print('Processing {0} {1}'.format(num_tasks,
90
+ 'task' if num_tasks == 1 else 'tasks sequentially'))
91
+ else:
92
+ if not num_workers:
93
+ num_workers = multiprocessing.cpu_count()
94
+ print('Processing {0} tasks with pool of {1} worker processes'.format(num_tasks, num_workers))
95
+
96
+ # start showing progress
97
+ with progress_bar(refresh_rate = CmdProgressBarRefreshRate.MODERATE) as p_bar:
98
+ def _make_progress(result):
99
+ nonlocal cpu_core_time
100
+ tasks_results.append(result)
101
+ if not quiet:
102
+ p_bar.info_msg = result.task_output
103
+ cpu_core_time += result.task_duration
104
+ p_bar.progress = len(tasks_results) / num_tasks * 100
105
+
106
+ if serial_exec:
107
+ # just loop through the queue of tasks
108
+ for task in tasks_queue:
109
+ result = self._process_task(task)
110
+ _make_progress(result)
111
+ else:
112
+ # init the pool and kick it off
113
+ with multiprocessing.Pool(num_workers) as pool:
114
+ for result in pool.imap_unordered(self._process_task, tasks_queue):
115
+ _make_progress(result)
116
+
117
+ # return tasks results, aggregate CPU cores time, and total time elapsed (via @timed)
118
+ return tasks_results, cpu_core_time
119
+
120
+
121
+ """
122
+ Python multiprocessing pickles stuff, and bound methods are not picklable
123
+ The code below serves as a workaround, more at:
124
+ http://bytes.com/topic/python/answers/552476-why-cant-you-pickle-instancemethods#edit2155350
125
+ """
126
+ def _pickle_method(method):
127
+ func_name = method.im_func.__name__
128
+ obj = method.im_self
129
+ cls = method.im_class
130
+
131
+ if func_name.startswith('__') and not func_name.endswith('__'):
132
+ cls_name = cls.__name__.lstrip('_')
133
+ if cls_name:
134
+ func_name = '_' + cls_name + func_name
135
+
136
+ return _unpickle_method, (func_name, obj, cls)
137
+
138
+ def _unpickle_method(func_name, obj, cls):
139
+ for cls in cls.mro():
140
+ try:
141
+ func = cls.__dict__[func_name]
142
+ except KeyError:
143
+ pass
144
+ else:
145
+ break
146
+ return func.__get__(obj, cls)
147
+
148
+ copyreg.pickle(types.MethodType, _pickle_method, _unpickle_method)
149
+
@@ -0,0 +1,194 @@
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
+ import subprocess, shlex, time, tempfile, shutil
15
+ import datetime, math, functools
16
+ from functools import wraps
17
+ from urllib.parse import urlparse
18
+ import urllib.request, urllib.error
19
+ from contextlib import contextmanager
20
+
21
+ ''' General-level utilities
22
+ '''
23
+
24
+ @contextmanager
25
+ def temp_dir():
26
+ ''' Temp dir context manager
27
+ '''
28
+ tmp_dir = tempfile.mkdtemp()
29
+ try:
30
+ yield tmp_dir
31
+ finally:
32
+ # remove tmp dir
33
+ shutil.rmtree(tmp_dir)
34
+
35
+
36
+ def timed(f):
37
+ """ A timing decorator
38
+ """
39
+ @wraps(f)
40
+ def wrapper(*args, **kwds):
41
+ start = time.time()
42
+ result = f(*args, **kwds)
43
+ elapsed = time.time() - start
44
+ return (result, elapsed)
45
+ return wrapper
46
+
47
+
48
+ class CmdProcessingError(Exception):
49
+ pass
50
+
51
+ @timed
52
+ def run_cmd(cmd, shell = False):
53
+ ''' Runs shell commands in a separate process
54
+ '''
55
+ if not shell:
56
+ cmd = shlex.split(cmd)
57
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell = shell)
58
+ output = proc.communicate()[0].decode('utf-8')
59
+ if proc.returncode != 0:
60
+ raise CmdProcessingError(output)
61
+ return output
62
+
63
+
64
+ class MiscHelpers:
65
+ @staticmethod
66
+ def int_num_digits(num):
67
+ ''' Number of digits in an int number
68
+ '''
69
+ if not isinstance(num, int):
70
+ try:
71
+ num = int(num)
72
+ except ValueError as e:
73
+ return 0
74
+ num_digits = 1
75
+ while (int(abs(num)/(10**num_digits)) > 0):
76
+ num_digits += 1
77
+ return num_digits
78
+
79
+ @staticmethod
80
+ def time_delta(td_str):
81
+ ''' Timedelta from the "hh:mm:ss[.xxx]" format
82
+ '''
83
+ hrs = mins = secs = None
84
+ td = td_str.split(':')
85
+ time_parts = range(len(td))
86
+ for _ in time_parts:
87
+ if secs is None:
88
+ secs = float(td.pop(-1))
89
+ elif mins is None:
90
+ mins = int(td.pop(-1))
91
+ elif hrs is None:
92
+ hrs = int(td.pop(-1))
93
+ else:
94
+ break
95
+ return datetime.timedelta(hours = hrs if hrs else 0,
96
+ minutes = mins if mins else 0,
97
+ seconds = secs if secs else 0)
98
+
99
+ @staticmethod
100
+ def time_delta_str(secs, num_miliseconds = 2):
101
+ ''' Timedelta string with specified number of miliseconds
102
+ '''
103
+ div = 10**num_miliseconds if num_miliseconds > 0 else 1
104
+ td_str = str(datetime.timedelta(seconds = math.ceil(secs*div)/div)).rstrip('0')
105
+ if td_str.endswith(':'):
106
+ td_str = '{}00'.format(td_str)
107
+ return td_str
108
+
109
+ @staticmethod
110
+ def percentile(values, percent):
111
+ '''
112
+ Find the percentile for a list of values
113
+ '''
114
+ if not values:
115
+ return None
116
+ else:
117
+ values.sort()
118
+
119
+ dv = (len(values) - 1) * percent / 100
120
+ floor = math.floor(dv)
121
+
122
+ ceil = math.ceil(dv)
123
+ if floor == ceil:
124
+ return values[int(dv)]
125
+
126
+ dc = values[int(floor)] * (ceil - dv)
127
+ dt = values[int(ceil)] * (dv - floor)
128
+
129
+ return dc + dt
130
+
131
+ @staticmethod
132
+ def median(l):
133
+ # the 50th percentile
134
+ return functools.partial(MiscHelpers.percentile, percent=50)(l)
135
+
136
+
137
+ class ImageLoader:
138
+ @staticmethod
139
+ def load_image_from_url(url):
140
+ ''' Loads image from an URL
141
+ '''
142
+ img = None
143
+ try:
144
+ responce = urllib.request.urlopen(url, timeout = 5)
145
+ except urllib.error.URLError as e:
146
+ print('A problem while retrieving image: "{}"'.format(e))
147
+ else:
148
+ content_type = dict(responce.getheaders())['Content-Type']
149
+ if content_type.split('/')[0] != 'image':
150
+ print('URL did not seem to return a valid image')
151
+ print('Received Content-Type: {}'.format(content_type))
152
+ else:
153
+ img = responce.read()
154
+
155
+ return img
156
+
157
+ @staticmethod
158
+ def load_image_from_file(fpath):
159
+ ''' Loads an image from disk via file path
160
+ '''
161
+ img = None
162
+ if fpath:
163
+ with open(fpath, 'rb') as f:
164
+ img = f.read()
165
+
166
+ return img
167
+
168
+ @staticmethod
169
+ def load_image(path_or_url):
170
+ ''' Loads an image from an URL or a file path
171
+ '''
172
+ url_parts = urlparse(path_or_url)
173
+ if url_parts.scheme in (None, '') and url_parts.netloc in (None, ''):
174
+ return ImageLoader.load_image_from_file(path_or_url)
175
+
176
+ if url_parts.scheme == 'file':
177
+ fpath = format(url_parts.path)
178
+ if url_parts.netloc == '~':
179
+ fpath = '~{}'.format(fpath)
180
+ return ImageLoader.load_image_from_file(path_or_url)
181
+
182
+ return ImageLoader.load_image_from_url(path_or_url)
183
+
184
+
185
+ # Quick dev test
186
+ if __name__ == '__main__':
187
+ td = MiscHelpers.time_delta('00:24.5764654645464')
188
+ print(td)
189
+ print(MiscHelpers.time_delta_str(td.total_seconds(), num_miliseconds = -1))
190
+
191
+ a = [2.08, 2.11, 2.18, 2.18, 2.2, 2.2, 2.21, 2.21, 2.3, 2.8, 2.97, 3.21, 3.4, 3.71, 3.8, 3.9, 4.21, 4.3, 4.4]
192
+ print(MiscHelpers.median(a))
193
+
194
+ print(MiscHelpers.percentile(a, 25))
File without changes
File without changes
@@ -0,0 +1,115 @@
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
+
18
+ class FFmpegCommands:
19
+ ''' Common FFmpeg commands / options
20
+ '''
21
+ MAP_ALL_STREAMS = ' -map 0'
22
+ COPY_CODECS = ' -c copy'
23
+
24
+ # queue size
25
+ MUXING_QUEUE_SIZE = ' -max_muxing_queue_size 1024'
26
+
27
+ # excluding streams
28
+ DISABLE_VIDEO = ' -vn'
29
+ DISABLE_AUDIO = ' -an'
30
+ DISABLE_SUBTITLES = ' -sn'
31
+
32
+ # Conversion options
33
+ CONVERT_COPY_VBR_QUALITY = ' -q:v 0 -q:a 0'
34
+ CONVERT_LOSSLESS = ' CONVERT_LOSSLESS_IF_POSSIBLE'
35
+ CONVERT_LOSSLESS_ALAC = ' -q:v 0 -acodec alac'
36
+ CONVERT_LOSSLESS_FLAC = ' -q:v 0 -acodec flac'
37
+ CONVERT_CHANGE_CONTAINER = ' -c copy -copyts'
38
+
39
+ # Log level
40
+ LOG_LEVEL_ERROR = ' -v error'
41
+ LOG_LEVEL_QUIET = ' -v quiet'
42
+
43
+ # Segment
44
+ SEGMENT = ' -f segment'
45
+ SEGMENT_TIME = ' -segment_time'
46
+ SEGMENT_TIMES = ' -segment_times'
47
+ SEGMENT_RESET_TIMESTAMPS = ' -reset_timestamps 1'
48
+
49
+ @staticmethod
50
+ def exclude_input_stream(stream_idx):
51
+ return ' -map -0:{}'.format(stream_idx)
52
+
53
+ @staticmethod
54
+ def include_input_stream(stream_idx):
55
+ return ' -map 0:{}'.format(stream_idx)
56
+
57
+
58
+ class FFmpegBitMaskOptions(IntEnum):
59
+ ''' FFmpeg commands / options bitmasks
60
+ '''
61
+ MAP_ALL_STREAMS = (1<<0)
62
+ COPY_CODECS = (1<<1)
63
+
64
+ MUXING_QUEUE_SIZE = (1<<4)
65
+
66
+ DISABLE_VIDEO = (1<<5)
67
+ DISABLE_AUDIO = (1<<6)
68
+ DISABLE_SUBTITLES = (1<<7)
69
+
70
+ @classmethod
71
+ def ff_general_options(cls, ff_gbm_options):
72
+ options_str = ''
73
+ if ff_gbm_options:
74
+ for bm_option in cls:
75
+ if bm_option & ff_gbm_options == bm_option:
76
+ options_str = ''.join((options_str, cls._option_str_value(bm_option)))
77
+ return options_str
78
+
79
+ @staticmethod
80
+ def _option_str_value(bm_option):
81
+ if bm_option == FFmpegBitMaskOptions.MAP_ALL_STREAMS:
82
+ return FFmpegCommands.MAP_ALL_STREAMS
83
+ elif bm_option == FFmpegBitMaskOptions.COPY_CODECS:
84
+ return FFmpegCommands.COPY_CODECS
85
+
86
+ elif bm_option == FFmpegBitMaskOptions.DISABLE_VIDEO:
87
+ return FFmpegCommands.DISABLE_VIDEO
88
+ elif bm_option == FFmpegBitMaskOptions.DISABLE_AUDIO:
89
+ return FFmpegCommands.DISABLE_AUDIO
90
+ elif bm_option == FFmpegBitMaskOptions.DISABLE_SUBTITLES:
91
+ return FFmpegCommands.DISABLE_SUBTITLES
92
+ elif bm_option == FFmpegBitMaskOptions.MUXING_QUEUE_SIZE:
93
+ return FFmpegCommands.MUXING_QUEUE_SIZE
94
+
95
+ else:
96
+ return ''
97
+
98
+ # Quick dev test
99
+ if __name__ == '__main__':
100
+ copy_codecs = True
101
+ options = 0
102
+ options |= FFmpegBitMaskOptions.DISABLE_SUBTITLES
103
+ if copy_codecs:
104
+ options |= FFmpegBitMaskOptions.COPY_CODECS
105
+ options |= FFmpegBitMaskOptions.DISABLE_VIDEO
106
+ options |= FFmpegBitMaskOptions.MUXING_QUEUE_SIZE
107
+ print(FFmpegBitMaskOptions.ff_general_options(options))
108
+
109
+ exclude_stream = 1
110
+ print('exclude video stream {}: {}'.format(exclude_stream, FFmpegCommands.exclude_input_stream(exclude_stream)))
111
+
112
+
113
+
114
+
115
+