auto-save 2026-05-13 20:40 (~4)
This commit is contained in:
46
api/main.py
46
api/main.py
@@ -36,6 +36,11 @@ VIDEO_MODEL_ALIASES = {
|
||||
"kling": os.getenv("VIDEO_MODEL_KLING", "kling").strip() or "kling",
|
||||
"veo3": os.getenv("VIDEO_MODEL_VEO3", "veo3").strip() or "veo3",
|
||||
}
|
||||
VIDEO_API_BASE_URL = os.getenv("VIDEO_API_BASE_URL", "").strip()
|
||||
VIDEO_API_KEY = os.getenv("VIDEO_API_KEY", "").strip()
|
||||
VIDEO_CREATE_PATH = os.getenv("VIDEO_CREATE_PATH", "/videos").strip() or "/videos"
|
||||
VIDEO_STATUS_PATH = os.getenv("VIDEO_STATUS_PATH", "/videos/{id}").strip() or "/videos/{id}"
|
||||
VIDEO_CONTENT_PATH = os.getenv("VIDEO_CONTENT_PATH", "/videos/{id}/content").strip() or "/videos/{id}/content"
|
||||
VIDEO_DURATION_FIELD = os.getenv("VIDEO_DURATION_FIELD", "seconds").strip() or "seconds"
|
||||
|
||||
# OpenAI 客户端(OpenAI 兼容网关,含 SKG ezlink)
|
||||
@@ -191,6 +196,32 @@ def public_api_base() -> str:
|
||||
return (LLM_BASE_URL or "https://api.openai.com/v1").rstrip("/")
|
||||
|
||||
|
||||
def video_api_base() -> str:
|
||||
return (VIDEO_API_BASE_URL or LLM_BASE_URL or "https://api.openai.com/v1").rstrip("/")
|
||||
|
||||
|
||||
def video_api_key() -> str:
|
||||
return VIDEO_API_KEY or LLM_API_KEY
|
||||
|
||||
|
||||
def video_path(template: str, **values: str) -> str:
|
||||
path = template.format(**values)
|
||||
return path if path.startswith("/") else f"/{path}"
|
||||
|
||||
|
||||
def ensure_video_api_configured() -> None:
|
||||
base = video_api_base()
|
||||
# 已探测:SKG ezlink 当前只开了 chat/images,/videos 返回 404。
|
||||
# 没有显式 VIDEO_API_BASE_URL 时,不再把这个 404 伪装成一次“生成失败”。
|
||||
if not VIDEO_API_BASE_URL and "ai.skg.com/ezlink" in base:
|
||||
raise HTTPException(
|
||||
503,
|
||||
"当前 SKG ezlink 网关未开通生视频 /videos 端点;请配置 VIDEO_API_BASE_URL/VIDEO_API_KEY 接 Seedance、Kling 或 Veo 3 的真实视频 API 后再生成。",
|
||||
)
|
||||
if not video_api_key():
|
||||
raise HTTPException(503, "VIDEO_API_KEY 或 LLM_API_KEY 未配置,无法调用生视频 API")
|
||||
|
||||
|
||||
def storyboard_ref_path(job_id: str, ref: dict | None) -> Path | None:
|
||||
if not ref:
|
||||
return None
|
||||
@@ -758,6 +789,8 @@ def health() -> dict:
|
||||
"rewrite": REWRITE_MODEL,
|
||||
"video": VIDEO_MODEL,
|
||||
"video_aliases": VIDEO_MODEL_ALIASES,
|
||||
"video_base_url": video_api_base() if VIDEO_API_BASE_URL else "",
|
||||
"video_configured": bool(VIDEO_API_BASE_URL and video_api_key()),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1641,7 +1674,7 @@ def download_generated_video(client, base: str, headers: dict, provider_id: str,
|
||||
url = direct_url if direct_url.startswith("http") else f"{base}{direct_url if direct_url.startswith('/') else '/' + direct_url}"
|
||||
r = client.get(url, headers=headers if url.startswith(base) else None)
|
||||
else:
|
||||
r = client.get(f"{base}/videos/{provider_id}/content", headers=headers)
|
||||
r = client.get(f"{base}{video_path(VIDEO_CONTENT_PATH, id=provider_id)}", headers=headers)
|
||||
r.raise_for_status()
|
||||
out_mp4.write_bytes(r.content)
|
||||
|
||||
@@ -1652,8 +1685,8 @@ def render_storyboard_video(job_id: str, local_id: str, provider_id: str, ref_pa
|
||||
out_dir = job_dir(job_id) / "storyboard_videos" / local_id
|
||||
ref_img = out_dir / "reference.jpg"
|
||||
out_mp4 = out_dir / "video.mp4"
|
||||
base = public_api_base()
|
||||
headers = {"Authorization": f"Bearer {LLM_API_KEY}"}
|
||||
base = video_api_base()
|
||||
headers = {"Authorization": f"Bearer {video_api_key()}"}
|
||||
|
||||
try:
|
||||
prepare_video_reference(ref_path, ref_img)
|
||||
@@ -1663,7 +1696,7 @@ def render_storyboard_video(job_id: str, local_id: str, provider_id: str, ref_pa
|
||||
payload[VIDEO_DURATION_FIELD] = seconds
|
||||
with ref_img.open("rb") as fh:
|
||||
create = client.post(
|
||||
f"{base}/videos",
|
||||
f"{base}{video_path(VIDEO_CREATE_PATH)}",
|
||||
headers=headers,
|
||||
data=payload,
|
||||
files={"input_reference": ("reference.jpg", fh, "image/jpeg")},
|
||||
@@ -1679,7 +1712,7 @@ def render_storyboard_video(job_id: str, local_id: str, provider_id: str, ref_pa
|
||||
deadline = time.time() + 420
|
||||
while status in {"queued", "in_progress"} and time.time() < deadline:
|
||||
time.sleep(8)
|
||||
poll = client.get(f"{base}/videos/{video_api_id}", headers=headers)
|
||||
poll = client.get(f"{base}{video_path(VIDEO_STATUS_PATH, id=video_api_id)}", headers=headers)
|
||||
poll.raise_for_status()
|
||||
pdata = poll.json()
|
||||
status = normalize_video_status(pdata.get("status"))
|
||||
@@ -1712,8 +1745,7 @@ def generate_storyboard_video(job_id: str, idx: int, req: GenerateStoryboardVide
|
||||
frame = next((f for f in job.frames if f.index == idx), None)
|
||||
if not frame:
|
||||
raise HTTPException(404, "frame not found")
|
||||
if not LLM_API_KEY:
|
||||
raise HTTPException(500, "LLM_API_KEY 未配置,无法调用视频生成 API")
|
||||
ensure_video_api_configured()
|
||||
prompt = req.prompt.strip()
|
||||
if not prompt:
|
||||
raise HTTPException(400, "prompt required")
|
||||
|
||||
Reference in New Issue
Block a user