auto-save 2026-05-13 21:29 (~7)
This commit is contained in:
52
api/main.py
52
api/main.py
@@ -125,6 +125,8 @@ class StoryboardScene(BaseModel):
|
||||
v2: 4 图槽 + 时长(复制粘贴模式)— 主体 / 场景 / 产品 / 动作 各一张图
|
||||
v1 字段保留兼容(subject/product/scene/action/reference_ids)"""
|
||||
duration: float = 0
|
||||
first_image: dict | None = None
|
||||
last_image: dict | None = None
|
||||
# 4 图槽:dict 含 {kind, frame_idx, element_id?, cutout_id?, label}
|
||||
subject_image: dict | None = None
|
||||
scene_image: dict | None = None
|
||||
@@ -1647,6 +1649,8 @@ class UpdateStoryboardReq(BaseModel):
|
||||
class GenerateStoryboardVideoReq(BaseModel):
|
||||
prompt: str
|
||||
duration: float = 4
|
||||
first_image: dict | None = None
|
||||
last_image: dict | None = None
|
||||
subject_image: dict | None = None
|
||||
scene_image: dict | None = None
|
||||
product_image: dict | None = None
|
||||
@@ -1758,7 +1762,15 @@ def ark_reference_data_url(ref_img: Path) -> str:
|
||||
return f"data:{mime};base64,{base64.b64encode(ref_img.read_bytes()).decode('ascii')}"
|
||||
|
||||
|
||||
def submit_video_create(client, url: str, headers: dict, ref_img: Path, payload: dict, source_ref: VideoSourceRef | None = None):
|
||||
def submit_video_create(
|
||||
client,
|
||||
url: str,
|
||||
headers: dict,
|
||||
ref_img: Path,
|
||||
payload: dict,
|
||||
source_ref: VideoSourceRef | None = None,
|
||||
last_img: Path | None = None,
|
||||
):
|
||||
if video_uses_ark():
|
||||
content = [{"type": "text", "text": payload["prompt"]}]
|
||||
if source_ref and source_ref.kind == "source_video" and source_ref.url:
|
||||
@@ -1776,6 +1788,14 @@ def submit_video_create(client, url: str, headers: dict, ref_img: Path, payload:
|
||||
"role": "first_frame",
|
||||
}
|
||||
)
|
||||
if last_img and last_img.exists():
|
||||
content.append(
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {"url": ark_reference_data_url(last_img)},
|
||||
"role": "last_frame",
|
||||
}
|
||||
)
|
||||
data = {
|
||||
"model": payload["model"],
|
||||
"content": content,
|
||||
@@ -1801,17 +1821,33 @@ def submit_video_create(client, url: str, headers: dict, ref_img: Path, payload:
|
||||
)
|
||||
|
||||
|
||||
def render_storyboard_video(job_id: str, local_id: str, provider_id: str, ref_path: Path, prompt: str, model: str, seconds: str, size: str, source_ref: VideoSourceRef | None = None) -> None:
|
||||
def render_storyboard_video(
|
||||
job_id: str,
|
||||
local_id: str,
|
||||
provider_id: str,
|
||||
ref_path: Path,
|
||||
prompt: str,
|
||||
model: str,
|
||||
seconds: str,
|
||||
size: str,
|
||||
source_ref: VideoSourceRef | None = None,
|
||||
last_ref_path: Path | None = None,
|
||||
) -> None:
|
||||
import httpx
|
||||
|
||||
out_dir = job_dir(job_id) / "storyboard_videos" / local_id
|
||||
ref_img = out_dir / "reference.jpg"
|
||||
last_img = out_dir / "last_reference.jpg"
|
||||
out_mp4 = out_dir / "video.mp4"
|
||||
base = video_api_base()
|
||||
headers = {"Authorization": f"Bearer {video_api_key()}"}
|
||||
|
||||
try:
|
||||
prepare_video_reference(ref_path, ref_img)
|
||||
prepared_last_img: Path | None = None
|
||||
if last_ref_path and last_ref_path.exists():
|
||||
prepare_video_reference(last_ref_path, last_img)
|
||||
prepared_last_img = last_img
|
||||
update_generated_video(job_id, local_id, status="in_progress", progress=5)
|
||||
with httpx.Client(timeout=120) as client:
|
||||
payload = {"model": model, "prompt": prompt, "size": size}
|
||||
@@ -1819,10 +1855,13 @@ def render_storyboard_video(job_id: str, local_id: str, provider_id: str, ref_pa
|
||||
create = None
|
||||
create_errors: list[str] = []
|
||||
for create_path in VIDEO_CREATE_PATHS:
|
||||
resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, source_ref)
|
||||
resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, source_ref, prepared_last_img)
|
||||
if video_uses_ark() and source_ref and resp.status_code in {400, 422}:
|
||||
create_errors.append(f"{video_path(create_path)} + reference_video -> HTTP {resp.status_code}: {resp.text[:160]}")
|
||||
resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, None)
|
||||
resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, None, prepared_last_img)
|
||||
if video_uses_ark() and prepared_last_img and resp.status_code in {400, 422}:
|
||||
create_errors.append(f"{video_path(create_path)} + last_frame -> HTTP {resp.status_code}: {resp.text[:160]}")
|
||||
resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, None, None)
|
||||
if resp.status_code < 400:
|
||||
create = resp
|
||||
break
|
||||
@@ -1879,11 +1918,12 @@ def generate_storyboard_video(job_id: str, idx: int, req: GenerateStoryboardVide
|
||||
if not prompt:
|
||||
raise HTTPException(400, "prompt required")
|
||||
|
||||
ref = req.product_image or req.subject_image or req.scene_image or req.action_image
|
||||
ref = req.first_image or req.subject_image or req.product_image or req.scene_image or req.action_image
|
||||
ref_path = storyboard_ref_path(job_id, ref) or (job_dir(job_id) / "frames" / f"{idx:03d}.jpg")
|
||||
if not ref_path.exists():
|
||||
raise HTTPException(404, "reference image missing")
|
||||
poster = storyboard_ref_url(job_id, ref) or f"/jobs/{job_id}/frames/{idx}.jpg"
|
||||
last_ref_path = storyboard_ref_path(job_id, req.last_image)
|
||||
|
||||
local_id = uuid.uuid4().hex[:12]
|
||||
model = resolve_video_model(req.model)
|
||||
@@ -1905,7 +1945,7 @@ def generate_storyboard_video(job_id: str, idx: int, req: GenerateStoryboardVide
|
||||
source_ref = req.source_ref
|
||||
if source_ref and source_ref.kind == "source_video" and not source_ref.url:
|
||||
source_ref = None
|
||||
bg.add_task(render_storyboard_video, job_id, local_id, "", ref_path, prompt, model, seconds, req.size, source_ref)
|
||||
bg.add_task(render_storyboard_video, job_id, local_id, "", ref_path, prompt, model, seconds, req.size, source_ref, last_ref_path)
|
||||
return job
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user