1
This commit is contained in:
@@ -55,6 +55,16 @@ from app.services.generation.billing_service import (
|
||||
get_next_credit_attempt_no,
|
||||
)
|
||||
from app.services.generation.refund_service import mark_generation_record_failed_and_refund_once
|
||||
from app.services.generation.ai.engine_service import (
|
||||
get_image_engine,
|
||||
get_video_engine,
|
||||
image_supported_sizes,
|
||||
parse_json_list,
|
||||
)
|
||||
from app.services.generation.media_reference_service import (
|
||||
calculate_media_reference_usage,
|
||||
validate_media_reference_usage_for_engine,
|
||||
)
|
||||
from app.services.media_token_usage_snapshot_service import sync_generation_record_media_token_snapshot
|
||||
from app.services.credit_record_meta_service import build_generation_record_prompt_meta
|
||||
from app.services.video_cover_service import async_create_video_cover_for_local_video
|
||||
@@ -70,6 +80,36 @@ router = APIRouter(prefix="/generation-records", tags=["generation"])
|
||||
logger = logging.getLogger("videogen")
|
||||
|
||||
|
||||
def _engine_snapshot(record: GenerationRecord) -> dict | None:
|
||||
if not record.engine_snapshot_json:
|
||||
return None
|
||||
try:
|
||||
value = json.loads(record.engine_snapshot_json)
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
return None
|
||||
return value if isinstance(value, dict) else None
|
||||
|
||||
|
||||
def _validate_video_engine_selection(engine, *, aspect_ratio: str, resolution: str, duration: int) -> None:
|
||||
ratios = [str(item) for item in parse_json_list(engine.supported_ratios, [])]
|
||||
resolutions = [str(item) for item in parse_json_list(engine.supported_resolutions, [])]
|
||||
durations = [int(item) for item in parse_json_list(engine.supported_durations, []) if str(item).isdigit()]
|
||||
if ratios and aspect_ratio not in ratios:
|
||||
raise HTTPException(status_code=400, detail="当前视频引擎不支持所选画面比例")
|
||||
if resolutions and resolution not in resolutions:
|
||||
raise HTTPException(status_code=400, detail="当前视频引擎不支持所选分辨率")
|
||||
if durations and duration not in durations:
|
||||
raise HTTPException(status_code=400, detail="当前视频引擎不支持所选时长")
|
||||
if int(engine.max_duration or 0) > 0 and duration > int(engine.max_duration):
|
||||
raise HTTPException(status_code=400, detail="生成时长超过当前视频引擎上限")
|
||||
|
||||
|
||||
def _validate_image_engine_selection(engine, *, image_size: str) -> None:
|
||||
sizes = image_supported_sizes(engine)
|
||||
if sizes and image_size not in sizes:
|
||||
raise HTTPException(status_code=400, detail="当前图片引擎不支持所选画面分辨率")
|
||||
|
||||
|
||||
def _record_to_out(record: GenerationRecord, project_name: str, refs_override: list[dict] | None = None) -> GenerationRecordOut:
|
||||
refs = refs_override
|
||||
if refs is None and record.media_references:
|
||||
@@ -112,6 +152,10 @@ def _record_to_out(record: GenerationRecord, project_name: str, refs_override: l
|
||||
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 '',
|
||||
references=refs,
|
||||
engine_id=record.engine_id,
|
||||
engine_name=(_engine_snapshot(record) or {}).get("name"),
|
||||
engine_snapshot=_engine_snapshot(record),
|
||||
include_media_references=bool(record.include_media_references),
|
||||
text_credits_cost=round(record.text_credits_cost or 0.00, 2),
|
||||
# text_tokens_used=record.text_tokens_used or 0,
|
||||
credits_cost=round(record.credits_cost or 0.00, 2),
|
||||
@@ -436,7 +480,11 @@ async def generate_record_resource(
|
||||
raise InvalidStatusError("该任务生成失败,请联系客服进行修复")
|
||||
|
||||
await assert_user_resource_capacity_available(db, current_user.id)
|
||||
attempt_no = await get_next_credit_attempt_no(db, owner_type=OWNER_GENERATION_RECORD, owner_id=record.id)
|
||||
attempt_no = await get_next_credit_attempt_no(
|
||||
db, owner_type=OWNER_GENERATION_RECORD, owner_id=record.id
|
||||
)
|
||||
selected_engine_id = req.engine_id or record.engine_id
|
||||
record.include_media_references = bool(req.include_media_references)
|
||||
|
||||
from app.services.generation.pipeline.generation_record_service import (
|
||||
commit_and_enqueue_generation_record,
|
||||
@@ -444,56 +492,83 @@ async def generate_record_resource(
|
||||
)
|
||||
|
||||
if record.gen_type == GenerationType.video:
|
||||
if req.aspect_ratio not in ASPECT_RATIOS:
|
||||
aspect_ratio = req.aspect_ratio or record.aspect_ratio
|
||||
resolution = req.resolution or record.resolution
|
||||
if aspect_ratio not in ASPECT_RATIOS:
|
||||
raise HTTPException(status_code=400, detail="不支持的画面比例")
|
||||
if req.resolution not in RESOLUTIONS:
|
||||
if resolution not in RESOLUTIONS:
|
||||
raise HTTPException(status_code=400, detail="不支持的分辨率")
|
||||
from app.services.video_gen import get_active_engine
|
||||
engine = await get_video_engine(db, selected_engine_id)
|
||||
_validate_video_engine_selection(
|
||||
engine,
|
||||
aspect_ratio=aspect_ratio,
|
||||
resolution=resolution,
|
||||
duration=int(record.duration or 5),
|
||||
)
|
||||
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 = []
|
||||
supported_provider_resolutions = parse_json_list(engine.supported_resolutions, [])
|
||||
provider_resolution, upscale_enabled, upscale_snapshot_json = await build_video_upscale_snapshot(
|
||||
db,
|
||||
target_resolution=req.resolution,
|
||||
aspect_ratio=req.aspect_ratio,
|
||||
target_resolution=resolution,
|
||||
aspect_ratio=aspect_ratio,
|
||||
supported_provider_resolutions=supported_provider_resolutions,
|
||||
)
|
||||
billing = await charge_generation_media_by_params(
|
||||
db, user_id=current_user.id, record_id=record.id, gen_type="video",
|
||||
duration=record.duration or 5, resolution=req.resolution, engine_id=engine.id,
|
||||
project_name=project_name, description_prefix=project_name + "-",
|
||||
owner_type=OWNER_GENERATION_RECORD, attempt_no=attempt_no,
|
||||
)
|
||||
record.aspect_ratio = req.aspect_ratio
|
||||
record.resolution = req.resolution
|
||||
record.aspect_ratio = aspect_ratio
|
||||
record.resolution = resolution
|
||||
record.provider_generation_resolution = provider_resolution
|
||||
record.video_upscale_enabled_snapshot = upscale_enabled
|
||||
record.video_upscale_snapshot_json = upscale_snapshot_json
|
||||
else:
|
||||
from app.services.image_gen import get_active_image_engine
|
||||
engine = await get_active_image_engine(db)
|
||||
engine = await get_image_engine(db, selected_engine_id)
|
||||
image_size = req.image_size or record.image_size or engine.default_size or "2K"
|
||||
billing = await charge_generation_media_by_params(
|
||||
db, user_id=current_user.id, record_id=record.id, gen_type="image",
|
||||
image_size=image_size, engine_id=engine.id, project_name=project_name,
|
||||
description_prefix=project_name + "-", owner_type=OWNER_GENERATION_RECORD, attempt_no=attempt_no,
|
||||
)
|
||||
_validate_image_engine_selection(engine, image_size=image_size)
|
||||
record.image_size = image_size
|
||||
record.provider_generation_resolution = None
|
||||
record.video_upscale_enabled_snapshot = False
|
||||
record.video_upscale_snapshot_json = None
|
||||
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + float(billing.total_charged or 0), 2)
|
||||
reference_usage = calculate_media_reference_usage(
|
||||
record.media_references,
|
||||
include=bool(record.include_media_references),
|
||||
)
|
||||
validate_media_reference_usage_for_engine(
|
||||
reference_usage,
|
||||
gen_type=record.gen_type,
|
||||
engine=engine,
|
||||
)
|
||||
billing = await charge_generation_media_for_record(
|
||||
db,
|
||||
record=record,
|
||||
project_name=project_name,
|
||||
description_prefix=project_name + "-",
|
||||
attempt_no=attempt_no,
|
||||
engine_id=engine.id,
|
||||
)
|
||||
record.credits_cost = round(
|
||||
float(record.credits_cost or 0) + float(billing.total_charged or 0), 2
|
||||
)
|
||||
prepare_generation_record_execution(record, engine=engine, attempt_no=attempt_no)
|
||||
await db.flush()
|
||||
await commit_and_enqueue_generation_record(db, record, reason="generation_record_api_generate")
|
||||
record_id_snapshot = str(record.id)
|
||||
await commit_and_enqueue_generation_record(
|
||||
db, record, reason="generation_record_api_generate"
|
||||
)
|
||||
|
||||
refreshed = await db.execute(
|
||||
select(GenerationRecord, Project.name)
|
||||
.join(Project, GenerationRecord.project_id == Project.id)
|
||||
.where(GenerationRecord.id == record_id_snapshot)
|
||||
.limit(1)
|
||||
)
|
||||
refreshed_row = refreshed.first()
|
||||
if not refreshed_row:
|
||||
raise RecordNotFoundError()
|
||||
record, project_name = refreshed_row
|
||||
refs = await resolve_private_portrait_reference_display_urls(
|
||||
db, json.loads(record.media_references) if record.media_references else None, user_id=current_user.id
|
||||
db,
|
||||
json.loads(record.media_references) if record.media_references else None,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
return _record_to_out(record, project_name, refs_override=refs)
|
||||
|
||||
@@ -529,43 +604,82 @@ async def retry_generation(
|
||||
raise InvalidStatusError("该任务生成失败,请联系客服进行修复")
|
||||
|
||||
await assert_user_resource_capacity_available(db, current_user.id)
|
||||
attempt_no = await get_next_credit_attempt_no(db, owner_type=OWNER_GENERATION_RECORD, owner_id=record.id)
|
||||
attempt_no = await get_next_credit_attempt_no(
|
||||
db, owner_type=OWNER_GENERATION_RECORD, owner_id=record.id
|
||||
)
|
||||
from app.services.generation.pipeline.generation_record_service import (
|
||||
commit_and_enqueue_generation_record,
|
||||
prepare_generation_record_execution,
|
||||
)
|
||||
|
||||
if record.gen_type == GenerationType.video:
|
||||
from app.services.video_gen import get_active_engine
|
||||
engine = await get_video_engine(db, record.engine_id)
|
||||
_validate_video_engine_selection(
|
||||
engine,
|
||||
aspect_ratio=record.aspect_ratio or "16:9",
|
||||
resolution=record.resolution or "480p",
|
||||
duration=int(record.duration or 5),
|
||||
)
|
||||
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 "480p", aspect_ratio=record.aspect_ratio or "16:9",
|
||||
supported_provider_resolutions=supported_provider_resolutions,
|
||||
db,
|
||||
target_resolution=record.resolution or "480p",
|
||||
aspect_ratio=record.aspect_ratio or "16:9",
|
||||
supported_provider_resolutions=parse_json_list(engine.supported_resolutions, []),
|
||||
)
|
||||
record.provider_generation_resolution = provider_resolution
|
||||
record.video_upscale_enabled_snapshot = upscale_enabled
|
||||
record.video_upscale_snapshot_json = upscale_snapshot_json
|
||||
else:
|
||||
from app.services.image_gen import get_active_image_engine
|
||||
engine = await get_active_image_engine(db)
|
||||
engine = await get_image_engine(db, record.engine_id)
|
||||
_validate_image_engine_selection(
|
||||
engine, image_size=record.image_size or engine.default_size or "2K"
|
||||
)
|
||||
|
||||
reference_usage = calculate_media_reference_usage(
|
||||
record.media_references,
|
||||
include=bool(record.include_media_references),
|
||||
)
|
||||
validate_media_reference_usage_for_engine(
|
||||
reference_usage,
|
||||
gen_type=record.gen_type,
|
||||
engine=engine,
|
||||
)
|
||||
billing = await charge_generation_media_for_record(
|
||||
db, record=record, project_name=project_name, description_prefix="资源生成重试-", attempt_no=attempt_no, engine_id=engine.id
|
||||
db,
|
||||
record=record,
|
||||
project_name=project_name,
|
||||
description_prefix="资源生成重试-",
|
||||
attempt_no=attempt_no,
|
||||
engine_id=engine.id,
|
||||
)
|
||||
record.credits_cost = round(
|
||||
float(record.credits_cost or 0) + float(billing.total_charged or 0), 2
|
||||
)
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + float(billing.total_charged or 0), 2)
|
||||
record.manual_retry_count = int(record.manual_retry_count or 0) + 1
|
||||
record.retry_count = int(record.manual_retry_count or 0)
|
||||
prepare_generation_record_execution(record, engine=engine, attempt_no=attempt_no)
|
||||
await db.flush()
|
||||
await commit_and_enqueue_generation_record(db, record, reason="generation_record_api_retry")
|
||||
record_id_snapshot = str(record.id)
|
||||
await commit_and_enqueue_generation_record(
|
||||
db, record, reason="generation_record_api_retry"
|
||||
)
|
||||
|
||||
refreshed = await db.execute(
|
||||
select(GenerationRecord, Project.name)
|
||||
.join(Project, GenerationRecord.project_id == Project.id)
|
||||
.where(GenerationRecord.id == record_id_snapshot)
|
||||
.limit(1)
|
||||
)
|
||||
refreshed_row = refreshed.first()
|
||||
if not refreshed_row:
|
||||
raise RecordNotFoundError()
|
||||
record, project_name = refreshed_row
|
||||
refs = await resolve_private_portrait_reference_display_urls(
|
||||
db, json.loads(record.media_references) if record.media_references else None, user_id=current_user.id
|
||||
db,
|
||||
json.loads(record.media_references) if record.media_references else None,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
return _record_to_out(record, project_name, refs_override=refs)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user