feat: 普号增加25s普通视频、增加pro系列高清模型、统一模型名字、过载不再计入错误禁用计数

This commit is contained in:
TheSmallHanCat
2025-12-24 19:39:28 +08:00
parent 2c2fd44b6a
commit fad83533bf
8 changed files with 226 additions and 69 deletions

View File

@@ -110,14 +110,15 @@ python main.py
| 功能 | 模型 | 说明 | | 功能 | 模型 | 说明 |
|------|------|------| |------|------|------|
| 文生图 | `sora-image*` | 使用 `content` 为字符串 | | 文生图 | `gpt-image*` | 使用 `content` 为字符串 |
| 图生图 | `sora-image*` | 使用 `content` 数组 + `image_url` | | 图生图 | `gpt-image*` | 使用 `content` 数组 + `image_url` |
| 文生视频 | `sora-video*` | 使用 `content` 为字符串 | | 文生视频 | `sora2-*` | 使用 `content` 为字符串 |
| 图生视频 | `sora-video*` | 使用 `content` 数组 + `image_url` | | 图生视频 | `sora2-*` | 使用 `content` 数组 + `image_url` |
| 创建角色 | `sora-video*` | 使用 `content` 数组 + `video_url` | | 视频风格 | `sora2-*` | 在提示词中使用 `{风格ID}` 格式,如 `{anime}提示词` |
| 角色生成视频 | `sora-video*` | 使用 `content` 数组 + `video_url` + 文本 | | 创建角色 | `sora2-*` | 使用 `content` 数组 + `video_url` |
| Remix | `sora-video*` | `content` 中包含 Remix ID | | 角色生成视频 | `sora2-*` | 使用 `content` 数组 + `video_url` + 文本 |
| 视频分镜 | `sora-video*` | 在 `content`使用```[时长s]提示词```格式触发 | | Remix | `sora2-*` | 在 `content`包含 Remix ID |
| 视频分镜 | `sora2-*` | 在 `content` 中使用```[时长s]提示词```格式触发 |
--- ---
@@ -135,20 +136,44 @@ python main.py
| 模型 | 说明 | 尺寸 | | 模型 | 说明 | 尺寸 |
|------|------|------| |------|------|------|
| `sora-image` | 文生图(默认 | 360×360 | | `gpt-image` | 文生图(正方形 | 360×360 |
| `sora-image-landscape` | 文生图(横屏) | 540×360 | | `gpt-image-landscape` | 文生图(横屏) | 540×360 |
| `sora-image-portrait` | 文生图(竖屏) | 360×540 | | `gpt-image-portrait` | 文生图(竖屏) | 360×540 |
**视频模型** **视频模型**
**标准版Sora2**
| 模型 | 时长 | 方向 | 说明 | | 模型 | 时长 | 方向 | 说明 |
|------|------|------|------| |------|------|------|------|
| `sora-video-10s` | 10秒 | 横屏 | 文生视频/图生视频 | | `sora2-landscape-10s` | 10秒 | 横屏 | 文生视频/图生视频 |
| `sora-video-15s` | 15秒 | 横屏 | 文生视频/图生视频 | | `sora2-landscape-15s` | 15秒 | 横屏 | 文生视频/图生视频 |
| `sora-video-landscape-10s` | 10秒 | 横屏 | 文生视频/图生视频 | | `sora2-landscape-25s` | 25秒 | 横屏 | 文生视频/图生视频 |
| `sora-video-landscape-15s` | 15秒 | 屏 | 文生视频/图生视频 | | `sora2-portrait-10s` | 10秒 | 屏 | 文生视频/图生视频 |
| `sora-video-portrait-10s` | 10秒 | 竖屏 | 文生视频/图生视频 | | `sora2-portrait-15s` | 15秒 | 竖屏 | 文生视频/图生视频 |
| `sora-video-portrait-15s` | 15秒 | 竖屏 | 文生视频/图生视频 | | `sora2-portrait-25s` | 25秒 | 竖屏 | 文生视频/图生视频 |
**Pro 版(需要 ChatGPT Pro 订阅)**
| 模型 | 时长 | 方向 | 说明 |
|------|------|------|------|
| `sora2pro-landscape-10s` | 10秒 | 横屏 | Pro 质量文生视频/图生视频 |
| `sora2pro-landscape-15s` | 15秒 | 横屏 | Pro 质量文生视频/图生视频 |
| `sora2pro-landscape-25s` | 25秒 | 横屏 | Pro 质量文生视频/图生视频 |
| `sora2pro-portrait-10s` | 10秒 | 竖屏 | Pro 质量文生视频/图生视频 |
| `sora2pro-portrait-15s` | 15秒 | 竖屏 | Pro 质量文生视频/图生视频 |
| `sora2pro-portrait-25s` | 25秒 | 竖屏 | Pro 质量文生视频/图生视频 |
**Pro HD 版(需要 ChatGPT Pro 订阅,高清质量)**
| 模型 | 时长 | 方向 | 说明 |
|------|------|------|------|
| `sora2pro-hd-landscape-10s` | 10秒 | 横屏 | Pro 高清文生视频/图生视频 |
| `sora2pro-hd-landscape-15s` | 15秒 | 横屏 | Pro 高清文生视频/图生视频 |
| `sora2pro-hd-portrait-10s` | 10秒 | 竖屏 | Pro 高清文生视频/图生视频 |
| `sora2pro-hd-portrait-15s` | 15秒 | 竖屏 | Pro 高清文生视频/图生视频 |
> **注意:** Pro 系列模型需要 ChatGPT Pro 订阅(`plan_type: "chatgpt_pro"`)。如果没有 Pro 账号,请求这些模型会返回错误。
#### 请求示例 #### 请求示例
@@ -159,13 +184,14 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-image", "model": "gpt-image",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
"content": "一只可爱的小猫咪" "content": "一只可爱的小猫咪"
} }
] ],
"stream": true
}' }'
``` ```
@@ -176,7 +202,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-image", "model": "gpt-image",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -205,7 +231,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -223,7 +249,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -254,13 +280,14 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
"content": "https://sora.chatgpt.com/p/s_68e3a06dcd888191b150971da152c1f5改成水墨画风格" "content": "https://sora.chatgpt.com/p/s_68e3a06dcd888191b150971da152c1f5改成水墨画风格"
} }
] ],
"stream": true
}' }'
``` ```
@@ -280,13 +307,14 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
"content": "[5.0s]猫猫从飞机上跳伞 [5.0s]猫猫降落 [10.0s]猫猫在田野奔跑" "content": "[5.0s]猫猫从飞机上跳伞 [5.0s]猫猫降落 [10.0s]猫猫在田野奔跑"
} }
] ],
"stream": true
}' }'
``` ```
@@ -322,7 +350,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -340,7 +368,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -358,7 +386,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -394,7 +422,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -421,7 +449,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \
-H "Authorization: Bearer han1234" \ -H "Authorization: Bearer han1234" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -461,7 +489,7 @@ response = requests.post(
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
json={ json={
"model": "sora-video-landscape-10s", "model": "sora2-landscape-10s",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",

View File

@@ -27,7 +27,7 @@ base_url = "http://127.0.0.1:8000"
[generation] [generation]
image_timeout = 300 image_timeout = 300
video_timeout = 1500 video_timeout = 3000
[admin] [admin]
error_ban_threshold = 3 error_ban_threshold = 3

View File

@@ -27,7 +27,7 @@ base_url = "http://127.0.0.1:8000"
[generation] [generation]
image_timeout = 300 image_timeout = 300
video_timeout = 1500 video_timeout = 3000
[admin] [admin]
error_ban_threshold = 3 error_ban_threshold = 3

View File

@@ -163,7 +163,7 @@ class Config:
@property @property
def video_timeout(self) -> int: def video_timeout(self) -> int:
"""Get video generation timeout in seconds""" """Get video generation timeout in seconds"""
return self._config.get("generation", {}).get("video_timeout", 1500) return self._config.get("generation", {}).get("video_timeout", 3000)
def set_video_timeout(self, timeout: int): def set_video_timeout(self, timeout: int):
"""Set video generation timeout in seconds""" """Set video generation timeout in seconds"""

View File

@@ -144,12 +144,12 @@ class Database:
if count[0] == 0: if count[0] == 0:
# Get generation config from config_dict if provided, otherwise use defaults # Get generation config from config_dict if provided, otherwise use defaults
image_timeout = 300 image_timeout = 300
video_timeout = 1500 video_timeout = 3000
if config_dict: if config_dict:
generation_config = config_dict.get("generation", {}) generation_config = config_dict.get("generation", {})
image_timeout = generation_config.get("image_timeout", 300) image_timeout = generation_config.get("image_timeout", 300)
video_timeout = generation_config.get("video_timeout", 1500) video_timeout = generation_config.get("video_timeout", 3000)
await db.execute(""" await db.execute("""
INSERT INTO generation_config (id, image_timeout, video_timeout) INSERT INTO generation_config (id, image_timeout, video_timeout)
@@ -401,7 +401,7 @@ class Database:
CREATE TABLE IF NOT EXISTS generation_config ( CREATE TABLE IF NOT EXISTS generation_config (
id INTEGER PRIMARY KEY DEFAULT 1, id INTEGER PRIMARY KEY DEFAULT 1,
image_timeout INTEGER DEFAULT 300, image_timeout INTEGER DEFAULT 300,
video_timeout INTEGER DEFAULT 1500, video_timeout INTEGER DEFAULT 3000,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
@@ -1013,7 +1013,7 @@ class Database:
return GenerationConfig(**dict(row)) return GenerationConfig(**dict(row))
# If no row exists, return a default config # If no row exists, return a default config
# This should not happen in normal operation as _ensure_config_rows should create it # This should not happen in normal operation as _ensure_config_rows should create it
return GenerationConfig(image_timeout=300, video_timeout=1500) return GenerationConfig(image_timeout=300, video_timeout=3000)
async def update_generation_config(self, image_timeout: int = None, video_timeout: int = None): async def update_generation_config(self, image_timeout: int = None, video_timeout: int = None):
"""Update generation configuration""" """Update generation configuration"""
@@ -1027,10 +1027,10 @@ class Database:
current = dict(row) current = dict(row)
# Update only provided fields # Update only provided fields
new_image_timeout = image_timeout if image_timeout is not None else current.get("image_timeout", 300) new_image_timeout = image_timeout if image_timeout is not None else current.get("image_timeout", 300)
new_video_timeout = video_timeout if video_timeout is not None else current.get("video_timeout", 1500) new_video_timeout = video_timeout if video_timeout is not None else current.get("video_timeout", 3000)
else: else:
new_image_timeout = image_timeout if image_timeout is not None else 300 new_image_timeout = image_timeout if image_timeout is not None else 300
new_video_timeout = video_timeout if video_timeout is not None else 1500 new_video_timeout = video_timeout if video_timeout is not None else 3000
await db.execute(""" await db.execute("""
UPDATE generation_config UPDATE generation_config

View File

@@ -19,52 +19,139 @@ from ..core.logger import debug_logger
# Model configuration # Model configuration
MODEL_CONFIG = { MODEL_CONFIG = {
"sora-image": { "gpt-image": {
"type": "image", "type": "image",
"width": 360, "width": 360,
"height": 360 "height": 360
}, },
"sora-image-landscape": { "gpt-image-landscape": {
"type": "image", "type": "image",
"width": 540, "width": 540,
"height": 360 "height": 360
}, },
"sora-image-portrait": { "gpt-image-portrait": {
"type": "image", "type": "image",
"width": 360, "width": 360,
"height": 540 "height": 540
}, },
# Video models with 10s duration (300 frames) # Video models with 10s duration (300 frames)
"sora-video-10s": { "sora2-landscape-10s": {
"type": "video", "type": "video",
"orientation": "landscape", "orientation": "landscape",
"n_frames": 300 "n_frames": 300
}, },
"sora-video-landscape-10s": { "sora2-portrait-10s": {
"type": "video",
"orientation": "landscape",
"n_frames": 300
},
"sora-video-portrait-10s": {
"type": "video", "type": "video",
"orientation": "portrait", "orientation": "portrait",
"n_frames": 300 "n_frames": 300
}, },
# Video models with 15s duration (450 frames) # Video models with 15s duration (450 frames)
"sora-video-15s": { "sora2-landscape-15s": {
"type": "video", "type": "video",
"orientation": "landscape", "orientation": "landscape",
"n_frames": 450 "n_frames": 450
}, },
"sora-video-landscape-15s": { "sora2-portrait-15s": {
"type": "video",
"orientation": "landscape",
"n_frames": 450
},
"sora-video-portrait-15s": {
"type": "video", "type": "video",
"orientation": "portrait", "orientation": "portrait",
"n_frames": 450 "n_frames": 450
},
# Video models with 25s duration (750 frames)
"sora2-landscape-25s": {
"type": "video",
"orientation": "landscape",
"n_frames": 750,
"model": "sy_8",
"size": "small"
},
"sora2-portrait-25s": {
"type": "video",
"orientation": "portrait",
"n_frames": 750,
"model": "sy_8",
"size": "small"
},
# Pro video models (require Pro subscription)
"sora2pro-landscape-10s": {
"type": "video",
"orientation": "landscape",
"n_frames": 300,
"model": "sy_ore",
"size": "small",
"require_pro": True
},
"sora2pro-portrait-10s": {
"type": "video",
"orientation": "portrait",
"n_frames": 300,
"model": "sy_ore",
"size": "small",
"require_pro": True
},
"sora2pro-landscape-15s": {
"type": "video",
"orientation": "landscape",
"n_frames": 450,
"model": "sy_ore",
"size": "small",
"require_pro": True
},
"sora2pro-portrait-15s": {
"type": "video",
"orientation": "portrait",
"n_frames": 450,
"model": "sy_ore",
"size": "small",
"require_pro": True
},
"sora2pro-landscape-25s": {
"type": "video",
"orientation": "landscape",
"n_frames": 750,
"model": "sy_ore",
"size": "small",
"require_pro": True
},
"sora2pro-portrait-25s": {
"type": "video",
"orientation": "portrait",
"n_frames": 750,
"model": "sy_ore",
"size": "small",
"require_pro": True
},
# Pro HD video models (require Pro subscription, high quality)
"sora2pro-hd-landscape-10s": {
"type": "video",
"orientation": "landscape",
"n_frames": 300,
"model": "sy_ore",
"size": "large",
"require_pro": True
},
"sora2pro-hd-portrait-10s": {
"type": "video",
"orientation": "portrait",
"n_frames": 300,
"model": "sy_ore",
"size": "large",
"require_pro": True
},
"sora2pro-hd-landscape-15s": {
"type": "video",
"orientation": "landscape",
"n_frames": 450,
"model": "sy_ore",
"size": "large",
"require_pro": True
},
"sora2pro-hd-portrait-15s": {
"type": "video",
"orientation": "portrait",
"n_frames": 450,
"model": "sy_ore",
"size": "large",
"require_pro": True
} }
} }
@@ -294,10 +381,20 @@ class GenerationHandler:
return return
# Streaming mode: proceed with actual generation # Streaming mode: proceed with actual generation
# Check if model requires Pro subscription
require_pro = model_config.get("require_pro", False)
# Select token (with lock for image generation, Sora2 quota check for video generation) # Select token (with lock for image generation, Sora2 quota check for video generation)
token_obj = await self.load_balancer.select_token(for_image_generation=is_image, for_video_generation=is_video) # If Pro is required, filter for Pro tokens only
token_obj = await self.load_balancer.select_token(
for_image_generation=is_image,
for_video_generation=is_video,
require_pro=require_pro
)
if not token_obj: if not token_obj:
if is_image: if require_pro:
raise Exception("No available Pro tokens. Pro models require a ChatGPT Pro subscription.")
elif is_image:
raise Exception("No available tokens for image generation. All tokens are either disabled, cooling down, locked, or expired.") raise Exception("No available tokens for image generation. All tokens are either disabled, cooling down, locked, or expired.")
else: else:
raise Exception("No available tokens for video generation. All tokens are either disabled, cooling down, Sora2 quota exhausted, don't support Sora2, or expired.") raise Exception("No available tokens for video generation. All tokens are either disabled, cooling down, Sora2 quota exhausted, don't support Sora2, or expired.")
@@ -383,12 +480,18 @@ class GenerationHandler:
) )
else: else:
# Normal video generation # Normal video generation
# Get model and size from config (default to sy_8 and small for backward compatibility)
sora_model = model_config.get("model", "sy_8")
video_size = model_config.get("size", "small")
task_id = await self.sora_client.generate_video( task_id = await self.sora_client.generate_video(
clean_prompt, token_obj.token, clean_prompt, token_obj.token,
orientation=model_config["orientation"], orientation=model_config["orientation"],
media_id=media_id, media_id=media_id,
n_frames=n_frames, n_frames=n_frames,
style_id=style_id style_id=style_id,
model=sora_model,
size=video_size
) )
else: else:
task_id = await self.sora_client.generate_image( task_id = await self.sora_client.generate_image(
@@ -1271,10 +1374,16 @@ class GenerationHandler:
# Get n_frames from model configuration # Get n_frames from model configuration
n_frames = model_config.get("n_frames", 300) # Default to 300 frames (10s) n_frames = model_config.get("n_frames", 300) # Default to 300 frames (10s)
# Get model and size from config (default to sy_8 and small for backward compatibility)
sora_model = model_config.get("model", "sy_8")
video_size = model_config.get("size", "small")
task_id = await self.sora_client.generate_video( task_id = await self.sora_client.generate_video(
full_prompt, token_obj.token, full_prompt, token_obj.token,
orientation=model_config["orientation"], orientation=model_config["orientation"],
n_frames=n_frames n_frames=n_frames,
model=sora_model,
size=video_size
) )
debug_logger.log_info(f"Video generation started, task_id: {task_id}") debug_logger.log_info(f"Video generation started, task_id: {task_id}")
@@ -1282,7 +1391,7 @@ class GenerationHandler:
task = Task( task = Task(
task_id=task_id, task_id=task_id,
token_id=token_obj.id, token_id=token_obj.id,
model=f"sora-video-{model_config['orientation']}", model=f"sora2-video-{model_config['orientation']}",
prompt=full_prompt, prompt=full_prompt,
status="processing", status="processing",
progress=0.0 progress=0.0
@@ -1375,7 +1484,7 @@ class GenerationHandler:
task = Task( task = Task(
task_id=task_id, task_id=task_id,
token_id=token_obj.id, token_id=token_obj.id,
model=f"sora-video-{model_config['orientation']}", model=f"sora2-video-{model_config['orientation']}",
prompt=f"remix:{remix_target_id} {clean_prompt}", prompt=f"remix:{remix_target_id} {clean_prompt}",
status="processing", status="processing",
progress=0.0 progress=0.0

View File

@@ -17,13 +17,14 @@ class LoadBalancer:
# Use image timeout from config as lock timeout # Use image timeout from config as lock timeout
self.token_lock = TokenLock(lock_timeout=config.image_timeout) self.token_lock = TokenLock(lock_timeout=config.image_timeout)
async def select_token(self, for_image_generation: bool = False, for_video_generation: bool = False) -> Optional[Token]: async def select_token(self, for_image_generation: bool = False, for_video_generation: bool = False, require_pro: bool = False) -> Optional[Token]:
""" """
Select a token using random load balancing Select a token using random load balancing
Args: Args:
for_image_generation: If True, only select tokens that are not locked for image generation and have image_enabled=True for_image_generation: If True, only select tokens that are not locked for image generation and have image_enabled=True
for_video_generation: If True, filter out tokens with Sora2 quota exhausted (sora2_cooldown_until not expired), tokens that don't support Sora2, and tokens with video_enabled=False for_video_generation: If True, filter out tokens with Sora2 quota exhausted (sora2_cooldown_until not expired), tokens that don't support Sora2, and tokens with video_enabled=False
require_pro: If True, only select tokens with ChatGPT Pro subscription (plan_type="chatgpt_pro")
Returns: Returns:
Selected token or None if no available tokens Selected token or None if no available tokens
@@ -56,6 +57,13 @@ class LoadBalancer:
if not active_tokens: if not active_tokens:
return None return None
# Filter for Pro tokens if required
if require_pro:
pro_tokens = [token for token in active_tokens if token.plan_type == "chatgpt_pro"]
if not pro_tokens:
return None
active_tokens = pro_tokens
# If for video generation, filter out tokens with Sora2 quota exhausted and tokens without Sora2 support # If for video generation, filter out tokens with Sora2 quota exhausted and tokens without Sora2 support
if for_video_generation: if for_video_generation:
from datetime import datetime from datetime import datetime

View File

@@ -254,8 +254,20 @@ class SoraClient:
return result["id"] return result["id"]
async def generate_video(self, prompt: str, token: str, orientation: str = "landscape", async def generate_video(self, prompt: str, token: str, orientation: str = "landscape",
media_id: Optional[str] = None, n_frames: int = 450, style_id: Optional[str] = None) -> str: media_id: Optional[str] = None, n_frames: int = 450, style_id: Optional[str] = None,
"""Generate video (text-to-video or image-to-video)""" model: str = "sy_8", size: str = "small") -> str:
"""Generate video (text-to-video or image-to-video)
Args:
prompt: Video generation prompt
token: Access token
orientation: Video orientation (landscape/portrait)
media_id: Optional image media_id for image-to-video
n_frames: Number of frames (300/450/750)
style_id: Optional style ID
model: Model to use (sy_8 for standard, sy_ore for pro)
size: Video size (small for standard, large for HD)
"""
inpaint_items = [] inpaint_items = []
if media_id: if media_id:
inpaint_items = [{ inpaint_items = [{
@@ -267,9 +279,9 @@ class SoraClient:
"kind": "video", "kind": "video",
"prompt": prompt, "prompt": prompt,
"orientation": orientation, "orientation": orientation,
"size": "small", "size": size,
"n_frames": n_frames, "n_frames": n_frames,
"model": "sy_8", "model": model,
"inpaint_items": inpaint_items, "inpaint_items": inpaint_items,
"style_id": style_id "style_id": style_id
} }