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("&amp;#39;", "'").replace("&amp;quot;", '"').replace("&amp;amp;", "&").replace("&#39;", "'")
    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!"}