Spaces:
Sleeping
Sleeping
File size: 5,764 Bytes
e573d34 5847526 e573d34 a3436de e573d34 f1c5d90 5847526 a3436de 5847526 a3436de 5847526 a3436de 5847526 a3436de 0f0adb1 5847526 0f0adb1 5847526 0f0adb1 5847526 0f0adb1 5847526 0f0adb1 5847526 0f0adb1 5847526 e573d34 5847526 f1c5d90 e573d34 5847526 e573d34 | 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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import requests
import re
import json
from pydantic import BaseModel
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class TranscriptRequest(BaseModel):
url: str
class TranslateRequest(BaseModel):
text: str
targetLang: str
def extract_video_id(url: str) -> str:
pattern = r'(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})'
match = re.search(pattern, url)
return match.group(1) if match else None
def parse_caption_xml(xml_string: str) -> str:
text_matches = re.findall(r'<text[^>]*>([^<]*)</text>', xml_string)
if not text_matches:
return ""
full_text = " ".join(text_matches)
full_text = full_text.replace("&#39;", "'").replace("&quot;", '"').replace("&amp;", "&").replace("'", "'")
return full_text
def fetch_via_proxy(proxy_base: str, target_url: str, timeout: int = 15) -> requests.Response:
"""Fetches a URL through a public CORS/Web proxy."""
encoded_url = requests.utils.quote(target_url, safe='')
proxy_url = f"{proxy_base}{encoded_url}"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/json"
}
response = requests.get(proxy_url, headers=headers, timeout=timeout)
if response.status_code != 200:
raise Exception(f"Proxy returned status {response.status_code}")
return response
@app.post("/api/transcript")
async def get_transcript(req: TranscriptRequest):
video_id = extract_video_id(req.url)
if not video_id:
raise HTTPException(status_code=400, detail="Invalid YouTube URL")
# List of reliable public web proxies
proxies = [
"https://api.allorigins.win/raw?url=",
"https://corsproxy.io/?",
"https://api.codetabs.com/v1/proxy?quest="
]
for proxy in proxies:
try:
print(f"Trying proxy {proxy} for video {video_id}...")
# 1. Fetch YouTube Watch Page HTML via Proxy
yt_url = f"https://www.youtube.com/watch?v={video_id}"
response = fetch_via_proxy(proxy, yt_url, timeout=15)
html = response.text
# 2. Find the ytInitialPlayerResponse JSON in the HTML
match = re.search(r'ytInitialPlayerResponse\s*=\s*(\{.*?\})\s*;', html, re.DOTALL)
if not match:
print(f"Failed to find player data via {proxy}")
continue
data = json.loads(match.group(1))
# Check playability
playability = data.get("playabilityStatus", {})
if playability.get("status") == "ERROR":
raise Exception(playability.get("reason", "Video unavailable"))
# 3. Extract caption tracks
caption_tracks = data.get("captions", {}).get("playerCaptionsTracklistRenderer", {}).get("captionTracks", [])
if not caption_tracks:
print(f"No captions found via {proxy}")
continue
# Prefer English, fallback to first available
selected_track = next((t for t in caption_tracks if t.get("languageCode") == "en"), caption_tracks[0])
caption_url = selected_track.get("baseUrl")
if not caption_url:
continue
print(f"Found captions, fetching XML via proxy...")
# 4. Fetch the actual caption XML via the same proxy
caption_response = fetch_via_proxy(proxy, caption_url, timeout=10)
# 5. Parse the XML into plain text
full_text = parse_caption_xml(caption_response.text)
if not full_text.strip():
continue
print(f"✅ Successfully fetched transcript via {proxy}. Length: {len(full_text)} chars")
return {"text": full_text}
except Exception as e:
print(f"❌ Proxy {proxy} failed: {e}")
continue
raise HTTPException(status_code=500, detail="Failed to fetch transcript. All proxy methods failed or video has no captions.")
# ========================================================
# TRANSLATE API
# ========================================================
@app.post("/api/translate")
async def translate_text(req: TranslateRequest):
if not req.text or not req.targetLang:
raise HTTPException(status_code=400, detail="Missing text or target language")
try:
url = f"https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl={req.targetLang}&dt=t"
res = requests.post(url,
headers={
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
},
data=f"q={requests.utils.quote(req.text)}",
timeout=15
)
if res.status_code != 200:
raise Exception(f"Google API returned {res.status_code}")
data = res.json()
translated = "".join([item[0] for item in data[0]])
return {"translatedText": translated}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Translation failed: {str(e)}")
# KEEP-ALIVE ENDPOINT
@app.get("/api/health")
async def health_check():
return {"status": "ok", "message": "Transcript API is running!"} |