37 headless tests covering the logic that is cheap to break: - split planner: plan_disc_split, chapter helpers (whole-fit, multi-disc partition, single-movie chapter/even-cut split, contiguous coverage) - High-profile fill-disc bitrate math (disc count + bounds + capacity) - ffmpeg command generation: profiles, text/image hardsub, 2-pass, nice, progress-to-file, even pad/crop + positive offsets, -ss seek, and the AC3 448k audio-clamp regression - ffprobe subtitle detection: text/image classification, tags/disposition tests/conftest.py makes the in-tree package importable and installs the gettext _ builtin so modules run headless. Forgejo Actions workflow runs the suite on push/PR. Update CLAUDE.md's testing section accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
119 lines
3.6 KiB
Python
119 lines
3.6 KiB
Python
"""Shared test fixtures/helpers for the devedeng-extended test suite.
|
|
|
|
These tests exercise pure logic and command generation without launching the
|
|
GTK UI. They import the package from ``src/`` and stub out the gettext ``_``
|
|
builtin so modules that call ``_(...)`` at runtime work headless.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import gettext
|
|
|
|
# make the in-tree package importable without installing
|
|
_SRC = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
# ensure the gettext _() builtin exists (the app normally installs it at start)
|
|
gettext.install("devedeng")
|
|
|
|
import pytest
|
|
|
|
|
|
class FakeMovie:
|
|
"""Minimal stand-in for file_movie for the pure split/bitrate planners.
|
|
|
|
Only the attributes those functions read are provided.
|
|
"""
|
|
|
|
element_type = "file_movie"
|
|
|
|
def __init__(self, rate_kBps=586.5, length=3600, chapters_step=5,
|
|
divide=True, manual=None, audio_streams=1, subtitles=0):
|
|
self._rate = rate_kBps
|
|
self.original_length = length
|
|
self.chapter_size = chapters_step
|
|
self.divide_in_chapters = divide
|
|
self.chapter_list_entry = manual
|
|
self.audio_streams = audio_streams
|
|
self.subtitles_list = [None] * subtitles
|
|
|
|
def get_kbytes_per_second(self):
|
|
return self._rate
|
|
|
|
|
|
@pytest.fixture
|
|
def make_movie():
|
|
"""Factory for FakeMovie instances used by the planner tests."""
|
|
def _make(**kwargs):
|
|
return FakeMovie(**kwargs)
|
|
return _make
|
|
|
|
|
|
class FakeFileProject:
|
|
"""Stand-in for a file_movie passed to ffmpeg/avconv.convert_file.
|
|
|
|
Defaults describe a typical 1080p 16:9 source targeting NTSC DVD with no
|
|
hardsub. Override fields per test.
|
|
"""
|
|
|
|
def __init__(self, **overrides):
|
|
self.file_name = "/movies/input.mkv"
|
|
self.title_name = "input"
|
|
self.two_pass_encoding = False
|
|
self.volume = 100
|
|
self.audio_delay = 0.0
|
|
self.copy_sound = False
|
|
self.no_reencode_audio_video = False
|
|
self.is_mpeg_ps = False
|
|
self.video_list = [0]
|
|
self.audio_list = [1]
|
|
self.deinterlace = "deinterlace_none"
|
|
self.prerotation = 0
|
|
self.rotation = "rotation_0"
|
|
self.mirror_vertical = False
|
|
self.mirror_horizontal = False
|
|
self.original_width = 1920
|
|
self.original_height = 1080
|
|
self.width_midle = 1920
|
|
self.height_midle = 1080
|
|
self.width_final = 720
|
|
self.height_final = 480
|
|
self.original_fps = 24
|
|
self.format_pal = False
|
|
self.sound5_1 = False
|
|
self.gop12 = True
|
|
self.video_rate_final = 4500
|
|
self.audio_rate_final = 192
|
|
self.aspect_ratio_final = 1.7777777777777777
|
|
self.original_length = 7200
|
|
self.hardsub_index = -1
|
|
self.hardsub_kind = "text"
|
|
self.embedded_subtitles = []
|
|
for k, v in overrides.items():
|
|
setattr(self, k, v)
|
|
|
|
|
|
@pytest.fixture
|
|
def make_file_project():
|
|
def _make(**overrides):
|
|
return FakeFileProject(**overrides)
|
|
return _make
|
|
|
|
|
|
def ffmpeg_converter(disc_type="dvd", profile="compatibility"):
|
|
"""Build an ffmpeg converter with the config singleton set for a test."""
|
|
import devedeng.configuration_data
|
|
import devedeng.ffmpeg
|
|
cfg = devedeng.configuration_data.configuration.get_config()
|
|
cfg.disc_type = disc_type
|
|
cfg.encode_profile = profile
|
|
return devedeng.ffmpeg.ffmpeg()
|
|
|
|
|
|
def arg_after(cmd, flag):
|
|
"""Return the argument following ``flag`` in an argv list, or None."""
|
|
for i, a in enumerate(cmd):
|
|
if a == flag and i + 1 < len(cmd):
|
|
return cmd[i + 1]
|
|
return None
|