ztoolx commited on
Commit
a78de46
·
verified ·
1 Parent(s): 3585061
Files changed (1) hide show
  1. main.py +28 -25
main.py CHANGED
@@ -2,6 +2,7 @@ from fastapi import FastAPI, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from curl_cffi import requests as cffi_requests
4
  import re
 
5
  from pydantic import BaseModel
6
 
7
  app = FastAPI()
@@ -42,45 +43,47 @@ async def get_transcript(req: TranscriptRequest):
42
  raise HTTPException(status_code=400, detail="Invalid YouTube URL")
43
 
44
  try:
45
- print(f"Fetching InnerTube data for video_id: {video_id} using Chrome Impersonation...")
46
 
47
- # YouTube's internal API endpoint
48
- innertube_url = "https://www.youtube.com/youtubei/v1/player?prettyPrint=false"
49
-
50
- payload = {
51
- "context": {
52
- "client": {
53
- "clientName": "WEB",
54
- "clientVersion": "2.20241210.00.00",
55
- "hl": "en",
56
- "gl": "US"
57
- }
58
- },
59
- "videoId": video_id
60
- }
61
 
62
  headers = {
63
- "Content-Type": "application/json",
64
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
65
- "Origin": "https://www.youtube.com",
 
66
  "Referer": "https://www.youtube.com/",
67
  "Cookie": "CONSENT=YES+1; SOCS=CAESHAgBEhJnd3NfMjAyMzEwMjUtMF9SQzIaAmVuIAEaBgiA-LaoBg"
68
  }
69
 
70
- # 1. Fetch the video player data (Impersonating Chrome to bypass SSLError)
71
- response = cffi_requests.post(innertube_url, json=payload, headers=headers, impersonate="chrome131", timeout=10)
72
 
73
  if response.status_code != 200:
74
- raise HTTPException(status_code=500, detail=f"YouTube API returned status {response.status_code}")
75
 
76
- data = response.json()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  # Check if video is playable
79
  playability = data.get("playabilityStatus", {})
80
  if playability.get("status") == "ERROR":
81
  raise HTTPException(status_code=404, detail=playability.get("reason", "Video unavailable"))
82
 
83
- # 2. Extract caption tracks
84
  caption_tracks = data.get("captions", {}).get("playerCaptionsTracklistRenderer", {}).get("captionTracks", [])
85
 
86
  if not caption_tracks:
@@ -100,15 +103,15 @@ async def get_transcript(req: TranscriptRequest):
100
  if not caption_url:
101
  raise HTTPException(status_code=404, detail="Caption URL not found.")
102
 
103
- print(f"Fetching captions from URL using Chrome Impersonation...")
104
 
105
- # 3. Fetch the actual caption XML (Impersonating Chrome)
106
  caption_response = cffi_requests.get(caption_url, impersonate="chrome131", timeout=10)
107
 
108
  if caption_response.status_code != 200:
109
  raise HTTPException(status_code=500, detail=f"Caption fetch failed with status {caption_response.status_code}")
110
 
111
- # 4. Parse the XML into plain text
112
  full_text = parse_caption_xml(caption_response.text)
113
 
114
  if not full_text.strip():
 
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from curl_cffi import requests as cffi_requests
4
  import re
5
+ import json
6
  from pydantic import BaseModel
7
 
8
  app = FastAPI()
 
43
  raise HTTPException(status_code=400, detail="Invalid YouTube URL")
44
 
45
  try:
46
+ print(f"Scraping HTML page for video_id: {video_id} using Chrome Impersonation...")
47
 
48
+ # 1. Fetch the actual YouTube watch page HTML (Exactly like your local Node app does)
49
+ html_url = f"https://www.youtube.com/watch?v={video_id}"
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  headers = {
 
52
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
53
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
54
+ "Accept-Language": "en-US,en;q=0.9",
55
  "Referer": "https://www.youtube.com/",
56
  "Cookie": "CONSENT=YES+1; SOCS=CAESHAgBEhJnd3NfMjAyMzEwMjUtMF9SQzIaAmVuIAEaBgiA-LaoBg"
57
  }
58
 
59
+ # Impersonate Chrome 131 to bypass SSL/TLS fingerprinting
60
+ response = cffi_requests.get(html_url, headers=headers, impersonate="chrome131", timeout=15)
61
 
62
  if response.status_code != 200:
63
+ raise HTTPException(status_code=500, detail=f"YouTube returned status {response.status_code}")
64
 
65
+ html = response.text
66
+
67
+ # 2. Find the ytInitialPlayerResponse JSON embedded in the HTML
68
+ match = re.search(r'var ytInitialPlayerResponse\s*=\s*(\{.*?\});', html)
69
+ if not match:
70
+ match = re.search(r'ytInitialPlayerResponse\s*=\s*(\{.*?\});', html)
71
+
72
+ if not match:
73
+ raise HTTPException(status_code=404, detail="Could not find player data in HTML. YouTube might be blocking the request.")
74
+
75
+ # 3. Parse the JSON data
76
+ try:
77
+ data = json.loads(match.group(1))
78
+ except json.JSONDecodeError:
79
+ raise HTTPException(status_code=500, detail="Failed to parse YouTube player data.")
80
 
81
  # Check if video is playable
82
  playability = data.get("playabilityStatus", {})
83
  if playability.get("status") == "ERROR":
84
  raise HTTPException(status_code=404, detail=playability.get("reason", "Video unavailable"))
85
 
86
+ # 4. Extract caption tracks
87
  caption_tracks = data.get("captions", {}).get("playerCaptionsTracklistRenderer", {}).get("captionTracks", [])
88
 
89
  if not caption_tracks:
 
103
  if not caption_url:
104
  raise HTTPException(status_code=404, detail="Caption URL not found.")
105
 
106
+ print(f"Found captions, fetching XML from {caption_url}...")
107
 
108
+ # 5. Fetch the actual caption XML
109
  caption_response = cffi_requests.get(caption_url, impersonate="chrome131", timeout=10)
110
 
111
  if caption_response.status_code != 200:
112
  raise HTTPException(status_code=500, detail=f"Caption fetch failed with status {caption_response.status_code}")
113
 
114
+ # 6. Parse the XML into plain text
115
  full_text = parse_caption_xml(caption_response.text)
116
 
117
  if not full_text.strip():