Compare commits

..

6 Commits

Author SHA1 Message Date
xiaoxia 5520e18e2e fix: 修复 usePreviewAssets 无限循环请求
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 39s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m32s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m0s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m40s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m49s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m21s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m28s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m47s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m53s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 3m2s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 52s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m8s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 4m12s
AI Code Review / AI Code Review (pull_request) Successful in 6m47s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m0s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 7m50s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 6m21s
CI/CD Pipeline / CI Gate (pull_request) Successful in 37s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m55s
CI/CD Pipeline / Integration Tests (push) Successful in 4m34s
CI/CD Pipeline / Unit Tests (push) Successful in 15m20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 16m18s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Failing after 698h8m16s
CI/CD Pipeline / Deploy Production (push) Failing after 698h8m17s
CI/CD Pipeline / CI Gate (push) Failing after 698h8m21s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 698h8m22s
CI/CD Pipeline / Build Production API Image (push) Failing after 698h8m24s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 698h17m18s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 698h17m20s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 698h18m3s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 698h17m22s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 698h18m7s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 698h18m9s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 698h23m10s
CI/CD Pipeline / PR Build API Image (push) Failing after 698h23m14s
CI/CD Pipeline / Frontend Lint (push) Failing after 698h23m16s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 698h25m4s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 698h26m1s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 698h26m4s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 698h26m40s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 698h26m41s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 698h26m43s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 698h26m44s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 698h26m45s
CI/CD Pipeline / Build Production Web Image (push) Failing after 698h42m4s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 698h51m46s
CI/CD Pipeline / PR Build Web Image (push) Failing after 698h56m53s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 698h59m44s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 699h0m21s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 699h0m25s
- 添加 useStableArray hook 稳定化 assetIds 引用
- 只有数组内容真正变化时才触发重新请求
- 避免父组件 re-render 导致的无效 API 调用
2026-08-18 12:59:02 +08:00
xiaoxia abb8e0fcaf chore: 清理 generation.py 中未使用的渲染函数和常量 (#1413)
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m3s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m8s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m11s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m21s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m59s
CI/CD Pipeline / Integration Tests (push) Successful in 3m17s
CI/CD Pipeline / Unit Tests (push) Successful in 11m32s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 15m33s
CI/CD Pipeline / Build Staging API Image (push) Successful in 32m13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m28s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 41s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m2s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m0s
CI/CD Pipeline / Canary Release to Production (push) Failing after 699h21m27s
CI/CD Pipeline / CI Gate (push) Failing after 699h45m31s
CI/CD Pipeline / Production Browser E2E (push) Failing after 699h45m29s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 699h45m31s
CI/CD Pipeline / Build Production API Image (push) Failing after 699h45m31s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 699h56m1s
CI/CD Pipeline / PR Build API Image (push) Failing after 699h56m4s
CI/CD Pipeline / Frontend Lint (push) Failing after 699h56m22s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 699h58m13s
CI/CD Pipeline / Deploy Production (push) Failing after 700h19m11s
CI/CD Pipeline / Build Production Web Image (push) Failing after 700h19m12s
CI/CD Pipeline / PR Build Web Image (push) Failing after 700h29m43s
2026-08-18 11:28:15 +08:00
xiaoxia 6b16f0c738 refactor: 预览播放器架构改造 - 前端素材切片播放替代FFmpeg预览渲染 (#1414)
CI/CD Pipeline / Validate - Code Quality (push) Failing after 0s
CI/CD Pipeline / Validate - Migration (alembic) (push) Failing after 0s
CI/CD Pipeline / Build Staging API Image (push) Failing after 0s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 0s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 0s
CI/CD Pipeline / Unit Tests (push) Failing after 0s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 0s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m11s
CI/CD Pipeline / Integration Tests (push) Failing after 0s
CI/CD Pipeline / Production Browser E2E (push) Failing after 700h19m39s
CI/CD Pipeline / Canary Release to Production (push) Failing after 700h19m41s
CI/CD Pipeline / Deploy Production (push) Failing after 700h19m41s
CI/CD Pipeline / Build Production Web Image (push) Failing after 700h19m42s
CI/CD Pipeline / Build Production API Image (push) Failing after 700h19m43s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 700h20m42s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 700h20m43s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 700h20m44s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 700h20m45s
CI/CD Pipeline / PR Build Web Image (push) Failing after 700h20m45s
CI/CD Pipeline / Frontend Lint (push) Failing after 700h20m48s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 700h20m54s
CI/CD Pipeline / CI Gate (push) Failing after 700h53m20s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 700h53m22s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 700h54m23s
CI/CD Pipeline / PR Build API Image (push) Failing after 700h54m26s
2026-08-18 11:05:33 +08:00
SaaS Frontend f6f9d6fc99 ci: retry - transient runner failures on previous commit
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Failing after 0s
CI/CD Pipeline / Build Staging API Image (push) Failing after 0s
CI/CD Pipeline / Validate - Migration (alembic) (push) Failing after 1s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 0s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 0s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 0s
CI/CD Pipeline / Integration Tests (push) Successful in 2m1s
CI/CD Pipeline / Unit Tests (push) Successful in 7m5s
CI/CD Pipeline / Production Browser E2E (push) Failing after 703h16m41s
CI/CD Pipeline / Deploy Production (push) Failing after 703h16m42s
CI/CD Pipeline / CI Gate (push) Failing after 703h16m43s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 703h16m43s
CI/CD Pipeline / Build Production API Image (push) Failing after 703h16m43s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 703h23m45s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 703h23m45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 703h23m49s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 703h23m46s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 703h23m50s
CI/CD Pipeline / PR Build Web Image (push) Failing after 703h23m50s
CI/CD Pipeline / Frontend Lint (push) Failing after 703h23m51s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 703h25m11s
CI/CD Pipeline / Canary Release to Production (push) Failing after 703h50m21s
CI/CD Pipeline / Build Production Web Image (push) Failing after 703h50m23s
CI/CD Pipeline / PR Build API Image (push) Failing after 703h57m31s
2026-08-18 03:08:01 +08:00
SaaS Frontend 55825a4b28 fix: 更新 smoke test 引用 - useStep5Preview 替换为 usePreviewAssets/useSegmentScheduler (#1414)
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m35s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m6s
CI/CD Pipeline / Unit Tests (push) Failing after 20m52s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 20m53s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 20m53s
CI/CD Pipeline / Build Staging API Image (push) Failing after 20m53s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 20m54s
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Failing after 708h41m52s
CI/CD Pipeline / PR Build Web Image (push) Failing after 708h41m52s
CI/CD Pipeline / Frontend Lint (push) Failing after 708h41m54s
CI/CD Pipeline / PR Build API Image (push) Failing after 708h41m53s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 708h41m56s
2026-08-18 02:44:32 +08:00
SaaS Frontend 48a9d11dae refactor: 预览播放器架构改造 - 前端素材切片播放替代FFmpeg预览渲染 (#1414)
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m21s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m50s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m11s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 6m36s
CI/CD Pipeline / Integration Tests (push) Successful in 2m42s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 9m51s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 10m17s
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Failing after 708h52m15s
CI/CD Pipeline / PR Build Web Image (push) Failing after 708h52m17s
CI/CD Pipeline / Frontend Lint (push) Failing after 708h52m17s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 708h52m20s
CI/CD Pipeline / PR Build API Image (push) Failing after 709h25m57s
- 新建 FrontendPreviewPlayer + useSegmentScheduler + usePreviewAssets
- 删除 useStep5Preview.ts(后端FFmpeg预览轮询逻辑)
- Step5预览改为前端直接播放素材片段 + CSS标题叠加
- 预览等待时间从数十秒降到即时,标题样式调整实时生效
2026-08-18 02:34:07 +08:00
5 changed files with 46 additions and 225 deletions
@@ -64,8 +64,11 @@ export function usePreviewAssets(assetIds: string[], enabled: boolean): UsePrevi
const [ready, setReady] = useState(false)
const requestIdRef = useRef(0)
// 稳定化 assetIds:只有内容真正变化时才更新引用
const stableAssetIds = useStableArray(assetIds)
const load = useCallback(async () => {
if (!assetIds.length || !enabled) {
if (!stableAssetIds.length || !enabled) {
setAssets([])
setReady(false)
return
@@ -76,7 +79,7 @@ export function usePreviewAssets(assetIds: string[], enabled: boolean): UsePrevi
setReady(false)
try {
const result = await fetchAssetsByIds(assetIds)
const result = await fetchAssetsByIds(stableAssetIds)
// 防止竞态:只保留最新请求的结果
if (requestIdRef.current === thisRequestId) {
setAssets(result)
@@ -92,7 +95,7 @@ export function usePreviewAssets(assetIds: string[], enabled: boolean): UsePrevi
setLoading(false)
}
}
}, [assetIds, enabled])
}, [stableAssetIds, enabled])
useEffect(() => {
load()
@@ -101,4 +104,22 @@ export function usePreviewAssets(assetIds: string[], enabled: boolean): UsePrevi
return { assets, loading, ready, reload: load }
}
/**
* useStableArray — 数组内容稳定化 Hook
* 只有数组内容真正变化时才返回新的引用,避免父组件 re-render 导致的无效更新
*/
function useStableArray<T>(array: T[]): T[] {
const ref = useRef<T[]>(array)
// 比较数组内容是否真正变化
const hasChanged =
array.length !== ref.current.length || array.some((item, index) => item !== ref.current[index])
if (hasChanged) {
ref.current = array
}
return ref.current
}
export default usePreviewAssets
@@ -47,7 +47,8 @@ describe("GeneratePage module smoke test", () => {
})
})
import "@/pages/generate/hooks/useGenerateVideo"
import "@/pages/generate/hooks/useStep5Preview"
import "@/pages/generate/hooks/usePreviewAssets"
import "@/pages/generate/hooks/useSegmentScheduler"
import "@/pages/generate/hooks/generate-video/useGenerationPolling"
import "@/pages/generate/hooks/useGenerateFormState"
import "@/pages/generate/hooks/useGenerateFormState/useTemplateSelection"
@@ -5,7 +5,9 @@
import { describe, it, expect } from "vitest"
import "@/pages/generate/components/Step5GeneratePreview"
import "@/pages/generate/hooks/useStep5Preview"
import "@/pages/generate/hooks/usePreviewAssets"
import "@/pages/generate/hooks/useSegmentScheduler"
import "@/pages/generate/components/FrontendPreviewPlayer"
import "@/pages/generate/hooks/useStepNavigation"
import "@/pages/generate/components/GenerateStepContent"
import "@/pages/generate/GeneratePage"
-158
View File
@@ -15,7 +15,6 @@ from __future__ import annotations
import json
import logging
import os
import tempfile
import time
from pathlib import Path
@@ -37,8 +36,6 @@ from packages.domain.bgm_utils import merge_bgm_config
OUTPUT_WIDTH = 1280
OUTPUT_HEIGHT = 720
OUTPUT_FPS = 25.0
OUTPUT_DURATION_SECONDS = 5.0
GENERATED_FILES_DIR = Path(os.getenv("GENERATED_FILES_DIR", "/app/generated"))
logger = logging.getLogger(__name__)
@@ -155,7 +152,6 @@ def _flush_logs(task_id: str, gen_task) -> None:
# ── 共享工具模块导入 ──────────────────────────────────────────────────────────
from video_processing.dedup_helpers import create_video_record_and_dedup
from video_processing.ffmpeg_utils import FFMPEG_BIN, run_ffmpeg
from video_processing.oss_helpers import (
download_asset,
get_signed_download_url,
@@ -330,61 +326,6 @@ def _build_plan_and_clips_from_task(
return plan, clips, asset_path_map
def _create_fallback_clip(output_path: Path, title: str) -> None:
"""创建 fallback 视频(无素材时)"""
safe_title = title.replace(":", "\\:").replace("'", "\\'")[:80]
run_ffmpeg(
[
FFMPEG_BIN,
"-y",
"-f",
"lavfi",
"-i",
f"color=c=#111827:s={OUTPUT_WIDTH}x{OUTPUT_HEIGHT}:d={OUTPUT_DURATION_SECONDS}:r={int(OUTPUT_FPS)}",
"-vf",
f"drawtext=text='{safe_title}':fontcolor=white:fontsize=48:x=(w-text_w)/2:y=(h-text_h)/2",
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-movflags",
"+faststart",
str(output_path),
]
)
def _mux_audio_track(video_path: Path, audio_path: str, output_path: Path) -> None:
"""将音频轨混入已渲染的视频(后处理步骤)。
使用 FFmpeg 将视频和音频合并,视频时长为准,音频不足则循环,
音频过长则截断。
"""
command = [
FFMPEG_BIN,
"-y",
"-i",
str(video_path),
"-i",
audio_path,
"-c:v",
"copy",
"-c:a",
"aac",
"-b:a",
"192k",
"-shortest",
"-map",
"0:v:0",
"-map",
"1:a:0",
"-movflags",
"+faststart",
str(output_path),
]
run_ffmpeg(command)
def _download_voice_asset(voice_library_id: str, local_path: Path) -> bool:
"""下载配音文件。
@@ -428,105 +369,6 @@ def _download_voice_asset(voice_library_id: str, local_path: Path) -> bool:
return download_asset(storage_key, local_path)
def _prepare_bgm_track(
*,
bgm_config: dict,
temp_path: Path,
task_id: str = "",
) -> str | None:
"""准备 BGM 音频文件(下载到本地).
支持 3 种来源(按优先级):
1. audio_url — 外部直链 URL(最高优先级)
2. asset_id — 素材库中的音频素材
3. preset_id — 预设 BGM 库
Returns:
BGM 本地文件路径,准备失败返回 None
"""
from urllib.parse import urlparse
audio_url = bgm_config.get("audio_url", "") or ""
asset_id = bgm_config.get("asset_id", "") or ""
preset_id = bgm_config.get("preset_id", "") or ""
bgm_file = temp_path / f"bgm_{task_id or 'track'}.mp3"
# 优先级1:外部直链 URL
if audio_url:
try:
parsed = urlparse(audio_url)
if parsed.scheme in ("http", "https"):
from video_processing.url_security import (
ALLOWED_AUDIO_MIME_TYPES,
safe_download_file,
)
logger.info("[task_id=%s] [BGM] 从URL下载: %s", task_id, audio_url[:80])
safe_download_file(
audio_url,
str(bgm_file),
purpose="bgm_download",
allowed_mime_types=ALLOWED_AUDIO_MIME_TYPES,
timeout=60.0,
)
if bgm_file.exists() and bgm_file.stat().st_size > 0:
return str(bgm_file)
except Exception as e:
logger.warning("[task_id=%s] [BGM] URL下载失败: %s", task_id, e)
# 优先级2:素材库素材
if asset_id:
try:
from app.core.db import SessionLocal
from packages.adapters.sqlalchemy_impl.models import AssetModel
session = SessionLocal()
try:
model = session.query(AssetModel).filter(AssetModel.id == asset_id).first()
if model and (model.storage_key or model.file_url):
# 兼容存量数据:storage_key 为空时 fallback 到 file_url
storage_key = model.storage_key or model.file_url
logger.info("[task_id=%s] [BGM] 从素材库下载: asset_id=%s", task_id, asset_id)
ok = download_asset(storage_key, bgm_file)
if ok and bgm_file.exists() and bgm_file.stat().st_size > 0:
return str(bgm_file)
finally:
session.close()
except Exception as e:
logger.warning("[task_id=%s] [BGM] 素材库下载失败: %s", task_id, e)
# 优先级3:预设 BGM 库
if preset_id:
try:
from packages.domain.preset_bgm import get_preset_bgm
preset = get_preset_bgm(preset_id)
if preset and preset.audio_url:
from video_processing.url_security import (
ALLOWED_AUDIO_MIME_TYPES,
safe_download_file,
)
logger.info("[task_id=%s] [BGM] 从预设库下载: preset_id=%s", task_id, preset_id)
safe_download_file(
preset.audio_url,
str(bgm_file),
purpose="bgm_preset_download",
allowed_mime_types=ALLOWED_AUDIO_MIME_TYPES,
timeout=60.0,
)
if bgm_file.exists() and bgm_file.stat().st_size > 0:
return str(bgm_file)
except Exception as e:
logger.warning("[task_id=%s] [BGM] 预设库下载失败: %s", task_id, e)
# 所有来源都失败
logger.warning("[task_id=%s] [BGM] 所有来源都无法获取BGM,跳过", task_id)
return None
def _verify_url_accessible(
url: str,
timeout: float = 10.0,
+17 -62
View File
@@ -14,11 +14,7 @@ import pytest
from video_processing.unified_render_service import (
UnifiedRenderService,
)
from worker_app.tasks.generation import (
_build_plan_and_clips_from_task,
_create_fallback_clip,
_mux_audio_track,
)
from worker_app.tasks.generation import _build_plan_and_clips_from_task
pytestmark = pytest.mark.skipif(
not shutil.which("ffmpeg"),
@@ -62,61 +58,6 @@ def _generate_test_audio(path: Path, duration: float = 5.0) -> None:
subprocess.run(cmd, check=True, capture_output=True, timeout=30)
# ── 测试 _create_fallback_clip ────────────────────────────────────────────────
class TestFallbackClip:
"""测试 fallback 视频生成。"""
def test_fallback_clip_creates_video(self):
with tempfile.TemporaryDirectory() as tmpdir:
output = Path(tmpdir) / "fallback.mp4"
_create_fallback_clip(output, "Test Fallback")
assert output.exists()
assert output.stat().st_size > 0
# ── 测试 _mux_audio_track ────────────────────────────────────────────────────
class TestMuxAudioTrack:
"""测试视频+音频混合。"""
def test_mux_audio_into_video(self):
with tempfile.TemporaryDirectory() as tmpdir:
video_path = Path(tmpdir) / "video.mp4"
audio_path = Path(tmpdir) / "audio.aac"
output_path = Path(tmpdir) / "output.mp4"
_generate_test_video(video_path, duration=3.0)
_generate_test_audio(audio_path, duration=5.0)
_mux_audio_track(video_path, str(audio_path), output_path)
assert output_path.exists()
assert output_path.stat().st_size > 0
# 验证输出文件包含音频轨
probe_cmd = [
"ffprobe",
"-v",
"quiet",
"-show_streams",
"-select_streams",
"a",
"-of",
"csv=p=0",
str(output_path),
]
result = subprocess.run(probe_cmd, capture_output=True, text=True, timeout=10)
# 如果有音频流,输出非空
assert result.stdout.strip() != "" or result.returncode == 0
# ── 测试 PlanGenerator → UnifiedRenderService 全链路 ─────────────────────────
class TestFullPipeline:
"""验证从虚拟 plan 构建到渲染输出的完整流程。"""
@@ -175,12 +116,26 @@ class TestFullPipeline:
)
render_result = service.render()
# 混音
# 混音 - 直接用 ffmpeg_mux_audio_track 已被清理)
audio_path = work_dir / "voice.aac"
_generate_test_audio(audio_path, duration=5.0)
final_path = work_dir / "final.mp4"
_mux_audio_track(render_result.output_path, str(audio_path), final_path)
mux_cmd = [
"ffmpeg",
"-y",
"-i",
str(render_result.output_path),
"-i",
str(audio_path),
"-c:v",
"copy",
"-c:a",
"aac",
"-shortest",
str(final_path),
]
subprocess.run(mux_cmd, check=True, capture_output=True, timeout=30)
assert final_path.exists()
assert final_path.stat().st_size > 0