4aeb1d5b66
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 2m41s
CI/CD Pipeline / Integration Tests (push) Successful in 3m46s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m32s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m35s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 4m27s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m31s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 9m14s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 4m10s
CI/CD Pipeline / Unit Tests (push) Successful in 17m43s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m28s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m4s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 6m2s
CI/CD Pipeline / Validate - Security (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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
fix(render): 修复生成视频时长短于模板要求时长 (#1614)
98 lines
3.5 KiB
Python
98 lines
3.5 KiB
Python
"""Tests for duration compensation when source video is shorter than configured duration."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from apps.worker.video_processing.unified_render_service import ResolvedClip, UnifiedRenderService
|
|
|
|
|
|
class TestClipEffectiveDurationWithSpeedCompensation:
|
|
"""Test _clip_effective_duration handles speed < 1 correctly."""
|
|
|
|
def test_normal_speed_returns_min(self):
|
|
"""When speed=1.0, effective_duration = min(duration, actual_duration)."""
|
|
clip = ResolvedClip(
|
|
clip_id="c1",
|
|
asset_id="a1",
|
|
local_path=Path("/tmp/fake.mp4"),
|
|
clip_type="main",
|
|
order=0,
|
|
start_time=0.0,
|
|
duration=4.0,
|
|
actual_duration=3.0, # shorter than configured
|
|
playback_speed=1.0,
|
|
)
|
|
# Without speed compensation, effective = min(4, 3) = 3
|
|
assert UnifiedRenderService._clip_effective_duration(clip) == 3.0
|
|
|
|
def test_compensated_speed_returns_configured_duration(self):
|
|
"""When speed < 1 (compensated), effective_duration = configured duration."""
|
|
clip = ResolvedClip(
|
|
clip_id="c1",
|
|
asset_id="a1",
|
|
local_path=Path("/tmp/fake.mp4"),
|
|
clip_type="main",
|
|
order=0,
|
|
start_time=0.0,
|
|
duration=4.0,
|
|
actual_duration=3.0, # shorter than configured
|
|
playback_speed=0.75, # compensated: 3/4 = 0.75
|
|
)
|
|
# With speed compensation, effective = configured duration = 4.0
|
|
assert UnifiedRenderService._clip_effective_duration(clip) == 4.0
|
|
|
|
def test_zero_actual_duration_returns_configured(self):
|
|
"""When actual_duration=0, effective_duration = configured duration."""
|
|
clip = ResolvedClip(
|
|
clip_id="c1",
|
|
asset_id="a1",
|
|
local_path=Path("/tmp/fake.mp4"),
|
|
clip_type="main",
|
|
order=0,
|
|
start_time=0.0,
|
|
duration=4.0,
|
|
actual_duration=0.0,
|
|
playback_speed=1.0,
|
|
)
|
|
assert UnifiedRenderService._clip_effective_duration(clip) == 4.0
|
|
|
|
def test_compensated_speed_with_actual_zero(self):
|
|
"""When speed < 1 and actual=0, still returns configured duration."""
|
|
clip = ResolvedClip(
|
|
clip_id="c1",
|
|
asset_id="a1",
|
|
local_path=Path("/tmp/fake.mp4"),
|
|
clip_type="main",
|
|
order=0,
|
|
start_time=0.0,
|
|
duration=4.0,
|
|
actual_duration=0.0,
|
|
playback_speed=0.5,
|
|
)
|
|
assert UnifiedRenderService._clip_effective_duration(clip) == 4.0
|
|
|
|
|
|
class TestClipAdjustedDurationWithSpeedCompensation:
|
|
"""Test _clip_adjusted_duration accounts for compensated speed."""
|
|
|
|
def test_adjusted_duration_with_compensation(self):
|
|
"""Adjusted duration = min(duration, actual) / speed.
|
|
With compensation: min(4,3)/0.75 = 3/0.75 = 4.0
|
|
This equals the configured duration, which is the goal.
|
|
"""
|
|
clip = ResolvedClip(
|
|
clip_id="c1",
|
|
asset_id="a1",
|
|
local_path=Path("/tmp/fake.mp4"),
|
|
clip_type="main",
|
|
order=0,
|
|
start_time=0.0,
|
|
duration=4.0,
|
|
actual_duration=3.0,
|
|
playback_speed=0.75, # compensated
|
|
)
|
|
adjusted = UnifiedRenderService._clip_adjusted_duration(clip)
|
|
# min(4,3)/0.75 = 3/0.75 = 4.0 (matches configured duration)
|
|
assert abs(adjusted - 4.0) < 0.01
|