Update main.py
Browse files
main.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
import math
|
| 3 |
import logging
|
|
|
|
| 4 |
from pyrogram import Client
|
| 5 |
from pyrogram.session import Session, Auth
|
| 6 |
from pyrogram import raw
|
|
@@ -10,17 +11,12 @@ from fastapi.responses import StreamingResponse
|
|
| 10 |
import uvicorn
|
| 11 |
|
| 12 |
# --- CONFIGURATION ---
|
| 13 |
-
# These are loaded from the Secrets you set in Step 2
|
| 14 |
API_ID = int(os.environ.get("API_ID"))
|
| 15 |
API_HASH = os.environ.get("API_HASH")
|
| 16 |
BOT_TOKEN = os.environ.get("BOT_TOKEN")
|
| 17 |
STORAGE_CHANNEL = int(os.environ.get("STORAGE_CHANNEL"))
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
# --- BOT CLIENT (No Login Required) ---
|
| 22 |
-
# We use a Bot Token, so no phone number login is needed.
|
| 23 |
-
# 'no_updates=True' makes it lighter (it won't read chat messages, only files).
|
| 24 |
client = Client(
|
| 25 |
"worker_bot",
|
| 26 |
api_id=API_ID,
|
|
@@ -30,13 +26,29 @@ client = Client(
|
|
| 30 |
no_updates=True
|
| 31 |
)
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
print("β³ Connecting to Telegram...")
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
# --- STREAMING LOGIC ---
|
| 40 |
class ByteStreamer:
|
| 41 |
def __init__(self, client: Client):
|
| 42 |
self.client = client
|
|
@@ -44,7 +56,6 @@ class ByteStreamer:
|
|
| 44 |
async def yield_file(self, file_id, offset, first_part_cut, last_part_cut, part_count, chunk_size):
|
| 45 |
client = self.client
|
| 46 |
|
| 47 |
-
# 1. Create a direct Media Session (High Performance)
|
| 48 |
ms = client.media_sessions.get(file_id.dc_id)
|
| 49 |
if not ms:
|
| 50 |
if file_id.dc_id != await client.storage.dc_id():
|
|
@@ -55,7 +66,6 @@ class ByteStreamer:
|
|
| 55 |
ms = client.session
|
| 56 |
client.media_sessions[file_id.dc_id] = ms
|
| 57 |
|
| 58 |
-
# 2. Define File Location
|
| 59 |
loc = raw.types.InputDocumentFileLocation(
|
| 60 |
id=file_id.media_id,
|
| 61 |
access_hash=file_id.access_hash,
|
|
@@ -63,7 +73,6 @@ class ByteStreamer:
|
|
| 63 |
thumb_size=file_id.thumbnail_size
|
| 64 |
)
|
| 65 |
|
| 66 |
-
# 3. The Loop (Fetch Chunks)
|
| 67 |
curr = 1
|
| 68 |
while curr <= part_count:
|
| 69 |
try:
|
|
@@ -75,7 +84,6 @@ class ByteStreamer:
|
|
| 75 |
chunk = r.bytes
|
| 76 |
if not chunk: break
|
| 77 |
|
| 78 |
-
# Cut the first/last chunks if needed for seeking
|
| 79 |
if part_count == 1:
|
| 80 |
yield chunk[first_part_cut:last_part_cut]
|
| 81 |
elif curr == 1:
|
|
@@ -95,8 +103,6 @@ class ByteStreamer:
|
|
| 95 |
@app.get("/stream/{message_id}/{filename}")
|
| 96 |
async def stream_handler(req: Request, message_id: int, filename: str):
|
| 97 |
try:
|
| 98 |
-
# 1. Fetch File Info directly from Channel (No Database needed)
|
| 99 |
-
# We trust the Message ID passed in the URL
|
| 100 |
msg = await client.get_messages(STORAGE_CHANNEL, message_id)
|
| 101 |
media = msg.document or msg.video or msg.audio
|
| 102 |
|
|
@@ -106,8 +112,6 @@ async def stream_handler(req: Request, message_id: int, filename: str):
|
|
| 106 |
file_id = FileId.decode(media.file_id)
|
| 107 |
file_size = media.file_size
|
| 108 |
|
| 109 |
-
# 2. Handle Video Seeking (Range Headers)
|
| 110 |
-
# This allows the user to jump forward/backward in the video
|
| 111 |
range_header = req.headers.get("Range", 0)
|
| 112 |
from_bytes, until_bytes = 0, file_size - 1
|
| 113 |
if range_header:
|
|
@@ -116,15 +120,13 @@ async def stream_handler(req: Request, message_id: int, filename: str):
|
|
| 116 |
if s[1]: until_bytes = int(s[1])
|
| 117 |
|
| 118 |
req_len = until_bytes - from_bytes + 1
|
| 119 |
-
chunk_size = 1048576
|
| 120 |
|
| 121 |
-
# Math for calculating where to start downloading
|
| 122 |
offset = (from_bytes // chunk_size) * chunk_size
|
| 123 |
first_part_cut = from_bytes - offset
|
| 124 |
last_part_cut = (until_bytes % chunk_size) + 1
|
| 125 |
part_count = math.ceil(req_len / chunk_size)
|
| 126 |
|
| 127 |
-
# 3. Start Streaming
|
| 128 |
streamer = ByteStreamer(client)
|
| 129 |
body = streamer.yield_file(file_id, offset, first_part_cut, last_part_cut, part_count, chunk_size)
|
| 130 |
|
|
@@ -143,6 +145,4 @@ async def stream_handler(req: Request, message_id: int, filename: str):
|
|
| 143 |
raise HTTPException(404, "File Not Found")
|
| 144 |
|
| 145 |
if __name__ == "__main__":
|
| 146 |
-
# Hugging Face Spaces expect the app to run on port 7860
|
| 147 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 148 |
-
|
|
|
|
| 1 |
import os
|
| 2 |
import math
|
| 3 |
import logging
|
| 4 |
+
from contextlib import asynccontextmanager # <--- NEW IMPORT
|
| 5 |
from pyrogram import Client
|
| 6 |
from pyrogram.session import Session, Auth
|
| 7 |
from pyrogram import raw
|
|
|
|
| 11 |
import uvicorn
|
| 12 |
|
| 13 |
# --- CONFIGURATION ---
|
|
|
|
| 14 |
API_ID = int(os.environ.get("API_ID"))
|
| 15 |
API_HASH = os.environ.get("API_HASH")
|
| 16 |
BOT_TOKEN = os.environ.get("BOT_TOKEN")
|
| 17 |
STORAGE_CHANNEL = int(os.environ.get("STORAGE_CHANNEL"))
|
| 18 |
|
| 19 |
+
# --- BOT CLIENT ---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
client = Client(
|
| 21 |
"worker_bot",
|
| 22 |
api_id=API_ID,
|
|
|
|
| 26 |
no_updates=True
|
| 27 |
)
|
| 28 |
|
| 29 |
+
# --- π NEW LIFESPAN LOGIC ---
|
| 30 |
+
# This replaces @app.on_event("startup")
|
| 31 |
+
@asynccontextmanager
|
| 32 |
+
async def lifespan(app: FastAPI):
|
| 33 |
print("β³ Connecting to Telegram...")
|
| 34 |
+
try:
|
| 35 |
+
await client.start()
|
| 36 |
+
print("β
Worker Started & Connected!")
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"β Failed to connect: {e}")
|
| 39 |
+
|
| 40 |
+
yield # The application runs here
|
| 41 |
+
|
| 42 |
+
print("π Stopping Worker...")
|
| 43 |
+
try:
|
| 44 |
+
await client.stop()
|
| 45 |
+
except:
|
| 46 |
+
pass
|
| 47 |
+
|
| 48 |
+
# Initialize FastAPI with the lifespan handler
|
| 49 |
+
app = FastAPI(lifespan=lifespan)
|
| 50 |
|
| 51 |
+
# --- STREAMING LOGIC (Unchanged) ---
|
| 52 |
class ByteStreamer:
|
| 53 |
def __init__(self, client: Client):
|
| 54 |
self.client = client
|
|
|
|
| 56 |
async def yield_file(self, file_id, offset, first_part_cut, last_part_cut, part_count, chunk_size):
|
| 57 |
client = self.client
|
| 58 |
|
|
|
|
| 59 |
ms = client.media_sessions.get(file_id.dc_id)
|
| 60 |
if not ms:
|
| 61 |
if file_id.dc_id != await client.storage.dc_id():
|
|
|
|
| 66 |
ms = client.session
|
| 67 |
client.media_sessions[file_id.dc_id] = ms
|
| 68 |
|
|
|
|
| 69 |
loc = raw.types.InputDocumentFileLocation(
|
| 70 |
id=file_id.media_id,
|
| 71 |
access_hash=file_id.access_hash,
|
|
|
|
| 73 |
thumb_size=file_id.thumbnail_size
|
| 74 |
)
|
| 75 |
|
|
|
|
| 76 |
curr = 1
|
| 77 |
while curr <= part_count:
|
| 78 |
try:
|
|
|
|
| 84 |
chunk = r.bytes
|
| 85 |
if not chunk: break
|
| 86 |
|
|
|
|
| 87 |
if part_count == 1:
|
| 88 |
yield chunk[first_part_cut:last_part_cut]
|
| 89 |
elif curr == 1:
|
|
|
|
| 103 |
@app.get("/stream/{message_id}/{filename}")
|
| 104 |
async def stream_handler(req: Request, message_id: int, filename: str):
|
| 105 |
try:
|
|
|
|
|
|
|
| 106 |
msg = await client.get_messages(STORAGE_CHANNEL, message_id)
|
| 107 |
media = msg.document or msg.video or msg.audio
|
| 108 |
|
|
|
|
| 112 |
file_id = FileId.decode(media.file_id)
|
| 113 |
file_size = media.file_size
|
| 114 |
|
|
|
|
|
|
|
| 115 |
range_header = req.headers.get("Range", 0)
|
| 116 |
from_bytes, until_bytes = 0, file_size - 1
|
| 117 |
if range_header:
|
|
|
|
| 120 |
if s[1]: until_bytes = int(s[1])
|
| 121 |
|
| 122 |
req_len = until_bytes - from_bytes + 1
|
| 123 |
+
chunk_size = 1048576
|
| 124 |
|
|
|
|
| 125 |
offset = (from_bytes // chunk_size) * chunk_size
|
| 126 |
first_part_cut = from_bytes - offset
|
| 127 |
last_part_cut = (until_bytes % chunk_size) + 1
|
| 128 |
part_count = math.ceil(req_len / chunk_size)
|
| 129 |
|
|
|
|
| 130 |
streamer = ByteStreamer(client)
|
| 131 |
body = streamer.yield_file(file_id, offset, first_part_cut, last_part_cut, part_count, chunk_size)
|
| 132 |
|
|
|
|
| 145 |
raise HTTPException(404, "File Not Found")
|
| 146 |
|
| 147 |
if __name__ == "__main__":
|
|
|
|
| 148 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|