超分功能完成
This commit is contained in:
@@ -41,6 +41,7 @@ from app.services.resource_capacity_service import assert_user_resource_capacity
|
||||
from app.services.upload_resource import delete_unbound_upload_resource, upload_reference_file, cleanup_upload_resource_files_after_commit
|
||||
from app.services.upload_resource.log_service import log_upload_resource_exception, safe_rollback_with_log
|
||||
from app.enums.upload_resource import UploadResourceEventEnum, UploadResourceModuleEnum, UploadResourceTypeEnum
|
||||
from app.enums.generation_status import GenerationRecordPipelineStage
|
||||
from app.services.generation.billing_service import (
|
||||
CHARGE_TEXT_PROMPT,
|
||||
OWNER_GENERATION_RECORD,
|
||||
@@ -101,6 +102,8 @@ def _record_to_out(record: GenerationRecord, project_name: str, refs_override: l
|
||||
image_proportion=record.image_proportion,
|
||||
image_px=record.image_px,
|
||||
status=record.status,
|
||||
pipeline_stage=record.pipeline_stage,
|
||||
video_upscale_enabled=bool(record.video_upscale_enabled_snapshot),
|
||||
video_url=build_resource_signed_url(record.video_url) if record.video_url else '',
|
||||
video_cover_url=build_resource_signed_url(record.video_cover_url) if record.video_cover_url else '',
|
||||
image_url=build_resource_signed_url(record.image_url) if record.image_url else '',
|
||||
@@ -416,6 +419,8 @@ async def generate(
|
||||
record, project_name = row
|
||||
if record.status not in ("prompt_optimized", "failed"):
|
||||
raise InvalidStatusError("当前状态不允许生成")
|
||||
if record.pipeline_stage == GenerationRecordPipelineStage.UPSCALE_FAILED.value:
|
||||
raise InvalidStatusError("该任务生成失败,请联系客服进行修复")
|
||||
|
||||
await assert_user_resource_capacity_available(db, current_user.id)
|
||||
|
||||
@@ -432,6 +437,21 @@ async def generate(
|
||||
if req.resolution not in RESOLUTIONS:
|
||||
raise HTTPException(status_code=400, detail="不支持的分辨率")
|
||||
|
||||
from app.services.video_gen import get_active_engine
|
||||
from app.services.video_upscale.snapshot_service import build_video_upscale_snapshot
|
||||
|
||||
engine = await get_active_engine(db)
|
||||
try:
|
||||
supported_provider_resolutions = json.loads(engine.supported_resolutions or "[]")
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
supported_provider_resolutions = []
|
||||
provider_resolution, upscale_enabled, upscale_snapshot_json = await build_video_upscale_snapshot(
|
||||
db,
|
||||
target_resolution=req.resolution,
|
||||
aspect_ratio=req.aspect_ratio,
|
||||
supported_provider_resolutions=supported_provider_resolutions,
|
||||
)
|
||||
|
||||
duration = record.duration or 5
|
||||
media_billing = await charge_generation_media_by_params(
|
||||
db,
|
||||
@@ -448,6 +468,10 @@ async def generate(
|
||||
|
||||
record.aspect_ratio = req.aspect_ratio
|
||||
record.resolution = req.resolution
|
||||
record.provider_generation_resolution = provider_resolution
|
||||
record.video_upscale_enabled_snapshot = upscale_enabled
|
||||
record.video_upscale_snapshot_json = upscale_snapshot_json
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.CREATING_PROVIDER_TASK.value
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
|
||||
record.status = "generating"
|
||||
record.error_message = None
|
||||
@@ -458,11 +482,10 @@ async def generate(
|
||||
await db.flush()
|
||||
|
||||
try:
|
||||
from app.services.video_gen import get_active_engine, submit_video_task
|
||||
from app.services.video_gen import submit_video_task
|
||||
from app.services.error_codes import extract_error_message
|
||||
from app.services.video_queue import task_queue
|
||||
|
||||
engine = await get_active_engine(db)
|
||||
task_id = await submit_video_task(
|
||||
db,
|
||||
engine,
|
||||
@@ -470,9 +493,11 @@ async def generate(
|
||||
include_media_references=False,
|
||||
)
|
||||
record.seedance_task_id = task_id
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.WAITING_REMOTE.value
|
||||
await db.flush()
|
||||
await task_queue.enqueue(record_id)
|
||||
except Exception as e:
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.FAILED.value
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db,
|
||||
record=record,
|
||||
@@ -502,6 +527,10 @@ async def generate(
|
||||
record.video_url = None
|
||||
record.video_cover_url = None
|
||||
record.seedance_task_id = None
|
||||
record.provider_generation_resolution = None
|
||||
record.video_upscale_enabled_snapshot = False
|
||||
record.video_upscale_snapshot_json = None
|
||||
record.pipeline_stage = None
|
||||
await db.flush()
|
||||
|
||||
try:
|
||||
@@ -543,6 +572,8 @@ async def retry_generation(
|
||||
record, project_name = row
|
||||
if record.status != "failed":
|
||||
raise InvalidStatusError("只有失败的记录可以重试")
|
||||
if record.pipeline_stage == GenerationRecordPipelineStage.UPSCALE_FAILED.value:
|
||||
raise InvalidStatusError("该任务生成失败,请联系客服进行修复")
|
||||
|
||||
await assert_user_resource_capacity_available(db, current_user.id)
|
||||
|
||||
@@ -551,6 +582,27 @@ async def retry_generation(
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
owner_id=record.id,
|
||||
)
|
||||
engine = None
|
||||
if record.gen_type == GenerationType.video:
|
||||
from app.services.video_gen import get_active_engine
|
||||
from app.services.video_upscale.snapshot_service import build_video_upscale_snapshot
|
||||
|
||||
engine = await get_active_engine(db)
|
||||
try:
|
||||
supported_provider_resolutions = json.loads(engine.supported_resolutions or "[]")
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
supported_provider_resolutions = []
|
||||
provider_resolution, upscale_enabled, upscale_snapshot_json = await build_video_upscale_snapshot(
|
||||
db,
|
||||
target_resolution=record.resolution or "",
|
||||
aspect_ratio=record.aspect_ratio or "",
|
||||
supported_provider_resolutions=supported_provider_resolutions,
|
||||
)
|
||||
record.provider_generation_resolution = provider_resolution
|
||||
record.video_upscale_enabled_snapshot = upscale_enabled
|
||||
record.video_upscale_snapshot_json = upscale_snapshot_json
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.CREATING_PROVIDER_TASK.value
|
||||
|
||||
media_billing = await charge_generation_media_for_record(
|
||||
db,
|
||||
record=record,
|
||||
@@ -572,8 +624,8 @@ async def retry_generation(
|
||||
try:
|
||||
from app.services.video_queue import task_queue
|
||||
if record.gen_type == GenerationType.video:
|
||||
from app.services.video_gen import get_active_engine, submit_video_task, extract_error_message
|
||||
engine = await get_active_engine(db)
|
||||
from app.services.video_gen import submit_video_task
|
||||
assert engine is not None
|
||||
task_id = await submit_video_task(
|
||||
db,
|
||||
engine,
|
||||
@@ -581,10 +633,13 @@ async def retry_generation(
|
||||
include_media_references=False,
|
||||
)
|
||||
record.seedance_task_id = task_id
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.WAITING_REMOTE.value
|
||||
await db.flush()
|
||||
await task_queue.enqueue(record_id)
|
||||
except Exception as e:
|
||||
from app.services.error_codes import extract_error_message
|
||||
if record.gen_type == GenerationType.video:
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.FAILED.value
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db,
|
||||
record=record,
|
||||
@@ -679,6 +734,8 @@ async def get_queue_status(
|
||||
return {
|
||||
"record_id": record.id,
|
||||
"status": record.status,
|
||||
"pipeline_stage": record.pipeline_stage,
|
||||
"video_upscale_enabled": bool(record.video_upscale_enabled_snapshot),
|
||||
"queue_position": queue_position,
|
||||
"estimated_wait_seconds": estimated_wait_seconds,
|
||||
}
|
||||
@@ -705,65 +762,54 @@ async def seedance_callback(request: Request, db: AsyncSession = Depends(get_db)
|
||||
record = result.scalar_one_or_none()
|
||||
if not record:
|
||||
return {"message": "record not found"}
|
||||
if record.status == "completed":
|
||||
return {"message": "already completed"}
|
||||
if str(record.pipeline_stage or "").startswith("upscale_"):
|
||||
return {"message": "upscale already started"}
|
||||
|
||||
if task_status == "succeeded":
|
||||
remote_url = data.get("content", {}).get("video_url", "")
|
||||
record.status = "completed"
|
||||
storage_path = None
|
||||
file_size_bytes = 0
|
||||
# Download video to local storage
|
||||
if settings.STORAGE_TYPE == "local" and remote_url:
|
||||
try:
|
||||
from app.services.video_gen import download_video
|
||||
date_dir = datetime.now().strftime("%Y/%m/%d")
|
||||
dest_dir = os.path.join(settings.STORAGE_LOCAL_PATH, date_dir)
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
dest = os.path.join(dest_dir, f"{record.id}.mp4")
|
||||
await download_video(remote_url, dest)
|
||||
record.video_url = f"/generate/videos/{date_dir}/{record.id}.mp4"
|
||||
cover_url, _cover_storage_path = await async_create_video_cover_for_local_video(
|
||||
record_id=record.id,
|
||||
video_path=dest,
|
||||
date_dir=date_dir,
|
||||
log_prefix=f"SeedanceCallback视频封面生成 record_id={record.id}",
|
||||
)
|
||||
record.video_cover_url = cover_url
|
||||
storage_path = dest
|
||||
file_size_bytes = safe_file_size(dest)
|
||||
except Exception as e:
|
||||
logger.warning(f"Callback download failed, using remote URL: {e}")
|
||||
record.video_url = remote_url
|
||||
else:
|
||||
record.video_url = remote_url
|
||||
record.generated_at = datetime.now(CST)
|
||||
if record.video_url:
|
||||
await record_generation_record_generated_resource(
|
||||
db,
|
||||
record,
|
||||
resource_url=record.video_url,
|
||||
storage_path=storage_path,
|
||||
file_size_bytes=file_size_bytes,
|
||||
remote_url=remote_url,
|
||||
generated_at=record.generated_at,
|
||||
remote_url = str(data.get("content", {}).get("video_url", "") or "").strip()
|
||||
if not remote_url:
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.FAILED.value
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db, record=record, error_message="供应商回调成功但未返回视频地址"
|
||||
)
|
||||
# Extract video token usage from callback
|
||||
usage = data.get("usage", {})
|
||||
if usage:
|
||||
record.video_tokens_used = usage.get("total_tokens", 0)
|
||||
await sync_generation_record_media_token_snapshot(db, record, provider_response=data)
|
||||
# Log callback response
|
||||
from app.services.video_gen import _log_video_response
|
||||
_log_video_response(record.id, data)
|
||||
# Notify user
|
||||
from app.services.notification import create_notification
|
||||
from app.api.v1.notifications import push_notification_to_user
|
||||
notif = await create_notification(
|
||||
db, record.user_id, "视频生成完成",
|
||||
"您的视频已生成完成,可以查看了。", "video", record.id,
|
||||
)
|
||||
await push_notification_to_user(record.user_id, notif)
|
||||
else:
|
||||
usage = data.get("usage", {}) if isinstance(data.get("usage"), dict) else {}
|
||||
from app.services.video_queue import handle_generation_record_video_succeeded
|
||||
try:
|
||||
entered_upscale = await handle_generation_record_video_succeeded(
|
||||
db,
|
||||
record,
|
||||
remote_url=remote_url,
|
||||
provider_response=data,
|
||||
video_tokens=usage.get("total_tokens", 0),
|
||||
)
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
result = await db.execute(
|
||||
select(GenerationRecord).where(GenerationRecord.id == record.id).with_for_update().limit(1)
|
||||
)
|
||||
failed_record = result.scalar_one_or_none()
|
||||
if failed_record:
|
||||
failed_record.pipeline_stage = GenerationRecordPipelineStage.FAILED.value
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db, record=failed_record, error_message=f"视频结果下载失败: {exc}"
|
||||
)
|
||||
entered_upscale = False
|
||||
from app.services.video_gen import _log_video_response
|
||||
_log_video_response(record.id, data)
|
||||
if not entered_upscale and record.status == "completed":
|
||||
from app.services.notification import create_notification
|
||||
from app.api.v1.notifications import push_notification_to_user
|
||||
notif = await create_notification(
|
||||
db, record.user_id, "视频生成完成",
|
||||
"您的视频已生成完成,可以查看了。", "video", record.id,
|
||||
)
|
||||
await push_notification_to_user(record.user_id, notif)
|
||||
elif task_status == "failed":
|
||||
error_message = data.get("error", "视频生成失败")
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.FAILED.value
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db,
|
||||
record=record,
|
||||
|
||||
Reference in New Issue
Block a user