Source code for camtasia.builders.timeline_builder
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from camtasia.project import Project
from camtasia.timeline.clips.base import BaseClip
[docs]
class TimelineBuilder:
"""High-level builder for assembling Camtasia timelines.
Provides a cursor-based API for sequentially placing clips
with automatic timing management.
Usage:
builder = TimelineBuilder(project)
builder.add_audio_sequence(['intro.wav', 'main.wav'], pause=1.0)
builder.add_background_image('bg.png')
builder.add_title('My Video', duration=5.0)
"""
def __init__(self, project: Project) -> None:
self._project = project
self._cursor: float = 0.0
@property
def cursor(self) -> float:
"""Current timeline position in seconds."""
return self._cursor
@cursor.setter
def cursor(self, value: float) -> None:
"""Set the current cursor position in seconds."""
if value < 0:
raise ValueError(f'Cursor must be non-negative, got {value}')
self._cursor = value
[docs]
def advance(self, seconds: float) -> TimelineBuilder:
"""Move cursor forward by seconds."""
self._cursor += seconds
return self
[docs]
def seek(self, seconds: float) -> TimelineBuilder:
"""Set cursor to an absolute position."""
self.cursor = seconds
return self
[docs]
def add_audio(
self,
file_path: str | Path,
*,
track_name: str = 'Audio',
duration: float | None = None,
) -> BaseClip:
"""Import and place an audio file at the cursor position.
Advances the cursor by the clip duration.
"""
media = self._project.import_media(Path(file_path))
track = self._project.timeline.get_or_create_track(track_name)
dur = duration if duration is not None else (media.duration_seconds if media.duration_seconds is not None else 5.0)
clip = track.add_audio(media.id, start_seconds=self._cursor, duration_seconds=dur)
self._cursor += dur
return clip
[docs]
def add_pause(self, seconds: float) -> TimelineBuilder:
"""Add a pause (advance cursor without placing a clip)."""
self._cursor += seconds
return self
[docs]
def add_image(
self,
file_path: str | Path,
*,
track_name: str = 'Content',
duration: float = 5.0,
) -> BaseClip:
"""Import and place an image at the cursor position.
Does NOT advance the cursor (images are visual overlays).
"""
media = self._project.import_media(Path(file_path))
track = self._project.timeline.get_or_create_track(track_name)
return track.add_image(media.id, start_seconds=self._cursor, duration_seconds=duration)
[docs]
def add_title(
self,
text: str,
*,
track_name: str = 'Titles',
duration: float = 5.0,
subtitle: str = '',
) -> BaseClip:
"""Add a title card at the cursor position.
Does NOT advance the cursor.
"""
track = self._project.timeline.get_or_create_track(track_name)
return track.add_title(text, start_seconds=self._cursor, duration_seconds=duration)