-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscene_parser.py
More file actions
100 lines (81 loc) · 3.27 KB
/
Copy pathscene_parser.py
File metadata and controls
100 lines (81 loc) · 3.27 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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# LEVEL 5 — SCENE PARSER
# Converts AI-generated timestamped scripts into structured scene lists.
# Expected input format: [MM:SS - MM:SS] Narration text here
# Also handles HH:MM:SS variants and flexible spacing.
import re
# ── Timestamp helpers ─────────────────────────────────────────────────────────
def timestamp_to_seconds(ts: str) -> float:
"""
Convert timestamp string to seconds.
Accepts: SS, MM:SS, HH:MM:SS (integers or floats in the seconds position).
"""
ts = ts.strip()
parts = ts.split(':')
try:
if len(parts) == 3:
return int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2])
elif len(parts) == 2:
return int(parts[0]) * 60 + float(parts[1])
else:
return float(parts[0])
except (ValueError, IndexError):
return 0.0
def seconds_to_timestamp(s: float) -> str:
"""Format seconds as MM:SS (or HH:MM:SS for long durations)."""
s = max(0.0, s)
h = int(s // 3600)
m = int((s % 3600) // 60)
sec = s % 60
if h > 0:
return f"{h:02d}:{m:02d}:{sec:05.2f}"
return f"{m:02d}:{sec:05.2f}"
# ── Core parser ───────────────────────────────────────────────────────────────
# Matches: [MM:SS - MM:SS], [HH:MM:SS – HH:MM:SS], [MM:SS-MM:SS] etc.
_PATTERN = re.compile(
r'\[(\d{1,2}:\d{2}(?::\d{2}(?:\.\d+)?)?)\s*[-–—]\s*(\d{1,2}:\d{2}(?::\d{2}(?:\.\d+)?)?)\]'
r'[ \t]*(.+?)(?=\n\[|\Z)',
re.DOTALL,
)
def extract_scenes(script_text: str) -> list:
"""
Parse a timestamped AI script into a structured scene list.
Input example:
[00:10 - 00:20] The city awakens as our hero steps onto the rooftop.
[00:21 - 00:35] The villain emerges from the shadows, ready to strike.
Returns a list of dicts:
{
'start_str': '00:10',
'end_str': '00:20',
'start': 10.0, # seconds
'end': 20.0,
'duration': 10.0,
'narration': 'The city awakens...',
}
"""
scenes = []
for match in _PATTERN.finditer(script_text):
start_str = match.group(1).strip()
end_str = match.group(2).strip()
narration = match.group(3).strip().replace('\n', ' ')
start = timestamp_to_seconds(start_str)
end = timestamp_to_seconds(end_str)
if end <= start:
# Malformed range — skip
continue
scenes.append({
'start_str': start_str,
'end_str': end_str,
'start': start,
'end': end,
'duration': round(end - start, 2),
'narration': narration,
})
return scenes
def scenes_to_text(scenes: list) -> str:
"""Serialise a scene list back to the [MM:SS - MM:SS] text format."""
lines = []
for s in scenes:
start = s.get('start_str') or seconds_to_timestamp(s['start'])
end = s.get('end_str') or seconds_to_timestamp(s['end'])
lines.append(f"[{start} - {end}] {s.get('narration', '')}")
return '\n'.join(lines)