mirror of
https://github.com/TheSmallHanCat/sora2api.git
synced 2026-02-04 02:04:42 +08:00
feat: 普号增加25s普通视频、增加pro系列高清模型、统一模型名字、过载不再计入错误禁用计数
This commit is contained in:
92
README.md
92
README.md
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user