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 CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '3.2.3'
21
- __version_tuple__ = version_tuple = (3, 2, 3)
20
+ __version__ = version = '3.2.5'
21
+ __version_tuple__ = version_tuple = (3, 2, 5)
@@ -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 selector.count(":") == 1:
144
- stream_label, stream_type = selector.split(":", 1)
145
- return stream.video if stream_type == "v" else stream.audio
146
- elif selector.count(":") == 2:
147
- stream_label, stream_type, stream_index = selector.split(":", 2)
148
- return (
149
- stream.video_stream(int(stream_index))
150
- if stream_type == "v"
151
- else stream.audio_stream(int(stream_index))
152
- )
153
- else:
154
- return stream
155
- else:
156
- return stream
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
- filter_units = filter_complex.split(";")
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 = param_str.strip().split(":")
346
+ param_parts = re.split(r"(?<!\\):", param_str)
340
347
  for part in param_parts:
341
348
  if "=" in part:
342
- key, value = part.split("=", 1)
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
- if output_typing == StreamType.video:
373
- stream_mapping[output_label] = VideoStream(node=filter_node, index=idx)
374
- elif output_typing == StreamType.audio:
375
- stream_mapping[output_label] = AudioStream(node=filter_node, index=idx)
376
- else:
377
- raise FFMpegValueError(f"Unknown stream type: {output_typing}")
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
- case _:
115
- raise ValueError(f"Unknown node type: {type(stream.node)}")
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:
@@ -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
- def video_stream(self, index: int) -> VideoStream:
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)
@@ -0,0 +1,7 @@
1
+ from ..dag.nodes import FilterableStream
2
+
3
+
4
+ class SubtitleStream(FilterableStream):
5
+ """
6
+ A stream that contains subtitle data.
7
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: typed-ffmpeg-compatible
3
- Version: 3.2.3
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=P9n86yqDQj4uxDqvcIKGlJWsA9YZ7G1BSuEXNqVACeI,511
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=--2j1QPPOB2pEQzqFM4VpINokbJilcegtRqTRF2_Jfg,31588
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=oo4e8Ldwk0OkrZtHucfuGR5JDFF8xY8omNKPMDyUpQ8,11506
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=Nu6M7uV4aMNQf_kxADZuBdpDwFx_B7Z7x0p5j32n9iA,1500
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.3.dist-info/licenses/LICENSE,sha256=8Aaya5i_09Cou2i3QMxTwz6uHGzi_fGA4uhkco07-A4,1066
54
- typed_ffmpeg_compatible-3.2.3.dist-info/METADATA,sha256=HwRj91ZONWRkUUACimtXw8TNwhZveMe358-cYoBYaUU,8385
55
- typed_ffmpeg_compatible-3.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
56
- typed_ffmpeg_compatible-3.2.3.dist-info/entry_points.txt,sha256=kUQvZ27paV-07qtkIFV-emKsYtjFOTw9kknBRSXPs04,45
57
- typed_ffmpeg_compatible-3.2.3.dist-info/top_level.txt,sha256=vuASJGVRQiNmhWY1pt0RXESWSNkknWXqWLIRAU7H_L4,13
58
- typed_ffmpeg_compatible-3.2.3.dist-info/RECORD,,
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,,