| """ |
| Build speech segments from transcript, removing silences >= 0.3s and uh/um fillers. |
| Then execute the ffmpeg trim+concat cut, scaling 4K → 1080p with dense keyframes. |
| """ |
| import json, subprocess, sys |
| from pathlib import Path |
|
|
| TRANSCRIPT = Path(r"D:\PromptEngineer48\In-Progress\P11-Editor\edit\transcripts\Mem0_1.json") |
| SOURCE = Path(r"D:\PromptEngineer48\In-Progress\P11-Editor\Mem0_1.mp4") |
| OUT_BASE = Path(r"D:\PromptEngineer48\In-Progress\P11-Editor\edit\hf\base_cut.mp4") |
|
|
| THRESHOLD = 0.30 |
| PAD = 0.08 |
| FADE_MS = 30 |
|
|
| FILLERS = {"uh", "um"} |
|
|
| 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)) |
|
|
| |
| VIDEO_DUR = 805.5 |
| 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 |
|
|
| total_dur = sum(b - a for a, b in segs) |
| print(f"Segments: {len(segs)}") |
| print(f"Total kept: {total_dur:.1f}s = {total_dur/60:.1f}min (cut {VIDEO_DUR - total_dur:.1f}s)") |
| for i, (a, b) in enumerate(segs): |
| print(f" [{i:03d}] {a:.3f} -> {b:.3f} ({b-a:.2f}s)") |
|
|
| |
| |
| |
| PASSES = 4 |
| source_dur = VIDEO_DUR |
| bucket_dur = source_dur / PASSES |
| tmp_dir = OUT_BASE.parent / "tmp_passes" |
| tmp_dir.mkdir(exist_ok=True) |
|
|
| tmp_files = [] |
| for p in range(PASSES): |
| t_lo = p * bucket_dur |
| t_hi = (p + 1) * bucket_dur |
| bucket_segs = [(a, b) for a, b in segs if b > t_lo and a < t_hi] |
| if not bucket_segs: |
| continue |
| expr = "+".join(f"between(t,{a},{b})" for a, b in bucket_segs) |
| fc = ( |
| f"[0:v]select='{expr}',setpts=N/30/TB,scale=1920:1080[outv];" |
| f"[0:a]aselect='{expr}',asetpts=N/SR/TB[outa]" |
| ) |
| fc_f = tmp_dir / f"fc_pass{p}.txt" |
| fc_f.write_text(fc, encoding="utf-8") |
| tmp_out = tmp_dir / f"pass{p}.mp4" |
| tmp_files.append(tmp_out) |
| print(f"Pass {p}: {len(bucket_segs)} segs, source {t_lo:.0f}-{t_hi:.0f}s -> {tmp_out.name}") |
| r = subprocess.run([ |
| "ffmpeg", "-y", "-i", str(SOURCE), |
| "-/filter_complex", str(fc_f), |
| "-map", "[outv]", "-map", "[outa]", |
| "-c:v", "libx264", "-crf", "18", "-preset", "fast", |
| "-g", "30", "-keyint_min", "30", |
| "-c:a", "aac", "-b:a", "192k", |
| str(tmp_out) |
| ], capture_output=True, text=True) |
| if r.returncode != 0: |
| print(f"Pass {p} STDERR:", r.stderr[-2000:]) |
| sys.exit(1) |
| print(f" Pass {p} done") |
|
|
| |
| concat_list = tmp_dir / "concat.txt" |
| concat_list.write_text( |
| "\n".join(f"file '{f.as_posix()}'" for f in tmp_files), |
| encoding="utf-8" |
| ) |
| print(f"\nConcatenating {len(tmp_files)} passes -> {OUT_BASE}") |
| r = subprocess.run([ |
| "ffmpeg", "-y", "-f", "concat", "-safe", "0", |
| "-i", str(concat_list), |
| "-c:v", "libx264", "-crf", "18", "-preset", "fast", |
| "-g", "30", "-keyint_min", "30", |
| "-c:a", "aac", "-b:a", "192k", |
| str(OUT_BASE) |
| ], capture_output=True, text=True) |
| if r.returncode != 0: |
| print("Concat STDERR:", r.stderr[-2000:]) |
| sys.exit(1) |
| print(f"Done! -> {OUT_BASE}") |
|
|