Source code for camtasia.operations.diff
"""Compare two Camtasia projects and report differences."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from camtasia.project import Project
[docs]
@dataclass
class ProjectDiff:
"""Differences between two Camtasia projects."""
tracks_added: list[int] = field(default_factory=list)
tracks_removed: list[int] = field(default_factory=list)
clips_added: list[tuple[int, int]] = field(default_factory=list) # (track_idx, clip_id)
clips_removed: list[tuple[int, int]] = field(default_factory=list)
media_added: list[int] = field(default_factory=list) # media IDs
media_removed: list[int] = field(default_factory=list)
settings_changed: dict[str, tuple] = field(default_factory=dict) # key: (old, new)
@property
def has_changes(self) -> bool:
"""Whether any differences were found."""
return bool(
self.tracks_added or self.tracks_removed
or self.clips_added or self.clips_removed
or self.media_added or self.media_removed
or self.settings_changed
)
[docs]
def summary(self) -> str:
"""Human-readable summary of changes."""
lines = []
if self.tracks_added:
lines.append(f'Tracks added: {self.tracks_added}')
if self.tracks_removed:
lines.append(f'Tracks removed: {self.tracks_removed}')
if self.clips_added:
lines.append(f'Clips added: {len(self.clips_added)}')
if self.clips_removed:
lines.append(f'Clips removed: {len(self.clips_removed)}')
if self.media_added:
lines.append(f'Media added: {len(self.media_added)}')
if self.media_removed:
lines.append(f'Media removed: {len(self.media_removed)}')
if self.settings_changed:
for k, (old, new) in self.settings_changed.items():
lines.append(f'{k}: {old} -> {new}')
return '\n'.join(lines) if lines else 'No changes'
[docs]
def diff_projects(a: Project, b: Project) -> ProjectDiff:
"""Compare two projects and return their differences."""
result = ProjectDiff()
# Track differences
a_tracks = {t.index for t in a.timeline.tracks}
b_tracks = {t.index for t in b.timeline.tracks}
result.tracks_added = sorted(b_tracks - a_tracks)
result.tracks_removed = sorted(a_tracks - b_tracks)
# Clip differences (on shared tracks)
for idx in a_tracks & b_tracks:
a_clips = {c.id for c in a.timeline.tracks[idx].clips}
b_clips = {c.id for c in b.timeline.tracks[idx].clips}
for cid in sorted(b_clips - a_clips):
result.clips_added.append((idx, cid))
for cid in sorted(a_clips - b_clips):
result.clips_removed.append((idx, cid))
# Media differences
a_media = {m.id for m in a.media_bin}
b_media = {m.id for m in b.media_bin}
result.media_added = sorted(b_media - a_media)
result.media_removed = sorted(a_media - b_media)
# Settings differences
for key in ('width', 'height'):
av = getattr(a, key)
bv = getattr(b, key)
if av != bv:
result.settings_changed[key] = (av, bv)
return result