| import uvicorn |
| import httpx |
| from fastapi import FastAPI, Response, Request, HTTPException |
| from fastapi.responses import StreamingResponse |
| from fastapi.middleware.cors import CORSMiddleware |
| from contextlib import asynccontextmanager |
|
|
| |
| 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", |
| "Referer": "https://media.javtrailers.com/", |
| } |
|
|
| |
| http_client: httpx.AsyncClient = None |
|
|
| |
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| global http_client |
| http_client = httpx.AsyncClient( |
| headers=HEADERS, |
| timeout=httpx.Timeout(10.0, read=30.0), |
| follow_redirects=True |
| ) |
| yield |
| await http_client.aclose() |
|
|
| app = FastAPI(lifespan=lifespan) |
|
|
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
|
|
| def rewrite_m3u8_content(content_text: str, base_url: str) -> str: |
| lines = content_text.splitlines() |
| new_lines = [] |
| |
| for line in lines: |
| line = line.strip() |
| if not line: |
| continue |
| |
| if line.startswith("#"): |
| new_lines.append(line) |
| elif line.startswith("http://") or line.startswith("https://"): |
| new_lines.append(line) |
| else: |
| new_lines.append(f"/proxy-segment?base={base_url}&segment={line}") |
| |
| return "\n".join(new_lines) |
|
|
| async def stream_generator(response: httpx.Response): |
| """Safely iterates over the stream, handling upstream disconnects.""" |
| try: |
| async for chunk in response.aiter_raw(): |
| yield chunk |
| except (httpx.ReadError, httpx.RemoteProtocolError, httpx.ConnectError) as e: |
| print(f"Warning: Stream interrupted by upstream: {e}") |
| finally: |
| await response.aclose() |
|
|
| |
|
|
| @app.get("/") |
| def home(): |
| return { |
| "status": "Running", |
| "usage_hls": "/proxy-playlist?url=...", |
| "usage_mp4": "/proxy-video?url=..." |
| } |
|
|
| |
| |
| |
| @app.get("/proxy-video") |
| async def proxy_video(url: str, request: Request): |
| try: |
| |
| |
| req_headers = {} |
| if "range" in request.headers: |
| req_headers["Range"] = request.headers["range"] |
|
|
| |
| req = http_client.build_request("GET", url, headers=req_headers) |
| |
| |
| r = await http_client.send(req, stream=True) |
| |
| |
| if r.status_code >= 400: |
| await r.aclose() |
| return Response(content=f"Upstream Error {r.status_code}", status_code=r.status_code) |
|
|
| |
| |
| resp_headers = { |
| "Accept-Ranges": "bytes", |
| "Content-Type": r.headers.get("Content-Type", "video/mp4"), |
| } |
| |
| |
| if "Content-Length" in r.headers: |
| resp_headers["Content-Length"] = r.headers["Content-Length"] |
| if "Content-Range" in r.headers: |
| resp_headers["Content-Range"] = r.headers["Content-Range"] |
|
|
| |
| |
| return StreamingResponse( |
| stream_generator(r), |
| status_code=r.status_code, |
| headers=resp_headers, |
| media_type="video/mp4" |
| ) |
|
|
| except httpx.RequestError as e: |
| return Response(content=f"Video Error: {e}", status_code=502) |
|
|
|
|
| |
|
|
| @app.get("/proxy-playlist") |
| async def proxy_playlist(url: str): |
| try: |
| resp = await http_client.get(url) |
| resp.raise_for_status() |
| |
| base_url = str(resp.url).rsplit('/', 1)[0] + '/' |
| modified_m3u8 = rewrite_m3u8_content(resp.text, base_url) |
| |
| return Response(content=modified_m3u8, media_type="application/vnd.apple.mpegurl") |
| except httpx.RequestError as e: |
| return Response(content=f"Proxy Error: {e}", status_code=502) |
|
|
| @app.get("/proxy-segment") |
| async def proxy_segment(base: str, segment: str): |
| full_url = base + segment |
| |
| |
| if segment.split('?')[0].endswith('.m3u8'): |
| try: |
| resp = await http_client.get(full_url) |
| resp.raise_for_status() |
| |
| new_base = str(resp.url).rsplit('/', 1)[0] + '/' |
| modified_m3u8 = rewrite_m3u8_content(resp.text, new_base) |
| |
| return Response(content=modified_m3u8, media_type="application/vnd.apple.mpegurl") |
| except httpx.RequestError as e: |
| return Response(content=f"Playlist Error: {e}", status_code=502) |
|
|
| |
| try: |
| req = http_client.build_request("GET", full_url) |
| r = await http_client.send(req, stream=True) |
| |
| if r.status_code >= 400: |
| await r.aclose() |
| return Response(content=f"Upstream Error {r.status_code}", status_code=r.status_code) |
|
|
| return StreamingResponse( |
| stream_generator(r), |
| media_type="video/MP2T" |
| ) |
| except httpx.RequestError as e: |
| print(f"Connection failed for {segment}: {e}") |
| return Response(content="Segment unavailable", status_code=502) |
|
|
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |