typed-ffmpeg-compatible 3.2.3__py3-none-any.whl → 3.2.5__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.
- typed_ffmpeg/_version.py +2 -2
- typed_ffmpeg/compile/compile_cli.py +42 -23
- typed_ffmpeg/compile/compile_python.py +10 -2
- typed_ffmpeg/streams/av.py +25 -2
- typed_ffmpeg/streams/subtitle.py +7 -0
- {typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/METADATA +1 -1
- {typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/RECORD +11 -10
- {typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/WHEEL +0 -0
- {typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/entry_points.txt +0 -0
- {typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/licenses/LICENSE +0 -0
- {typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/top_level.txt +0 -0
typed_ffmpeg/_version.py
CHANGED
@@ -38,6 +38,7 @@ from ..exceptions import FFMpegValueError
|
|
38
38
|
from ..schema import Default
|
39
39
|
from ..streams.audio import AudioStream
|
40
40
|
from ..streams.av import AVStream
|
41
|
+
from ..streams.subtitle import SubtitleStream
|
41
42
|
from ..streams.video import VideoStream
|
42
43
|
from ..utils.escaping import escape
|
43
44
|
from ..utils.lazy_eval.schema import LazyValue
|
@@ -140,20 +141,24 @@ def parse_stream_selector(
|
|
140
141
|
stream = mapping[stream_label]
|
141
142
|
|
142
143
|
if isinstance(stream, AVStream):
|
143
|
-
if
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
144
|
+
if ":" in selector:
|
145
|
+
if selector.count(":") == 1:
|
146
|
+
stream_label, stream_type = selector.split(":", 1)
|
147
|
+
stream_index = None
|
148
|
+
elif selector.count(":") == 2:
|
149
|
+
stream_label, stream_type, _stream_index = selector.split(":", 2)
|
150
|
+
stream_index = int(_stream_index)
|
151
|
+
|
152
|
+
match stream_type:
|
153
|
+
case "v":
|
154
|
+
return stream.video_stream(stream_index)
|
155
|
+
case "a":
|
156
|
+
return stream.audio_stream(stream_index)
|
157
|
+
case "s":
|
158
|
+
return stream.subtitle_stream(stream_index)
|
159
|
+
case _:
|
160
|
+
raise FFMpegValueError(f"Unknown stream type: {stream_type}")
|
161
|
+
return stream
|
157
162
|
|
158
163
|
|
159
164
|
def _is_filename(token: str) -> bool:
|
@@ -309,7 +314,8 @@ def parse_filter_complex(
|
|
309
314
|
Returns:
|
310
315
|
Updated stream mapping with new filter outputs added
|
311
316
|
"""
|
312
|
-
|
317
|
+
# Use re.split with negative lookbehind to handle escaped semicolons
|
318
|
+
filter_units = re.split(r"(?<!\\);", filter_complex)
|
313
319
|
|
314
320
|
for filter_unit in filter_units:
|
315
321
|
pattern = re.compile(
|
@@ -335,11 +341,12 @@ def parse_filter_complex(
|
|
335
341
|
|
336
342
|
# Parse filter parameters into key-value pairs
|
337
343
|
filter_params = {}
|
344
|
+
param_str = param_str and param_str.strip()
|
338
345
|
if param_str:
|
339
|
-
param_parts =
|
346
|
+
param_parts = re.split(r"(?<!\\):", param_str)
|
340
347
|
for part in param_parts:
|
341
348
|
if "=" in part:
|
342
|
-
key, value =
|
349
|
+
key, value = re.split(r"(?<!\\)=", part, 1)
|
343
350
|
filter_params[key.strip()] = value.strip()
|
344
351
|
|
345
352
|
assert isinstance(filter_name, str), f"Expected filter name, got {filter_name}"
|
@@ -369,12 +376,17 @@ def parse_filter_complex(
|
|
369
376
|
for idx, (output_label, output_typing) in enumerate(
|
370
377
|
zip(output_labels, filter_node.output_typings)
|
371
378
|
):
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
379
|
+
match output_typing:
|
380
|
+
case StreamType.video:
|
381
|
+
stream_mapping[output_label] = VideoStream(
|
382
|
+
node=filter_node, index=idx
|
383
|
+
)
|
384
|
+
case StreamType.audio:
|
385
|
+
stream_mapping[output_label] = AudioStream(
|
386
|
+
node=filter_node, index=idx
|
387
|
+
)
|
388
|
+
case _:
|
389
|
+
raise FFMpegValueError(f"Unknown stream type: {output_typing}")
|
378
390
|
|
379
391
|
return stream_mapping
|
380
392
|
|
@@ -606,6 +618,7 @@ def get_stream_label(stream: Stream, context: DAGContext | None = None) -> str:
|
|
606
618
|
For input streams, labels follow FFmpeg's stream specifier syntax:
|
607
619
|
- Video streams: "0:v" (first input, video stream)
|
608
620
|
- Audio streams: "0:a" (first input, audio stream)
|
621
|
+
- Subtitle streams: "0:s" (first input, subtitle stream)
|
609
622
|
- AV streams: "0" (first input, all streams)
|
610
623
|
|
611
624
|
For filter outputs, labels use the filter's label:
|
@@ -647,6 +660,12 @@ def get_stream_label(stream: Stream, context: DAGContext | None = None) -> str:
|
|
647
660
|
f"{get_node_label(stream.node, context)}:a:{stream.index}"
|
648
661
|
)
|
649
662
|
return f"{get_node_label(stream.node, context)}:a"
|
663
|
+
case SubtitleStream():
|
664
|
+
if stream.index is not None:
|
665
|
+
return (
|
666
|
+
f"{get_node_label(stream.node, context)}:s:{stream.index}"
|
667
|
+
)
|
668
|
+
return f"{get_node_label(stream.node, context)}:s"
|
650
669
|
case _:
|
651
670
|
raise FFMpegValueError(
|
652
671
|
f"Unknown stream type: {stream.__class__.__name__}"
|
@@ -17,6 +17,7 @@ from ..dag.nodes import (
|
|
17
17
|
from ..dag.schema import Node, Stream
|
18
18
|
from ..streams.audio import AudioStream
|
19
19
|
from ..streams.av import AVStream
|
20
|
+
from ..streams.subtitle import SubtitleStream
|
20
21
|
from ..streams.video import VideoStream
|
21
22
|
from .context import DAGContext
|
22
23
|
from .validate import validate
|
@@ -107,12 +108,19 @@ def get_input_var_name(
|
|
107
108
|
return f"{get_output_var_name(stream.node, context)}[{stream.index}]"
|
108
109
|
else:
|
109
110
|
return f"{get_output_var_name(stream.node, context)}"
|
111
|
+
case SubtitleStream():
|
112
|
+
match stream.node:
|
113
|
+
case InputNode():
|
114
|
+
if stream.index is not None:
|
115
|
+
return f"{get_output_var_name(stream.node, context)}.subtitle_stream({stream.index})"
|
116
|
+
else:
|
117
|
+
return f"{get_output_var_name(stream.node, context)}.subtitle"
|
110
118
|
case OutputStream():
|
111
119
|
return f"{get_output_var_name(stream.node, context)}"
|
112
120
|
case GlobalStream():
|
113
121
|
return f"{get_output_var_name(stream.node, context)}"
|
114
|
-
|
115
|
-
|
122
|
+
|
123
|
+
raise ValueError(f"Unknown stream type: {type(stream)}") # pragma: no cover
|
116
124
|
|
117
125
|
|
118
126
|
def get_output_var_name(node: Node, context: DAGContext) -> str:
|
typed_ffmpeg/streams/av.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from ..dag.nodes import InputNode
|
2
2
|
from .audio import AudioStream
|
3
|
+
from .subtitle import SubtitleStream
|
3
4
|
from .video import VideoStream
|
4
5
|
|
5
6
|
|
@@ -30,7 +31,17 @@ class AVStream(AudioStream, VideoStream):
|
|
30
31
|
"""
|
31
32
|
return AudioStream(node=self.node, index=self.index)
|
32
33
|
|
33
|
-
|
34
|
+
@property
|
35
|
+
def subtitle(self) -> SubtitleStream:
|
36
|
+
"""
|
37
|
+
Get the subtitle stream from the input node.
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
SubtitleStream: The subtitle stream from the input node.
|
41
|
+
"""
|
42
|
+
return SubtitleStream(node=self.node, index=self.index)
|
43
|
+
|
44
|
+
def video_stream(self, index: int | None = None) -> VideoStream:
|
34
45
|
"""
|
35
46
|
Get the video stream from the input node with a specified index.
|
36
47
|
|
@@ -42,7 +53,7 @@ class AVStream(AudioStream, VideoStream):
|
|
42
53
|
"""
|
43
54
|
return VideoStream(node=self.node, index=index)
|
44
55
|
|
45
|
-
def audio_stream(self, index: int) -> AudioStream:
|
56
|
+
def audio_stream(self, index: int | None = None) -> AudioStream:
|
46
57
|
"""
|
47
58
|
Get the audio stream from the input node with a specified index.
|
48
59
|
|
@@ -53,3 +64,15 @@ class AVStream(AudioStream, VideoStream):
|
|
53
64
|
AudioStream: The audio stream from the input node.
|
54
65
|
"""
|
55
66
|
return AudioStream(node=self.node, index=index)
|
67
|
+
|
68
|
+
def subtitle_stream(self, index: int | None = None) -> SubtitleStream:
|
69
|
+
"""
|
70
|
+
Get the subtitle stream from the input node with a specified index.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
index: The index of the subtitle stream.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
SubtitleStream: The subtitle stream from the input node.
|
77
|
+
"""
|
78
|
+
return SubtitleStream(node=self.node, index=index)
|
{typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: typed-ffmpeg-compatible
|
3
|
-
Version: 3.2.
|
3
|
+
Version: 3.2.5
|
4
4
|
Summary: Modern Python FFmpeg wrappers offer comprehensive support for complex filters, complete with detailed typing and documentation.
|
5
5
|
Author-email: lucemia <lucemia@gmail.com>
|
6
6
|
License-Expression: MIT
|
@@ -1,5 +1,5 @@
|
|
1
1
|
typed_ffmpeg/__init__.py,sha256=n1SZfENc9xhZ0eA3ZzkBNPuIY-Pt3-rOwB8-uUj5olU,1592
|
2
|
-
typed_ffmpeg/_version.py,sha256=
|
2
|
+
typed_ffmpeg/_version.py,sha256=VJzOHz1iq5b2MvgVxeYYraqogSncMB5aG1qIW2TvKks,511
|
3
3
|
typed_ffmpeg/base.py,sha256=C5Tqbx2I0c-09D7aXKZoGkspu-lAAeAhuOns5zr3PXQ,6304
|
4
4
|
typed_ffmpeg/exceptions.py,sha256=D4SID6WOwkjVV8O8mAjrEDHWn-8BRDnK_jteaDof1SY,2474
|
5
5
|
typed_ffmpeg/filters.py,sha256=_lBpGZgPHK3KgGVrw-TCdQEsBRuEXVIgwggYNGd80MU,110076
|
@@ -13,9 +13,9 @@ typed_ffmpeg/common/cache.py,sha256=j0JvfX7jewLpdJWxgo7Pwze0BkUJdYGHX2uGR8BZ-9M,
|
|
13
13
|
typed_ffmpeg/common/schema.py,sha256=qM8yfMX9UU3EAQSNsTrr-SAmyqKx8eQCXTtu3RJWkEk,19673
|
14
14
|
typed_ffmpeg/common/serialize.py,sha256=dLim0DBP5CdJ1JiMV9xEmmh1XMSIhBOWs61EopAL15s,7719
|
15
15
|
typed_ffmpeg/compile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
-
typed_ffmpeg/compile/compile_cli.py,sha256
|
16
|
+
typed_ffmpeg/compile/compile_cli.py,sha256=4XCkBC2WGP-P03XWVI3tgoBs--oQLvhuqyocSk8HhMw,32491
|
17
17
|
typed_ffmpeg/compile/compile_json.py,sha256=YCiTyfAnUVSbFr7BiQpmJYs13K5sa-xo77Iih33mb6I,992
|
18
|
-
typed_ffmpeg/compile/compile_python.py,sha256=
|
18
|
+
typed_ffmpeg/compile/compile_python.py,sha256=zxaG9DC0r8lO_cOV9HBN6cnxy3-wCZyvo3-4M47F84E,11914
|
19
19
|
typed_ffmpeg/compile/context.py,sha256=macQ3HhEJ73j_WbWYtU9GCQCzcB_KQGAPimcuU-WOac,10946
|
20
20
|
typed_ffmpeg/compile/validate.py,sha256=QsWksdvlRwWw6hnatFo-ABakms1qDXRbEmvMQGRLrD8,9579
|
21
21
|
typed_ffmpeg/dag/__init__.py,sha256=qAApSNqjbZ1DtUaV5bSku9RwG7MpMPa1HJO764cSBt4,849
|
@@ -37,8 +37,9 @@ typed_ffmpeg/ffprobe/schema.py,sha256=PBgBWYGO8wKnI0Lae9oCJ1Nprhv2ciPkdLrumzPVll
|
|
37
37
|
typed_ffmpeg/ffprobe/xml2json.py,sha256=1TSuxR7SYc-M_-JmE-1khHGbXCapgW0Oap9kzL0nwNg,2455
|
38
38
|
typed_ffmpeg/streams/__init__.py,sha256=Nt9uWpcVI1sQLl5Qt_kBCBcWOGZA1vczCQ0qvFbSko0,141
|
39
39
|
typed_ffmpeg/streams/audio.py,sha256=2oNRhb5UetWtlPG3NW73AjUZoFPl303yMv-6W1sGWt0,259054
|
40
|
-
typed_ffmpeg/streams/av.py,sha256=
|
40
|
+
typed_ffmpeg/streams/av.py,sha256=q7zqNyr44vBT7tiR2nKUGTf3g93HtNVA_WSwCiysmgI,2240
|
41
41
|
typed_ffmpeg/streams/channel_layout.py,sha256=hGagaoc1tnWS8K1yiITp4f_92qq5e7C_zB15bHOL0DI,1162
|
42
|
+
typed_ffmpeg/streams/subtitle.py,sha256=zhbRKUYH0C972Hurm4hK_dqLvT4Y6v_VNZFzQrkCm9A,141
|
42
43
|
typed_ffmpeg/streams/video.py,sha256=cQwHfew75YO_dbZjmpUYd-nXt1JHN0M7suFKKe5UH5s,453576
|
43
44
|
typed_ffmpeg/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
45
|
typed_ffmpeg/utils/escaping.py,sha256=m6CTEBwWZTFdtZHTHW-3pQCgkpdZb9f9ynoO-gsD7uM,2937
|
@@ -50,9 +51,9 @@ typed_ffmpeg/utils/view.py,sha256=QCSlQoQkRBI-T0sWjiywGgM9DlKd8Te3CB2ZYX-pEVU,34
|
|
50
51
|
typed_ffmpeg/utils/lazy_eval/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
52
|
typed_ffmpeg/utils/lazy_eval/operator.py,sha256=QWybd-UH3VdDa8kgWkqAMi3WV0b0WF1d1JixQr6is2E,4136
|
52
53
|
typed_ffmpeg/utils/lazy_eval/schema.py,sha256=WSg-E3MS3itN1AT6Dq4Z9btnRHEReuN3o6zruXou7h4,9623
|
53
|
-
typed_ffmpeg_compatible-3.2.
|
54
|
-
typed_ffmpeg_compatible-3.2.
|
55
|
-
typed_ffmpeg_compatible-3.2.
|
56
|
-
typed_ffmpeg_compatible-3.2.
|
57
|
-
typed_ffmpeg_compatible-3.2.
|
58
|
-
typed_ffmpeg_compatible-3.2.
|
54
|
+
typed_ffmpeg_compatible-3.2.5.dist-info/licenses/LICENSE,sha256=8Aaya5i_09Cou2i3QMxTwz6uHGzi_fGA4uhkco07-A4,1066
|
55
|
+
typed_ffmpeg_compatible-3.2.5.dist-info/METADATA,sha256=GSSr2eBSGWwgMrZZBx4wgAcRx6bas71nQjLxF8GVJPA,8385
|
56
|
+
typed_ffmpeg_compatible-3.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
57
|
+
typed_ffmpeg_compatible-3.2.5.dist-info/entry_points.txt,sha256=kUQvZ27paV-07qtkIFV-emKsYtjFOTw9kknBRSXPs04,45
|
58
|
+
typed_ffmpeg_compatible-3.2.5.dist-info/top_level.txt,sha256=vuASJGVRQiNmhWY1pt0RXESWSNkknWXqWLIRAU7H_L4,13
|
59
|
+
typed_ffmpeg_compatible-3.2.5.dist-info/RECORD,,
|
File without changes
|
{typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/entry_points.txt
RENAMED
File without changes
|
{typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
{typed_ffmpeg_compatible-3.2.3.dist-info → typed_ffmpeg_compatible-3.2.5.dist-info}/top_level.txt
RENAMED
File without changes
|