typed-ffmpeg-compatible 2.6.4__tar.gz → 2.7.0__tar.gz

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 (47) hide show
  1. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/PKG-INFO +1 -1
  2. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/pyproject.toml +1 -1
  3. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/common/schema.py +4 -1
  4. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/common/serialize.py +43 -25
  5. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/nodes.py +1 -4
  6. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/schema.py +2 -1
  7. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/LICENSE +0 -0
  8. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/README.md +0 -0
  9. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/__init__.py +0 -0
  10. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/base.py +0 -0
  11. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/common/__init__.py +0 -0
  12. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/__init__.py +0 -0
  13. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/compile.py +0 -0
  14. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/context.py +0 -0
  15. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/factory.py +0 -0
  16. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/global_runnable/__init__.py +0 -0
  17. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/global_runnable/global_args.py +0 -0
  18. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/global_runnable/runnable.py +0 -0
  19. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/io/__init__.py +0 -0
  20. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/io/_input.py +0 -0
  21. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/io/_output.py +0 -0
  22. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/io/output_args.py +0 -0
  23. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/utils.py +0 -0
  24. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/dag/validate.py +0 -0
  25. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/exceptions.py +0 -0
  26. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/filters.py +0 -0
  27. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/info.py +0 -0
  28. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/probe.py +0 -0
  29. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/py.typed +0 -0
  30. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/schema.py +0 -0
  31. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/sources.py +0 -0
  32. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/streams/__init__.py +0 -0
  33. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/streams/audio.py +0 -0
  34. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/streams/av.py +0 -0
  35. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/streams/channel_layout.py +0 -0
  36. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/streams/video.py +0 -0
  37. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/types.py +0 -0
  38. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/__init__.py +0 -0
  39. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/escaping.py +0 -0
  40. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/forzendict.py +0 -0
  41. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/lazy_eval/__init__.py +0 -0
  42. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/lazy_eval/operator.py +0 -0
  43. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/lazy_eval/schema.py +0 -0
  44. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/run.py +0 -0
  45. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/snapshot.py +0 -0
  46. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/typing.py +0 -0
  47. {typed_ffmpeg_compatible-2.6.4 → typed_ffmpeg_compatible-2.7.0}/src/typed_ffmpeg/utils/view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: typed-ffmpeg-compatible
3
- Version: 2.6.4
3
+ Version: 2.7.0
4
4
  Summary: Modern Python FFmpeg wrappers offer comprehensive support for complex filters, complete with detailed typing and documentation.
5
5
  Home-page: https://livingbio.github.io/typed-ffmpeg/
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "typed-ffmpeg-compatible"
3
- version = "2.6.4"
3
+ version = "2.7.0"
4
4
  description = "Modern Python FFmpeg wrappers offer comprehensive support for complex filters, complete with detailed typing and documentation."
5
5
  authors = ["lucemia <lucemia@gmail.com>"]
6
6
  readme = "README.md"
@@ -12,7 +12,10 @@ from dataclasses import dataclass
12
12
  from enum import Enum
13
13
  from typing import Literal
14
14
 
15
+ from ffmpeg.common.serialize import Serializable, serializable
15
16
 
17
+
18
+ @serializable
16
19
  class StreamType(str, Enum):
17
20
  """
18
21
  Enumeration of possible stream types in FFmpeg.
@@ -198,7 +201,7 @@ class FFMpegFilterDef:
198
201
 
199
202
 
200
203
  @dataclass(frozen=True, kw_only=True)
201
- class FFMpegFilter:
204
+ class FFMpegFilter(Serializable):
202
205
  """
203
206
  Comprehensive representation of an FFmpeg filter with all its metadata.
204
207
 
@@ -9,7 +9,6 @@ saved to disk and loaded back.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import importlib
13
12
  import json
14
13
  from dataclasses import fields, is_dataclass
15
14
  from enum import Enum
@@ -19,43 +18,62 @@ from typing import Any
19
18
 
20
19
  from ..utils.forzendict import FrozenDict
21
20
 
21
+ CLASS_REGISTRY: dict[str, type[Serializable | Enum]] = {}
22
+ """
23
+ A registry of classes that have been loaded, and can be deserialized.
24
+ """
25
+
26
+
27
+ def serializable(
28
+ cls: type[Serializable] | type[Enum],
29
+ ) -> type[Serializable] | type[Enum]:
30
+ """
31
+ Register a class with the serialization system.
32
+ """
33
+ assert cls.__name__ not in CLASS_REGISTRY, (
34
+ f"Class {cls.__name__} already registered"
35
+ )
36
+ CLASS_REGISTRY[cls.__name__] = cls
37
+
38
+ return cls
39
+
22
40
 
