Repository navigation
Expand file tree
/
Copy pathtranscript.py
More file actions
81 lines (60 loc) · 2.97 KB
/
Copy pathtranscript.py
File metadata and controls
81 lines (60 loc) · 2.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"""Transcript data model for CapsQual.
Defines the core Transcript dataclass and shared constants used across
parsers, generators, and the GUI.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from typing import Optional
# ── Shared constants ──────────────────────────────────────────────
INDENT_PLACEHOLDER = '␣' # U+2423 OPEN BOX — visible in viewer
# Regex for pause symbols (atomic tokens)
PAUSE_PATTERN = re.compile(
r'\(\.\)|\(-+\)|\(\d+(?:\.\d+)?\)|°h+|h+°|@\(\.\)@|@\(\d+s\)@|//|<<.*?>>|\[.*?\]|\(\(.*?\)\)|└'
)
# Regex for ALL atomic tokens during line-wrapping.
# Includes formatting markers (#@B, #@/B, #@I, #@/I, #@U, #@/U),
# pause symbols, and overlap markers.
# Excludes comment-like wrappers (<<...>>, ((...)), [...]) which are
# variable-length and should be allowed to break across lines.
ATOMIC_PATTERN = re.compile(
r'#@[BIU]|#@/[BIU]|\(\.\)|\(-+\)|\(\d+(?:\.\d+)?\)|°h+|h+°|@\(\.\)@|@\(\d+s\)@|//|└'
)
# ── Core data model ───────────────────────────────────────────────
@dataclass
class Transcript:
"""The central data model for a CapsQual transcript.
Holds all data needed by generators (formatting/export) and the GUI.
Replaces the ad-hoc ``editor.srt_blocks`` + ``editor.speakers`` +
``editor.cjk_mode`` + ``editor.file_has_timestamps`` attributes.
"""
blocks: list[dict] = field(default_factory=list)
"""List of segment dicts. Each dict has at least:
- ``index``: int
- ``start_time`` / ``end_time``: str (``HH:MM:SS,mmm``)
- ``text`` / ``raw_text``: str
- ``speaker``: int | None (index into ``speakers``)
- ``is_turn_start``: bool
- ``is_pause`` / ``is_comment`` / ``is_empty``: bool (optional)
- ``overlap_info``: dict | None (optional)
"""
speakers: list[str] = field(default_factory=lambda: ["A", "B", "C", "D"])
"""List of speaker names (default A, B, C, D)."""
cjk_mode: bool = False
"""When True, use CJK-aware wrapping (no spaces between tokens)."""
file_has_timestamps: bool = True
"""Whether the source file contained timestamp information."""
# ── convenience helpers ────────────────────────────────────
@property
def num_speakers(self) -> int:
return len(self.speakers)
def speaker_name(self, speaker_idx: Optional[int]) -> str:
"""Return the display name for a speaker index, or empty string."""
if speaker_idx is not None and 0 <= speaker_idx < len(self.speakers):
return self.speakers[speaker_idx]
return ""
def __getitem__(self, index: int) -> dict:
"""Access blocks by index directly."""
return self.blocks[index]
def __len__(self) -> int:
return len(self.blocks)