deffcode 0.2.4__py3-none-any.whl → 0.2.6__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.
- deffcode/ffdecoder.py +90 -33
- deffcode/ffhelper.py +27 -18
- deffcode/sourcer.py +90 -29
- deffcode/version.py +1 -1
- deffcode-0.2.6.dist-info/METADATA +492 -0
- deffcode-0.2.6.dist-info/RECORD +11 -0
- {deffcode-0.2.4.dist-info → deffcode-0.2.6.dist-info}/WHEEL +1 -1
- deffcode-0.2.4.dist-info/METADATA +0 -491
- deffcode-0.2.4.dist-info/RECORD +0 -11
- {deffcode-0.2.4.dist-info → deffcode-0.2.6.dist-info}/LICENSE +0 -0
- {deffcode-0.2.4.dist-info → deffcode-0.2.6.dist-info}/top_level.txt +0 -0
deffcode/ffdecoder.py
CHANGED
|
@@ -19,6 +19,7 @@ limitations under the License.
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
# import the necessary packages
|
|
22
|
+
import platform
|
|
22
23
|
import logging
|
|
23
24
|
import numpy as np
|
|
24
25
|
import subprocess as sp
|
|
@@ -106,10 +107,16 @@ class FFdecoder:
|
|
|
106
107
|
# handles user-defined parameters
|
|
107
108
|
self.__extra_params = {}
|
|
108
109
|
|
|
109
|
-
#
|
|
110
|
+
# handles process to be frames written
|
|
110
111
|
self.__process = None
|
|
111
112
|
|
|
112
|
-
#
|
|
113
|
+
# handles disabling window for ffmpeg subprocess on Windows
|
|
114
|
+
self.__ffmpeg_window_disabler_patch = False
|
|
115
|
+
|
|
116
|
+
# checks machine OS
|
|
117
|
+
self.__machine_OS = platform.system()
|
|
118
|
+
|
|
119
|
+
# handles exclusive metadata
|
|
113
120
|
self.__ff_pixfmt_metadata = None # metadata
|
|
114
121
|
self.__raw_frame_num = None # raw-frame number
|
|
115
122
|
self.__raw_frame_pixfmt = None # raw-frame pixformat
|
|
@@ -127,23 +134,26 @@ class FFdecoder:
|
|
|
127
134
|
# operation mode variable
|
|
128
135
|
self.__opmode = None
|
|
129
136
|
|
|
130
|
-
#
|
|
137
|
+
# handles termination
|
|
131
138
|
self.__terminate_stream = False
|
|
132
139
|
|
|
133
140
|
# cleans and reformat user-defined parameters
|
|
134
141
|
self.__extra_params = {
|
|
135
|
-
str(k).strip():
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
str(k).strip(): (
|
|
143
|
+
str(v).strip()
|
|
144
|
+
if not (v is None)
|
|
145
|
+
and not isinstance(v, (dict, list, int, float, tuple))
|
|
146
|
+
else v
|
|
147
|
+
)
|
|
138
148
|
for k, v in ffparams.items()
|
|
139
149
|
}
|
|
140
150
|
|
|
141
|
-
#
|
|
151
|
+
# handles custom Sourcer API params
|
|
142
152
|
sourcer_params = self.__extra_params.pop("-custom_sourcer_params", {})
|
|
143
153
|
# reset improper values
|
|
144
154
|
sourcer_params = {} if not isinstance(sourcer_params, dict) else sourcer_params
|
|
145
155
|
|
|
146
|
-
#
|
|
156
|
+
# handles user ffmpeg pre-headers(parameters such as `-re`) parameters (must be a list)
|
|
147
157
|
self.__ffmpeg_prefixes = self.__extra_params.pop("-ffprefixes", [])
|
|
148
158
|
# check if not valid type
|
|
149
159
|
if not isinstance(self.__ffmpeg_prefixes, list):
|
|
@@ -199,6 +209,35 @@ class FFdecoder:
|
|
|
199
209
|
# handle valid FFmpeg assets location
|
|
200
210
|
self.__ffmpeg = self.__sourcer_metadata["ffmpeg_binary_path"]
|
|
201
211
|
|
|
212
|
+
# handle YUV pixel formats(such as `yuv420p`, `yuv444p`, `nv12`, `nv21` etc.)
|
|
213
|
+
# patch for compatibility with OpenCV APIs.
|
|
214
|
+
self.__cv_patch = self.__extra_params.pop("-enforce_cv_patch", False)
|
|
215
|
+
if not (isinstance(self.__cv_patch, bool)):
|
|
216
|
+
self.__cv_patch = False
|
|
217
|
+
self.__verbose_logs and logger.critical(
|
|
218
|
+
"Enforcing OpenCV compatibility patch for YUV/NV video frames."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# handle disabling window for ffmpeg subprocess on Windows OS
|
|
222
|
+
# this patch prevents ffmpeg creation window from opening when
|
|
223
|
+
# building exe files
|
|
224
|
+
ffmpeg_window_disabler_patch = self.__extra_params.pop(
|
|
225
|
+
"-disable_ffmpeg_window", False
|
|
226
|
+
)
|
|
227
|
+
if ffmpeg_window_disabler_patch and isinstance(
|
|
228
|
+
ffmpeg_window_disabler_patch, bool
|
|
229
|
+
):
|
|
230
|
+
# check if value is valid
|
|
231
|
+
if self.__machine_OS != "Windows" or self.__verbose_logs:
|
|
232
|
+
logger.warning(
|
|
233
|
+
"Optional `-disable_ffmpeg_window` flag is only available on Windows OS with `verbose=False`. Discarding!"
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
self.__ffmpeg_window_disabler_patch = ffmpeg_window_disabler_patch
|
|
237
|
+
else:
|
|
238
|
+
# handle improper values
|
|
239
|
+
self.__ffmpeg_window_disabler_patch = False
|
|
240
|
+
|
|
202
241
|
# handle pass-through audio mode works in conjunction with WriteGear [TODO]
|
|
203
242
|
self.__passthrough_mode = self.__extra_params.pop("-passthrough_audio", False)
|
|
204
243
|
if not (isinstance(self.__passthrough_mode, bool)):
|
|
@@ -279,7 +318,6 @@ class FFdecoder:
|
|
|
279
318
|
self.__custom_resolution = None
|
|
280
319
|
|
|
281
320
|
def formulate(self):
|
|
282
|
-
|
|
283
321
|
"""
|
|
284
322
|
This method formulates all necessary FFmpeg pipeline arguments and executes it inside the FFmpeg `subprocess` pipe.
|
|
285
323
|
|
|
@@ -411,13 +449,15 @@ class FFdecoder:
|
|
|
411
449
|
else:
|
|
412
450
|
logger.warning(
|
|
413
451
|
"{} Switching to default `{}` pixel-format!".format(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
452
|
+
(
|
|
453
|
+
"Provided FFmpeg does not supports `{}` pixel-format.".format(
|
|
454
|
+
self.__sourcer_metadata["output_frames_pixfmt"]
|
|
455
|
+
if "output_frames_pixfmt" in self.__sourcer_metadata
|
|
456
|
+
else self.__frame_format
|
|
457
|
+
)
|
|
458
|
+
if self.__frame_format != "null"
|
|
459
|
+
else "No usable pixel-format defined."
|
|
460
|
+
),
|
|
421
461
|
default_pixfmt,
|
|
422
462
|
)
|
|
423
463
|
)
|
|
@@ -428,7 +468,9 @@ class FFdecoder:
|
|
|
428
468
|
for x in self.__ff_pixfmt_metadata
|
|
429
469
|
if x[0] == rawframe_pixfmt
|
|
430
470
|
][0]
|
|
431
|
-
raw_bit_per_component =
|
|
471
|
+
raw_bit_per_component = (
|
|
472
|
+
rawframesbpp // self.__raw_frame_depth if self.__raw_frame_depth else 0
|
|
473
|
+
)
|
|
432
474
|
if 4 <= raw_bit_per_component <= 8:
|
|
433
475
|
self.__raw_frame_dtype = np.dtype("u1")
|
|
434
476
|
elif 8 < raw_bit_per_component <= 16 and rawframe_pixfmt.endswith(
|
|
@@ -456,9 +498,9 @@ class FFdecoder:
|
|
|
456
498
|
self.__raw_frame_pixfmt = rawframe_pixfmt
|
|
457
499
|
# also override as metadata(if available)
|
|
458
500
|
if "output_frames_pixfmt" in self.__sourcer_metadata:
|
|
459
|
-
self.__sourcer_metadata[
|
|
460
|
-
|
|
461
|
-
|
|
501
|
+
self.__sourcer_metadata["output_frames_pixfmt"] = (
|
|
502
|
+
self.__raw_frame_pixfmt
|
|
503
|
+
)
|
|
462
504
|
|
|
463
505
|
# handle raw-frame resolution
|
|
464
506
|
# notify FFmpeg `-s` parameter cannot be assigned directly
|
|
@@ -598,7 +640,7 @@ class FFdecoder:
|
|
|
598
640
|
self.__raw_frame_num = None
|
|
599
641
|
# log that number of frames are unknown
|
|
600
642
|
self.__verbose_logs and logger.info(
|
|
601
|
-
"
|
|
643
|
+
"Number of frames in given source are unknown. Live/Network/Looping stream detected!"
|
|
602
644
|
)
|
|
603
645
|
|
|
604
646
|
# log Mode of Operation
|
|
@@ -626,11 +668,15 @@ class FFdecoder:
|
|
|
626
668
|
self.__process is None
|
|
627
669
|
), "Pipeline is not running! You must call `formulate()` method first."
|
|
628
670
|
|
|
629
|
-
# formulated raw frame size
|
|
671
|
+
# formulated raw frame size and apply YUV pixel formats patch(if applicable)
|
|
630
672
|
raw_frame_size = (
|
|
631
|
-
self.
|
|
632
|
-
|
|
633
|
-
|
|
673
|
+
(self.__raw_frame_resolution[0] * (self.__raw_frame_resolution[1] * 3 // 2))
|
|
674
|
+
if self.__raw_frame_pixfmt.startswith(("yuv", "nv")) and self.__cv_patch
|
|
675
|
+
else (
|
|
676
|
+
self.__raw_frame_depth
|
|
677
|
+
* self.__raw_frame_resolution[0]
|
|
678
|
+
* self.__raw_frame_resolution[1]
|
|
679
|
+
)
|
|
634
680
|
)
|
|
635
681
|
# next dataframe as numpy ndarray
|
|
636
682
|
nparray = None
|
|
@@ -668,6 +714,12 @@ class FFdecoder:
|
|
|
668
714
|
self.__raw_frame_depth,
|
|
669
715
|
)
|
|
670
716
|
)[:, :, 0]
|
|
717
|
+
elif self.__raw_frame_pixfmt.startswith(("yuv", "nv")) and self.__cv_patch:
|
|
718
|
+
# reconstruct exclusive YUV formats frames for OpenCV APIs
|
|
719
|
+
frame = frame.reshape(
|
|
720
|
+
self.__raw_frame_resolution[1] * 3 // 2,
|
|
721
|
+
self.__raw_frame_resolution[0],
|
|
722
|
+
)
|
|
671
723
|
else:
|
|
672
724
|
# reconstruct default frames
|
|
673
725
|
frame = frame.reshape(
|
|
@@ -783,9 +835,11 @@ class FFdecoder:
|
|
|
783
835
|
self.__verbose_logs and logger.info(
|
|
784
836
|
"Updating `{}`{} metadata property to `{}`.".format(
|
|
785
837
|
key,
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
838
|
+
(
|
|
839
|
+
" and its counterpart"
|
|
840
|
+
if key in counterpart_prop.values()
|
|
841
|
+
else ""
|
|
842
|
+
),
|
|
789
843
|
value[key],
|
|
790
844
|
)
|
|
791
845
|
)
|
|
@@ -817,7 +871,6 @@ class FFdecoder:
|
|
|
817
871
|
raise ValueError("Invalid datatype metadata assigned. Aborting!")
|
|
818
872
|
|
|
819
873
|
def __launch_FFdecoderline(self, input_params, output_params):
|
|
820
|
-
|
|
821
874
|
"""
|
|
822
875
|
This Internal method executes FFmpeg pipeline arguments inside a `subprocess` pipe in a new process.
|
|
823
876
|
|
|
@@ -856,7 +909,13 @@ class FFdecoder:
|
|
|
856
909
|
else:
|
|
857
910
|
# In silent mode
|
|
858
911
|
self.__process = sp.Popen(
|
|
859
|
-
cmd,
|
|
912
|
+
cmd,
|
|
913
|
+
stdin=sp.DEVNULL,
|
|
914
|
+
stdout=sp.PIPE,
|
|
915
|
+
stderr=sp.DEVNULL,
|
|
916
|
+
creationflags=( # this prevents ffmpeg creation window from opening when building exe files on Windows
|
|
917
|
+
sp.DETACHED_PROCESS if self.__ffmpeg_window_disabler_patch else 0
|
|
918
|
+
),
|
|
860
919
|
)
|
|
861
920
|
|
|
862
921
|
def terminate(self):
|
|
@@ -877,9 +936,7 @@ class FFdecoder:
|
|
|
877
936
|
# close `stdout` output
|
|
878
937
|
self.__process.stdout and self.__process.stdout.close()
|
|
879
938
|
# terminate/kill process if still processing
|
|
880
|
-
|
|
881
|
-
# demuxers prefer kill
|
|
882
|
-
self.__process.kill()
|
|
939
|
+
self.__process.poll() is None and self.__process.terminate()
|
|
883
940
|
# wait if not exiting
|
|
884
941
|
self.__process.wait()
|
|
885
942
|
self.__process = None
|
deffcode/ffhelper.py
CHANGED
|
@@ -27,8 +27,7 @@ import subprocess as sp
|
|
|
27
27
|
|
|
28
28
|
from tqdm import tqdm
|
|
29
29
|
from pathlib import Path
|
|
30
|
-
from requests.adapters import HTTPAdapter
|
|
31
|
-
from requests.packages.urllib3.util.retry import Retry
|
|
30
|
+
from requests.adapters import HTTPAdapter, Retry
|
|
32
31
|
|
|
33
32
|
# import utils packages
|
|
34
33
|
from .utils import logger_handler, delete_file_safe
|
|
@@ -48,7 +47,7 @@ class TimeoutHTTPAdapter(HTTPAdapter):
|
|
|
48
47
|
A custom Transport Adapter with default timeouts
|
|
49
48
|
"""
|
|
50
49
|
|
|
51
|
-
def __init__(self, *args, **kwargs):
|
|
50
|
+
def __init__(self, *args, **kwargs):
|
|
52
51
|
self.timeout = DEFAULT_TIMEOUT
|
|
53
52
|
if "timeout" in kwargs:
|
|
54
53
|
self.timeout = kwargs["timeout"]
|
|
@@ -215,7 +214,11 @@ def download_ffmpeg_binaries(path, os_windows=False, os_bit=""):
|
|
|
215
214
|
http.mount("https://", adapter)
|
|
216
215
|
response = http.get(file_url, stream=True)
|
|
217
216
|
response.raise_for_status()
|
|
218
|
-
total_length =
|
|
217
|
+
total_length = (
|
|
218
|
+
response.headers.get("content-length")
|
|
219
|
+
if "content-length" in response.headers
|
|
220
|
+
else len(response.content)
|
|
221
|
+
)
|
|
219
222
|
assert not (
|
|
220
223
|
total_length is None
|
|
221
224
|
), "[Helper:ERROR] :: Failed to retrieve files, check your Internet connectivity!"
|
|
@@ -339,14 +342,15 @@ def get_supported_demuxers(path):
|
|
|
339
342
|
|
|
340
343
|
**Returns:** List of supported demuxers.
|
|
341
344
|
"""
|
|
345
|
+
# extract and clean FFmpeg output
|
|
342
346
|
demuxers = check_sp_output([path, "-hide_banner", "-demuxers"])
|
|
343
347
|
splitted = [x.decode("utf-8").strip() for x in demuxers.split(b"\n")]
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
outputs =
|
|
349
|
-
# return output
|
|
348
|
+
split_index = [idx for idx, s in enumerate(splitted) if "--" in s][0]
|
|
349
|
+
supported_demuxers = splitted[split_index + 1 : len(splitted) - 1]
|
|
350
|
+
# search all demuxers
|
|
351
|
+
outputs = [re.search(r"\s[a-z0-9_,-]{2,}\s", d) for d in supported_demuxers]
|
|
352
|
+
outputs = [o.group(0) for o in outputs if o]
|
|
353
|
+
# return demuxers output
|
|
350
354
|
return [o.strip() if not ("," in o) else o.split(",")[-1].strip() for o in outputs]
|
|
351
355
|
|
|
352
356
|
|
|
@@ -515,10 +519,12 @@ def extract_device_n_demuxer(path, machine_OS=None, verbose=False):
|
|
|
515
519
|
logger.info(
|
|
516
520
|
"[{}]: {}".format(
|
|
517
521
|
idx,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
+
(
|
|
523
|
+
dev
|
|
524
|
+
if machine_OS != "Linux"
|
|
525
|
+
else "{} at path `{}`".format(
|
|
526
|
+
next(iter(dev.values()))[0], next(iter(dev.keys()))
|
|
527
|
+
)
|
|
522
528
|
),
|
|
523
529
|
)
|
|
524
530
|
)
|
|
@@ -629,8 +635,11 @@ def is_valid_url(path, url=None, verbose=False):
|
|
|
629
635
|
protocols = check_sp_output([path, "-hide_banner", "-protocols"])
|
|
630
636
|
splitted = [x.decode("utf-8").strip() for x in protocols.split(b"\n")]
|
|
631
637
|
supported_protocols = splitted[splitted.index("Output:") + 1 : len(splitted) - 1]
|
|
632
|
-
#
|
|
633
|
-
|
|
638
|
+
# RTSP is a demuxer somehow
|
|
639
|
+
# support both RTSP and RTSPS(over SSL)
|
|
640
|
+
supported_protocols += (
|
|
641
|
+
["rtsp", "rtsps"] if "rtsp" in get_supported_demuxers(path) else []
|
|
642
|
+
)
|
|
634
643
|
# Test and return result whether scheme is supported
|
|
635
644
|
if extracted_scheme_url and extracted_scheme_url in supported_protocols:
|
|
636
645
|
verbose and logger.debug(
|
|
@@ -674,7 +683,7 @@ def check_sp_output(*args, **kwargs):
|
|
|
674
683
|
retcode = process.poll()
|
|
675
684
|
# handle return code
|
|
676
685
|
if retcode and not (retrieve_stderr):
|
|
677
|
-
logger.error("[
|
|
686
|
+
logger.error("[Pipeline-Error] :: {}".format(output.decode("utf-8")))
|
|
678
687
|
cmd = kwargs.get("args")
|
|
679
688
|
if cmd is None:
|
|
680
689
|
cmd = args[0]
|
|
@@ -683,7 +692,7 @@ def check_sp_output(*args, **kwargs):
|
|
|
683
692
|
raise error
|
|
684
693
|
# raise error if no output
|
|
685
694
|
bool(output) or bool(stderr) or logger.error(
|
|
686
|
-
"[
|
|
695
|
+
"[Pipeline-Error] :: Pipeline failed to exact any data from command: {}!".format(
|
|
687
696
|
args[0] if args else []
|
|
688
697
|
)
|
|
689
698
|
)
|
deffcode/sourcer.py
CHANGED
|
@@ -87,7 +87,7 @@ class Sourcer:
|
|
|
87
87
|
verbose (bool): enables/disables verbose.
|
|
88
88
|
sourcer_params (dict): provides the flexibility to control supported internal and FFmpeg parameters.
|
|
89
89
|
"""
|
|
90
|
-
# checks
|
|
90
|
+
# checks machine OS
|
|
91
91
|
self.__machine_OS = platform.system()
|
|
92
92
|
|
|
93
93
|
# define internal parameters
|
|
@@ -100,9 +100,11 @@ class Sourcer:
|
|
|
100
100
|
|
|
101
101
|
# sanitize sourcer_params
|
|
102
102
|
self.__sourcer_params = {
|
|
103
|
-
str(k).strip():
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
str(k).strip(): (
|
|
104
|
+
str(v).strip()
|
|
105
|
+
if not isinstance(v, (dict, list, int, float, tuple))
|
|
106
|
+
else v
|
|
107
|
+
)
|
|
106
108
|
for k, v in sourcer_params.items()
|
|
107
109
|
}
|
|
108
110
|
|
|
@@ -199,6 +201,7 @@ class Sourcer:
|
|
|
199
201
|
|
|
200
202
|
# various source stream params
|
|
201
203
|
self.__default_video_resolution = "" # handles stream resolution
|
|
204
|
+
self.__default_video_orientation = "" # handles stream's video orientation
|
|
202
205
|
self.__default_video_framerate = "" # handles stream framerate
|
|
203
206
|
self.__default_video_bitrate = "" # handles stream's video bitrate
|
|
204
207
|
self.__default_video_pixfmt = "" # handles stream's video pixfmt
|
|
@@ -218,6 +221,7 @@ class Sourcer:
|
|
|
218
221
|
self.__output_frames_resolution = "" # handles output stream resolution
|
|
219
222
|
self.__output_framerate = "" # handles output stream framerate
|
|
220
223
|
self.__output_frames_pixfmt = "" # handles output frame pixel format
|
|
224
|
+
self.__output_orientation = "" # handles output frame orientation
|
|
221
225
|
|
|
222
226
|
# check whether metadata probed or not?
|
|
223
227
|
self.__metadata_probed = False
|
|
@@ -251,6 +255,7 @@ class Sourcer:
|
|
|
251
255
|
if video_rfparams:
|
|
252
256
|
self.__default_video_resolution = video_rfparams["resolution"]
|
|
253
257
|
self.__default_video_framerate = video_rfparams["framerate"]
|
|
258
|
+
self.__default_video_orientation = video_rfparams["orientation"]
|
|
254
259
|
|
|
255
260
|
# parse output parameters through filters (if available)
|
|
256
261
|
if not (self.__metadata_output is None):
|
|
@@ -261,6 +266,7 @@ class Sourcer:
|
|
|
261
266
|
if out_video_rfparams:
|
|
262
267
|
self.__output_frames_resolution = out_video_rfparams["resolution"]
|
|
263
268
|
self.__output_framerate = out_video_rfparams["framerate"]
|
|
269
|
+
self.__output_orientation = out_video_rfparams["orientation"]
|
|
264
270
|
# parse output pixel-format
|
|
265
271
|
self.__output_frames_pixfmt = self.__extract_video_pixfmt(
|
|
266
272
|
default_stream=default_stream_indexes[0], extract_output=True
|
|
@@ -357,11 +363,18 @@ class Sourcer:
|
|
|
357
363
|
"source_video_resolution": self.__default_video_resolution,
|
|
358
364
|
"source_video_pixfmt": self.__default_video_pixfmt,
|
|
359
365
|
"source_video_framerate": self.__default_video_framerate,
|
|
366
|
+
"source_video_orientation": self.__default_video_orientation,
|
|
360
367
|
"source_video_decoder": self.__default_video_decoder,
|
|
361
368
|
"source_duration_sec": self.__default_source_duration,
|
|
362
369
|
"approx_video_nframes": (
|
|
363
370
|
int(self.__approx_video_nframes)
|
|
364
371
|
if self.__approx_video_nframes
|
|
372
|
+
and not any(
|
|
373
|
+
"loop" in x for x in self.__ffmpeg_prefixes
|
|
374
|
+
) # check if any loops in prefix
|
|
375
|
+
and not any(
|
|
376
|
+
"loop" in x for x in dict2Args(self.__sourcer_params)
|
|
377
|
+
) # check if any loops in filters
|
|
365
378
|
else None
|
|
366
379
|
),
|
|
367
380
|
"source_video_bitrate": self.__default_video_bitrate,
|
|
@@ -379,6 +392,7 @@ class Sourcer:
|
|
|
379
392
|
"output_frames_resolution": self.__output_frames_resolution,
|
|
380
393
|
"output_frames_pixfmt": self.__output_frames_pixfmt,
|
|
381
394
|
"output_framerate": self.__output_framerate,
|
|
395
|
+
"output_orientation": self.__output_orientation,
|
|
382
396
|
}
|
|
383
397
|
)
|
|
384
398
|
else:
|
|
@@ -391,6 +405,7 @@ class Sourcer:
|
|
|
391
405
|
"output_frames_resolution": self.__default_video_resolution,
|
|
392
406
|
"output_frames_pixfmt": self.__default_video_pixfmt,
|
|
393
407
|
"output_framerate": self.__default_video_framerate,
|
|
408
|
+
"output_orientation": self.__default_video_orientation,
|
|
394
409
|
}
|
|
395
410
|
)
|
|
396
411
|
# log it
|
|
@@ -483,14 +498,18 @@ class Sourcer:
|
|
|
483
498
|
self.__source_demuxer = source_demuxer
|
|
484
499
|
self.__verbose_logs and logger.debug(
|
|
485
500
|
"Successfully configured device `{}` at index `{}` with demuxer `{}`.".format(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
501
|
+
(
|
|
502
|
+
self.__extracted_devices_list[index]
|
|
503
|
+
if self.__machine_OS != "Linux"
|
|
504
|
+
else next(
|
|
505
|
+
iter(self.__extracted_devices_list[index].values())
|
|
506
|
+
)[0]
|
|
507
|
+
),
|
|
508
|
+
(
|
|
509
|
+
index
|
|
510
|
+
if index >= 0
|
|
511
|
+
else len(self.__extracted_devices_list) + index
|
|
512
|
+
),
|
|
494
513
|
self.__source_demuxer,
|
|
495
514
|
)
|
|
496
515
|
)
|
|
@@ -590,9 +609,11 @@ class Sourcer:
|
|
|
590
609
|
]
|
|
591
610
|
if video_bitrate_text:
|
|
592
611
|
selected_stream = video_bitrate_text[
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
612
|
+
(
|
|
613
|
+
default_stream
|
|
614
|
+
if default_stream > 0 and default_stream < len(video_bitrate_text)
|
|
615
|
+
else 0
|
|
616
|
+
)
|
|
596
617
|
]
|
|
597
618
|
filtered_bitrate = re.findall(
|
|
598
619
|
r",\s[0-9]+\s\w\w[\/]s", selected_stream.strip()
|
|
@@ -624,9 +645,11 @@ class Sourcer:
|
|
|
624
645
|
]
|
|
625
646
|
if meta_text:
|
|
626
647
|
selected_stream = meta_text[
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
648
|
+
(
|
|
649
|
+
default_stream
|
|
650
|
+
if default_stream > 0 and default_stream < len(meta_text)
|
|
651
|
+
else 0
|
|
652
|
+
)
|
|
630
653
|
]
|
|
631
654
|
filtered_pixfmt = re.findall(
|
|
632
655
|
r"Video:\s[a-z0-9_-]*", selected_stream.strip()
|
|
@@ -660,9 +683,11 @@ class Sourcer:
|
|
|
660
683
|
)
|
|
661
684
|
if meta_text:
|
|
662
685
|
selected_stream = meta_text[
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
686
|
+
(
|
|
687
|
+
default_stream
|
|
688
|
+
if default_stream > 0 and default_stream < len(meta_text)
|
|
689
|
+
else 0
|
|
690
|
+
)
|
|
666
691
|
]
|
|
667
692
|
filtered_pixfmt = re.findall(
|
|
668
693
|
r",\s[a-z][a-z0-9_-]*", selected_stream.strip()
|
|
@@ -689,9 +714,11 @@ class Sourcer:
|
|
|
689
714
|
result = {}
|
|
690
715
|
if meta_text:
|
|
691
716
|
selected_stream = meta_text[
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
717
|
+
(
|
|
718
|
+
default_stream
|
|
719
|
+
if default_stream > 0 and default_stream < len(meta_text)
|
|
720
|
+
else 0
|
|
721
|
+
)
|
|
695
722
|
]
|
|
696
723
|
# filter data
|
|
697
724
|
filtered_audio_bitrate = re.findall(
|
|
@@ -719,7 +746,7 @@ class Sourcer:
|
|
|
719
746
|
|
|
720
747
|
def __extract_resolution_framerate(self, default_stream=0, extract_output=False):
|
|
721
748
|
"""
|
|
722
|
-
This Internal method parses default video-stream resolution and framerate from metadata.
|
|
749
|
+
This Internal method parses default video-stream resolution, orientation, and framerate from metadata.
|
|
723
750
|
|
|
724
751
|
Parameters:
|
|
725
752
|
default_stream (int): selects specific audio-stream in case of multiple ones.
|
|
@@ -742,12 +769,30 @@ class Sourcer:
|
|
|
742
769
|
if all(x in line for x in identifiers)
|
|
743
770
|
]
|
|
744
771
|
)
|
|
772
|
+
# extract video orientation metadata if available
|
|
773
|
+
identifiers_orientation = ["displaymatrix:", "rotation"]
|
|
774
|
+
meta_text_orientation = (
|
|
775
|
+
[
|
|
776
|
+
line.strip()
|
|
777
|
+
for line in self.__ffsp_output.split("\n")
|
|
778
|
+
if all(x in line for x in identifiers_orientation)
|
|
779
|
+
]
|
|
780
|
+
if not extract_output
|
|
781
|
+
else [
|
|
782
|
+
line.strip()
|
|
783
|
+
for line in self.__metadata_output.split("\n")
|
|
784
|
+
if all(x in line for x in identifiers_orientation)
|
|
785
|
+
]
|
|
786
|
+
)
|
|
787
|
+
# use metadata if available
|
|
745
788
|
result = {}
|
|
746
789
|
if meta_text:
|
|
747
790
|
selected_stream = meta_text[
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
791
|
+
(
|
|
792
|
+
default_stream
|
|
793
|
+
if default_stream > 0 and default_stream < len(meta_text)
|
|
794
|
+
else 0
|
|
795
|
+
)
|
|
751
796
|
]
|
|
752
797
|
|
|
753
798
|
# filter data
|
|
@@ -775,7 +820,23 @@ class Sourcer:
|
|
|
775
820
|
if filtered_resolution:
|
|
776
821
|
result["resolution"] = [int(x) for x in filtered_resolution[0]]
|
|
777
822
|
|
|
778
|
-
|
|
823
|
+
# extract video orientation metadata
|
|
824
|
+
if meta_text_orientation:
|
|
825
|
+
selected_stream = meta_text_orientation[
|
|
826
|
+
(
|
|
827
|
+
default_stream
|
|
828
|
+
if default_stream > 0 and default_stream < len(meta_text)
|
|
829
|
+
else 0
|
|
830
|
+
)
|
|
831
|
+
]
|
|
832
|
+
filtered_orientation = re.findall(
|
|
833
|
+
r"[-]?\d+\.\d+", selected_stream.strip()
|
|
834
|
+
)
|
|
835
|
+
result["orientation"] = float(filtered_orientation[0])
|
|
836
|
+
else:
|
|
837
|
+
result["orientation"] = 0.0
|
|
838
|
+
|
|
839
|
+
return result if result and (len(result) == 3) else {}
|
|
779
840
|
|
|
780
841
|
def __extract_duration(self, inseconds=True):
|
|
781
842
|
"""
|
deffcode/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.2.
|
|
1
|
+
__version__ = "0.2.6"
|