""" Build output-timeline mapping and generate the first ~50s slice composition. Source times -> output times using the segment list from build_cut.py. """ import json, re, sys from pathlib import Path TRANSCRIPT = Path(r"D:\PromptEngineer48\In-Progress\P11-Editor\edit\transcripts\Mem0_1.json") HF_DIR = Path(r"D:\PromptEngineer48\In-Progress\P11-Editor\edit\hf") SKILL = Path(r"C:\Users\palas\.claude\skills\screencast-hype") sys.path.insert(0, str(SKILL / "scripts")) from captions_html import build_captions # ---- same segment list as build_cut.py ---- THRESHOLD = 0.30 PAD = 0.08 FILLERS = {"uh", "um"} VIDEO_DUR = 805.5 data = json.load(open(TRANSCRIPT, encoding="utf-8")) words = [w for w in data["words"] if w.get("type") == "word"] clean = [w for w in words if w["text"].strip().lower().rstrip(",.") not in FILLERS] segs = [] s = e = None for w in clean: if s is None: s, e = w["start"], w["end"] elif w["start"] - e <= THRESHOLD: e = w["end"] else: segs.append((max(0, s - PAD), e + PAD)) s, e = w["start"], w["end"] if s is not None: segs.append((max(0, s - PAD), e + PAD)) clamped = [] for a, b in segs: a = round(max(0.0, a), 4) b = round(min(VIDEO_DUR, b), 4) if clamped and a < clamped[-1][1]: a = clamped[-1][1] if b > a: clamped.append((a, b)) segs = clamped def src_to_out(src_t): out_offset = 0.0 for (a, b) in segs: if src_t <= a: return out_offset if src_t <= b: return out_offset + (src_t - a) out_offset += (b - a) return out_offset # ---- slice window ---- SLICE_END_OUT = 50.0 out_acc = 0.0 src_end = segs[-1][1] for (a, b) in segs: seg_dur = b - a if out_acc + seg_dur >= SLICE_END_OUT: src_end = a + (SLICE_END_OUT - out_acc) break out_acc += seg_dur print(f"Slice: output 0..{SLICE_END_OUT}s | source 0..{src_end:.1f}s") # ---- timing constants ---- T_TITLE_IN = 1.0 T_TITLE_OUT = 9.0 T_CHIP_IN = src_to_out(15.0) T_CHIP_OUT = T_CHIP_IN + 8.0 TOTAL_DUR = SLICE_END_OUT # ---- captions (suppress during title card window) ---- raw_divs, raw_tweens = build_captions(words, start=0.0, end=src_end, per=2, map_time=src_to_out) def filter_tweens(tweens_str): lines = tweens_str.split("\n") out, skip = [], False for line in lines: m = re.search(r',(\d+\.\d+)\);$', line) t_val = float(m.group(1)) if m else None if t_val is not None and T_TITLE_IN <= t_val <= T_TITLE_OUT: skip = True continue if skip and "opacity:0" in line: skip = False continue skip = False out.append(line) return "\n".join(out) cap_divs = raw_divs filtered = filter_tweens(raw_tweens) # Hard-zero ALL caps at every fromTo entry point so previous cap is never # still visible when the next one pops in. Inject tl.set(".cap",{opacity:0},T) # just before every fromTo. GSAP processes set() before fromTo at same time, # so the specific cap's fromTo then overrides just that element. def inject_set_resets(tweens_str): lines = tweens_str.split("\n ") result = [] for line in lines: if line.strip().startswith("tl.fromTo("): m = re.search(r',(\d+\.\d+)\);$', line) if m: t = float(m.group(1)) # inject instant reset for ALL caps just before this entry result.append(f'tl.set(".cap",{{opacity:0}},{max(t-0.001,0):.3f});') result.append(line) return "\n ".join(result) cap_tweens = inject_set_resets(filtered) # ---- generate HTML ---- html = f"""