style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m1s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m57s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m25s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 4m42s
AI Code Review / AI Code Review (pull_request) Successful in 6m57s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m42s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 13m49s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 15m30s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 16m28s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 17m34s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 37m57s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m1s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m57s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m25s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 4m42s
AI Code Review / AI Code Review (pull_request) Successful in 6m57s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m42s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 13m49s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 15m30s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 16m28s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 17m34s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 37m57s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
This commit is contained in:
@@ -1163,7 +1163,9 @@ def _step_render(job: ViralVideoJob, copy_result: dict, tts_audio_url: str | Non
|
||||
usage = result.get("usage")
|
||||
if not video_path or not Path(video_path).exists() or Path(video_path).stat().st_size == 0:
|
||||
raise RuntimeError("Seedance 视频生成失败:返回空文件或路径不存在")
|
||||
logger.info("[爆款视频] Seedance 单次生成完成: %s size=%d usage=%s", video_path, Path(video_path).stat().st_size, usage)
|
||||
logger.info(
|
||||
"[爆款视频] Seedance 单次生成完成: %s size=%d usage=%s", video_path, Path(video_path).stat().st_size, usage
|
||||
)
|
||||
return str(video_path), (usage if isinstance(usage, dict) else None)
|
||||
|
||||
|
||||
@@ -1566,6 +1568,7 @@ def _try_refund_viral_video(job: ViralVideoJob) -> None:
|
||||
"""爆款视频生成失败:若已预扣积分则全额退款。"""
|
||||
try:
|
||||
from packages.shared import get_shared_settings
|
||||
|
||||
_s = get_shared_settings()
|
||||
if not _s.points_enabled:
|
||||
return
|
||||
@@ -1573,12 +1576,16 @@ def _try_refund_viral_video(job: ViralVideoJob) -> None:
|
||||
if prepaid <= 0:
|
||||
return
|
||||
from packages.domain.points_service import PointsService
|
||||
|
||||
svc = PointsService()
|
||||
# 使用独立 session(避免污染外层事务)
|
||||
ssn = SessionLocal()
|
||||
try:
|
||||
svc.refund_viral_video(
|
||||
job.user_id, prepaid, getattr(job, "credits_transaction_id", "") or "", ssn,
|
||||
job.user_id,
|
||||
prepaid,
|
||||
getattr(job, "credits_transaction_id", "") or "",
|
||||
ssn,
|
||||
)
|
||||
job.credits_prepaid = 0.0
|
||||
finally:
|
||||
@@ -1591,6 +1598,7 @@ def _settle_viral_video(job: ViralVideoJob, usage: dict | None) -> None:
|
||||
"""爆款视频生成成功:按实际 usage 结算,多退少补,写 credits_cost。"""
|
||||
try:
|
||||
from packages.shared import get_shared_settings
|
||||
|
||||
_s = get_shared_settings()
|
||||
if not _s.points_enabled:
|
||||
job.credits_cost = 0.0
|
||||
@@ -1602,6 +1610,7 @@ def _settle_viral_video(job: ViralVideoJob, usage: dict | None) -> None:
|
||||
return
|
||||
from packages.domain.points_rules import calculate_viral_video_credits, resolve_video_dimensions
|
||||
from packages.domain.points_service import PointsService
|
||||
|
||||
w, h = resolve_video_dimensions(
|
||||
getattr(job, "video_resolution", "720p") or "720p",
|
||||
getattr(job, "video_ratio", "9:16") or "9:16",
|
||||
@@ -1613,7 +1622,8 @@ def _settle_viral_video(job: ViralVideoJob, usage: dict | None) -> None:
|
||||
actual_tokens = int(at)
|
||||
actual_credits = calculate_viral_video_credits(
|
||||
int(getattr(job, "duration", 15) or 15),
|
||||
w, h,
|
||||
w,
|
||||
h,
|
||||
getattr(job, "video_model", "") or "seedance-2.5",
|
||||
actual_tokens=actual_tokens,
|
||||
)
|
||||
@@ -1683,11 +1693,9 @@ def _run_render_pipeline(job_id: str, session, repo, job) -> dict:
|
||||
_set_stage(job, repo, session, ViralVideoStage.RENDERING, "正在生成视频(约1-3分钟)...")
|
||||
video_path = None
|
||||
usage = None
|
||||
render_err = None
|
||||
try:
|
||||
video_path, usage = _step_render(job, copy_result, tts_url)
|
||||
except Exception as e:
|
||||
render_err = e
|
||||
logger.error("[爆款视频][阶段3] Seedance 生成失败,触发退款: %s", e, exc_info=True)
|
||||
# 退款
|
||||
_try_refund_viral_video(job)
|
||||
|
||||
@@ -962,9 +962,9 @@ class ViralVideoJobModel(Base):
|
||||
) # v1.6: 编导脚本结构{overview,scene_and_lighting,shots,hard_constraints,negative_prompts,voiceover_script}
|
||||
result_video_url = Column(String(1000), nullable=False, default="")
|
||||
credits_cost = Column(Float, nullable=False, default=0)
|
||||
video_resolution = Column(String(20), nullable=False, default='720p')
|
||||
video_resolution = Column(String(20), nullable=False, default="720p")
|
||||
credits_prepaid = Column(Float, nullable=False, default=0.0)
|
||||
credits_transaction_id = Column(String(36), nullable=False, default='')
|
||||
credits_transaction_id = Column(String(36), nullable=False, default="")
|
||||
error_msg = Column(Text, nullable=False, default="")
|
||||
retry_count = Column(Integer, nullable=False, default=0)
|
||||
started_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
@@ -32,9 +32,21 @@ VIRAL_VIDEO_FPS = 24
|
||||
|
||||
# 分辨率别名映射 -> 标准 key
|
||||
_RESOLUTION_ALIASES: dict[str, str] = {
|
||||
"480p": "480p", "普清": "480p", "default": "480p", "low": "480p", "sd": "480p",
|
||||
"720p": "720p", "高清": "720p", "medium": "720p", "hd": "720p",
|
||||
"1080p": "1080p", "超清": "1080p", "high": "1080p", "ultra": "1080p", "全能": "1080p", "fhd": "1080p",
|
||||
"480p": "480p",
|
||||
"普清": "480p",
|
||||
"default": "480p",
|
||||
"low": "480p",
|
||||
"sd": "480p",
|
||||
"720p": "720p",
|
||||
"高清": "720p",
|
||||
"medium": "720p",
|
||||
"hd": "720p",
|
||||
"1080p": "1080p",
|
||||
"超清": "1080p",
|
||||
"high": "1080p",
|
||||
"ultra": "1080p",
|
||||
"全能": "1080p",
|
||||
"fhd": "1080p",
|
||||
}
|
||||
# 分辨率 -> 高度
|
||||
_RESOLUTION_HEIGHT: dict[str, int] = {"480p": 480, "720p": 720, "1080p": 1080}
|
||||
|
||||
@@ -328,7 +328,9 @@ class PointsService:
|
||||
if not res.get("success"):
|
||||
logger.warning(
|
||||
"[viral_video] 结算补扣余额不足 user_id=%s extra=%.2f balance=%s (不阻塞任务完成)",
|
||||
user_id, extra, res.get("balance"),
|
||||
user_id,
|
||||
extra,
|
||||
res.get("balance"),
|
||||
)
|
||||
return {"success": bool(res.get("success")), "action": "deduct", "diff": extra, "amount": extra}
|
||||
except Exception:
|
||||
|
||||
@@ -128,6 +128,7 @@ class TestViralVideoJobDefaults:
|
||||
assert job.result_video_url == ""
|
||||
assert job.error_msg == ""
|
||||
|
||||
|
||||
class TestViralVideoStage:
|
||||
"""阶段枚举测试。"""
|
||||
|
||||
|
||||
@@ -212,7 +212,10 @@ class TestCallVideoGeneration:
|
||||
with patch("packages.shared.ai_service.get_doubao_client") as mock_get:
|
||||
mock_client = MagicMock()
|
||||
mock_client.is_available = True
|
||||
mock_client.video_generation.return_value = {"video_path": str(out), "usage": {"completion_tokens": 1000000}}
|
||||
mock_client.video_generation.return_value = {
|
||||
"video_path": str(out),
|
||||
"usage": {"completion_tokens": 1000000},
|
||||
}
|
||||
mock_get.return_value = mock_client
|
||||
result = call_video_generation(prompt="测试", image_url="https://img/x.jpg", duration=5, ratio="9:16")
|
||||
assert result is not None and result["video_path"] == str(out)
|
||||
@@ -238,7 +241,10 @@ class TestCallVideoGenerationV16:
|
||||
with patch("packages.shared.ai_service.get_doubao_client") as mock_get:
|
||||
mock_client = MagicMock()
|
||||
mock_client.is_available = True
|
||||
mock_client.video_generation.return_value = {"video_path": str(out), "usage": {"completion_tokens": 1500000}}
|
||||
mock_client.video_generation.return_value = {
|
||||
"video_path": str(out),
|
||||
"usage": {"completion_tokens": 1500000},
|
||||
}
|
||||
mock_get.return_value = mock_client
|
||||
result = call_video_generation(
|
||||
prompt="测试",
|
||||
|
||||
Reference in New Issue
Block a user