fix: resolve CI lint failures (black/isort/flake8 E741)
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m24s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m25s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m57s
CI/CD Pipeline / Build Production Runtime Images (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m8s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (push) Successful in 3m17s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m24s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m25s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m57s
CI/CD Pipeline / Build Production Runtime Images (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m8s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (push) Successful in 3m17s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
- Fix flake8 E741 ambiguous variable name 'l' in unified_render_service.py and test_unified_render_service.py - Apply black formatting to 4 files - Apply isort import sorting to 8 files
This commit is contained in:
@@ -2,13 +2,10 @@
|
||||
视频处理模块
|
||||
"""
|
||||
|
||||
from .processor import VideoProcessor, VideoResult
|
||||
|
||||
# 共享工具模块(供 editing_modes / generation / edit_plan_generation 等复用)
|
||||
from . import ffmpeg_utils
|
||||
from . import oss_helpers
|
||||
from . import dedup_helpers
|
||||
from .unified_render_service import UnifiedRenderService, RenderResult
|
||||
from . import dedup_helpers, ffmpeg_utils, oss_helpers
|
||||
from .processor import VideoProcessor, VideoResult
|
||||
from .unified_render_service import RenderResult, UnifiedRenderService
|
||||
|
||||
__all__ = [
|
||||
"VideoProcessor",
|
||||
|
||||
@@ -91,9 +91,7 @@ def create_video_record_and_dedup(
|
||||
|
||||
# (b) 批次内查重(仅当有 batch_id 时)
|
||||
if not duplicate_result and batch_id:
|
||||
duplicate_result = deduplicator.check_batch_duplicate(
|
||||
fingerprint, batch_id, video_id, session
|
||||
)
|
||||
duplicate_result = deduplicator.check_batch_duplicate(fingerprint, batch_id, video_id, session)
|
||||
|
||||
if duplicate_result:
|
||||
generated_video.is_duplicate = True
|
||||
|
||||
@@ -21,7 +21,7 @@ else:
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from video_processing.ffmpeg_utils import FFMPEG_BIN, run_ffmpeg, probe_video_info
|
||||
from video_processing.ffmpeg_utils import FFMPEG_BIN, probe_video_info, run_ffmpeg
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from video_processing.ffmpeg_utils import (
|
||||
FFMPEG_BIN,
|
||||
DEFAULT_OUTPUT_WIDTH,
|
||||
DEFAULT_OUTPUT_HEIGHT,
|
||||
DEFAULT_FPS,
|
||||
DEFAULT_OUTPUT_HEIGHT,
|
||||
DEFAULT_OUTPUT_WIDTH,
|
||||
DEFAULT_TRANSITION_DURATION,
|
||||
FFMPEG_BIN,
|
||||
build_xfade_filter_chain,
|
||||
probe_duration,
|
||||
probe_video_info,
|
||||
@@ -273,7 +273,7 @@ class UnifiedRenderService:
|
||||
)
|
||||
|
||||
# 按 z_index 排序返回
|
||||
layers = sorted(layer_map.values(), key=lambda l: l.z_index)
|
||||
layers = sorted(layer_map.values(), key=lambda lyr: lyr.z_index)
|
||||
return layers
|
||||
|
||||
def _build_filter_complex(self, layers: list[RenderLayer]) -> tuple[str, list[str]]:
|
||||
|
||||
@@ -30,13 +30,12 @@ OUTPUT_FPS = 25.0
|
||||
|
||||
# ── 共享工具模块导入 ──────────────────────────────────────────────────────────
|
||||
|
||||
from video_processing.dedup_helpers import create_video_record_and_dedup
|
||||
from video_processing.oss_helpers import (
|
||||
download_asset,
|
||||
upload_to_oss,
|
||||
)
|
||||
from video_processing.unified_render_service import UnifiedRenderService
|
||||
from video_processing.dedup_helpers import create_video_record_and_dedup
|
||||
|
||||
|
||||
# ── Repository imports (延迟导入避免循环依赖) ─────────────────────────────────
|
||||
|
||||
|
||||
@@ -85,16 +85,15 @@ def _update_task_status(task_id: str, status_action: str, **kwargs) -> bool:
|
||||
|
||||
# ── 共享工具模块导入 ──────────────────────────────────────────────────────────
|
||||
|
||||
from video_processing.ffmpeg_utils import FFMPEG_BIN, run_ffmpeg, probe_duration
|
||||
from video_processing.dedup_helpers import create_video_record_and_dedup
|
||||
from video_processing.ffmpeg_utils import FFMPEG_BIN, probe_duration, run_ffmpeg
|
||||
from video_processing.oss_helpers import (
|
||||
download_asset,
|
||||
oss_bucket,
|
||||
upload_to_oss,
|
||||
)
|
||||
from video_processing.dedup_helpers import create_video_record_and_dedup
|
||||
from video_processing.unified_render_service import UnifiedRenderService
|
||||
|
||||
|
||||
# ── 虚拟 Plan / Clip(内存中构建,不写数据库) ────────────────────────────────
|
||||
|
||||
|
||||
@@ -156,24 +155,28 @@ def _build_plan_and_clips_from_task(
|
||||
# 1 main + N-1 overlay
|
||||
for i, p in enumerate(downloaded_paths):
|
||||
clip_type = "main" if i == 0 else "overlay"
|
||||
clips.append(_VirtualClip(
|
||||
id=f"vc_{i:03d}",
|
||||
plan_id=task_id,
|
||||
clip_type=clip_type,
|
||||
order=i,
|
||||
asset_id=path_to_asset_id[p],
|
||||
))
|
||||
clips.append(
|
||||
_VirtualClip(
|
||||
id=f"vc_{i:03d}",
|
||||
plan_id=task_id,
|
||||
clip_type=clip_type,
|
||||
order=i,
|
||||
asset_id=path_to_asset_id[p],
|
||||
)
|
||||
)
|
||||
elif mode == "voice_over":
|
||||
# N 个 main(config.role=b_roll)
|
||||
for i, p in enumerate(downloaded_paths):
|
||||
clips.append(_VirtualClip(
|
||||
id=f"vc_{i:03d}",
|
||||
plan_id=task_id,
|
||||
clip_type="main",
|
||||
order=i,
|
||||
asset_id=path_to_asset_id[p],
|
||||
config={"role": "b_roll"},
|
||||
))
|
||||
clips.append(
|
||||
_VirtualClip(
|
||||
id=f"vc_{i:03d}",
|
||||
plan_id=task_id,
|
||||
clip_type="main",
|
||||
order=i,
|
||||
asset_id=path_to_asset_id[p],
|
||||
config={"role": "b_roll"},
|
||||
)
|
||||
)
|
||||
elif mode == "voice_pip":
|
||||
# 1 background + 1 corner_voice + N-2 b_roll
|
||||
for i, p in enumerate(downloaded_paths):
|
||||
@@ -183,23 +186,27 @@ def _build_plan_and_clips_from_task(
|
||||
clip_type = "corner_voice"
|
||||
else:
|
||||
clip_type = "b_roll"
|
||||
clips.append(_VirtualClip(
|
||||
id=f"vc_{i:03d}",
|
||||
plan_id=task_id,
|
||||
clip_type=clip_type,
|
||||
order=i,
|
||||
asset_id=path_to_asset_id[p],
|
||||
))
|
||||
clips.append(
|
||||
_VirtualClip(
|
||||
id=f"vc_{i:03d}",
|
||||
plan_id=task_id,
|
||||
clip_type=clip_type,
|
||||
order=i,
|
||||
asset_id=path_to_asset_id[p],
|
||||
)
|
||||
)
|
||||
else:
|
||||
# ONE_TAKE (default): N 个 main clips
|
||||
for i, p in enumerate(downloaded_paths):
|
||||
clips.append(_VirtualClip(
|
||||
id=f"vc_{i:03d}",
|
||||
plan_id=task_id,
|
||||
clip_type="main",
|
||||
order=i,
|
||||
asset_id=path_to_asset_id[p],
|
||||
))
|
||||
clips.append(
|
||||
_VirtualClip(
|
||||
id=f"vc_{i:03d}",
|
||||
plan_id=task_id,
|
||||
clip_type="main",
|
||||
order=i,
|
||||
asset_id=path_to_asset_id[p],
|
||||
)
|
||||
)
|
||||
|
||||
return plan, clips, asset_path_map
|
||||
|
||||
@@ -237,15 +244,23 @@ def _mux_audio_track(video_path: Path, audio_path: str, output_path: Path) -> No
|
||||
command = [
|
||||
FFMPEG_BIN,
|
||||
"-y",
|
||||
"-i", str(video_path),
|
||||
"-i", audio_path,
|
||||
"-c:v", "copy",
|
||||
"-c:a", "aac",
|
||||
"-b:a", "192k",
|
||||
"-i",
|
||||
str(video_path),
|
||||
"-i",
|
||||
audio_path,
|
||||
"-c:v",
|
||||
"copy",
|
||||
"-c:a",
|
||||
"aac",
|
||||
"-b:a",
|
||||
"192k",
|
||||
"-shortest",
|
||||
"-map", "0:v:0",
|
||||
"-map", "1:a:0",
|
||||
"-movflags", "+faststart",
|
||||
"-map",
|
||||
"0:v:0",
|
||||
"-map",
|
||||
"1:a:0",
|
||||
"-movflags",
|
||||
"+faststart",
|
||||
str(output_path),
|
||||
]
|
||||
run_ffmpeg(command)
|
||||
@@ -380,9 +395,7 @@ def generate_video(self, task_id: str) -> dict:
|
||||
output_path = temp_path / output_name
|
||||
|
||||
# 1. 从素材库下载视频素材
|
||||
downloaded_videos = _download_library_assets(
|
||||
asset_library_id, temp_path, asset_ids=task_asset_ids or None
|
||||
)
|
||||
downloaded_videos = _download_library_assets(asset_library_id, temp_path, asset_ids=task_asset_ids or None)
|
||||
|
||||
# 2. 下载配音(如有)
|
||||
audio_path: str | None = None
|
||||
@@ -485,5 +498,3 @@ def generate_video(self, task_id: str) -> dict:
|
||||
"task_id": task_id,
|
||||
"error": str(error),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,13 +14,12 @@ import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from worker_app.tasks.generation import _build_plan_and_clips_from_task
|
||||
from video_processing.unified_render_service import (
|
||||
RenderResult,
|
||||
UnifiedRenderService,
|
||||
_resolve_layer_role,
|
||||
)
|
||||
from worker_app.tasks.generation import _build_plan_and_clips_from_task
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not shutil.which("ffmpeg"),
|
||||
|
||||
@@ -9,20 +9,19 @@ import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from video_processing.unified_render_service import (
|
||||
RenderResult,
|
||||
UnifiedRenderService,
|
||||
)
|
||||
from worker_app.tasks.generation import (
|
||||
_build_plan_and_clips_from_task,
|
||||
_mux_audio_track,
|
||||
_create_fallback_clip,
|
||||
OUTPUT_WIDTH,
|
||||
OUTPUT_HEIGHT,
|
||||
OUTPUT_WIDTH,
|
||||
_build_plan_and_clips_from_task,
|
||||
_create_fallback_clip,
|
||||
_mux_audio_track,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
@@ -215,11 +214,11 @@ class TestFullPipeline:
|
||||
|
||||
def test_dedup_helper_integration(self):
|
||||
"""验证 dedup_helpers.create_video_record_and_dedup 的导入和签名。"""
|
||||
from video_processing.dedup_helpers import create_video_record_and_dedup
|
||||
|
||||
# 只验证函数存在且签名正确(不实际调用,需要数据库)
|
||||
import inspect
|
||||
|
||||
from video_processing.dedup_helpers import create_video_record_and_dedup
|
||||
|
||||
sig = inspect.signature(create_video_record_and_dedup)
|
||||
params = set(sig.parameters.keys())
|
||||
expected = {
|
||||
|
||||
@@ -8,19 +8,17 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from video_processing.unified_render_service import (
|
||||
ResolvedClip,
|
||||
RenderLayer,
|
||||
RenderResult,
|
||||
ResolvedClip,
|
||||
UnifiedRenderService,
|
||||
_resolve_layer_role,
|
||||
)
|
||||
|
||||
|
||||
# ── Fixtures ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -167,12 +165,12 @@ class TestGroupClipsIntoLayers:
|
||||
resolved = svc._resolve_clips()
|
||||
layers = svc._group_clips_into_layers(resolved)
|
||||
|
||||
roles = {l.role for l in layers}
|
||||
roles = {lyr.role for lyr in layers}
|
||||
assert "main" in roles
|
||||
assert "overlay" in roles
|
||||
|
||||
main_layer = next(l for l in layers if l.role == "main")
|
||||
overlay_layer = next(l for l in layers if l.role == "overlay")
|
||||
main_layer = next(lyr for lyr in layers if lyr.role == "main")
|
||||
overlay_layer = next(lyr for lyr in layers if lyr.role == "overlay")
|
||||
assert len(main_layer.clips) == 1
|
||||
assert len(overlay_layer.clips) == 2
|
||||
assert overlay_layer.z_index > main_layer.z_index
|
||||
@@ -206,7 +204,7 @@ class TestGroupClipsIntoLayers:
|
||||
resolved = svc._resolve_clips()
|
||||
layers = svc._group_clips_into_layers(resolved)
|
||||
|
||||
roles = {l.role for l in layers}
|
||||
roles = {lyr.role for lyr in layers}
|
||||
assert roles == {"background", "corner_voice", "broll"}
|
||||
assert len(layers) == 3
|
||||
|
||||
@@ -390,10 +388,12 @@ class TestRender:
|
||||
asset_paths = {"asset_c1.mp4": Path("/tmp/asset_c1.mp4")}
|
||||
svc = _make_service(clips, asset_paths)
|
||||
|
||||
with _patch_path_exists(), \
|
||||
patch("video_processing.unified_render_service.probe_duration", return_value=5.0), \
|
||||
patch.object(svc, "_execute_ffmpeg") as mock_exec, \
|
||||
patch.object(svc, "_probe_output", return_value=(5.0, 1024, 1280, 720)):
|
||||
with (
|
||||
_patch_path_exists(),
|
||||
patch("video_processing.unified_render_service.probe_duration", return_value=5.0),
|
||||
patch.object(svc, "_execute_ffmpeg") as mock_exec,
|
||||
patch.object(svc, "_probe_output", return_value=(5.0, 1024, 1280, 720)),
|
||||
):
|
||||
result = svc.render()
|
||||
|
||||
assert isinstance(result, RenderResult)
|
||||
|
||||
Reference in New Issue
Block a user