#!/usr/bin/env python3
"""Format a transcript slice into a screenplay-scroll script.json.
Reads a Scribe-like transcript ({words:[{start,end,text}]}), slices [start,end],
splits into sentences with per-line word-aware wrapping, and emits the JSON the
ScreenplayScroll Remotion comp consumes: {duration,fps,title,lines:[{kind,text,t,end}]}.
Times are remapped to the OUTPUT timeline (0 = clip start).
"""
import argparse
import json
def fmt(words, rs, re_, title, fps=24, width=28, cue="19 KEYS"):
seg = [w for w in words if w["start"] >= rs - 0.05 and w["end"] <= re_ + 0.05]
# ALWAYS end on a full point: drop any trailing partial sentence
last = max((i for i, w in enumerate(seg) if w["text"].rstrip().endswith((".", "?", "!"))),
default=len(seg) - 1)
seg = seg[: last + 1]
if seg:
re_ = min(re_, seg[-1]["end"] + 0.12) # keep video end aligned to the full stop
lines = []
def push(k, t, tt, end=False):
lines.append({"kind": k, "text": t, "t": round(max(0, tt), 2), "end": end})
if seg:
push("cue", cue, seg[0]["start"] - rs)
sents, cur = [], []
for w in seg:
cur.append(w)
if w["text"].endswith((".", "?", "!")):
sents.append(cur); cur = []
if cur:
sents.append(cur)
prev = None
for sent in sents:
if prev is not None and (sent[0]["start"] - prev) > 1.3:
push("cue", cue, sent[0]["start"] - rs)
line, lt = [], None
for w in sent:
if lt is None:
lt = w["start"] - rs
if line and len(" ".join(x["text"] for x in line)) + 1 + len(w["text"]) > width:
push("dia", " ".join(x["text"] for x in line), lt); line = []; lt = w["start"] - rs
line.append(w)
if line:
push("dia", " ".join(x["text"] for x in line), lt, end=True)
prev = sent[-1]["end"]
return {"duration": round(re_ - rs, 2), "fps": fps, "title": title, "lines": lines}
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("transcript")
ap.add_argument("--start", type=float, required=True)
ap.add_argument("--end", type=float, required=True)
ap.add_argument("--title", required=True)
ap.add_argument("--out", action="append", required=True, help="output path(s)")
a = ap.parse_args()
d = json.load(open(a.transcript))
words = d["words"] if isinstance(d, dict) and "words" in d else d
out = fmt(words, a.start, a.end, a.title)
for p in a.out:
json.dump(out, open(p, "w"), indent=1)
print(f"{out['duration']}s, {len(out['lines'])} lines, title={a.title!r}")