bigbossmonster commited on
Commit
9ee80cc
·
verified ·
1 Parent(s): d469d1d

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +61 -20
main.py CHANGED
@@ -1,12 +1,11 @@
1
  import uvicorn
2
  import httpx
3
- from fastapi import FastAPI, Response, HTTPException
4
  from fastapi.responses import StreamingResponse
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from contextlib import asynccontextmanager
7
 
8
  # --- Configuration ---
9
- # Mimic a real browser to avoid being blocked by the video server
10
  HEADERS = {
11
  "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",
12
  "Referer": "https://media.javtrailers.com/",
@@ -15,18 +14,16 @@ HEADERS = {
15
  # Global client variable
16
  http_client: httpx.AsyncClient = None
17
 
18
- # --- Lifespan Management (Best Practice) ---
19
  @asynccontextmanager
20
  async def lifespan(app: FastAPI):
21
- # Startup: Create a shared client with longer timeouts
22
  global http_client
23
  http_client = httpx.AsyncClient(
24
  headers=HEADERS,
25
- timeout=httpx.Timeout(10.0, read=30.0), # 30s read timeout for video buffering
26
  follow_redirects=True
27
  )
28
  yield
29
- # Shutdown: Clean up resources
30
  await http_client.aclose()
31
 
32
  app = FastAPI(lifespan=lifespan)
@@ -56,36 +53,84 @@ def rewrite_m3u8_content(content_text: str, base_url: str) -> str:
56
  elif line.startswith("http://") or line.startswith("https://"):
57
  new_lines.append(line)
58
  else:
59
- # Rewrite relative paths
60
  new_lines.append(f"/proxy-segment?base={base_url}&segment={line}")
61
 
62
  return "\n".join(new_lines)
63
 
64
  async def stream_generator(response: httpx.Response):
65
- """
66
- Safely iterates over the stream.
67
- If the upstream server cuts the connection (ReadError), we stop gracefully
68
- instead of crashing the whole worker.
69
- """
70
  try:
71
  async for chunk in response.aiter_raw():
72
  yield chunk
73
  except (httpx.ReadError, httpx.RemoteProtocolError, httpx.ConnectError) as e:
74
  print(f"Warning: Stream interrupted by upstream: {e}")
75
  finally:
76
- # Always close the response explicitly
77
  await response.aclose()
78
 
79
  # --- Routes ---
80
 
81
  @app.get("/")
82
  def home():
83
- return {"status": "Running", "usage": "/proxy-playlist?url=..."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  @app.get("/proxy-playlist")
86
  async def proxy_playlist(url: str):
87
  try:
88
- # Use the global client
89
  resp = await http_client.get(url)
90
  resp.raise_for_status()
91
 
@@ -113,15 +158,11 @@ async def proxy_segment(base: str, segment: str):
113
  except httpx.RequestError as e:
114
  return Response(content=f"Playlist Error: {e}", status_code=502)
115
 
116
- # CASE 2: Video Segment (.ts) - STREAMING
117
  try:
118
- # Build request manually to fine-tune streaming
119
  req = http_client.build_request("GET", full_url)
120
-
121
- # Send request but DO NOT read body yet (stream=True)
122
  r = await http_client.send(req, stream=True)
123
 
124
- # If upstream returns 404 or 403, fail early
125
  if r.status_code >= 400:
126
  await r.aclose()
127
  return Response(content=f"Upstream Error {r.status_code}", status_code=r.status_code)
 
1
  import uvicorn
2
  import httpx
3
+ from fastapi import FastAPI, Response, Request, HTTPException # Added Request
4
  from fastapi.responses import StreamingResponse
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from contextlib import asynccontextmanager
7
 
8
  # --- Configuration ---
 
9
  HEADERS = {
10
  "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",
11
  "Referer": "https://media.javtrailers.com/",
 
14
  # Global client variable
15
  http_client: httpx.AsyncClient = None
16
 
17
+ # --- Lifespan Management ---
18
  @asynccontextmanager
19
  async def lifespan(app: FastAPI):
 
20
  global http_client
21
  http_client = httpx.AsyncClient(
22
  headers=HEADERS,
23
+ timeout=httpx.Timeout(10.0, read=30.0),
24
  follow_redirects=True
25
  )
26
  yield
 
27
  await http_client.aclose()
28
 
29
  app = FastAPI(lifespan=lifespan)
 
53
  elif line.startswith("http://") or line.startswith("https://"):
54
  new_lines.append(line)
55
  else:
 
56
  new_lines.append(f"/proxy-segment?base={base_url}&segment={line}")
57
 
58
  return "\n".join(new_lines)
59
 
60
  async def stream_generator(response: httpx.Response):
61
+ """Safely iterates over the stream, handling upstream disconnects."""
 
 
 
 
62
  try:
63
  async for chunk in response.aiter_raw():
64
  yield chunk
65
  except (httpx.ReadError, httpx.RemoteProtocolError, httpx.ConnectError) as e:
66
  print(f"Warning: Stream interrupted by upstream: {e}")
67
  finally:
 
68
  await response.aclose()
69
 
70
  # --- Routes ---
71
 
72
  @app.get("/")
73
  def home():
74
+ return {
75
+ "status": "Running",
76
+ "usage_hls": "/proxy-playlist?url=...",
77
+ "usage_mp4": "/proxy-video?url=..."
78
+ }
79
+
80
+ # ---------------------------------------------------------
81
+ # NEW: MP4 Proxy Support (Supports Seeking/Scrubbing)
82
+ # ---------------------------------------------------------
83
+ @app.get("/proxy-video")
84
+ async def proxy_video(url: str, request: Request):
85
+ try:
86
+ # 1. Forward the 'Range' header if the browser sent one.
87
+ # This allows the browser to request specific byte chunks (seeking).
88
+ req_headers = {}
89
+ if "range" in request.headers:
90
+ req_headers["Range"] = request.headers["range"]
91
+
92
+ # 2. Build the request manually
93
+ req = http_client.build_request("GET", url, headers=req_headers)
94
+
95
+ # 3. Send request (stream=True)
96
+ r = await http_client.send(req, stream=True)
97
+
98
+ # 4. Handle errors
99
+ if r.status_code >= 400:
100
+ await r.aclose()
101
+ return Response(content=f"Upstream Error {r.status_code}", status_code=r.status_code)
102
+
103
+ # 5. Prepare Response Headers
104
+ # We must forward Content-Range/Length so the browser knows the video size.
105
+ resp_headers = {
106
+ "Accept-Ranges": "bytes", # Tell browser we support seeking
107
+ "Content-Type": r.headers.get("Content-Type", "video/mp4"),
108
+ }
109
+
110
+ # Only add these if they exist in the upstream response
111
+ if "Content-Length" in r.headers:
112
+ resp_headers["Content-Length"] = r.headers["Content-Length"]
113
+ if "Content-Range" in r.headers:
114
+ resp_headers["Content-Range"] = r.headers["Content-Range"]
115
+
116
+ # 6. Return StreamingResponse
117
+ # We pass r.status_code (usually 206 or 200) directly.
118
+ return StreamingResponse(
119
+ stream_generator(r),
120
+ status_code=r.status_code,
121
+ headers=resp_headers,
122
+ media_type="video/mp4"
123
+ )
124
+
125
+ except httpx.RequestError as e:
126
+ return Response(content=f"Video Error: {e}", status_code=502)
127
+
128
+
129
+ # --- Existing HLS Routes ---
130
 
131
  @app.get("/proxy-playlist")
132
  async def proxy_playlist(url: str):
133
  try:
 
134
  resp = await http_client.get(url)
135
  resp.raise_for_status()
136
 
 
158
  except httpx.RequestError as e:
159
  return Response(content=f"Playlist Error: {e}", status_code=502)
160
 
161
+ # CASE 2: Video Segment (.ts)
162
  try:
 
163
  req = http_client.build_request("GET", full_url)
 
 
164
  r = await http_client.send(req, stream=True)
165
 
 
166
  if r.status_code >= 400:
167
  await r.aclose()
168
  return Response(content=f"Upstream Error {r.status_code}", status_code=r.status_code)