Skip to content

Commit

Permalink
Fix #180 partial eval (#346)
Browse files Browse the repository at this point in the history
* fix #180, add Lazy Operator

* add test case

* udpate test case

* update snapshot

* add partical

* wip

* wip

* fix import path

* fix

* fix

* update templ

* update snapshot

* docstrings

* fix
  • Loading branch information
lucemia authored Mar 18, 2024
1 parent 9d06345 commit 47166c1
Show file tree
Hide file tree
Showing 47 changed files with 941 additions and 29 deletions.
4 changes: 4 additions & 0 deletions docs/src/utils/lazy_eval/operator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
::: ffmpeg.utils.lazy_eval.operator
options:
show_source: false
members_order: source
4 changes: 4 additions & 0 deletions docs/src/utils/lazy_eval/schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
::: ffmpeg.utils.lazy_eval.schema
options:
show_source: false
members_order: source
7 changes: 5 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,17 @@ nav:
- utils: src/dag/utils.md
- validate: src/dag/validate.md
- global_runnable:
- runnable: src/dag/global_runnable/runnable.md
- global_args: src/dag/global_runnable/global_args.md
- runnable: src/dag/global_runnable/runnable.md
- global_args: src/dag/global_runnable/global_args.md
- utils:
- escaping: src/utils/escaping.md
- run: src/utils/run.md
- snapshot: src/utils/snapshot.md
- typing: src/utils/typing.md
- view: src/utils/view.md
- lazy_eval:
- schema: src/utils/lazy_eval/schema.md
- operator: src/utils/lazy_eval/operator.md
watch:
- "src"
- "docs"
Expand Down
4 changes: 2 additions & 2 deletions src/ffmpeg/dag/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from ..common.schema import FFMpegFilterDef, StreamType
from ..schema import Auto
from ..utils.run import _to_tuple
from ..utils.run import ignore_default
from .nodes import FilterableStream, FilterNode


Expand All @@ -27,5 +27,5 @@ def filter_node_factory(filter: FFMpegFilterDef, *inputs: FilterableStream, **kw
input_typings=input_typings,
output_typings=output_typings,
inputs=inputs,
kwargs=_to_tuple(kwargs),
kwargs=ignore_default(kwargs),
)
3 changes: 3 additions & 0 deletions src/ffmpeg/dag/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ..exceptions import FFMpegTypeError, FFMpegValueError
from ..schema import Default, StreamType
from ..utils.escaping import escape
from ..utils.lazy_eval.schema import LazyValue
from ..utils.typing import override
from .global_runnable.runnable import GlobalRunable
from .io.output_args import OutputArgs
Expand Down Expand Up @@ -131,6 +132,8 @@ def get_args(self, context: DAGContext = None) -> list[str]:

commands = []
for key, value in self.kwargs:
assert not isinstance(value, LazyValue), f"LazyValue should have been evaluated: {key}={value}"

# Note: the -nooption syntax cannot be used for boolean AVOptions, use -option 0/-option 1.
if isinstance(value, bool):
value = str(int(value))
Expand Down
4 changes: 3 additions & 1 deletion src/ffmpeg/dag/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from functools import cached_property
from typing import TYPE_CHECKING, Literal

from ..utils.lazy_eval.schema import LazyValue
from .utils import is_dag

