test(unit): 第69波 - 统一渲染+音频+适配器纯逻辑测试
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 21s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m11s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 56s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 34s
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 Web Image (pull_request) Successful in 35s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 38s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m58s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 35s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 53s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m6s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
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 Staging (Watchtower auto-deploy) (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m15s
CI/CD Pipeline / Deploy Production (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 / Production Browser E2E (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 3m47s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m58s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m10s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 33s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 21s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m11s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 56s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 34s
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 Web Image (pull_request) Successful in 35s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 38s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m58s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 35s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 53s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m6s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
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 Staging (Watchtower auto-deploy) (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m15s
CI/CD Pipeline / Deploy Production (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 / Production Browser E2E (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 3m47s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m58s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m10s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 33s
- unified_render_service: _resolve_layer_role图层映射 + _LAYER_Z_INDEX层级 + ResolvedClip/RenderLayer数据结构 - render_audio: clip_effective_duration时长计算 + clip_has_audio缓存机制 + RenderContext - render_adapter: _parse_resolution解析(16种边界) + RenderAdapterResult数据结构 +53 tests 全绿
This commit is contained in:
Executable
+154
@@ -0,0 +1,154 @@
|
||||
"""渲染适配器纯逻辑测试 — _parse_resolution 等纯函数."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from video_processing.render_adapter import (
|
||||
DEFAULT_OUTPUT_HEIGHT,
|
||||
DEFAULT_OUTPUT_WIDTH,
|
||||
RenderAdapterResult,
|
||||
_parse_resolution,
|
||||
)
|
||||
|
||||
|
||||
class TestParseResolution:
|
||||
"""_parse_resolution 分辨率字符串解析测试."""
|
||||
|
||||
def test_standard_format(self):
|
||||
"""标准 宽x高 格式."""
|
||||
w, h = _parse_resolution("1920x1080")
|
||||
assert w == 1920
|
||||
assert h == 1080
|
||||
|
||||
def test_portrait_format(self):
|
||||
"""竖屏格式."""
|
||||
w, h = _parse_resolution("1080x1920")
|
||||
assert w == 1080
|
||||
assert h == 1920
|
||||
|
||||
def test_lowercase_x(self):
|
||||
"""小写x."""
|
||||
w, h = _parse_resolution("1280x720")
|
||||
assert w == 1280
|
||||
assert h == 720
|
||||
|
||||
def test_uppercase_x_returns_default(self):
|
||||
"""大写X不匹配小写x → 返回默认值(只支持小写x)."""
|
||||
w, h = _parse_resolution("1280X720")
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_none_returns_default(self):
|
||||
"""None返回默认值."""
|
||||
w, h = _parse_resolution(None)
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_empty_string_returns_default(self):
|
||||
"""空字符串返回默认值."""
|
||||
w, h = _parse_resolution("")
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_no_x_returns_default(self):
|
||||
"""没有x的字符串返回默认值."""
|
||||
w, h = _parse_resolution("1080p")
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_invalid_width_returns_default(self):
|
||||
"""宽度无效返回默认值."""
|
||||
w, h = _parse_resolution("abcx1080")
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_invalid_height_returns_default(self):
|
||||
"""高度无效返回默认值."""
|
||||
w, h = _parse_resolution("1920xabc")
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_zero_width_returns_default(self):
|
||||
"""宽度为0返回默认值."""
|
||||
w, h = _parse_resolution("0x1080")
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_zero_height_returns_default(self):
|
||||
"""高度为0返回默认值."""
|
||||
w, h = _parse_resolution("1920x0")
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_negative_width_returns_default(self):
|
||||
"""负宽度返回默认值."""
|
||||
w, h = _parse_resolution("-100x1080")
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_with_spaces(self):
|
||||
"""带空格的能正确strip."""
|
||||
w, h = _parse_resolution(" 1920 x 1080 ")
|
||||
assert w == 1920
|
||||
assert h == 1080
|
||||
|
||||
def test_multiple_x_returns_default(self):
|
||||
"""多个x的字符串解析失败 → 返回默认值."""
|
||||
w, h = _parse_resolution("100x200x300")
|
||||
# split("x", 1)后h部分是"200x300",int失败返回默认
|
||||
assert w == DEFAULT_OUTPUT_WIDTH
|
||||
assert h == DEFAULT_OUTPUT_HEIGHT
|
||||
|
||||
def test_square_resolution(self):
|
||||
"""正方形分辨率."""
|
||||
w, h = _parse_resolution("512x512")
|
||||
assert w == 512
|
||||
assert h == 512
|
||||
|
||||
def test_default_values_are_reasonable(self):
|
||||
"""默认值合理(竖屏短视频)."""
|
||||
assert DEFAULT_OUTPUT_WIDTH > 0
|
||||
assert DEFAULT_OUTPUT_HEIGHT > 0
|
||||
# 默认是竖屏 1080x1920
|
||||
assert DEFAULT_OUTPUT_WIDTH == 1080
|
||||
assert DEFAULT_OUTPUT_HEIGHT == 1920
|
||||
|
||||
|
||||
class TestRenderAdapterResult:
|
||||
"""RenderAdapterResult 数据结构测试."""
|
||||
|
||||
def test_failure_defaults(self):
|
||||
"""失败结果默认值."""
|
||||
result = RenderAdapterResult(success=False)
|
||||
assert result.success is False
|
||||
assert result.output_url == ""
|
||||
assert result.output_path is None
|
||||
assert result.thumbnail_url == ""
|
||||
assert result.duration == 0.0
|
||||
assert result.file_size == 0
|
||||
assert result.width == 0
|
||||
assert result.height == 0
|
||||
|
||||
def test_success_with_values(self):
|
||||
"""成功结果带完整值."""
|
||||
result = RenderAdapterResult(
|
||||
success=True,
|
||||
output_url="https://example.com/output.mp4",
|
||||
output_path=Path("/tmp/output.mp4"),
|
||||
thumbnail_url="https://example.com/thumb.jpg",
|
||||
duration=30.5,
|
||||
file_size=1024000,
|
||||
width=1080,
|
||||
height=1920,
|
||||
)
|
||||
assert result.success is True
|
||||
assert result.output_url == "https://example.com/output.mp4"
|
||||
assert result.output_path == Path("/tmp/output.mp4")
|
||||
assert result.thumbnail_url == "https://example.com/thumb.jpg"
|
||||
assert result.duration == pytest.approx(30.5)
|
||||
assert result.file_size == 1024000
|
||||
assert result.width == 1080
|
||||
assert result.height == 1920
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
"""渲染音频纯逻辑测试 — clip_effective_duration + clip_has_audio缓存."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import field
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from video_processing.render_audio import (
|
||||
RenderContext,
|
||||
clip_effective_duration,
|
||||
clip_has_audio,
|
||||
)
|
||||
from video_processing.unified_render_service import ResolvedClip
|
||||
|
||||
|
||||
def _make_ctx(work_dir: str = "/tmp") -> RenderContext:
|
||||
"""创建测试用RenderContext."""
|
||||
return RenderContext(work_dir=Path(work_dir), plan_id="test_plan")
|
||||
|
||||
|
||||
def _make_clip(
|
||||
*,
|
||||
duration: float = 0.0,
|
||||
actual_duration: float = 0.0,
|
||||
local_path: str = "/tmp/test.mp4",
|
||||
clip_type: str = "main",
|
||||
clip_id: str = "c1",
|
||||
asset_id: str = "a1",
|
||||
order: int = 0,
|
||||
) -> ResolvedClip:
|
||||
"""快速创建测试用ResolvedClip."""
|
||||
return ResolvedClip(
|
||||
clip_id=clip_id,
|
||||
asset_id=asset_id,
|
||||
local_path=Path(local_path),
|
||||
clip_type=clip_type,
|
||||
order=order,
|
||||
duration=duration,
|
||||
actual_duration=actual_duration,
|
||||
)
|
||||
|
||||
|
||||
class TestClipEffectiveDuration:
|
||||
"""clip_effective_duration 有效时长计算测试."""
|
||||
|
||||
def test_both_zero_returns_zero(self):
|
||||
"""duration和actual_duration都是0 → 0."""
|
||||
clip = _make_clip(duration=0, actual_duration=0)
|
||||
assert clip_effective_duration(clip) == 0.0
|
||||
|
||||
def test_only_actual_duration(self):
|
||||
"""只有actual_duration → 返回actual_duration."""
|
||||
clip = _make_clip(duration=0, actual_duration=30.0)
|
||||
assert clip_effective_duration(clip) == pytest.approx(30.0)
|
||||
|
||||
def test_duration_less_than_actual(self):
|
||||
"""duration < actual → 返回duration(剪辑后的时长)."""
|
||||
clip = _make_clip(duration=10.0, actual_duration=30.0)
|
||||
assert clip_effective_duration(clip) == pytest.approx(10.0)
|
||||
|
||||
def test_duration_greater_than_actual(self):
|
||||
"""duration > actual → 返回actual(不能超过素材时长)."""
|
||||
clip = _make_clip(duration=50.0, actual_duration=30.0)
|
||||
assert clip_effective_duration(clip) == pytest.approx(30.0)
|
||||
|
||||
def test_duration_equals_actual(self):
|
||||
"""duration == actual → 返回该值."""
|
||||
clip = _make_clip(duration=20.0, actual_duration=20.0)
|
||||
assert clip_effective_duration(clip) == pytest.approx(20.0)
|
||||
|
||||
def test_no_actual_with_positive_duration(self):
|
||||
"""actual_duration=0但duration>0 → 返回duration(还没probe时)."""
|
||||
clip = _make_clip(duration=15.0, actual_duration=0.0)
|
||||
assert clip_effective_duration(clip) == pytest.approx(15.0)
|
||||
|
||||
|
||||
class TestClipHasAudio:
|
||||
"""clip_has_audio 音频探测+缓存测试."""
|
||||
|
||||
def test_has_audio_true(self):
|
||||
"""有音频时返回True."""
|
||||
ctx = _make_ctx()
|
||||
clip = _make_clip(local_path="/tmp/video1.mp4")
|
||||
|
||||
with patch("video_processing.render_audio.probe_has_audio", return_value=True):
|
||||
result = clip_has_audio(ctx, clip)
|
||||
assert result is True
|
||||
|
||||
def test_has_audio_false(self):
|
||||
"""无音频时返回False."""
|
||||
ctx = _make_ctx()
|
||||
clip = _make_clip(local_path="/tmp/video2.mp4")
|
||||
|
||||
with patch("video_processing.render_audio.probe_has_audio", return_value=False):
|
||||
result = clip_has_audio(ctx, clip)
|
||||
assert result is False
|
||||
|
||||
def test_cache_avoids_reprobe(self):
|
||||
"""同一个clip多次调用只probe一次(缓存生效)."""
|
||||
ctx = _make_ctx()
|
||||
clip = _make_clip(local_path="/tmp/cached.mp4")
|
||||
|
||||
call_count = 0
|
||||
|
||||
def fake_probe(path):
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
return True
|
||||
|
||||
with patch("video_processing.render_audio.probe_has_audio", side_effect=fake_probe):
|
||||
result1 = clip_has_audio(ctx, clip)
|
||||
result2 = clip_has_audio(ctx, clip)
|
||||
result3 = clip_has_audio(ctx, clip)
|
||||
|
||||
assert result1 is True
|
||||
assert result2 is True
|
||||
assert result3 is True
|
||||
assert call_count == 1 # 只调用了一次
|
||||
|
||||
def test_different_clips_both_probed(self):
|
||||
"""不同clip各自probe一次."""
|
||||
ctx = _make_ctx()
|
||||
clip1 = _make_clip(clip_id="c1", local_path="/tmp/v1.mp4")
|
||||
clip2 = _make_clip(clip_id="c2", local_path="/tmp/v2.mp4")
|
||||
|
||||
probe_call_count = 0
|
||||
|
||||
def fake_probe(path):
|
||||
nonlocal probe_call_count
|
||||
probe_call_count += 1
|
||||
return "v1" in str(path)
|
||||
|
||||
with patch("video_processing.render_audio.probe_has_audio", side_effect=fake_probe):
|
||||
r1 = clip_has_audio(ctx, clip1)
|
||||
r2 = clip_has_audio(ctx, clip2)
|
||||
|
||||
assert r1 is True
|
||||
assert r2 is False
|
||||
assert probe_call_count == 2
|
||||
|
||||
|
||||
class TestRenderContext:
|
||||
"""RenderContext 渲染上下文测试."""
|
||||
|
||||
def test_default_noise_reduction_none(self):
|
||||
"""默认无降噪配置."""
|
||||
ctx = _make_ctx()
|
||||
assert ctx.noise_reduction_config is None
|
||||
|
||||
def test_cache_starts_empty(self):
|
||||
"""音频缓存初始为空."""
|
||||
ctx = _make_ctx()
|
||||
assert ctx._audio_cache == {}
|
||||
Executable
+176
@@ -0,0 +1,176 @@
|
||||
"""统一渲染服务纯逻辑测试 — _resolve_layer_role + 图层配置 + 数据结构."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from video_processing.unified_render_service import (
|
||||
RenderLayer,
|
||||
ResolvedClip,
|
||||
_LAYER_Z_INDEX,
|
||||
_PIP_SCALE,
|
||||
_resolve_layer_role,
|
||||
)
|
||||
|
||||
|
||||
class TestResolveLayerRole:
|
||||
"""_resolve_layer_role 图层角色映射测试."""
|
||||
|
||||
def test_intro_is_main(self):
|
||||
"""intro片段 → main层."""
|
||||
assert _resolve_layer_role("intro", {}) == "main"
|
||||
|
||||
def test_outro_is_main(self):
|
||||
"""outro片段 → main层."""
|
||||
assert _resolve_layer_role("outro", {}) == "main"
|
||||
|
||||
def test_overlay_is_overlay(self):
|
||||
"""overlay片段 → overlay层."""
|
||||
assert _resolve_layer_role("overlay", {}) == "overlay"
|
||||
|
||||
def test_corner_voice(self):
|
||||
"""corner_voice片段 → corner_voice层."""
|
||||
assert _resolve_layer_role("corner_voice", {}) == "corner_voice"
|
||||
|
||||
def test_background(self):
|
||||
"""background片段 → background层."""
|
||||
assert _resolve_layer_role("background", {}) == "background"
|
||||
|
||||
def test_b_roll(self):
|
||||
"""b_roll片段 → broll层."""
|
||||
assert _resolve_layer_role("b_roll", {}) == "broll"
|
||||
|
||||
def test_main_default(self):
|
||||
"""main type默认 → main层."""
|
||||
assert _resolve_layer_role("main", {}) == "main"
|
||||
|
||||
def test_main_with_broll_role(self):
|
||||
"""main type + role=b_roll → broll层."""
|
||||
assert _resolve_layer_role("main", {"role": "b_roll"}) == "broll"
|
||||
|
||||
def test_main_with_audio_role(self):
|
||||
"""main type + role=audio → audio层."""
|
||||
assert _resolve_layer_role("main", {"role": "audio"}) == "audio"
|
||||
|
||||
def test_unknown_type_falls_back_to_main(self):
|
||||
"""未知类型 → main层."""
|
||||
assert _resolve_layer_role("random_type", {}) == "main"
|
||||
|
||||
def test_intro_ignores_role(self):
|
||||
"""intro/outro忽略role配置."""
|
||||
assert _resolve_layer_role("intro", {"role": "b_roll"}) == "main"
|
||||
assert _resolve_layer_role("outro", {"role": "overlay"}) == "main"
|
||||
|
||||
def test_empty_config(self):
|
||||
"""空config不影响结果."""
|
||||
assert _resolve_layer_role("main", {}) == "main"
|
||||
|
||||
def test_none_role(self):
|
||||
"""role=None时走默认."""
|
||||
assert _resolve_layer_role("main", {"role": None}) == "main"
|
||||
|
||||
|
||||
class TestLayerZIndex:
|
||||
"""_LAYER_Z_INDEX 图层层级配置测试."""
|
||||
|
||||
def test_background_lowest(self):
|
||||
"""background在最底层."""
|
||||
assert _LAYER_Z_INDEX["background"] == -1
|
||||
|
||||
def test_main_and_broll_same_level(self):
|
||||
"""main和broll在同一层(z=0)."""
|
||||
assert _LAYER_Z_INDEX["main"] == 0
|
||||
assert _LAYER_Z_INDEX["broll"] == 0
|
||||
|
||||
def test_overlay_and_corner_voice_above(self):
|
||||
"""overlay和corner_voice在z=1."""
|
||||
assert _LAYER_Z_INDEX["overlay"] == 1
|
||||
assert _LAYER_Z_INDEX["corner_voice"] == 1
|
||||
|
||||
def test_audio_highest(self):
|
||||
"""audio在z=2(最高,因为音频不涉及z顺序但参与混音)."""
|
||||
assert _LAYER_Z_INDEX["audio"] == 2
|
||||
|
||||
def test_pip_scale_is_positive(self):
|
||||
"""PiP缩放比例为正数."""
|
||||
assert _PIP_SCALE > 0
|
||||
assert _PIP_SCALE < 1.0
|
||||
|
||||
|
||||
class TestResolvedClip:
|
||||
"""ResolvedClip 数据结构测试."""
|
||||
|
||||
def test_default_values(self):
|
||||
"""默认值正确."""
|
||||
clip = ResolvedClip(
|
||||
clip_id="c1",
|
||||
asset_id="a1",
|
||||
local_path=Path("/tmp/test.mp4"),
|
||||
clip_type="main",
|
||||
order=0,
|
||||
)
|
||||
assert clip.start_time == 0.0
|
||||
assert clip.duration == 0.0
|
||||
assert clip.transition_effect == "cut"
|
||||
assert clip.transition_duration == 0.0
|
||||
assert clip.playback_speed == 1.0
|
||||
assert clip.config == {}
|
||||
assert clip.actual_duration == 0.0
|
||||
assert clip.trim_config is None
|
||||
|
||||
def test_custom_values(self):
|
||||
"""自定义值正确."""
|
||||
clip = ResolvedClip(
|
||||
clip_id="c2",
|
||||
asset_id="a2",
|
||||
local_path=Path("/tmp/video.mp4"),
|
||||
clip_type="overlay",
|
||||
order=1,
|
||||
start_time=5.0,
|
||||
duration=10.0,
|
||||
transition_effect="fade",
|
||||
transition_duration=0.5,
|
||||
playback_speed=1.5,
|
||||
)
|
||||
assert clip.clip_id == "c2"
|
||||
assert clip.asset_id == "a2"
|
||||
assert clip.clip_type == "overlay"
|
||||
assert clip.order == 1
|
||||
assert clip.start_time == 5.0
|
||||
assert clip.duration == 10.0
|
||||
assert clip.transition_effect == "fade"
|
||||
assert clip.transition_duration == 0.5
|
||||
assert clip.playback_speed == 1.5
|
||||
|
||||
|
||||
class TestRenderLayer:
|
||||
"""RenderLayer 数据结构测试."""
|
||||
|
||||
def test_default_values(self):
|
||||
"""默认值正确."""
|
||||
layer = RenderLayer(role="main")
|
||||
assert layer.clips == []
|
||||
assert layer.z_index == 0
|
||||
assert layer.opacity == 1.0
|
||||
assert layer.position is None
|
||||
|
||||
def test_with_clips(self):
|
||||
"""带片段的图层."""
|
||||
clip = ResolvedClip(
|
||||
clip_id="c1", asset_id="a1",
|
||||
local_path=Path("/tmp/t.mp4"),
|
||||
clip_type="main", order=0,
|
||||
)
|
||||
layer = RenderLayer(role="overlay", clips=[clip], z_index=1)
|
||||
assert len(layer.clips) == 1
|
||||
assert layer.z_index == 1
|
||||
assert layer.role == "overlay"
|
||||
|
||||
def test_background_layer(self):
|
||||
"""background图层配置."""
|
||||
layer = RenderLayer(role="background", z_index=-1, opacity=1.0)
|
||||
assert layer.role == "background"
|
||||
assert layer.z_index == -1
|
||||
assert layer.opacity == 1.0
|
||||
Reference in New Issue
Block a user