test(wave163): watermark_config水印配置 +88测 #1098
Executable
+602
@@ -0,0 +1,602 @@
|
||||
"""watermark_config 单元测试 - wave163
|
||||
|
||||
覆盖:
|
||||
- WatermarkConfig 数据类 / from_dict / validate / has_effect
|
||||
- calc_position 9宫格位置计算
|
||||
- calc_scroll_x 滚动计算
|
||||
- build_image_watermark_filter 图片水印滤镜
|
||||
- build_text_watermark_filter 文字水印滤镜
|
||||
- get_position_names / get_position_display_name
|
||||
- 常量验证
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.watermark_config import (
|
||||
DEFAULT_FONT_COLOR,
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_MARGIN_X,
|
||||
DEFAULT_MARGIN_Y,
|
||||
DEFAULT_MODE,
|
||||
DEFAULT_OPACITY,
|
||||
DEFAULT_POSITION,
|
||||
DEFAULT_SCALE,
|
||||
DEFAULT_SCROLL_SPEED,
|
||||
VALID_POSITIONS,
|
||||
WATERMARK_POSITIONS,
|
||||
WatermarkConfig,
|
||||
build_image_watermark_filter,
|
||||
build_text_watermark_filter,
|
||||
calc_position,
|
||||
calc_scroll_x,
|
||||
get_position_display_name,
|
||||
get_position_names,
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# 常量
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestConstants:
|
||||
def test_nine_positions(self):
|
||||
assert len(VALID_POSITIONS) == 9
|
||||
|
||||
def test_watermark_positions_contains_all(self):
|
||||
assert set(WATERMARK_POSITIONS.keys()) == VALID_POSITIONS
|
||||
|
||||
def test_default_position_valid(self):
|
||||
assert DEFAULT_POSITION in VALID_POSITIONS
|
||||
|
||||
def test_default_values(self):
|
||||
assert DEFAULT_MODE in ("text", "image")
|
||||
assert 0 < DEFAULT_SCALE <= 1.0
|
||||
assert 0 <= DEFAULT_OPACITY <= 1.0
|
||||
assert DEFAULT_FONT_SIZE > 0
|
||||
assert DEFAULT_MARGIN_X >= 0
|
||||
assert DEFAULT_MARGIN_Y >= 0
|
||||
assert DEFAULT_SCROLL_SPEED > 0
|
||||
assert isinstance(DEFAULT_FONT_COLOR, str)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# WatermarkConfig 默认值
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestWatermarkConfigDefaults:
|
||||
def test_default_constructor(self):
|
||||
config = WatermarkConfig()
|
||||
assert config.mode == DEFAULT_MODE
|
||||
assert config.position == DEFAULT_POSITION
|
||||
assert config.scale == DEFAULT_SCALE
|
||||
assert config.opacity == DEFAULT_OPACITY
|
||||
assert config.font_size == DEFAULT_FONT_SIZE
|
||||
assert config.font_color == DEFAULT_FONT_COLOR
|
||||
assert config.margin_x == DEFAULT_MARGIN_X
|
||||
assert config.margin_y == DEFAULT_MARGIN_Y
|
||||
assert config.scroll is False
|
||||
assert config.scroll_speed == DEFAULT_SCROLL_SPEED
|
||||
|
||||
def test_text_mode_default(self):
|
||||
config = WatermarkConfig()
|
||||
assert config.mode == "text"
|
||||
assert config.text == ""
|
||||
assert config.font_path == ""
|
||||
|
||||
def test_image_mode_default(self):
|
||||
config = WatermarkConfig(mode="image")
|
||||
assert config.image_path == ""
|
||||
|
||||
|
||||
# ============================================================
|
||||
# from_dict
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestFromDict:
|
||||
def test_none_returns_none(self):
|
||||
assert WatermarkConfig.from_dict(None) is None
|
||||
|
||||
def test_empty_dict_returns_none(self):
|
||||
assert WatermarkConfig.from_dict({}) is None
|
||||
|
||||
def test_disabled_returns_none(self):
|
||||
assert WatermarkConfig.from_dict({"enabled": False}) is None
|
||||
|
||||
def test_image_mode_missing_path_returns_none(self):
|
||||
result = WatermarkConfig.from_dict({"enabled": True, "mode": "image"})
|
||||
assert result is None
|
||||
|
||||
def test_image_mode_with_image_field(self):
|
||||
result = WatermarkConfig.from_dict({"enabled": True, "mode": "image", "image": "/path/to/wm.png"})
|
||||
assert result is not None
|
||||
assert result.mode == "image"
|
||||
assert result.image_path == "/path/to/wm.png"
|
||||
|
||||
def test_image_mode_with_image_path_field(self):
|
||||
result = WatermarkConfig.from_dict({"enabled": True, "mode": "image", "image_path": "/path/wm.png"})
|
||||
assert result is not None
|
||||
assert result.image_path == "/path/wm.png"
|
||||
|
||||
def test_text_mode_missing_text_returns_none(self):
|
||||
result = WatermarkConfig.from_dict({"enabled": True, "mode": "text"})
|
||||
assert result is None
|
||||
|
||||
def test_text_mode_with_text(self):
|
||||
result = WatermarkConfig.from_dict({"enabled": True, "mode": "text", "text": "hello"})
|
||||
assert result is not None
|
||||
assert result.mode == "text"
|
||||
assert result.text == "hello"
|
||||
|
||||
def test_invalid_position_falls_back_default(self):
|
||||
result = WatermarkConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"mode": "text",
|
||||
"text": "hello",
|
||||
"position": "invalid_pos",
|
||||
}
|
||||
)
|
||||
assert result is not None
|
||||
assert result.position == DEFAULT_POSITION
|
||||
|
||||
def test_custom_values_propagated(self):
|
||||
result = WatermarkConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"mode": "text",
|
||||
"text": "测试",
|
||||
"position": "top_left",
|
||||
"opacity": 0.5,
|
||||
"font_size": 32,
|
||||
"font_color": "red",
|
||||
"margin_x": 30,
|
||||
"margin_y": 40,
|
||||
"scroll": True,
|
||||
"scroll_speed": 100,
|
||||
}
|
||||
)
|
||||
assert result is not None
|
||||
assert result.position == "top_left"
|
||||
assert result.opacity == 0.5
|
||||
assert result.font_size == 32
|
||||
assert result.font_color == "red"
|
||||
assert result.margin_x == 30
|
||||
assert result.margin_y == 40
|
||||
assert result.scroll is True
|
||||
assert result.scroll_speed == 100
|
||||
|
||||
def test_image_mode_custom_values(self):
|
||||
result = WatermarkConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"mode": "image",
|
||||
"image_path": "/wm.png",
|
||||
"scale": 0.3,
|
||||
"opacity": 0.6,
|
||||
}
|
||||
)
|
||||
assert result is not None
|
||||
assert result.scale == 0.3
|
||||
assert result.opacity == 0.6
|
||||
|
||||
def test_default_mode_when_unspecified(self):
|
||||
# 只给enabled和text,mode默认text
|
||||
result = WatermarkConfig.from_dict({"enabled": True, "text": "hello"})
|
||||
assert result is not None
|
||||
assert result.mode == DEFAULT_MODE
|
||||
|
||||
def test_text_empty_string_returns_none(self):
|
||||
result = WatermarkConfig.from_dict({"enabled": True, "mode": "text", "text": ""})
|
||||
assert result is None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# validate
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestValidate:
|
||||
def test_valid_text_config(self):
|
||||
config = WatermarkConfig(mode="text", text="hello")
|
||||
ok, msg = config.validate()
|
||||
assert ok is True
|
||||
assert msg == ""
|
||||
|
||||
def test_valid_image_config(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png")
|
||||
ok, msg = config.validate()
|
||||
assert ok is True
|
||||
assert msg == ""
|
||||
|
||||
def test_invalid_position(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", position="nowhere")
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "位置" in msg
|
||||
|
||||
def test_opacity_too_high(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", opacity=1.5)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "透明度" in msg
|
||||
|
||||
def test_opacity_negative(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", opacity=-0.1)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "透明度" in msg
|
||||
|
||||
def test_opacity_boundary_zero(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", opacity=0.0)
|
||||
ok, _ = config.validate()
|
||||
assert ok is True
|
||||
|
||||
def test_opacity_boundary_one(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", opacity=1.0)
|
||||
ok, _ = config.validate()
|
||||
assert ok is True
|
||||
|
||||
def test_image_missing_path(self):
|
||||
config = WatermarkConfig(mode="image")
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "图片路径" in msg
|
||||
|
||||
def test_image_scale_too_small(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", scale=0.001)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "缩放" in msg
|
||||
|
||||
def test_image_scale_too_large(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", scale=1.5)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "缩放" in msg
|
||||
|
||||
def test_image_scale_boundary_low(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", scale=0.01)
|
||||
ok, _ = config.validate()
|
||||
assert ok is True
|
||||
|
||||
def test_image_scale_boundary_high(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", scale=1.0)
|
||||
ok, _ = config.validate()
|
||||
assert ok is True
|
||||
|
||||
def test_text_empty(self):
|
||||
config = WatermarkConfig(mode="text", text="")
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "文字内容" in msg
|
||||
|
||||
def test_font_size_zero(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", font_size=0)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "字体大小" in msg
|
||||
|
||||
def test_font_size_negative(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", font_size=-5)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "字体大小" in msg
|
||||
|
||||
def test_unknown_mode(self):
|
||||
config = WatermarkConfig(mode="video", text="hi")
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "模式" in msg
|
||||
|
||||
|
||||
# ============================================================
|
||||
# has_effect
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestHasEffect:
|
||||
def test_text_has_effect(self):
|
||||
config = WatermarkConfig(mode="text", text="hello", opacity=0.5, font_size=24)
|
||||
assert config.has_effect() is True
|
||||
|
||||
def test_text_empty_no_effect(self):
|
||||
config = WatermarkConfig(mode="text", text="")
|
||||
assert config.has_effect() is False
|
||||
|
||||
def test_text_zero_opacity_no_effect(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", opacity=0.0)
|
||||
assert config.has_effect() is False
|
||||
|
||||
def test_text_zero_font_size_no_effect(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", font_size=0)
|
||||
assert config.has_effect() is False
|
||||
|
||||
def test_image_has_effect(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", opacity=0.5)
|
||||
assert config.has_effect() is True
|
||||
|
||||
def test_image_no_path_no_effect(self):
|
||||
config = WatermarkConfig(mode="image")
|
||||
assert config.has_effect() is False
|
||||
|
||||
def test_image_zero_opacity_no_effect(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", opacity=0.0)
|
||||
assert config.has_effect() is False
|
||||
|
||||
def test_unknown_mode_no_effect(self):
|
||||
config = WatermarkConfig(mode="invalid")
|
||||
assert config.has_effect() is False
|
||||
|
||||
|
||||
# ============================================================
|
||||
# calc_position
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestCalcPosition:
|
||||
OUT_W = 1920
|
||||
OUT_H = 1080
|
||||
WM_W = 200
|
||||
WM_H = 50
|
||||
MX = 20
|
||||
MY = 20
|
||||
|
||||
def test_top_left(self):
|
||||
x, y = calc_position("top_left", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == 20
|
||||
assert y == 20
|
||||
|
||||
def test_top_center(self):
|
||||
x, y = calc_position("top_center", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == (1920 - 200) // 2
|
||||
assert y == 20
|
||||
|
||||
def test_top_right(self):
|
||||
x, y = calc_position("top_right", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == 1920 - 200 - 20
|
||||
assert y == 20
|
||||
|
||||
def test_center_left(self):
|
||||
x, y = calc_position("center_left", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == 20
|
||||
assert y == (1080 - 50) // 2
|
||||
|
||||
def test_center(self):
|
||||
x, y = calc_position("center", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == (1920 - 200) // 2
|
||||
assert y == (1080 - 50) // 2
|
||||
|
||||
def test_center_right(self):
|
||||
x, y = calc_position("center_right", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == 1920 - 200 - 20
|
||||
assert y == (1080 - 50) // 2
|
||||
|
||||
def test_bottom_left(self):
|
||||
x, y = calc_position("bottom_left", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == 20
|
||||
assert y == 1080 - 50 - 20
|
||||
|
||||
def test_bottom_center(self):
|
||||
x, y = calc_position("bottom_center", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == (1920 - 200) // 2
|
||||
assert y == 1080 - 50 - 20
|
||||
|
||||
def test_bottom_right(self):
|
||||
x, y = calc_position("bottom_right", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == 1920 - 200 - 20
|
||||
assert y == 1080 - 50 - 20
|
||||
|
||||
def test_invalid_position_defaults_bottom_right(self):
|
||||
x, y = calc_position("invalid", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
assert x == 1920 - 200 - 20
|
||||
assert y == 1080 - 50 - 20
|
||||
|
||||
def test_zero_margins(self):
|
||||
x, y = calc_position("top_left", self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, 0, 0)
|
||||
assert x == 0
|
||||
assert y == 0
|
||||
|
||||
def test_all_nine_positions_are_unique(self):
|
||||
positions = set()
|
||||
for pos in VALID_POSITIONS:
|
||||
pos_xy = calc_position(pos, self.OUT_W, self.OUT_H, self.WM_W, self.WM_H, self.MX, self.MY)
|
||||
positions.add(pos_xy)
|
||||
assert len(positions) == 9
|
||||
|
||||
|
||||
# ============================================================
|
||||
# calc_scroll_x
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestCalcScrollX:
|
||||
def test_returns_string(self):
|
||||
result = calc_scroll_x("bottom_left", 1920, 200, 50)
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_contains_output_width(self):
|
||||
result = calc_scroll_x("bottom_left", 1920, 200, 50)
|
||||
assert "1920" in result
|
||||
|
||||
def test_contains_wm_width(self):
|
||||
result = calc_scroll_x("bottom_left", 1920, 200, 50)
|
||||
assert "200" in result
|
||||
|
||||
def test_contains_speed(self):
|
||||
result = calc_scroll_x("bottom_left", 1920, 200, 50)
|
||||
assert "50" in result
|
||||
|
||||
def test_contains_mod_keyword(self):
|
||||
result = calc_scroll_x("bottom_left", 1920, 200, 50)
|
||||
assert "mod" in result
|
||||
|
||||
|
||||
# ============================================================
|
||||
# build_image_watermark_filter
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestBuildImageWatermarkFilter:
|
||||
def test_returns_tuple(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", position="top_left")
|
||||
result = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[out]", config)
|
||||
assert isinstance(result, tuple)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_filter_contains_overlay(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", position="bottom_right")
|
||||
filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[out]", config)
|
||||
assert "overlay" in filter_str
|
||||
|
||||
def test_filter_contains_scale(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", scale=0.3)
|
||||
filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[out]", config)
|
||||
assert "scale=" in filter_str
|
||||
|
||||
def test_opacity_applied_when_below_one(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", opacity=0.5)
|
||||
filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[out]", config)
|
||||
assert "colorchannelmixer" in filter_str
|
||||
assert "aa=0.5" in filter_str
|
||||
|
||||
def test_opacity_one_no_mixer(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", opacity=1.0)
|
||||
filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[out]", config)
|
||||
assert "colorchannelmixer" not in filter_str
|
||||
|
||||
def test_input_args_contain_image_path(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/path/to/wm.png")
|
||||
_, input_args = build_image_watermark_filter("[in]", "/path/to/wm.png", 1920, 1080, "[out]", config)
|
||||
assert input_args == ["-i", "/path/to/wm.png"]
|
||||
|
||||
def test_output_label_present(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png")
|
||||
filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[final]", config)
|
||||
assert "[final]" in filter_str
|
||||
|
||||
def test_scroll_mode_contains_t_variable(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", scroll=True, scroll_speed=60)
|
||||
filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[out]", config)
|
||||
assert "*t" in filter_str or "t*" in filter_str
|
||||
|
||||
def test_static_mode_no_t_variable_in_overlay_x(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png", scroll=False)
|
||||
filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[out]", config)
|
||||
# 静态模式 x 是具体数值,不包含 t 变量
|
||||
overlay_part = filter_str.split("overlay=")[1]
|
||||
assert "t" not in overlay_part.split(":")[0] or "wm" in overlay_part.split(":")[0]
|
||||
|
||||
def test_semicolon_separated_filters(self):
|
||||
config = WatermarkConfig(mode="image", image_path="/wm.png")
|
||||
filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1920, 1080, "[out]", config)
|
||||
assert ";" in filter_str
|
||||
|
||||
|
||||
# ============================================================
|
||||
# build_text_watermark_filter
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestBuildTextWatermarkFilter:
|
||||
def test_returns_string(self):
|
||||
config = WatermarkConfig(mode="text", text="hello")
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_contains_drawtext(self):
|
||||
config = WatermarkConfig(mode="text", text="hello")
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert "drawtext=" in result
|
||||
|
||||
def test_contains_text(self):
|
||||
config = WatermarkConfig(mode="text", text="测试水印")
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert "测试水印" in result
|
||||
|
||||
def test_fontsize_in_filter(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", font_size=36)
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert "fontsize=36" in result
|
||||
|
||||
def test_fontcolor_with_opacity(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", font_color="red", opacity=0.5)
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert "fontcolor=red@0.5" in result
|
||||
|
||||
def test_font_path_included(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", font_path="/fonts/simsun.ttf")
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert "fontfile=" in result
|
||||
assert "simsun.ttf" in result
|
||||
|
||||
def test_no_font_path_when_empty(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", font_path="")
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert "fontfile=" not in result
|
||||
|
||||
def test_text_colon_escaped(self):
|
||||
config = WatermarkConfig(mode="text", text="time: 00:00")
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
# 冒号应该被转义
|
||||
assert "time\\:" in result or "time\\\\:" in result
|
||||
|
||||
def test_scroll_mode_contains_mod(self):
|
||||
config = WatermarkConfig(mode="text", text="scrolling", scroll=True, scroll_speed=50)
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert "mod(" in result
|
||||
|
||||
def test_static_mode_has_numeric_x_y(self):
|
||||
config = WatermarkConfig(mode="text", text="hi", position="top_left")
|
||||
result = build_text_watermark_filter("[in]", "[out]", config, 1920, 1080)
|
||||
assert "x=" in result
|
||||
assert "y=" in result
|
||||
|
||||
def test_output_label_present(self):
|
||||
config = WatermarkConfig(mode="text", text="hi")
|
||||
result = build_text_watermark_filter("[in]", "[final_out]", config, 1920, 1080)
|
||||
assert "[final_out]" in result
|
||||
|
||||
def test_input_label_present(self):
|
||||
config = WatermarkConfig(mode="text", text="hi")
|
||||
result = build_text_watermark_filter("[video_in]", "[out]", config, 1920, 1080)
|
||||
assert result.startswith("[video_in]")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 工具函数
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestGetPositionNames:
|
||||
def test_returns_nine_names(self):
|
||||
names = get_position_names()
|
||||
assert len(names) == 9
|
||||
|
||||
def test_order_is_correct(self):
|
||||
names = get_position_names()
|
||||
# 按从上到下、从左到右
|
||||
assert names[0] == "top_left"
|
||||
assert names[1] == "top_center"
|
||||
assert names[2] == "top_right"
|
||||
assert names[3] == "center_left"
|
||||
assert names[4] == "center"
|
||||
assert names[5] == "center_right"
|
||||
assert names[6] == "bottom_left"
|
||||
assert names[7] == "bottom_center"
|
||||
assert names[8] == "bottom_right"
|
||||
|
||||
def test_all_valid(self):
|
||||
names = get_position_names()
|
||||
assert set(names) == VALID_POSITIONS
|
||||
|
||||
|
||||
class TestGetPositionDisplayName:
|
||||
def test_known_position(self):
|
||||
assert get_position_display_name("top_left") == "左上"
|
||||
assert get_position_display_name("center") == "中心"
|
||||
assert get_position_display_name("bottom_right") == "右下"
|
||||
|
||||
def test_unknown_position_returns_original(self):
|
||||
assert get_position_display_name("invalid") == "invalid"
|
||||
Reference in New Issue
Block a user