Spaces:
Running
Running
File size: 5,979 Bytes
0d8ae88 9ee80cc 0d8ae88 d469d1d 0d8ae88 d469d1d 0d8ae88 d469d1d 9ee80cc d469d1d 9ee80cc d469d1d 0d8ae88 ce09ed7 0d8ae88 d469d1d ce09ed7 d469d1d ce09ed7 d469d1d 9ee80cc d469d1d 0d8ae88 9ee80cc 0d8ae88 ce09ed7 d469d1d 0d8ae88 d469d1d ce09ed7 d469d1d 9ee80cc d469d1d ce09ed7 d469d1d ce09ed7 d469d1d ce09ed7 | 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | import uvicorn
import httpx
from fastapi import FastAPI, Response, Request, HTTPException # Added Request
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
# --- Configuration ---
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/",
}
# Global client variable
http_client: httpx.AsyncClient = None
# --- Lifespan Management ---
@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)
# Allow CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# --- Helpers ---
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()
# --- Routes ---
@app.get("/")
def home():
return {
"status": "Running",
"usage_hls": "/proxy-playlist?url=...",
"usage_mp4": "/proxy-video?url=..."
}
# ---------------------------------------------------------
# NEW: MP4 Proxy Support (Supports Seeking/Scrubbing)
# ---------------------------------------------------------
@app.get("/proxy-video")
async def proxy_video(url: str, request: Request):
try:
# 1. Forward the 'Range' header if the browser sent one.
# This allows the browser to request specific byte chunks (seeking).
req_headers = {}
if "range" in request.headers:
req_headers["Range"] = request.headers["range"]
# 2. Build the request manually
req = http_client.build_request("GET", url, headers=req_headers)
# 3. Send request (stream=True)
r = await http_client.send(req, stream=True)
# 4. Handle errors
if r.status_code >= 400:
await r.aclose()
return Response(content=f"Upstream Error {r.status_code}", status_code=r.status_code)
# 5. Prepare Response Headers
# We must forward Content-Range/Length so the browser knows the video size.
resp_headers = {
"Accept-Ranges": "bytes", # Tell browser we support seeking
"Content-Type": r.headers.get("Content-Type", "video/mp4"),
}
# Only add these if they exist in the upstream response
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"]
# 6. Return StreamingResponse
# We pass r.status_code (usually 206 or 200) directly.
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)
# --- Existing HLS Routes ---
@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
# CASE 1: Nested Playlist (.m3u8)
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)
# CASE 2: Video Segment (.ts)
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) |