Operations

Speed changes with full timeline re-sync.

Ported from the tested camtasia_stretch.py script that successfully rescaled a project from 1.07x audio to 1.0x on 2026-04-08.

camtasia.operations.speed.rescale_project(project_data, factor)[source]

Scale all timing values in a project by factor.

Mutates project_data in-place. For clips with existing speed changes, adjusts their scalar so source-media alignment is preserved.

Parameters:
  • project_data (dict[str, Any]) – The raw project JSON dict.

  • factor (Fraction) – Multiplicative factor for all tick values. Values > 1 stretch (slow down), < 1 compress (speed up).

Return type:

None

camtasia.operations.speed.set_audio_speed(project_data, target_speed=1.0)[source]

Rescale the project so audio clips play at target_speed.

Finds audio clips with a non-unity scalar, calculates the stretch factor needed, and calls rescale_project().

Parameters:
  • project_data (dict[str, Any]) – The raw project JSON dict.

  • target_speed (float) – Desired audio playback speed (1.0 = normal).

Return type:

Fraction

Returns:

The stretch factor that was applied.

Raises:

ValueError – No speed-changed audio clips found.

camtasia.operations.speed.rescale(project, factor)[source]

Scale all timing in a project by factor.

This is a convenience wrapper around rescale_project() that accepts a Project object.

Return type:

None

camtasia.operations.speed.normalize_audio_speed(project, target_speed=1.0)[source]

Rescale project so audio plays at target_speed.

Returns the original audio scalar.

Return type:

Fraction

Audio-video sync from transcript and timeline markers.

Implements the V3 labeled-markers workflow: given markers on a screen recording and a word-level transcript, calculate per-segment speed adjustments to align video with audio.

class camtasia.operations.sync.SyncSegment(video_start_ticks, video_end_ticks, audio_start_seconds, audio_end_seconds, scalar)[source]

Bases: object

A segment between two sync points with its speed adjustment.

Variables:
  • video_start_ticks – Segment start on the video timeline (ticks).

  • video_end_ticks – Segment end on the video timeline (ticks).

  • audio_start_seconds – Corresponding audio start (seconds).

  • audio_end_seconds – Corresponding audio end (seconds).

  • scalar – Camtasia scalar (video_duration / audio_duration in ticks).

video_start_ticks: int
video_end_ticks: int
audio_start_seconds: float
audio_end_seconds: float
scalar: Fraction
camtasia.operations.sync.match_marker_to_transcript(label, words)[source]

Fuzzy-match a marker label to words in a transcript.

Uses simple case-insensitive substring matching. Checks each word in the label against the running text of the transcript.

Parameters:
  • label (str) – Marker label text (e.g. “Selecting a recent batch run”).

  • words (list[Word]) – List of dicts with word, start, end keys.

Return type:

float | None

Returns:

Start timestamp (seconds) of the best match, or None.

camtasia.operations.sync.plan_sync(markers, transcript_words, edit_rate=705600000)[source]

Calculate per-segment speed adjustments to sync video with audio.

For each pair of consecutive markers, finds the corresponding audio timestamps via transcript matching and computes the scalar needed to align the video segment duration with the audio segment duration.

Parameters:
  • markers (list[tuple[str, int]]) – List of (label, video_time_ticks) from timeline.parameters.toc keyframes.

  • transcript_words (list[Word]) – List of dicts with word, start, end keys (from WhisperX or Audiate).

  • edit_rate (int) – Ticks per second (default 705,600,000).

Return type:

list[SyncSegment]

Returns:

List of SyncSegments, one per gap between consecutive markers.

camtasia.operations.sync.apply_sync(group, segments)[source]

Apply sync segments to a Group’s internal track.

Converts SyncSegment objects to the (source_start, source_end, timeline_duration) tuples expected by set_internal_segment_speeds.

Return type:

None

Template-based project creation and media source replacement.

camtasia.operations.template.clone_project_structure(source_data)[source]

Deep-copy a project, clearing media-specific content.

Preserves project settings, track structure, and effects templates. Empties the source bin and removes all clips from tracks.

Parameters:

source_data (dict[str, Any]) – The raw project JSON dict to use as a template.

Return type:

dict[str, Any]

Returns:

A new project dict with media content cleared.

camtasia.operations.template.replace_media_source(project_data, old_source_id, new_source_id)[source]

Replace all references to one media source with another.

Walks all clips (including nested StitchedMedia children and Group internal tracks) and replaces src fields.

