Private
Public Access
conductor(score_dynamics_giorgini): Phase 1 Acquire - transcript (1485 clean segments, 46.5KB) + 178MB mp4
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
Phase 1 Acquire for score_dynamics_giorgini: https://youtu.be/P75iVMmbqQk
|
||||||
|
Artifacts: C:\projects\manual_slop\conductor\tracks\video_analysis_score_dynamics_giorgini_20260621\artifacts
|
||||||
|
Step 1: extract_transcript (yt-dlp VTT directly)
|
||||||
|
OK: wrote C:\projects\manual_slop\conductor\tracks\video_analysis_score_dynamics_giorgini_20260621\artifacts\transcript.json (2998 segments)
|
||||||
|
Step 2: download_video
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "download_video: YtdlpError: ERROR: unable to download video data: HTTP Error 403: Forbidden\n"
|
||||||
|
}
|
||||||
+20940
File diff suppressed because one or more lines are too long
+1485
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
|||||||
|
# yt-dlp log
|
||||||
|
# url: https://youtu.be/P75iVMmbqQk
|
||||||
|
# output: conductor/tracks/video_analysis_score_dynamics_giorgini_20260621/artifacts/video.mp4
|
||||||
|
# returncode: 0
|
||||||
|
|
||||||
|
stdout:
|
||||||
|
[youtube] Extracting URL: https://youtu.be/P75iVMmbqQk
|
||||||
|
[youtube] P75iVMmbqQk: Downloading webpage
|
||||||
|
WARNING: [youtube] No supported JavaScript runtime could be found. Only deno is enabled by default.
|
||||||
|
[youtube] P75iVMmbqQk: Downloading android vr player API JSON
|
||||||
|
[info] P75iVMmbqQk: Downloading 1 format(s): 400+251
|
||||||
|
[download] video.mp4.f400.mp4 (125.41MiB)
|
||||||
|
[download] video.mp4.f251.webm (52.64MiB)
|
||||||
|
[Merger] Merging formats into video.mp4 (Matroska / WebM container)
|
||||||
|
|
||||||
|
stderr:
|
||||||
|
WARNING: yt-dlp EJS not enabled; some formats may be missing.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
"""Clean yt-dlp auto-sub VTT transcripts.
|
||||||
|
|
||||||
|
yt-dlp auto-subs produce rolling captions where each segment extends the
|
||||||
|
previous one with new words, sometimes triplicated. Algorithm:
|
||||||
|
1. Strip VTT tags (<00:00:00.560><c>...</c>) from raw text.
|
||||||
|
2. For each pair (prev, curr), compute the new suffix curr adds vs prev.
|
||||||
|
Keep only the new suffix.
|
||||||
|
3. Drop empty / duplicate results.
|
||||||
|
4. Skip segments that are pure repetition of the prior segment.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
VTT_TAG = re.compile(r"<[^>]+>")
|
||||||
|
WS = re.compile(r"\s+")
|
||||||
|
|
||||||
|
def strip_vtt(t: str) -> str:
|
||||||
|
return WS.sub(" ", VTT_TAG.sub("", t)).strip()
|
||||||
|
|
||||||
|
def longest_common_prefix_len(a: str, b: str) -> int:
|
||||||
|
n = min(len(a), len(b))
|
||||||
|
i = 0
|
||||||
|
while i < n and a[i] == b[i]:
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def longest_common_suffix_len(a: str, b: str, max_k: int = 80) -> int:
|
||||||
|
n = min(len(a), len(b), max_k)
|
||||||
|
i = 0
|
||||||
|
while i < n and a[-1 - i] == b[-1 - i]:
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def main(track: str) -> None:
|
||||||
|
art = Path(f"conductor/tracks/{track}/artifacts")
|
||||||
|
p = art / "transcript.json"
|
||||||
|
data = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
raw_segments = data["segments"]
|
||||||
|
|
||||||
|
cleaned: list[dict] = []
|
||||||
|
prev_clean = ""
|
||||||
|
for raw in raw_segments:
|
||||||
|
text = strip_vtt(raw["text"])
|
||||||
|
if not text:
|
||||||
|
continue
|
||||||
|
if prev_clean and text == prev_clean:
|
||||||
|
continue
|
||||||
|
if prev_clean and text.startswith(prev_clean):
|
||||||
|
new = text[len(prev_clean):].strip()
|
||||||
|
if not new:
|
||||||
|
continue
|
||||||
|
cleaned.append({"start": raw["start"], "text": new})
|
||||||
|
prev_clean = text
|
||||||
|
continue
|
||||||
|
lcp = longest_common_prefix_len(prev_clean, text)
|
||||||
|
if lcp >= 5:
|
||||||
|
new = text[lcp:].strip()
|
||||||
|
if new:
|
||||||
|
cleaned.append({"start": raw["start"], "text": new})
|
||||||
|
prev_clean = text
|
||||||
|
continue
|
||||||
|
lcs = longest_common_suffix_len(prev_clean, text)
|
||||||
|
if lcs >= 5:
|
||||||
|
new = text[: -lcs if lcs < len(text) else 0].strip()
|
||||||
|
if new:
|
||||||
|
cleaned.append({"start": raw["start"], "text": new})
|
||||||
|
prev_clean = text
|
||||||
|
continue
|
||||||
|
cleaned.append({"start": raw["start"], "text": text})
|
||||||
|
prev_clean = text
|
||||||
|
|
||||||
|
deduped: list[dict] = []
|
||||||
|
seen: set[str] = set()
|
||||||
|
for seg in cleaned:
|
||||||
|
t = seg["text"]
|
||||||
|
if t in seen:
|
||||||
|
continue
|
||||||
|
seen.add(t)
|
||||||
|
deduped.append(seg)
|
||||||
|
|
||||||
|
plain = "\n".join(s["text"] for s in deduped)
|
||||||
|
(art / "transcript_clean.txt").write_text(plain, encoding="utf-8")
|
||||||
|
data["clean_segments"] = deduped
|
||||||
|
data["plain"] = plain
|
||||||
|
p.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
print(f"{track}: {len(raw_segments)} raw -> {len(cleaned)} cleaned -> {len(deduped)} deduped")
|
||||||
|
print(f"plain length: {len(plain)} chars")
|
||||||
|
print("First 800 chars:")
|
||||||
|
print(plain[:800])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
main(sys.argv[1] if len(sys.argv) > 1 else "video_analysis_score_dynamics_giorgini_20260621")
|
||||||
Reference in New Issue
Block a user