if TYPE_CHECKING:
Expand Down Expand Up @@ -74,7 +75,8 @@ class Node(HashableBaseModel, ABC):
Each node in the DAG represents a single operation that transforms the data from its input form to its output form. The node is an essential component of the DAG, as it defines the nature of the operations that are performed on the data.
"""

kwargs: tuple[tuple[str, str | int | float | bool], ...] = ()
# Filter_Node_Option_Type
kwargs: tuple[tuple[str, str | int | float | bool | LazyValue], ...] = ()
"""
Represents the keyword arguments of the node.
"""
Expand Down
4 changes: 4 additions & 0 deletions src/ffmpeg/tests/__snapshots__/test_lazy_value.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# serializer version: 1
# name: test_symbol
OutputStream(node=OutputNode(kwargs=(), inputs=(VideoStream(node=FilterNode(kwargs=(('w', Symbol(key='w')),), inputs=(AVStream(node=InputNode(kwargs=(), inputs=(), filename='input.mp4'), index=None),), name='scale', input_typings=(<StreamType.video: 'video'>,), output_typings=(<StreamType.video: 'video'>,)), index=0),), filename='output.mp4'), index=None)
# ---
10 changes: 10 additions & 0 deletions src/ffmpeg/tests/test_lazy_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from syrupy.assertion import SnapshotAssertion

from ..base import input
from ..utils.lazy_eval.schema import Symbol


def test_symbol(snapshot: SnapshotAssertion) -> None:
w = Symbol("w")

assert snapshot() == input("input.mp4").scale(w=w).output(filename="output.mp4")
39 changes: 23 additions & 16 deletions src/ffmpeg/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
from typing import Literal

from .schema import Default
from .utils.lazy_eval.schema import LazyValue

Boolean = bool | Literal["true", "false", "1", "0"] | Default
Boolean = bool | Literal["true", "false", "1", "0"] | Default | LazyValue
"""
This represents FFmpeg's boolean type. It can accept either a Python boolean value (`True` or `False`)
or a string that represents a boolean value ("true", "false", "1", or "0").
"""

Duration = str | int | float
Duration = str | int | float | Default | LazyValue
"""
This represents FFmpeg's duration type. It can accept either a Python integer or float value
or a string that represents a duration value.
Expand All @@ -24,7 +25,7 @@
[Document](https://ffmpeg.org/ffmpeg-utils.html#Time-duration)
"""

Color = str
Color = str | Default | LazyValue
"""
It can be the name of a color as defined below (case insensitive match) or a [0x|#]RRGGBB[AA] sequence, possibly followed by @ and a string representing the alpha component.
The alpha component may be a string composed by "0x" followed by an hexadecimal number or a decimal number between 0.0 and 1.0, which represents the opacity value (‘0x00’ or ‘0.0’ means completely transparent, ‘0xff’ or ‘1.0’ completely opaque). If the alpha component is not specified then ‘0xff’ is assumed.
Expand All @@ -34,50 +35,50 @@
[Document](https://ffmpeg.org/ffmpeg-utils.html#Color)
"""

Flags = str
Flags = str | Default | LazyValue
"""
This represents FFmpeg's flags type. It accepts a string in the format "A+B",
where "A" and "B" are individual flags. For example, "fast+bilinear" would
represent two flags, "fast" and "bilinear", to be used in FFmpeg's command line.
"""

Dictionary = str
Dictionary = str | Default | LazyValue
# format A=B:C=D:E=F
Pix_fmt = str
Pix_fmt = str | Default | LazyValue
"""
please see `ffmpeg -pix_fmts` for a list of supported pixel formats.
"""

Int = int | Default
Int = int | Default | LazyValue
"""
This represents FFmpeg's integer type. It can accept either a Python integer value
or a string that represents a integer value.
"""

Int64 = int | Default
Int64 = int | Default | LazyValue
"""
This represents FFmpeg's integer type. It can accept either a Python integer value
or a string that represents a integer value.
"""

Double = int | float | Default
Double = int | float | Default | LazyValue
"""
This represents FFmpeg's double type. It can accept either a Python integer or float value
or a string that represents a double value.
"""
# TODO: more info
Float = int | float | Default
Float = int | float | Default | LazyValue
"""
This represents FFmpeg's float type. It can accept either a Python integer or float value
or a string that represents a float value.
"""
String = str | int | float
String = str | int | float | Default | LazyValue
"""
This represents FFmpeg's string type. It can accept either a Python string value
or a int/float that will be converted to a string.
"""

Video_rate = str | int | float
Video_rate = str | int | float | Default | LazyValue
"""
Specify the frame rate of a video, expressed as the number of frames generated per second. It has to be a string in the format frame_rate_num/frame_rate_den, an integer number, a float number or a valid video frame rate abbreviation.
Expand All @@ -86,15 +87,15 @@
"""

# TODO: enum
Image_size = str
Image_size = str | Default | LazyValue
"""
Specify the size of the sourced video, it may be a string of the form widthxheight, or the name of a size abbreviation.
Note:
[Document](https://ffmpeg.org/ffmpeg-utils.html#Video-size)
"""

Rational = str
Rational = str | Default | LazyValue
"""
Specify the frame rate of a video, expressed as the number of frames generated per second. It has to be a string in the format frame_rate_num/frame_rate_den, an integer number, a float number or a valid video frame rate abbreviation.
Expand All @@ -103,5 +104,11 @@
"""


Sample_fmt = str
Binary = str
Sample_fmt = str | Default | LazyValue
Binary = str | Default | LazyValue


Filter_Node_Option_Type = str | int | float | bool | LazyValue
"""
This represents the type of options that can be used with FFmpeg's filter nodes.
"""
Empty file.
134 changes: 134 additions & 0 deletions src/ffmpeg/utils/lazy_eval/operator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from dataclasses import dataclass
from typing import Any

from .schema import LazyOperator


@dataclass(frozen=True, kw_only=True)
class Add(LazyOperator):
"""
A lazy operator for addition.
"""

def _eval(self, left: Any, right: Any) -> Any:
return left + right

def __str__(self) -> str:
return f"({self.left}+{self.right})"


@dataclass(frozen=True, kw_only=True)
class Sub(LazyOperator):
"""
A lazy operator for subtraction.
"""

def _eval(self, left: Any, right: Any) -> Any:
return left - right

def __str__(self) -> str:
return f"({self.left}-{self.right})"


@dataclass(frozen=True, kw_only=True)
class Mul(LazyOperator):
"""
A lazy operator for multiplication.
"""

def _eval(self, left: Any, right: Any) -> Any:
return left * right

def __str__(self) -> str:
return f"({self.left}*{self.right})"


@dataclass(frozen=True, kw_only=True)
class TrueDiv(LazyOperator):
"""
A lazy operator for true division.
"""

def _eval(self, left: Any, right: Any) -> Any:
return left / right

def __str__(self) -> str:
return f"({self.left}/{self.right})"


@dataclass(frozen=True, kw_only=True)
class Pow(LazyOperator):
"""
A lazy operator for exponentiation.
"""

def _eval(self, left: Any, right: Any) -> Any:
return left**right

def __str__(self) -> str:
return f"({self.left}**{self.right})"


@dataclass(frozen=True, kw_only=True)
class Neg(LazyOperator):
"""
A lazy operator for negation.
"""

def _eval(self, left: Any, right: Any) -> Any:
return -left

def __str__(self) -> str:
return f"-{self.left}"


@dataclass(frozen=True, kw_only=True)
class Pos(LazyOperator):
"""
A lazy operator for positive.
"""

def _eval(self, left: Any, right: Any) -> Any:
return +left

def __str__(self) -> str:
return f"+{self.left}"


@dataclass(frozen=True, kw_only=True)
class Abs(LazyOperator):
"""
A lazy operator for absolute value.
"""

def _eval(self, left: Any, right: Any) -> Any:
return abs(left)

def __str__(self) -> str:
return f"abs({self.left})"


@dataclass(frozen=True, kw_only=True)
class Mod(LazyOperator):
"""
A lazy operator for modulo.
"""

def _eval(self, left: Any, right: Any) -> Any:
return left % right

def __str__(self) -> str:
return f"({self.left}%{self.right})"


@dataclass(frozen=True, kw_only=True)
class FloorDiv(LazyOperator):
"""
A lazy operator for floor division.
"""

def _eval(self, left: Any, right: Any) -> Any:
return left // right

def __str__(self) -> str:
return f"({self.left}//{self.right})"
Loading

0 comments on commit 47166c1

Please sign in to comment.