Source code for camtasia.timeline.markers

"""Timeline and per-media markers (table-of-contents keyframes)."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Iterator

EDIT_RATE = 705_600_000


[docs] @dataclass class Marker: """A single marker (TOC keyframe). Args: name: The marker label text. time: Position in editRate ticks. """ name: str time: int @property def time_seconds(self) -> float: """Position in seconds.""" return self.time / EDIT_RATE def __repr__(self) -> str: return f'Marker(name={self.name!r}, time_seconds={self.time_seconds:.2f})'
[docs] class MarkerList: """Wraps a parameters dict that may contain toc keyframes. Handles the case where the parameters/toc/keyframes path doesn't exist yet — it is created on first add. Args: data: The parent dict containing a 'parameters' key (e.g. the timeline dict or a media dict). """ def __init__(self, data: dict[str, Any]) -> None: self._data = data @property def _keyframes(self) -> list[dict[str, Any]]: return ( # type: ignore[no-any-return] self._data .get('parameters', {}) .get('toc', {}) .get('keyframes', []) ) def _ensure_keyframes(self) -> list[dict[str, Any]]: """Return the keyframes list, creating the path if needed.""" params = self._data.setdefault('parameters', {}) toc = params.setdefault('toc', {'type': 'string'}) return toc.setdefault('keyframes', []) # type: ignore[no-any-return] def __len__(self) -> int: return len(self._keyframes) def __repr__(self) -> str: return f'MarkerList(count={len(self)})' def __iter__(self) -> Iterator[Marker]: for kf in self._keyframes: yield Marker(name=kf['value'], time=kf['time'])
[docs] def add(self, name: str, time_ticks: int) -> Marker: """Add a marker at the given time. Args: name: The marker label text. time_ticks: Position in editRate ticks. Returns: The newly created Marker. """ kf = { 'time': time_ticks, 'endTime': time_ticks, 'value': name, 'duration': 0, } self._ensure_keyframes().append(kf) return Marker(name=name, time=time_ticks)
[docs] def remove_at(self, time: int) -> None: """Remove all markers at the given time. Args: time: Position in editRate ticks. Raises: KeyError: No marker exists at the given time. """ keyframes = self._ensure_keyframes() before = len(keyframes) keyframes[:] = [kf for kf in keyframes if kf['time'] != time] if len(keyframes) == before: raise KeyError(f'No marker at time={time}')
[docs] def clear(self) -> None: """Remove all markers.""" self._ensure_keyframes().clear()
[docs] def replace(self, markers: list[tuple[str, int]]) -> None: """Replace all markers with a new set. Args: markers: List of (name, time_ticks) tuples. """ self.clear() for name, time_ticks in markers: self.add(name, time_ticks)