fbd89b4089
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m44s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m37s
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m34s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
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 API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 26s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web 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
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m39s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 4m7s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 4m43s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m58s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 11m18s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 11m37s
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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
- render_adapter 按非 audio 源片段顺序批量查 atom_clip.ai_tags, 仅 has_text 显式 false 标记无文字,其余(未打标签/true/null/查询失败)保守不翻转 - UnifiedRenderService 新增 clip_has_text 注入,None 维持 P1 全保守语义 - 删除残留 081_atom_clip_ai_tags.py(与 GPU PR 的 081 撞号,内容已由 082 承载), develop alembic 恢复单 head:080→081_add_gpu_lipsync→082_atom_clip_ai_tags - 新增 19 个测试(纯函数混合标记/服务门控/适配器解析/失败回退),全量 15819 passed
199 lines
7.3 KiB
Python
199 lines
7.3 KiB
Python
"""#1970 PR2 渲染服务微变换注入测试。
|
|
|
|
不做真实渲染,只验证 UnifiedRenderService 上微变换计划的开关、缓存、
|
|
滤镜注入与速度因子;纯参数生成在 test_1970_micro_transform_pure 覆盖。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
|
|
def _make_service(plan_config: dict | None = None, clips=None):
|
|
from video_processing.unified_render_service import UnifiedRenderService
|
|
|
|
svc = object.__new__(UnifiedRenderService)
|
|
svc.plan = MagicMock()
|
|
svc.plan.config = plan_config or {}
|
|
svc.plan.id = "plan-1"
|
|
svc.plan.clips = clips or []
|
|
svc._micro_plan_cache = None
|
|
svc._micro_plan_loaded = False
|
|
svc._clip_has_text = None
|
|
return svc
|
|
|
|
|
|
class TestDedupGate:
|
|
def test_default_enabled_when_config_missing(self):
|
|
svc = _make_service({})
|
|
assert svc._dedup_enabled() is True
|
|
|
|
def test_explicit_true(self):
|
|
svc = _make_service({"dedup_enabled": True})
|
|
assert svc._dedup_enabled() is True
|
|
|
|
def test_explicit_false(self):
|
|
svc = _make_service({"dedup_enabled": False})
|
|
assert svc._dedup_enabled() is False
|
|
|
|
def test_plan_none_config_treated_enabled(self):
|
|
svc = _make_service(None)
|
|
svc.plan.config = None
|
|
assert svc._dedup_enabled() is True
|
|
|
|
|
|
class TestPlanBuild:
|
|
def test_disabled_returns_none_and_cached(self):
|
|
svc = _make_service({"dedup_enabled": False, "generation_task_id": "t1"})
|
|
assert svc._get_micro_transform_plan(5) is None
|
|
# 第二次走缓存
|
|
svc._dedup_enabled = MagicMock(side_effect=AssertionError("不应再次计算"))
|
|
assert svc._get_micro_transform_plan(5) is None
|
|
|
|
def test_zero_clips_returns_none(self):
|
|
svc = _make_service({"generation_task_id": "t1"})
|
|
assert svc._get_micro_transform_plan(0) is None
|
|
|
|
def test_enabled_builds_reproducible_plan(self):
|
|
cfg = {"generation_task_id": "task-abc", "video_index": 2, "bgm": {"enabled": True}}
|
|
svc1 = _make_service(cfg)
|
|
svc2 = _make_service(dict(cfg))
|
|
p1 = svc1._get_micro_transform_plan(6)
|
|
p2 = svc2._get_micro_transform_plan(6)
|
|
assert p1 is not None and p2 is not None
|
|
assert [c.speed for c in p1.clips] == [c.speed for c in p2.clips]
|
|
assert p1.seed == p2.seed
|
|
assert len(p1.clips) == 6
|
|
|
|
def test_p1_conservative_no_hflip(self):
|
|
svc = _make_service({"generation_task_id": "t1"})
|
|
plan = svc._get_micro_transform_plan(30)
|
|
assert all(not c.hflip for c in plan.clips)
|
|
|
|
def test_no_bgm_config_zero_offset(self):
|
|
svc = _make_service({"generation_task_id": "t1"})
|
|
plan = svc._get_micro_transform_plan(3)
|
|
assert plan.bgm_start_offset == 0.0
|
|
|
|
def test_bgm_enabled_offset_in_range(self):
|
|
svc = _make_service({"generation_task_id": "t1", "bgm": {"enabled": True}})
|
|
plan = svc._get_micro_transform_plan(3)
|
|
assert 2.0 <= plan.bgm_start_offset <= 8.0
|
|
|
|
|
|
class TestFilterInjection:
|
|
def test_none_mt_noop(self):
|
|
svc = _make_service({})
|
|
from video_processing.unified_render_service import UnifiedRenderService
|
|
|
|
filters = ["scale=100:100"]
|
|
UnifiedRenderService._apply_micro_transform_video(filters, None)
|
|
UnifiedRenderService._apply_micro_hflip(filters, None)
|
|
assert filters == ["scale=100:100"]
|
|
|
|
def test_eq_injection(self):
|
|
svc = _make_service({"generation_task_id": "t1"})
|
|
plan = svc._get_micro_transform_plan(1)
|
|
mt = plan.clips[0]
|
|
from video_processing.unified_render_service import UnifiedRenderService
|
|
|
|
filters: list[str] = []
|
|
UnifiedRenderService._apply_micro_transform_video(filters, mt)
|
|
assert filters and filters[0].startswith("eq=brightness=")
|
|
assert "contrast=" in filters[0] and "saturation=" in filters[0]
|
|
|
|
def test_hflip_skipped_p1(self):
|
|
svc = _make_service({"generation_task_id": "t1"})
|
|
plan = svc._get_micro_transform_plan(10)
|
|
from video_processing.unified_render_service import UnifiedRenderService
|
|
|
|
for mt in plan.clips:
|
|
filters: list[str] = []
|
|
UnifiedRenderService._apply_micro_hflip(filters, mt)
|
|
assert filters == []
|
|
|
|
def test_speed_factor(self):
|
|
from video_processing.micro_transform_pure import ClipMicroTransform
|
|
from video_processing.unified_render_service import UnifiedRenderService
|
|
|
|
assert UnifiedRenderService._micro_speed_factor(None) == 1.0
|
|
assert UnifiedRenderService._micro_speed_factor(ClipMicroTransform(0, speed=1.025)) == pytest.approx(1.025)
|
|
assert UnifiedRenderService._micro_speed_factor(MagicMock(speed=0.97)) == pytest.approx(0.97)
|
|
|
|
def test_bgm_offset_reader_respects_flag(self):
|
|
svc_off = _make_service({"dedup_enabled": False})
|
|
assert svc_off._get_micro_bgm_offset() == 0.0
|
|
|
|
svc_on = _make_service({"generation_task_id": "t1", "bgm": {"enabled": True}}, clips=[MagicMock()])
|
|
off = svc_on._get_micro_bgm_offset()
|
|
assert 2.0 <= off <= 8.0
|
|
|
|
def test_bgm_offset_zero_without_bgm(self):
|
|
svc = _make_service({"generation_task_id": "t1"}, clips=[MagicMock()])
|
|
assert svc._get_micro_bgm_offset() == 0.0
|
|
|
|
|
|
class TestStreamCopyGate:
|
|
"""dedup 开启时微变换需要重编码,stream copy 必须被拒绝。"""
|
|
|
|
def _build(self, config):
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
from video_processing.unified_render_service import ResolvedClip, UnifiedRenderService
|
|
|
|
source = SimpleNamespace(
|
|
id="c1",
|
|
clip_type="main",
|
|
)
|
|
svc = object.__new__(UnifiedRenderService)
|
|
svc.output_width = 1280
|
|
svc.output_height = 720
|
|
svc.output_fps = 25
|
|
svc.plan = MagicMock()
|
|
svc.plan.id = "plan-1"
|
|
svc.plan.config = config
|
|
svc.plan.clips = [source]
|
|
svc.clips = [source]
|
|
svc._micro_plan_cache = None
|
|
svc._micro_plan_loaded = False
|
|
svc._clip_has_text = None
|
|
resolved = ResolvedClip(
|
|
clip_id="c1",
|
|
asset_id="a1",
|
|
local_path=Path("/tmp/a1.mp4"),
|
|
clip_type="main",
|
|
order=0,
|
|
)
|
|
info = {
|
|
"width": 1280,
|
|
"height": 720,
|
|
"fps": 25.0,
|
|
"video_codec": "h264",
|
|
"pix_fmt": "yuv420p",
|
|
"duration": 5.0,
|
|
"has_audio": True,
|
|
"audio_codec": "aac",
|
|
}
|
|
return svc, resolved, info
|
|
|
|
def test_dedup_enabled_blocks_stream_copy(self):
|
|
from unittest.mock import patch
|
|
|
|
svc, resolved, info = self._build({"dedup_enabled": True, "generation_task_id": "t1"})
|
|
with patch("video_processing.unified_render_service.probe_video_info", return_value=info):
|
|
can_copy, reason = svc._can_use_stream_copy(resolved)
|
|
assert can_copy is False
|
|
assert "微变换" in reason
|
|
|
|
def test_dedup_disabled_allows_stream_copy(self):
|
|
from unittest.mock import patch
|
|
|
|
svc, resolved, info = self._build({"dedup_enabled": False})
|
|
with patch("video_processing.unified_render_service.probe_video_info", return_value=info):
|
|
can_copy, _ = svc._can_use_stream_copy(resolved)
|
|
assert can_copy is True
|