23
- def load_class(path: str, strict: bool = True) -> Any:
41
+ class Serializable:
24
42
  """
25
- Load a class from a string path.
43
+ A base class for all serializable classes.
44
+ """
45
+
46
+ def __init_subclass__(cls, **kwargs: Any) -> None:
47
+ super().__init_subclass__(**kwargs)
48
+ serializable(cls)
49
+
50
+
51
+ def load_class(name: str) -> type[Serializable] | type[Enum]:
52
+ """
53
+ Load a class from its name.
26
54
 
27
- This function dynamically imports a class based on its fully qualified
28
- path (e.g., 'ffmpeg.dag.nodes.FilterNode'). It's used during deserialization
29
- to reconstruct objects from their class names.
55
+ This function looks up a class by its name in the CLASS_REGISTRY. It's used during
56
+ deserialization to reconstruct objects from their class names.
30
57
 
31
58
  Args:
32
- path: The fully qualified path to the class (module.submodule.ClassName)
33
- strict: If True, only allow loading classes from the ffmpeg package
34
- as a security measure
59
+ name: The simple class name (e.g., 'FilterNode')
35
60
 
36
61
  Returns:
37
62
  The class object that can be instantiated
38
63
 
39
64
  Raises:
40
- AssertionError: If strict is True and the path doesn't start with 'ffmpeg.'
41
- ImportError: If the module or class cannot be found
65
+ AssertionError: If the class name is not found in the registry
42
66
 
43
67
  Example:
44
68
  ```python
45
69
  # Load the FilterNode class
46
- FilterNode = load_class('ffmpeg.dag.nodes.FilterNode')
70
+ FilterNode = load_class('FilterNode')
47
71
  # Create an instance
48
72
  node = FilterNode(name='scale', ...)
49
73
  ```
50
74
  """
51
- if strict:
52
- assert path.startswith("ffmpeg."), (
53
- f"Only support loading class from ffmpeg package: {path}"
54
- )
55
-
56
- module_path, class_name = path.rsplit(".", 1)
57
- module = importlib.import_module(module_path)
58
- return getattr(module, class_name)
75
+ assert name in CLASS_REGISTRY, f"Class {name} not registered"
76
+ return CLASS_REGISTRY[name]
59
77
 
60
78
 
61
79
  def frozen(v: Any) -> Any:
@@ -110,7 +128,7 @@ def object_hook(obj: Any, strict: bool = True) -> Any:
110
128
  ```python
111
129
  # A JSON object with class information
112
130
  json_obj = {
113
- "__class__": "ffmpeg.dag.nodes.FilterNode",
131
+ "__class__": "FilterNode",
114
132
  "name": "scale",
115
133
  "kwargs": {"width": 1280, "height": 720},
116
134
  }
@@ -119,7 +137,7 @@ def object_hook(obj: Any, strict: bool = True) -> Any:
119
137
  """
120
138
  if isinstance(obj, dict):
121
139
  if obj.get("__class__"):
122
- cls = load_class(obj.pop("__class__"), strict=strict)
140
+ cls = load_class(obj.pop("__class__"))
123
141
 
124
142
  if is_dataclass(cls):
125
143
  # NOTE: in our application, the dataclass is always frozen
@@ -148,7 +166,7 @@ def loads(raw: str, strict: bool = True) -> Any:
148
166
  Example:
149
167
  ```python
150
168
  # Deserialize a filter graph from JSON
151
- json_str = '{"__class__": "ffmpeg.dag.nodes.FilterNode", "name": "scale", ...}'
169
+ json_str = '{"__class__": "FilterNode", "name": "scale", ...}'
152
170
  filter_node = loads(json_str)
153
171
  # filter_node is now a FilterNode instance
154
172
  ```
@@ -192,7 +210,7 @@ def to_dict_with_class_info(instance: Any) -> Any:
192
210
  return str(instance)
193
211
  elif is_dataclass(instance):
194
212
  return {
195
- "__class__": f"{instance.__class__.__module__}.{instance.__class__.__name__}",
213
+ "__class__": instance.__class__.__name__,
196
214
  **{
197
215
  k.name: to_dict_with_class_info(getattr(instance, k.name))
198
216
  for k in fields(instance)
@@ -200,7 +218,7 @@ def to_dict_with_class_info(instance: Any) -> Any:
200
218
  }
201
219
  elif isinstance(instance, Enum):
202
220
  return {
203
- "__class__": f"{instance.__class__.__module__}.{instance.__class__.__name__}",
221
+ "__class__": instance.__class__.__name__,
204
222
  "value": instance.value,
205
223
  }
206
224
  return instance
@@ -665,10 +665,7 @@ class OutputNode(Node):
665
665
  # !handle mapping
666
666
  commands = []
667
667
 
668
- if context and (
669
- any(isinstance(k.node, FilterNode) for k in self.inputs)
670
- or len([k for k in context.all_nodes if isinstance(k, OutputNode)]) > 1
671
- ):
668
+ if context:
672
669
  for input in self.inputs:
673
670
  if isinstance(input.node, InputNode):
674
671
  commands += ["-map", input.label(context)]
@@ -5,6 +5,7 @@ from dataclasses import dataclass, replace
5
5
  from functools import cached_property
6
6
  from typing import TYPE_CHECKING, Literal
7
7
 
8
+ from ..common.serialize import Serializable
8
9
  from ..utils.forzendict import FrozenDict
9
10
  from ..utils.lazy_eval.schema import LazyValue
10
11
  from .utils import is_dag
@@ -14,7 +15,7 @@ if TYPE_CHECKING:
14
15
 
15
16
 
16
17
  @dataclass(frozen=True, kw_only=True)
17
- class HashableBaseModel:
18
+ class HashableBaseModel(Serializable):
18
19
  """
19
20
  A base class for hashable dataclasses.
20
21
  """