Parameters:
  • project_data (dict[str, Any]) – The raw project JSON dict.

  • old_source_id (int) – Source bin ID to replace.

  • new_source_id (int) – Replacement source bin ID.

Return type:

int

Returns:

Number of clips updated.

camtasia.operations.template.duplicate_project(source_path, dest_path, *, clear_media=False)[source]

Duplicate a Camtasia project to a new location.

Copies the entire .cmproj bundle (including media files) to dest_path. If clear_media is True, removes all clips and media from the copy while preserving project settings (canvas size, edit rate, etc.).

Parameters:
  • source_path (str | Path) – Path to the source .cmproj project.

  • dest_path (str | Path) – Path for the new project copy.

  • clear_media (bool) – If True, strip all clips and media from the copy.

Return type:

Project

Returns:

The loaded Project at dest_path.

Compare two Camtasia projects and report differences.

class camtasia.operations.diff.ProjectDiff(tracks_added=<factory>, tracks_removed=<factory>, clips_added=<factory>, clips_removed=<factory>, media_added=<factory>, media_removed=<factory>, settings_changed=<factory>)[source]

Bases: object

Differences between two Camtasia projects.

tracks_added: list[int]
tracks_removed: list[int]
clips_added: list[tuple[int, int]]
clips_removed: list[tuple[int, int]]
media_added: list[int]
media_removed: list[int]
settings_changed: dict[str, tuple]
property has_changes: bool

Whether any differences were found.

summary()[source]

Human-readable summary of changes.

Return type:

str

camtasia.operations.diff.diff_projects(a, b)[source]

Compare two projects and return their differences.

Return type:

ProjectDiff

Batch operations — apply the same transformation to multiple clips at once.

camtasia.operations.batch.apply_to_clips(clips, fn)[source]

Apply a function to each clip. Returns count of clips processed.

Return type:

int

camtasia.operations.batch.apply_to_track(track, fn)[source]

Apply a function to every clip on a track.

Return type:

int

camtasia.operations.batch.apply_to_all_tracks(timeline, fn)[source]

Apply a function to every clip on every track.

Return type:

int

camtasia.operations.batch.set_opacity_all(clips, opacity)[source]

Set opacity on all clips.

Return type:

int

camtasia.operations.batch.fade_all(clips, fade_in=0.5, fade_out=0.5)[source]

Apply fade-in and fade-out to all clips.

Return type:

int

camtasia.operations.batch.scale_all(clips, factor)[source]

Set uniform scale on all clips.

Return type:

int

camtasia.operations.batch.move_all(clips, dx=0.0, dy=0.0)[source]

Offset all clips by (dx, dy) from their current position.

Return type:

int

camtasia.operations.layout.pack_track(track, gap_seconds=0.0)[source]

Remove gaps between clips, packing them end-to-end.

Sorts clips by start time, then repositions each to start immediately after the previous clip (plus optional gap).

Return type:

None

camtasia.operations.layout.ripple_insert(track, position_seconds, duration_seconds)[source]

Shift all clips at or after position forward by duration.

Creates a gap at the insertion point.

Return type:

None

camtasia.operations.layout.ripple_delete(track, clip_id)[source]

Remove a clip and shift subsequent clips backward to close the gap.

Return type:

None

camtasia.operations.layout.snap_to_grid(track, grid_seconds=1.0)[source]

Snap all clip start times to the nearest grid point.

Warning

Snapping can move two or more clips to the same grid point, creating overlapping clips on the track. Callers should check track.overlaps() afterward and resolve any collisions (e.g. by calling pack_track()).

Return type:

None

Utilities for cleaning up Camtasia projects.

camtasia.operations.cleanup.remove_orphaned_media(project)[source]

Remove media entries not referenced by any clip.

Returns list of removed media IDs.

Return type:

list[int]

camtasia.operations.cleanup.remove_empty_tracks(project)[source]

Remove all empty tracks from the project.

Returns count of tracks removed.

Return type:

int

camtasia.operations.cleanup.compact_project(project)[source]

Run all cleanup operations on a project.

Returns a summary dict with counts of items cleaned.

Return type:

CompactResult

Merge tracks from one project into another.

camtasia.operations.merge.merge_tracks(source, target, *, offset_seconds=0.0)[source]

Copy all non-empty tracks from source into target.

Clips are offset by offset_seconds on the target timeline. Media entries are copied to the target’s source bin with new IDs.

Parameters:
  • source (Project) – Project to copy tracks from.

  • target (Project) – Project to copy tracks into.

  • offset_seconds (float) – Time offset for all copied clips.

Return type:

int

Returns:

Number of tracks copied.