auto-save 2026-05-18 06:33 (~5)

This commit is contained in:
2026-05-18 06:33:24 +08:00
parent 4a5c549f08
commit 18fd8c9508
5 changed files with 247 additions and 64 deletions

View File

@@ -1134,6 +1134,17 @@ def _asset_url(job_id: str, asset_id: str) -> str:
return f"/jobs/{job_id}/assets/{asset_id}.jpg"
def _delete_subject_asset_file(job_id: str, asset_id: str) -> None:
if not asset_id:
return
p = job_dir(job_id) / "assets" / f"{asset_id}.jpg"
if p.exists():
try:
p.unlink()
except OSError:
pass
def _find_frame(job: Job, idx: int) -> KeyFrame:
frame = next((f for f in job.frames if f.index == idx), None)
if not frame:
@@ -3560,6 +3571,7 @@ class GenerateSubjectAssetsReq(BaseModel):
subject_style: Literal["transparent_human", "source_actor"] = "transparent_human"
reconstruction_mode: Literal["same", "similar"] = "same"
prompt: str = ""
replace_views: bool = False
class UpdateProductRefsReq(BaseModel):
@@ -3974,6 +3986,17 @@ def generate_subject_assets(job_id: str, idx: int, element_id: str, req: Generat
if sheet:
model_src = sheet
try:
with Image.open(_source_frame_path(job_id, idx)) as src_im:
source_is_portrait = src_im.height > src_im.width
except Exception:
source_is_portrait = False
canvas_clause = (
"Canvas and aspect ratio: the reference video frame is vertical, so output a vertical portrait 9:16-style image, not a square canvas and not a horizontal layout. "
if source_is_portrait
else "Canvas and aspect ratio: keep a single clean reference-image canvas with the same broad orientation as the source evidence. "
)
target = (el.name_en or el.name_zh).strip()
bg_phrase = "pure white" if req.background == "white" else "pure black"
similar_actor = req.subject_kind == "living" and req.subject_style == "source_actor" and req.reconstruction_mode == "similar"
@@ -4036,6 +4059,7 @@ def generate_subject_assets(job_id: str, idx: int, element_id: str, req: Generat
+ single_view_clause
+ identity_clause
+ identity_lock_clause
+ canvas_clause
+ prompt_extra_clause
+ actor_style_clause
+ "The subject must be complete, centered, full body or full object, head-to-feet visible when applicable, not cropped by the canvas. "
@@ -4082,12 +4106,45 @@ def generate_subject_assets(job_id: str, idx: int, element_id: str, req: Generat
if e.id == element_id:
e.subject_kind = req.subject_kind
e.cutout_background = req.background
e.subject_assets = (e.subject_assets or []) + generated
current_assets = e.subject_assets or []
if req.replace_views:
replaced_views = {asset.view for asset in generated}
for old_asset in current_assets:
if old_asset.view in replaced_views:
_delete_subject_asset_file(job_id, old_asset.id)
current_assets = [asset for asset in current_assets if asset.view not in replaced_views]
e.subject_assets = current_assets + generated
new_frames.append(f)
update(job, frames=new_frames, message=f"主体资产包生成完成 · {el.name_zh} · {len(generated)}")
return job
@app.delete("/jobs/{job_id}/frames/{idx}/elements/{element_id}/subject-assets/{asset_id}", response_model=Job)
def delete_subject_asset(job_id: str, idx: int, element_id: str, asset_id: str) -> Job:
"""删除某张主体白底视图。"""
job = JOBS.get(job_id)
if not job:
raise HTTPException(404, "job not found")
frame = _find_frame(job, idx)
el = next((e for e in frame.elements if e.id == element_id), None)
if not el:
raise HTTPException(404, "element not found")
assets = el.subject_assets or []
if not any(asset.id == asset_id for asset in assets):
raise HTTPException(404, "subject asset not found")
_delete_subject_asset_file(job_id, asset_id)
new_frames = []
for f in job.frames:
if f.index == idx:
for e in f.elements:
if e.id == element_id:
e.subject_assets = [asset for asset in (e.subject_assets or []) if asset.id != asset_id]
new_frames.append(f)
update(job, frames=new_frames, message=f"主体视图已删除 · {el.name_zh}")
return job
@app.delete("/jobs/{job_id}/frames/{idx}/elements/{element_id}/cutouts/{cutout_id}", response_model=Job)
def delete_cutout(job_id: str, idx: int, element_id: str, cutout_id: str) -> Job:
"""删除该元素的某张提取图"""