Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| acec550e36 |
Executable → Regular
+31
-15
@@ -113,8 +113,8 @@ from video_processing.oss_helpers import (
|
|||||||
get_signed_download_url,
|
get_signed_download_url,
|
||||||
upload_to_oss,
|
upload_to_oss,
|
||||||
)
|
)
|
||||||
from video_processing.unified_render_service import UnifiedRenderService
|
|
||||||
from video_processing.render_engine_resolver import ENGINE_LEGACY, ENGINE_UNIFIED
|
from video_processing.render_engine_resolver import ENGINE_LEGACY, ENGINE_UNIFIED
|
||||||
|
from video_processing.unified_render_service import UnifiedRenderService
|
||||||
|
|
||||||
# ── 虚拟 Plan / Clip(内存中构建,不写数据库) ────────────────────────────────
|
# ── 虚拟 Plan / Clip(内存中构建,不写数据库) ────────────────────────────────
|
||||||
|
|
||||||
@@ -619,7 +619,8 @@ def _render_with_legacy_engine(
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
main_clips = [
|
main_clips = [
|
||||||
c for c in virtual_clips
|
c
|
||||||
|
for c in virtual_clips
|
||||||
if c.clip_type in ("main", "b_roll", "background")
|
if c.clip_type in ("main", "b_roll", "background")
|
||||||
or (c.clip_type == "main" and c.config.get("role") == "b_roll")
|
or (c.clip_type == "main" and c.config.get("role") == "b_roll")
|
||||||
]
|
]
|
||||||
@@ -674,17 +675,27 @@ def _render_with_legacy_engine(
|
|||||||
filter_complex = ";".join(fc_parts)
|
filter_complex = ";".join(fc_parts)
|
||||||
|
|
||||||
command = [
|
command = [
|
||||||
FFMPEG_BIN, "-y",
|
FFMPEG_BIN,
|
||||||
|
"-y",
|
||||||
*input_args,
|
*input_args,
|
||||||
"-filter_complex", filter_complex,
|
"-filter_complex",
|
||||||
"-map", video_label,
|
filter_complex,
|
||||||
"-map", audio_label,
|
"-map",
|
||||||
"-c:v", "libx264",
|
video_label,
|
||||||
"-crf", "23",
|
"-map",
|
||||||
"-preset", "medium",
|
audio_label,
|
||||||
"-c:a", "aac",
|
"-c:v",
|
||||||
"-b:a", "192k",
|
"libx264",
|
||||||
"-movflags", "+faststart",
|
"-crf",
|
||||||
|
"23",
|
||||||
|
"-preset",
|
||||||
|
"medium",
|
||||||
|
"-c:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"192k",
|
||||||
|
"-movflags",
|
||||||
|
"+faststart",
|
||||||
str(output_path),
|
str(output_path),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -694,7 +705,9 @@ def _render_with_legacy_engine(
|
|||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
"[task_id=%s] [渲染] legacy 引擎 FFmpeg 失败: %s\nfilter_complex: %s",
|
"[task_id=%s] [渲染] legacy 引擎 FFmpeg 失败: %s\nfilter_complex: %s",
|
||||||
task_id, e, filter_complex[:500],
|
task_id,
|
||||||
|
e,
|
||||||
|
filter_complex[:500],
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -880,7 +893,9 @@ def generate_video(self, task_id: str) -> dict:
|
|||||||
render_elapsed = time.monotonic() - render_start
|
render_elapsed = time.monotonic() - render_start
|
||||||
logger.info(
|
logger.info(
|
||||||
"[task_id=%s] [渲染] legacy 引擎完成: 耗时=%.1fs, 时长=%.2fs",
|
"[task_id=%s] [渲染] legacy 引擎完成: 耗时=%.1fs, 时长=%.2fs",
|
||||||
task_id, render_elapsed, render_duration,
|
task_id,
|
||||||
|
render_elapsed,
|
||||||
|
render_duration,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 新引擎:UnifiedRenderService 图层架构
|
# 新引擎:UnifiedRenderService 图层架构
|
||||||
@@ -901,7 +916,8 @@ def generate_video(self, task_id: str) -> dict:
|
|||||||
render_elapsed = time.monotonic() - render_start
|
render_elapsed = time.monotonic() - render_start
|
||||||
logger.info(
|
logger.info(
|
||||||
"[task_id=%s] [渲染] unified 引擎完成: 耗时=%.1fs",
|
"[task_id=%s] [渲染] unified 引擎完成: 耗时=%.1fs",
|
||||||
task_id, render_elapsed,
|
task_id,
|
||||||
|
render_elapsed,
|
||||||
)
|
)
|
||||||
|
|
||||||
if gen_task:
|
if gen_task:
|
||||||
|
|||||||
Executable → Regular
+67
-23
@@ -19,9 +19,10 @@ from unittest.mock import MagicMock, patch
|
|||||||
os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret-key-for-testing")
|
os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret-key-for-testing")
|
||||||
os.environ.setdefault("DATABASE_URL", "sqlite:///test.db")
|
os.environ.setdefault("DATABASE_URL", "sqlite:///test.db")
|
||||||
|
|
||||||
import pytest
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
# ── Mock worker 模块以避免数据库连接 ──────────────────────────────────────────
|
# ── Mock worker 模块以避免数据库连接 ──────────────────────────────────────────
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "apps", "worker"))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "apps", "worker"))
|
||||||
@@ -68,11 +69,12 @@ class _TestClip:
|
|||||||
|
|
||||||
def test_resolver_unified_when_enabled_100_percent():
|
def test_resolver_unified_when_enabled_100_percent():
|
||||||
"""flag 全局开启(percentage=100)时,返回 unified。"""
|
"""flag 全局开启(percentage=100)时,返回 unified。"""
|
||||||
|
from video_processing.render_engine_resolver import RenderEngineResolver
|
||||||
|
|
||||||
from packages.adapters.redis.feature_flag_store import (
|
from packages.adapters.redis.feature_flag_store import (
|
||||||
FeatureFlagConfig,
|
FeatureFlagConfig,
|
||||||
InMemoryFeatureFlagStore,
|
InMemoryFeatureFlagStore,
|
||||||
)
|
)
|
||||||
from video_processing.render_engine_resolver import RenderEngineResolver
|
|
||||||
|
|
||||||
store = InMemoryFeatureFlagStore()
|
store = InMemoryFeatureFlagStore()
|
||||||
store.set(FeatureFlagConfig(name="render_engine", enabled=True, percentage=100))
|
store.set(FeatureFlagConfig(name="render_engine", enabled=True, percentage=100))
|
||||||
@@ -83,11 +85,12 @@ def test_resolver_unified_when_enabled_100_percent():
|
|||||||
|
|
||||||
def test_resolver_legacy_when_flag_disabled():
|
def test_resolver_legacy_when_flag_disabled():
|
||||||
"""flag 全局关闭时,返回默认引擎 legacy。"""
|
"""flag 全局关闭时,返回默认引擎 legacy。"""
|
||||||
|
from video_processing.render_engine_resolver import RenderEngineResolver
|
||||||
|
|
||||||
from packages.adapters.redis.feature_flag_store import (
|
from packages.adapters.redis.feature_flag_store import (
|
||||||
FeatureFlagConfig,
|
FeatureFlagConfig,
|
||||||
InMemoryFeatureFlagStore,
|
InMemoryFeatureFlagStore,
|
||||||
)
|
)
|
||||||
from video_processing.render_engine_resolver import RenderEngineResolver
|
|
||||||
|
|
||||||
store = InMemoryFeatureFlagStore()
|
store = InMemoryFeatureFlagStore()
|
||||||
store.set(FeatureFlagConfig(name="render_engine", enabled=False, percentage=100))
|
store.set(FeatureFlagConfig(name="render_engine", enabled=False, percentage=100))
|
||||||
@@ -98,11 +101,12 @@ def test_resolver_legacy_when_flag_disabled():
|
|||||||
|
|
||||||
def test_resolver_whitelist_overrides_percentage_0():
|
def test_resolver_whitelist_overrides_percentage_0():
|
||||||
"""白名单用户即使 percentage=0 也走 unified。"""
|
"""白名单用户即使 percentage=0 也走 unified。"""
|
||||||
|
from video_processing.render_engine_resolver import RenderEngineResolver
|
||||||
|
|
||||||
from packages.adapters.redis.feature_flag_store import (
|
from packages.adapters.redis.feature_flag_store import (
|
||||||
FeatureFlagConfig,
|
FeatureFlagConfig,
|
||||||
InMemoryFeatureFlagStore,
|
InMemoryFeatureFlagStore,
|
||||||
)
|
)
|
||||||
from video_processing.render_engine_resolver import RenderEngineResolver
|
|
||||||
|
|
||||||
store = InMemoryFeatureFlagStore()
|
store = InMemoryFeatureFlagStore()
|
||||||
store.set(
|
store.set(
|
||||||
@@ -121,11 +125,12 @@ def test_resolver_whitelist_overrides_percentage_0():
|
|||||||
|
|
||||||
def test_resolver_percentage_0_all_legacy():
|
def test_resolver_percentage_0_all_legacy():
|
||||||
"""percentage=0 且无白名单时,全部走 legacy。"""
|
"""percentage=0 且无白名单时,全部走 legacy。"""
|
||||||
|
from video_processing.render_engine_resolver import RenderEngineResolver
|
||||||
|
|
||||||
from packages.adapters.redis.feature_flag_store import (
|
from packages.adapters.redis.feature_flag_store import (
|
||||||
FeatureFlagConfig,
|
FeatureFlagConfig,
|
||||||
InMemoryFeatureFlagStore,
|
InMemoryFeatureFlagStore,
|
||||||
)
|
)
|
||||||
from video_processing.render_engine_resolver import RenderEngineResolver
|
|
||||||
|
|
||||||
store = InMemoryFeatureFlagStore()
|
store = InMemoryFeatureFlagStore()
|
||||||
store.set(FeatureFlagConfig(name="render_engine", enabled=True, percentage=0))
|
store.set(FeatureFlagConfig(name="render_engine", enabled=True, percentage=0))
|
||||||
@@ -137,11 +142,12 @@ def test_resolver_percentage_0_all_legacy():
|
|||||||
|
|
||||||
def test_resolver_default_unified_when_flag_off():
|
def test_resolver_default_unified_when_flag_off():
|
||||||
"""默认引擎设为 unified 且 flag 关闭时,返回 unified。"""
|
"""默认引擎设为 unified 且 flag 关闭时,返回 unified。"""
|
||||||
|
from video_processing.render_engine_resolver import RenderEngineResolver
|
||||||
|
|
||||||
from packages.adapters.redis.feature_flag_store import (
|
from packages.adapters.redis.feature_flag_store import (
|
||||||
FeatureFlagConfig,
|
FeatureFlagConfig,
|
||||||
InMemoryFeatureFlagStore,
|
InMemoryFeatureFlagStore,
|
||||||
)
|
)
|
||||||
from video_processing.render_engine_resolver import RenderEngineResolver
|
|
||||||
|
|
||||||
store = InMemoryFeatureFlagStore()
|
store = InMemoryFeatureFlagStore()
|
||||||
store.set(FeatureFlagConfig(name="render_engine", enabled=False, percentage=0))
|
store.set(FeatureFlagConfig(name="render_engine", enabled=False, percentage=0))
|
||||||
@@ -170,11 +176,23 @@ def test_legacy_engine_single_clip_keeps_original_fps():
|
|||||||
# 生成 1 秒 30fps 测试视频(带音频)
|
# 生成 1 秒 30fps 测试视频(带音频)
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[
|
[
|
||||||
"ffmpeg", "-y",
|
"ffmpeg",
|
||||||
"-f", "lavfi", "-i", "color=c=red:s=640x360:d=1:r=30",
|
"-y",
|
||||||
"-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo:d=1",
|
"-f",
|
||||||
"-c:v", "libx264", "-pix_fmt", "yuv420p",
|
"lavfi",
|
||||||
"-c:a", "aac", "-shortest",
|
"-i",
|
||||||
|
"color=c=red:s=640x360:d=1:r=30",
|
||||||
|
"-f",
|
||||||
|
"lavfi",
|
||||||
|
"-i",
|
||||||
|
"anullsrc=r=44100:cl=stereo:d=1",
|
||||||
|
"-c:v",
|
||||||
|
"libx264",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-c:a",
|
||||||
|
"aac",
|
||||||
|
"-shortest",
|
||||||
str(input_path),
|
str(input_path),
|
||||||
],
|
],
|
||||||
check=True,
|
check=True,
|
||||||
@@ -222,11 +240,23 @@ def test_legacy_engine_two_clips_concat_duration():
|
|||||||
color = "red" if idx == 0 else "blue"
|
color = "red" if idx == 0 else "blue"
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[
|
[
|
||||||
"ffmpeg", "-y",
|
"ffmpeg",
|
||||||
"-f", "lavfi", "-i", f"color=c={color}:s=640x360:d=1:r=30",
|
"-y",
|
||||||
"-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo:d=1",
|
"-f",
|
||||||
"-c:v", "libx264", "-pix_fmt", "yuv420p",
|
"lavfi",
|
||||||
"-c:a", "aac", "-shortest",
|
"-i",
|
||||||
|
f"color=c={color}:s=640x360:d=1:r=30",
|
||||||
|
"-f",
|
||||||
|
"lavfi",
|
||||||
|
"-i",
|
||||||
|
"anullsrc=r=44100:cl=stereo:d=1",
|
||||||
|
"-c:v",
|
||||||
|
"libx264",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-c:a",
|
||||||
|
"aac",
|
||||||
|
"-shortest",
|
||||||
str(inp),
|
str(inp),
|
||||||
],
|
],
|
||||||
check=True,
|
check=True,
|
||||||
@@ -264,11 +294,23 @@ def test_legacy_engine_broll_mode_supported():
|
|||||||
|
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[
|
[
|
||||||
"ffmpeg", "-y",
|
"ffmpeg",
|
||||||
"-f", "lavfi", "-i", "color=c=green:s=640x360:d=1:r=30",
|
"-y",
|
||||||
"-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo:d=1",
|
"-f",
|
||||||
"-c:v", "libx264", "-pix_fmt", "yuv420p",
|
"lavfi",
|
||||||
"-c:a", "aac", "-shortest",
|
"-i",
|
||||||
|
"color=c=green:s=640x360:d=1:r=30",
|
||||||
|
"-f",
|
||||||
|
"lavfi",
|
||||||
|
"-i",
|
||||||
|
"anullsrc=r=44100:cl=stereo:d=1",
|
||||||
|
"-c:v",
|
||||||
|
"libx264",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-c:a",
|
||||||
|
"aac",
|
||||||
|
"-shortest",
|
||||||
str(input_path),
|
str(input_path),
|
||||||
],
|
],
|
||||||
check=True,
|
check=True,
|
||||||
@@ -276,8 +318,10 @@ def test_legacy_engine_broll_mode_supported():
|
|||||||
)
|
)
|
||||||
|
|
||||||
clip = _TestClip(
|
clip = _TestClip(
|
||||||
asset_id="asset-1", duration=1.0,
|
asset_id="asset-1",
|
||||||
clip_type="main", config={"role": "b_roll"},
|
duration=1.0,
|
||||||
|
clip_type="main",
|
||||||
|
config={"role": "b_roll"},
|
||||||
)
|
)
|
||||||
asset_path_map = {"asset-1": input_path}
|
asset_path_map = {"asset-1": input_path}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user