Files
xiaoxia-saas/tests/unit/test_ffmpeg_encoding_optimization.py
T
xiaoxia e4723bfb1b
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (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 / Check push changed paths (push) Successful in 13s
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (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 / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (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 / PR Build Worker Image (pull_request) Successful in 55s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m14s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m10s
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 / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 40s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m31s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m56s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m24s
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
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m27s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 33s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 3m53s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 4m47s
CI/CD Pipeline / Integration Tests (push) Successful in 5m28s
AI Code Review / AI Code Review (pull_request) Successful in 6m51s
CI/CD Pipeline / Validate - Security (push) Successful in 7m4s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 9m19s
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Unit Tests (push) Successful in 10m32s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
perf(gpu): mezzanine relay直传跳过公网OSS(-18s), preset veryfast(-38%), 修复CI staging IP (#2063)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-27 14:48:54 +08:00

349 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for FFmpeg encoding optimization (#1758).
验证:
1. 集中编码常量正确定义,支持环境变量覆盖
2. 所有渲染路径(_execute_ffmpeg / _render_pass_through / normalize_video / processor)
使用统一的编码参数
3. preset 从 medium → veryfast(#2063 优化 fast→veryfast,再提速 38%)
4. threads=0 自动检测 CPU 核心数
"""
from __future__ import annotations
import importlib
import os
import sys
from unittest.mock import MagicMock, patch
import pytest
# ── 常量定义与默认值 ─────────────────────────────────────────────────────────
class TestEncodingConstants:
"""测试集中编码常量的定义与默认值."""
def test_default_preset_is_fast(self):
"""默认 preset 应为 fast(非 medium),确保速度提升."""
from shared.ffmpeg_utils import FFMPEG_ENCODE_PRESET
assert FFMPEG_ENCODE_PRESET == "veryfast"
def test_default_crf_is_23(self):
"""默认 CRF 保持 23,画质不变."""
from shared.ffmpeg_utils import FFMPEG_ENCODE_CRF
assert FFMPEG_ENCODE_CRF == "23"
def test_default_threads_is_auto(self):
"""默认线程数为 0(自动检测 CPU 核心数)."""
from shared.ffmpeg_utils import FFMPEG_ENCODE_THREADS
assert FFMPEG_ENCODE_THREADS == "0"
def test_preset_is_not_medium(self):
"""确保 preset 不再是 medium(验证改动生效)."""
from shared.ffmpeg_utils import FFMPEG_ENCODE_PRESET
assert FFMPEG_ENCODE_PRESET != "medium"
# ── 环境变量覆盖 ─────────────────────────────────────────────────────────────
class TestEnvironmentVariableOverride:
"""测试环境变量覆盖编码参数."""
def _reload_module(self):
"""重新加载模块以读取环境变量."""
import shared.ffmpeg_utils
return importlib.reload(shared.ffmpeg_utils)
def test_override_preset_via_env(self, monkeypatch):
"""通过环境变量覆盖 preset."""
monkeypatch.setenv("FFMPEG_ENCODE_PRESET", "ultrafast")
mod = self._reload_module()
assert mod.FFMPEG_ENCODE_PRESET == "ultrafast"
# 恢复
monkeypatch.delenv("FFMPEG_ENCODE_PRESET", raising=False)
self._reload_module()
def test_override_crf_via_env(self, monkeypatch):
"""通过环境变量覆盖 CRF."""
monkeypatch.setenv("FFMPEG_ENCODE_CRF", "18")
mod = self._reload_module()
assert mod.FFMPEG_ENCODE_CRF == "18"
monkeypatch.delenv("FFMPEG_ENCODE_CRF", raising=False)
self._reload_module()
def test_override_threads_via_env(self, monkeypatch):
"""通过环境变量覆盖线程数."""
monkeypatch.setenv("FFMPEG_ENCODE_THREADS", "4")
mod = self._reload_module()
assert mod.FFMPEG_ENCODE_THREADS == "4"
monkeypatch.delenv("FFMPEG_ENCODE_THREADS", raising=False)
self._reload_module()
# ── Worker 层 re-export ──────────────────────────────────────────────────────
class TestWorkerReExport:
"""测试 worker 层 ffmpeg_utils 正确 re-export 编码常量."""
def test_worker_reexports_preset(self):
"""worker ffmpeg_utils 应 re-export FFMPEG_ENCODE_PRESET."""
from video_processing.ffmpeg_utils import FFMPEG_ENCODE_PRESET
assert FFMPEG_ENCODE_PRESET == "veryfast"
def test_worker_reexports_crf(self):
"""worker ffmpeg_utils 应 re-export FFMPEG_ENCODE_CRF."""
from video_processing.ffmpeg_utils import FFMPEG_ENCODE_CRF
assert FFMPEG_ENCODE_CRF == "23"
def test_worker_reexports_threads(self):
"""worker ffmpeg_utils 应 re-export FFMPEG_ENCODE_THREADS."""
from video_processing.ffmpeg_utils import FFMPEG_ENCODE_THREADS
assert FFMPEG_ENCODE_THREADS == "0"
# ── _execute_ffmpeg 编码参数 ─────────────────────────────────────────────────
class TestExecuteFfmpegEncoding:
"""测试 _execute_ffmpeg 方法使用正确的编码参数."""
def _get_execute_command(self):
"""提取 _execute_ffmpeg 构建的 FFmpeg 命令."""
from video_processing.unified_render_service import UnifiedRenderService
service = UnifiedRenderService.__new__(UnifiedRenderService)
service.plan = MagicMock()
service.plan.id = "test-plan"
captured_cmd = []
def mock_run_ffmpeg(cmd, **kwargs):
captured_cmd.extend(cmd)
with patch(
"video_processing.unified_render_service.run_ffmpeg",
side_effect=mock_run_ffmpeg,
):
service._execute_ffmpeg(
"test_filter_complex",
["-i", "input.mp4"],
__import__("pathlib").Path("/tmp/output.mp4"),
)
return captured_cmd
def test_execute_uses_veryfast_preset(self):
"""_execute_ffmpeg 应使用 veryfast preset."""
cmd = self._get_execute_command()
idx = cmd.index("-preset")
assert cmd[idx + 1] == "veryfast"
def test_execute_uses_crf_23(self):
"""_execute_ffmpeg 应使用 CRF 23."""
cmd = self._get_execute_command()
idx = cmd.index("-crf")
assert cmd[idx + 1] == "23"
def test_execute_uses_auto_threads(self):
"""_execute_ffmpeg 应启用多线程."""
cmd = self._get_execute_command()
idx = cmd.index("-threads")
assert cmd[idx + 1] == "0"
def test_execute_no_medium_preset(self):
"""_execute_ffmpeg 不应再使用 medium preset."""
cmd = self._get_execute_command()
assert "medium" not in cmd
# ── _render_pass_through 编码参数 ────────────────────────────────────────────
class TestRenderPassThroughEncoding:
"""测试 _render_pass_through 方法使用正确的编码参数."""
def test_passthrough_command_contains_veryfast_preset(self):
"""_render_pass_through 命令应包含 veryfast preset."""
# 通过源码检查确认参数已替换
import inspect
from video_processing.unified_render_service import UnifiedRenderService
source = inspect.getsource(UnifiedRenderService._render_pass_through)
assert "FFMPEG_ENCODE_PRESET" in source
assert "FFMPEG_ENCODE_THREADS" in source
assert '"medium"' not in source
def test_passthrough_command_contains_threads(self):
"""_render_pass_through 命令应包含 threads 参数."""
import inspect
from video_processing.unified_render_service import UnifiedRenderService
source = inspect.getsource(UnifiedRenderService._render_pass_through)
assert "FFMPEG_ENCODE_THREADS" in source
# ── normalize_video 编码参数 ─────────────────────────────────────────────────
class TestNormalizeVideoEncoding:
"""测试 normalize_video 使用正确的编码参数."""
def test_normalize_uses_veryfast_preset(self):
"""normalize_video 应使用 veryfast preset."""
import inspect
from video_processing.ffmpeg_utils import normalize_video
source = inspect.getsource(normalize_video)
assert "FFMPEG_ENCODE_PRESET" in source
assert '"medium"' not in source
def test_normalize_uses_configurable_crf(self):
"""normalize_video CRF 应使用可配置常量."""
import inspect
from video_processing.ffmpeg_utils import normalize_video
source = inspect.getsource(normalize_video)
assert "FFMPEG_ENCODE_CRF" in source
def test_normalize_uses_threads(self):
"""normalize_video 应启用多线程."""
import inspect
from video_processing.ffmpeg_utils import normalize_video
source = inspect.getsource(normalize_video)
assert "FFMPEG_ENCODE_THREADS" in source
# ── random_edge_crop 编码参数 ────────────────────────────────────────────────
class TestRandomEdgeCropEncoding:
"""测试 random_edge_crop 函数使用统一编码参数."""
def test_crop_uses_unified_preset(self):
"""random_edge_crop 应使用统一 preset 常量(非硬编码 fast)."""
import inspect
from video_processing.ffmpeg_utils import random_edge_crop
source = inspect.getsource(random_edge_crop)
assert "FFMPEG_ENCODE_PRESET" in source
def test_crop_uses_unified_crf(self):
"""random_edge_crop 应使用统一 CRF 常量(非硬编码 18)."""
import inspect
from video_processing.ffmpeg_utils import random_edge_crop
source = inspect.getsource(random_edge_crop)
assert "FFMPEG_ENCODE_CRF" in source
assert '"18"' not in source
# ── processor.py 编码参数 ────────────────────────────────────────────────────
class TestProcessorEncoding:
"""测试 VideoProcessor 使用统一编码参数."""
def test_processor_uses_unified_preset(self):
"""VideoProcessor.concatenate_videos 应使用统一 preset."""
import inspect
from video_processing.processor import VideoProcessor
source = inspect.getsource(VideoProcessor.concatenate_videos)
assert "FFMPEG_ENCODE_PRESET" in source
assert 'preset="medium"' not in source
def test_processor_uses_unified_crf(self):
"""VideoProcessor.concatenate_videos 应使用统一 CRF."""
import inspect
from video_processing.processor import VideoProcessor
source = inspect.getsource(VideoProcessor.concatenate_videos)
assert "FFMPEG_ENCODE_CRF" in source
assert "crf=23" not in source
# ── 编码参数一致性验证 ────────────────────────────────────────────────────────
class TestEncodingConsistency:
"""验证所有渲染路径使用同一套编码参数."""
def test_all_paths_share_same_preset_source(self):
"""所有路径的 preset 均来自 FFMPEG_ENCODE_PRESET."""
import inspect
from video_processing.ffmpeg_utils import normalize_video, random_edge_crop
from video_processing.processor import VideoProcessor
from video_processing.unified_render_service import UnifiedRenderService
sources = [
inspect.getsource(UnifiedRenderService._execute_ffmpeg),
inspect.getsource(UnifiedRenderService._render_pass_through),
inspect.getsource(normalize_video),
inspect.getsource(random_edge_crop),
inspect.getsource(VideoProcessor.concatenate_videos),
]
for i, src in enumerate(sources):
assert "FFMPEG_ENCODE_PRESET" in src, f"渲染路径 #{i} 未使用 FFMPEG_ENCODE_PRESET"
def test_no_hardcoded_medium_preset_anywhere(self):
"""所有渲染代码中不应再有硬编码的 medium preset."""
import inspect
from video_processing.ffmpeg_utils import normalize_video
from video_processing.unified_render_service import UnifiedRenderService
for fn in [
UnifiedRenderService._execute_ffmpeg,
UnifiedRenderService._render_pass_through,
normalize_video,
]:
src = inspect.getsource(fn)
assert '"medium"' not in src, f"{fn.__name__} 仍有硬编码 medium preset"
def test_no_hardcoded_crf_23_anywhere(self):
"""所有渲染代码中不应再有硬编码的 CRF 23."""
import inspect
from video_processing.ffmpeg_utils import normalize_video
from video_processing.unified_render_service import UnifiedRenderService
for fn in [
UnifiedRenderService._execute_ffmpeg,
UnifiedRenderService._render_pass_through,
normalize_video,
]:
src = inspect.getsource(fn)
# 检查没有硬编码的 "23" 作为 CRF 值
# 注意:行中可能有其他 "23",只检查紧跟 -crf 之后的值
lines = src.split("\n")
for j, line in enumerate(lines):
if '"-crf"' in line or "'-crf'" in line:
# 下一个非空行应该是常量引用而非硬编码数字
for k in range(j + 1, min(j + 3, len(lines))):
stripped = lines[k].strip().strip(",").strip('"').strip("'")
if stripped:
assert stripped != "23", f"{fn.__name__} 仍有硬编码 CRF 23"
break