Files
wehub-resource-sync 9a6da7d40d
Publish Docker image / Build and publish (push) Failing after 0s
CI / Python tests (push) Failing after 4s
chore: import upstream snapshot with attribution
2026-07-13 12:03:55 +08:00

121 lines
4.5 KiB
Python

import sys
import unittest
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from moviepy import ImageClip
from app.models.schema import VideoTransitionMode
from app.services.utils import video_effects
def _gradient_clip(width=64, height=48, duration=1.0):
"""创建非均匀渐变画面,确保缩放前后的像素差异可以被可靠检测。"""
x = np.linspace(0, 255, width, dtype=np.uint8)
y = np.linspace(0, 255, height, dtype=np.uint8)
frame = np.stack(np.meshgrid(x, y), axis=-1).sum(axis=-1) % 256
rgb = np.stack([frame] * 3, axis=-1).astype(np.uint8)
return ImageClip(rgb).with_duration(duration)
def _detail_frame(width=128, height=96):
"""创建包含高频细节的 RGB 帧,用于观察亚像素缩放是否连续响应。"""
x = np.arange(width, dtype=np.int16)
y = np.arange(height, dtype=np.int16)[:, None]
return np.stack(
(
(x + y) % 256,
(3 * x + y) % 256,
(x + 3 * y) % 256,
),
axis=-1,
).astype(np.uint8)
class TestZoomTransitions(unittest.TestCase):
def test_schema_has_zoom_members(self):
self.assertEqual(VideoTransitionMode.zoom_in.value, "ZoomIn")
self.assertEqual(VideoTransitionMode.zoom_out.value, "ZoomOut")
def test_zoomin_preserves_geometry_and_zooms_over_time(self):
clip = _gradient_clip()
zoomed = video_effects.zoomin_transition(clip, 1)
self.addCleanup(zoomed.close)
self.addCleanup(clip.close)
self.assertEqual(zoomed.size, clip.size)
self.assertEqual(zoomed.duration, clip.duration)
first = zoomed.get_frame(0)
last = zoomed.get_frame(clip.duration - 0.01)
original = clip.get_frame(0)
self.assertEqual(first.shape, original.shape)
self.assertEqual(first.dtype, np.uint8)
# 放大从 1 倍开始,因此首帧应与原始画面保持一致。
np.testing.assert_allclose(first, original, atol=2)
# 末帧来自中心裁剪并放大后的区域,应与原始画面存在明显差异。
self.assertGreater(np.abs(last.astype(int) - original.astype(int)).max(), 2)
def test_zoomout_starts_zoomed_and_returns_to_source(self):
clip = _gradient_clip()
zoomed = video_effects.zoomout_transition(clip, 1)
self.addCleanup(zoomed.close)
self.addCleanup(clip.close)
self.assertEqual(zoomed.size, clip.size)
first = zoomed.get_frame(0)
last = zoomed.get_frame(clip.duration)
original = clip.get_frame(0)
# 缩小的首帧为 1.2 倍画面,结束时精确回到原始比例。
self.assertGreater(np.abs(first.astype(int) - original.astype(int)).max(), 2)
np.testing.assert_allclose(last, original, atol=2)
def test_zoom_frame_rejects_invalid_scale_factor(self):
frame = np.zeros((8, 8, 3), dtype=np.uint8)
with self.assertRaisesRegex(ValueError, "scale_factor"):
video_effects._zoom_frame(frame, 0)
def test_zoom_frame_responds_to_subpixel_scale_changes(self):
frame = _detail_frame()
first = video_effects._zoom_frame(frame, 1.1)
second = video_effects._zoom_frame(frame, 1.1001)
# 这两个比例在旧的整数裁剪算法中会落入相同裁剪尺寸,产生完全相同的帧,
# 随后在跨过整数边界时突然跳变。亚像素采样应当能响应这种微小比例变化。
self.assertGreater(np.count_nonzero(first != second), 0)
self.assertLessEqual(
np.abs(first.astype(np.int16) - second.astype(np.int16)).max(),
1,
)
def test_zoom_frame_keeps_center_stable_for_odd_resolution(self):
width, height = 59, 75
center_x, center_y = width // 2, height // 2
x = np.arange(width, dtype=np.int16) - center_x
y = np.arange(height, dtype=np.int16)[:, None] - center_y
radial = np.clip(x**2 + y**2, 0, 255).astype(np.uint8)
frame = np.stack((radial, radial, radial), axis=-1)
zoomed = video_effects._zoom_frame(frame, 1.2)
self.assertEqual(zoomed.shape, frame.shape)
self.assertEqual(zoomed.dtype, frame.dtype)
# 奇数宽高只有一个精确中心像素,缩放后该像素不应发生横向或纵向漂移。
np.testing.assert_allclose(
zoomed[center_y, center_x],
frame[center_y, center_x],
atol=1,
)
if __name__ == "__main__":
unittest.main()