Source code for camtasia.screenplay
import re
from dataclasses import dataclass, field
from pathlib import Path
[docs]
@dataclass
class VOBlock:
"""A voice-over text block within a screenplay section."""
id: str
text: str
section: str
[docs]
@dataclass
class PauseMarker:
"""A timed pause marker in the screenplay."""
duration_seconds: float
description: str
[docs]
@dataclass
class TransitionMarker:
"""A named transition marker between screenplay segments."""
name: str
[docs]
@dataclass
class ImageRef:
"""A reference to an image used in the screenplay."""
alt: str
path: str
[docs]
@dataclass
class ScreenplaySection:
"""A titled section of a screenplay containing VO blocks, pauses, transitions, and images."""
title: str
level: int
vo_blocks: list[VOBlock] = field(default_factory=list)
pauses: list[PauseMarker] = field(default_factory=list)
transitions: list[TransitionMarker] = field(default_factory=list)
images: list[ImageRef] = field(default_factory=list)
[docs]
@dataclass
class Screenplay:
"""A parsed screenplay composed of sections."""
sections: list[ScreenplaySection]
@property
def vo_blocks(self) -> list[VOBlock]:
"""Get all voice-over blocks across all sections."""
return [b for s in self.sections for b in s.vo_blocks]
@property
def total_pauses(self) -> float:
"""Get the total pause duration in seconds across all sections."""
return sum(p.duration_seconds for s in self.sections for p in s.pauses)
@property
def all_images(self) -> list[ImageRef]:
"""Get all image references across all sections."""
return [i for s in self.sections for i in s.images]
_VO_RE = re.compile(r'\[VO-([\d.]+)\].*?:\*\*\s*"([^"]+)"')
_PAUSE_RE = re.compile(r'PAUSE[:\s]+(\d+(?:\.\d+)?)\s*second')
_TRANSITION_RE = re.compile(r'TRANSITION:\s*(.+)')
_IMAGE_RE = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)')
_SECTION_RE = re.compile(r'^(#{2,3})\s+(.+)', re.MULTILINE)
[docs]
def parse_screenplay(path: str | Path) -> Screenplay:
"""Parse a markdown screenplay file into a Screenplay object."""
text = Path(path).read_text()
splits = list(_SECTION_RE.finditer(text))
sections: list[ScreenplaySection] = []
for i, m in enumerate(splits):
start = m.end()
end = splits[i + 1].start() if i + 1 < len(splits) else len(text)
chunk = text[start:end]
title = m.group(2).strip()
level = len(m.group(1))
section = ScreenplaySection(
title=title,
level=level,
vo_blocks=[VOBlock(id=v.group(1), text=v.group(2), section=title) for v in _VO_RE.finditer(chunk)],
pauses=[PauseMarker(duration_seconds=float(p.group(1)), description=p.group(0)) for p in _PAUSE_RE.finditer(chunk)],
transitions=[TransitionMarker(name=t.group(1).strip()) for t in _TRANSITION_RE.finditer(chunk)],
images=[ImageRef(alt=img.group(1), path=img.group(2)) for img in _IMAGE_RE.finditer(chunk)],
)
sections.append(section)
return Screenplay(sections=sections)