-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtranscripts_to_text.py
More file actions
87 lines (71 loc) · 3.15 KB
/
Copy pathtranscripts_to_text.py
File metadata and controls
87 lines (71 loc) · 3.15 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
"""
Fetch plain-text transcripts for a list of YouTube videos.
The job most people arrive with: a list of video URLs (a playlist export, a
content audit, a research corpus) and a need for one clean .txt of speech per
video, ready to feed a search index or a summariser.
pip install requests
export CHOCODATA_API_KEY="your_key"
python youtube_transcript_scraper_api_codes/transcripts_to_text.py
Reads video_urls.txt if present (one URL or id per line), otherwise runs the
sample list below. Writes one .txt per video into transcripts/ and prints a
one-line summary per video. Videos with captions disabled are reported, not
crashed on.
"""
import os
import re
import sys
import time
import requests
API = "https://api.chocodata.com/api/v1/youtube/transcript"
KEY = os.environ.get("CHOCODATA_API_KEY")
if not KEY:
sys.exit("Set CHOCODATA_API_KEY first. Free key (1,000 requests, one-time): https://chocodata.com")
SAMPLE = [
"https://www.youtube.com/watch?v=aircAruvnKk", # 3Blue1Brown, human captions
"https://www.youtube.com/watch?v=3Yqi3D4b2RQ", # NASA, short
"https://www.youtube.com/watch?v=LXb3EKWsInQ", # captions disabled -> reported
]
def load_targets() -> list:
if os.path.exists("video_urls.txt"):
with open("video_urls.txt", encoding="utf-8") as fh:
return [ln.strip() for ln in fh if ln.strip() and not ln.startswith("#")]
return SAMPLE
def fetch_text(video: str, retries: int = 1) -> dict:
"""format=text returns the combined transcript without the per-segment array."""
params = {"api_key": KEY, "format": "text"}
params["url" if "/" in video or "?" in video else "video_id"] = video
for attempt in range(retries + 1):
r = requests.get(API, params=params, timeout=90)
if r.status_code == 200:
return r.json()
if r.status_code == 401:
sys.exit("401 INVALID_API_KEY: check CHOCODATA_API_KEY. https://chocodata.com")
if r.status_code == 402:
sys.exit("402 INSUFFICIENT_CREDITS: top up or upgrade. https://chocodata.com/pricing")
if r.status_code == 429:
time.sleep(5)
continue
if r.status_code == 502 and attempt < retries:
time.sleep(8)
continue
return {"transcript_available": False, "reason": f"http_{r.status_code}"}
return {"transcript_available": False, "reason": "rate_limited"}
def slug(video: str) -> str:
m = re.search(r"([A-Za-z0-9_-]{11})", video)
return (m.group(1) if m else video)[:40]
def main() -> None:
os.makedirs("transcripts", exist_ok=True)
for video in load_targets():
data = fetch_text(video)
if not data.get("transcript_available"):
print(f" skip {slug(video)} ({data.get('reason')})")
continue
text = data.get("text", "")
path = os.path.join("transcripts", f"{slug(video)}.txt")
with open(path, "w", encoding="utf-8") as fh:
fh.write(text)
gen = "auto" if data.get("is_generated") else "human"
print(f" wrote {path} ({data.get('language')}/{gen}, "
f"{data.get('word_count')} words)")
if __name__ == "__main__":
main()