Source code for camtasia.effects.base
"""Base effect class wrapping Camtasia effect dicts."""
from __future__ import annotations
from typing import Any, Callable
from camtasia.types import _EffectData
[docs]
class Effect:
"""Thin wrapper around a Camtasia effect dict.
Effects are stored in a clip's ``effects`` array as dicts with keys:
``effectName``, ``bypassed``, ``category``, ``parameters``.
Each parameter is a nested dict like::
{"type": "double", "defaultValue": 16.0, "interp": "linr"}
Args:
data: The raw effect dict from the project JSON.
"""
def __init__(self, data: dict[str, Any]) -> None:
self._data: _EffectData = data # type: ignore[assignment]
@property
def data(self) -> _EffectData:
"""The underlying raw dict."""
return self._data
@property
def name(self) -> str:
"""Effect name identifier."""
return self._data["effectName"]
@property
def bypassed(self) -> bool:
"""Whether the effect is bypassed (disabled)."""
return bool(self._data.get("bypassed", False))
@bypassed.setter
def bypassed(self, value: bool) -> None:
"""Set whether the effect is bypassed."""
self._data["bypassed"] = value
@property
def category(self) -> str:
"""Effect category string."""
return self._data.get("category", "")
@property
def metadata(self) -> dict:
"""Top-level metadata dict for this effect."""
return self._data.get('metadata', {})
@property
def parameters(self) -> dict[str, Any]:
"""Effect parameters dict."""
return self._data.get("parameters", {})
[docs]
def get_parameter(self, name: str) -> Any:
"""Get a parameter's default value by name.
Args:
name: The parameter key inside the ``parameters`` dict.
Returns:
The ``defaultValue`` of the parameter, or the scalar value directly.
Raises:
KeyError: If the parameter does not exist.
"""
val = self.parameters[name]
return val['defaultValue'] if isinstance(val, dict) else val
[docs]
def set_parameter(self, name: str, value: Any) -> None:
"""Set a parameter's default value by name.
Args:
name: The parameter key inside the ``parameters`` dict.
value: The new default value.
Raises:
KeyError: If the parameter does not exist.
"""
val = self.parameters[name]
if isinstance(val, dict):
val["defaultValue"] = value
else:
self.parameters[name] = value
# ------------------------------------------------------------------
# Time-bounded effects
# ------------------------------------------------------------------
@property
def start(self) -> int | None:
"""Effect start time in ticks, or ``None`` if not time-bounded."""
return self._data.get('start')
@property
def duration(self) -> int | None:
"""Effect duration in ticks, or ``None`` if not time-bounded."""
return self._data.get('duration')
@property
def is_time_bounded(self) -> bool:
"""Whether this effect has explicit start/duration."""
return 'start' in self._data and 'duration' in self._data
@property
def left_edge_mods(self) -> list[dict[str, Any]]:
"""Left edge modifications (fade-in, etc.)."""
return self._data.get('leftEdgeMods', [])
@property
def right_edge_mods(self) -> list[dict[str, Any]]:
"""Right edge modifications (fade-out, etc.)."""
return self._data.get('rightEdgeMods', [])
def __eq__(self, other: object) -> bool:
"""Return True if both wrappers reference the same underlying dict."""
if not isinstance(other, Effect):
return NotImplemented
return self._data is other._data
def __hash__(self) -> int:
"""Return hash based on the underlying dict identity."""
return id(self._data)
def __repr__(self) -> str:
"""Return a developer-friendly string representation."""
return f"{type(self).__name__}(name={self.name!r})"
# Registry for effect_from_dict dispatch
_EFFECT_REGISTRY: dict[str, type[Effect]] = {}
[docs]
def register_effect(name: str) -> Callable[[type[Effect]], type[Effect]]:
"""Class decorator to register an Effect subclass for factory dispatch."""
def decorator(cls: type[Effect]) -> type[Effect]:
"""Inner decorator function."""
_EFFECT_REGISTRY[name] = cls
return cls
return decorator
[docs]
def effect_from_dict(data: dict[str, Any]) -> Effect:
"""Create the appropriate Effect subclass from a raw effect dict.
Args:
data: A dict with at least an ``effectName`` key.
Returns:
An instance of the matching Effect subclass, or a generic
``Effect`` if no specific class is registered.
"""
cls = _EFFECT_REGISTRY.get(data["effectName"], Effect)
return cls(data)