fix(8502): 重生图片自动作废旧视频;记录来源图片签名并提示stale;组图/重生视频路径唯一化

This commit is contained in:
Tony Zhang
2025-12-17 22:54:22 +08:00
parent 1e210ffccf
commit c6436da17e
3 changed files with 113 additions and 2 deletions

43
app.py
View File

@@ -11,6 +11,7 @@ from pathlib import Path
import pandas as pd
from time import perf_counter
from concurrent.futures import ThreadPoolExecutor, as_completed
from os import stat
# Import Backend Modules
import config
@@ -704,6 +705,9 @@ if st.session_state.view_mode == "workspace":
for s_id, path in results.items():
st.session_state.scene_images[s_id] = path
db.save_asset(st.session_state.project_id, s_id, "image", "completed", local_path=path)
# Invalidate stale video for this scene (group image regen also changes image)
db.clear_asset(st.session_state.project_id, s_id, "video", status="pending")
st.session_state.scene_videos.pop(s_id, None)
if len(results) == len(scenes):
st.success("组图生成完成!")
@@ -754,6 +758,9 @@ if st.session_state.view_mode == "workspace":
if img_path:
st.session_state.scene_images[scene_id] = img_path
db.save_asset(st.session_state.project_id, scene_id, "image", "completed", local_path=img_path)
# Invalidate stale video for this scene (image changed => old video is wrong)
db.clear_asset(st.session_state.project_id, scene_id, "video", status="pending")
st.session_state.scene_videos.pop(scene_id, None)
progress_bar.progress(done / total_scenes)
@@ -823,6 +830,9 @@ if st.session_state.view_mode == "workspace":
if new_path:
st.session_state.scene_images[scene_id] = new_path
db.save_asset(st.session_state.project_id, scene_id, "image", "completed", local_path=new_path)
# Invalidate stale video for this scene
db.clear_asset(st.session_state.project_id, scene_id, "video", status="pending")
st.session_state.scene_videos.pop(scene_id, None)
st.rerun()
if st.button("下一步:生成视频", type="primary"):
@@ -965,6 +975,22 @@ if st.session_state.view_mode == "workspace":
meta = (asset or {}).get("metadata") or {}
video_url = meta.get("video_url")
if video_url:
# Detect stale mapping: if source image signature differs, warn and avoid misleading preview
stale = False
try:
cur_img = st.session_state.scene_images.get(scene_id)
if cur_img and os.path.exists(cur_img):
st_img = stat(cur_img)
cur_size = int(getattr(st_img, "st_size", 0) or 0)
cur_mtime = float(getattr(st_img, "st_mtime", 0.0) or 0.0)
src_size = meta.get("source_image_size")
src_mtime = meta.get("source_image_mtime")
if (src_size and cur_size and int(src_size) != cur_size) or (src_mtime and cur_mtime and abs(float(src_mtime) - cur_mtime) > 1e-3):
stale = True
except Exception:
stale = False
if stale:
st.warning("检测到该视频可能基于旧图片生成(图片已更新)。请点击“提交图生视频任务”重新生成,以避免主体不一致。")
st.caption("URL 直连预览(不经服务器落盘)")
st.video(video_url)
else:
@@ -995,7 +1021,19 @@ if st.session_state.view_mode == "workspace":
for _ in range(60):
status, url = vid_gen.check_task_status(t_id)
if status == "succeeded":
new_path = vid_gen._download_video(url, f"scene_{scene_id}_video_{int(time.time())}.mp4")
# Use per-project unique name to avoid cross-project overwrite
out_name = path_utils.unique_filename(
prefix="scene_video",
ext="mp4",
project_id=st.session_state.project_id,
scene_id=scene_id,
extra=(t_id[-8:] if isinstance(t_id, str) else None),
)
new_path = vid_gen._download_video(
url,
out_name,
output_dir=path_utils.project_videos_dir(st.session_state.project_id),
)
if new_path:
st.session_state.scene_videos[scene_id] = new_path
db.save_asset(st.session_state.project_id, scene_id, "video", "completed", local_path=new_path, task_id=t_id)
@@ -1015,6 +1053,9 @@ if st.session_state.view_mode == "workspace":
if st.button("🔄 重新生成所有视频", type="secondary"):
# Clear videos and rerun
st.session_state.scene_videos = {}
# Also clear DB video assets to avoid stale URL preview
if st.session_state.project_id:
db.clear_assets(st.session_state.project_id, "video", status="pending")
st.rerun()
with c_act2:

View File

@@ -308,6 +308,59 @@ class DBManager:
finally:
session.close()
def clear_asset(self, project_id: str, scene_id: int, asset_type: str, *, status: str = "pending") -> None:
"""
Clear an asset record (keep the row, but remove paths/task/metadata).
Used to invalidate stale videos after images are regenerated.
"""
session = self._get_session()
try:
asset = session.query(SceneAsset).filter_by(
project_id=project_id,
scene_id=scene_id,
asset_type=asset_type,
).first()
if not asset:
return
asset.status = status
asset.local_path = None
asset.remote_url = None
asset.task_id = None
asset.metadata_json = "{}"
asset.updated_at = time.time()
session.commit()
except Exception as e:
session.rollback()
logger.error(f"Error clearing asset: {e}")
finally:
session.close()
def clear_assets(self, project_id: str, asset_type: str, *, status: str = "pending") -> int:
"""
Clear all assets of a type for a project. Returns number of rows affected.
"""
session = self._get_session()
try:
q = session.query(SceneAsset).filter_by(project_id=project_id, asset_type=asset_type)
rows = q.all()
if not rows:
return 0
for asset in rows:
asset.status = status
asset.local_path = None
asset.remote_url = None
asset.task_id = None
asset.metadata_json = "{}"
asset.updated_at = time.time()
session.commit()
return len(rows)
except Exception as e:
session.rollback()
logger.error(f"Error clearing assets: {e}")
return 0
finally:
session.close()
# --- Config/Prompt Operations ---
def get_config(self, key: str, default: Any = None) -> Any:

View File

@@ -6,6 +6,7 @@ import logging
import time
import requests
import os
from os import stat
from typing import Dict, Any, List, Optional
from pathlib import Path
@@ -56,6 +57,21 @@ class VideoGenerator:
task_id = self._submit_task(image_url, prompt)
if task_id:
try:
st = stat(image_path)
source_sig = {
"source_image_local_path": image_path,
"source_image_size": int(getattr(st, "st_size", 0) or 0),
"source_image_mtime": float(getattr(st, "st_mtime", 0.0) or 0.0),
"source_image_r2_url": image_url,
"submitted_at": time.time(),
}
except Exception:
source_sig = {
"source_image_local_path": image_path,
"source_image_r2_url": image_url,
"submitted_at": time.time(),
}
# 立即保存 task_id 到数据库,状态为 processing
db.save_asset(
project_id=project_id,
@@ -63,7 +79,8 @@ class VideoGenerator:
asset_type="video",
status="processing",
task_id=task_id,
local_path=None
local_path=None,
metadata=source_sig,
)
return task_id