rkihacker commited on
Commit
b416e9f
·
verified ·
1 Parent(s): 2b7fa05

Create main.py

Browse files
Files changed (1) hide show
  1. main.py +153 -0
main.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main.py
2
+
3
+ import requests
4
+ import uuid
5
+ import time
6
+ import brotli
7
+ import json
8
+ import asyncio
9
+ from typing import Literal
10
+
11
+ from fastapi import FastAPI, HTTPException
12
+ from pydantic import BaseModel, Field
13
+
14
+ # --- FastAPI App Initialization ---
15
+ app = FastAPI(
16
+ title="Sora Video Generation API",
17
+ description="An unofficial API wrapper to generate videos using the Bylo.ai Sora model.",
18
+ version="1.0.0",
19
+ )
20
+
21
+ # --- Pydantic Models for Request and Response ---
22
+ class SoraRequest(BaseModel):
23
+ prompt: str = Field(..., description="The text prompt to generate the video from.")
24
+ ratio: Literal["portrait", "landscape"] = Field(
25
+ "portrait",
26
+ description="The aspect ratio of the generated video."
27
+ )
28
+
29
+ # --- Core Logic (Adapted from your script) ---
30
+
31
+ def decode_response(resp: requests.Response):
32
+ """
33
+ Safely decode response: Brotli → UTF-8 → JSON, with automatic fallback if Brotli fails.
34
+ """
35
+ raw_data = resp.content
36
+ encoding = resp.headers.get("Content-Encoding", "").lower()
37
+
38
+ if encoding == "br":
39
+ try:
40
+ raw_data = brotli.decompress(raw_data)
41
+ except brotli.error:
42
+ pass # Fall back to raw UTF-8 if not actually Brotli compressed
43
+
44
+ try:
45
+ return json.loads(raw_data.decode("utf-8"))
46
+ except Exception as je:
47
+ snippet = raw_data[:500]
48
+ raise RuntimeError(f"Failed to decode JSON: {je}\nRaw response snippet:\n{snippet}")
49
+
50
+ def sora(prompt: str, ratio: str = "portrait"):
51
+ """
52
+ The core function to interact with the Bylo.ai Sora API.
53
+ This is a synchronous function intended to be run in a thread.
54
+ """
55
+ if not prompt:
56
+ raise ValueError("Prompt is required.")
57
+ if ratio not in ["portrait", "landscape"]:
58
+ raise ValueError("Available ratios: portrait, landscape.")
59
+
60
+ base_url = "https://api.bylo.ai/aimodels/api/v1/ai"
61
+
62
+ headers = {
63
+ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
64
+ "accept-language": "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7",
65
+ "accept-encoding": "gzip, deflate, br",
66
+ "cache-control": "max-age=0",
67
+ "connection": "keep-alive",
68
+ "content-type": "application/json; charset=UTF-8",
69
+ "dnt": "1",
70
+ "origin": "https://bylo.ai",
71
+ "pragma": "no-cache",
72
+ "referer": "https://bylo.ai/features/sora-2",
73
+ "sec-ch-prefers-color-scheme": "dark",
74
+ "sec-ch-ua": '"Chromium";v="137", "Not/A)Brand";v="24"',
75
+ "sec-ch-ua-mobile": "?1",
76
+ "user-agent": "Mozilla/5.0 (Linux; Android 15; SM-F958 Build/AP3A.240905.015) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.86 Mobile Safari/537.36",
77
+ "x-requested-with": "XMLHttpRequest",
78
+ "uniqueId": str(uuid.uuid4()).replace("-", "")
79
+ }
80
+
81
+ payload = {
82
+ "prompt": prompt,
83
+ "channel": "SORA2",
84
+ "pageId": 536,
85
+ "source": "bylo.ai",
86
+ "watermarkFlag": True,
87
+ "privateFlag": True,
88
+ "isTemp": True,
89
+ "vipFlag": True,
90
+ "model": "sora_video2",
91
+ "videoType": "text-to-video",
92
+ "aspectRatio": ratio
93
+ }
94
+
95
+ try:
96
+ # STEP 1: Create generation task
97
+ create_resp = requests.post(f"{base_url}/video/create", headers=headers, json=payload)
98
+ create_resp.raise_for_status()
99
+ task_data = decode_response(create_resp)
100
+
101
+ task_id = task_data.get("data")
102
+ if not task_id:
103
+ raise RuntimeError(f"API did not return task_id. Full response:\n{json.dumps(task_data, indent=2)}")
104
+
105
+ # STEP 2: Poll until done
106
+ # Set a reasonable timeout to prevent infinite loops
107
+ polling_start_time = time.time()
108
+ timeout_seconds = 300 # 5 minutes
109
+
110
+ while True:
111
+ if time.time() - polling_start_time > timeout_seconds:
112
+ raise RuntimeError("Polling timed out after 5 minutes.")
113
+
114
+ poll_resp = requests.get(f"{base_url}/{task_id}?channel=SORA2", headers=headers)
115
+ poll_resp.raise_for_status()
116
+ poll_json = decode_response(poll_resp)
117
+
118
+ state = poll_json.get("data", {}).get("state", 0)
119
+ if state > 0:
120
+ complete_data_str = poll_json.get("data", {}).get("completeData")
121
+ if not complete_data_str:
122
+ raise RuntimeError(f"Generation completed but no 'completeData' found. Full response: \n{poll_json}")
123
+ return json.loads(complete_data_str)
124
+
125
+ time.sleep(5) # Increased sleep time for polling
126
+
127
+ except requests.exceptions.RequestException as re:
128
+ raise RuntimeError(f"Network error during Sora generation: {re}")
129
+ except Exception as e:
130
+ # Re-raise exceptions to be caught by the endpoint handler
131
+ raise RuntimeError(f"Sora generation failed: {e}")
132
+
133
+
134
+ # --- API Endpoint ---
135
+ @app.post("/generate", summary="Create a new Sora video")
136
+ async def generate_video(request: SoraRequest):
137
+ """
138
+ Accepts a prompt and ratio, then generates a video by polling the Bylo.ai API.
139
+ """
140
+ try:
141
+ # Run the synchronous, blocking sora function in a separate thread
142
+ result = await asyncio.to_thread(sora, request.prompt, request.ratio)
143
+ return result
144
+ except ValueError as ve:
145
+ # Handle bad input from the user
146
+ raise HTTPException(status_code=400, detail=str(ve))
147
+ except RuntimeError as re:
148
+ # Handle errors from the external API or other processing issues
149
+ raise HTTPException(status_code=500, detail=str(re))
150
+
151
+ @app.get("/", include_in_schema=False)
152
+ def root():
153
+ return {"message": "Sora API is running. See /docs for usage."}