Anpassung --cut flag

This commit is contained in:
Javanaut
2026-04-16 19:02:57 +02:00
parent ab5e8e53e1
commit 3a87bbbba6
5 changed files with 83 additions and 42 deletions

View File

@@ -958,7 +958,6 @@ def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor):
metavar="DURATION|START,DURATION", metavar="DURATION|START,DURATION",
is_flag=False, is_flag=False,
flag_value=DEFAULT_CUT_OPTION_VALUE, flag_value=DEFAULT_CUT_OPTION_VALUE,
default=None,
callback=normalizeCutOption, callback=normalizeCutOption,
help=CUT_OPTION_HELP, help=CUT_OPTION_HELP,
) )

View File

@@ -95,7 +95,25 @@ def write_vtt(path: Path, lines: tuple[str, ...]) -> Path:
return path return path
def create_source_fixture(workdir: Path, filename: str, tracks: list[SourceTrackSpec], duration_seconds: int = 1) -> Path: def create_source_fixture(
workdir: Path,
filename: str,
tracks: list[SourceTrackSpec],
duration_seconds: int = 1,
*,
video_encoder: str = "libx264",
video_encoder_options: tuple[str, ...] = (
"-preset",
"ultrafast",
"-crf",
"35",
"-pix_fmt",
"yuv420p",
),
audio_encoder: str = "aac",
audio_encoder_options: tuple[str, ...] = ("-b:a", "48k"),
subtitle_encoder: str = "webvtt",
) -> Path:
output_path = workdir / filename output_path = workdir / filename
has_video = any(track.track_type == TrackType.VIDEO for track in tracks) has_video = any(track.track_type == TrackType.VIDEO for track in tracks)
@@ -189,21 +207,16 @@ def create_source_fixture(workdir: Path, filename: str, tracks: list[SourceTrack
command += map_tokens command += map_tokens
command += metadata_tokens command += metadata_tokens
command += disposition_tokens command += disposition_tokens
if has_video:
command += ["-c:v", video_encoder] + list(video_encoder_options)
if has_audio:
command += ["-c:a", audio_encoder] + list(audio_encoder_options)
if subtitle_input_indices:
command += ["-c:s", subtitle_encoder]
command += [ command += [
"-c:v",
"libx264",
"-preset",
"ultrafast",
"-crf",
"35",
"-pix_fmt",
"yuv420p",
"-c:a",
"aac",
"-b:a",
"48k",
"-c:s",
"webvtt",
"-t", "-t",
str(duration_seconds), str(duration_seconds),
"-shortest", "-shortest",

View File

@@ -18,7 +18,7 @@ from ffx.track_type import TrackType # noqa: E402
class UnmuxSequenceTests(unittest.TestCase): class UnmuxSequenceTests(unittest.TestCase):
def test_h265_video_unmux_uses_annex_b_bitstream_filter(self): def test_h265_video_unmux_uses_annex_b_bitstream_filter_without_forced_format(self):
track_descriptor = TrackDescriptor( track_descriptor = TrackDescriptor(
index=0, index=0,
sub_index=0, sub_index=0,
@@ -46,8 +46,6 @@ class UnmuxSequenceTests(unittest.TestCase):
"copy", "copy",
"-bsf:v", "-bsf:v",
"hevc_mp4toannexb", "hevc_mp4toannexb",
"-f",
"h265",
"episode_0_eng.h265", "episode_0_eng.h265",
], ],
sequence, sequence,

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
import sys import sys
import tempfile
import unittest import unittest
@@ -16,6 +17,7 @@ from ffx.i18n import set_current_language # noqa: E402
from ffx.logging_utils import get_ffx_logger # noqa: E402 from ffx.logging_utils import get_ffx_logger # noqa: E402
from ffx.track_codec import TrackCodec # noqa: E402 from ffx.track_codec import TrackCodec # noqa: E402
from ffx.track_type import TrackType # noqa: E402 from ffx.track_type import TrackType # noqa: E402
from tests.support.ffx_bundle import SourceTrackSpec, create_source_fixture # noqa: E402
class StaticConfig: class StaticConfig:
@@ -39,10 +41,26 @@ class FilePropertiesAssetProbeTests(unittest.TestCase):
} }
set_current_language("de") set_current_language("de")
media_path = ( with tempfile.TemporaryDirectory() as tmpdir:
Path(__file__).resolve().parents[1] media_path = create_source_fixture(
/ "assets" Path(tmpdir),
/ "Boruto; Naruto Next Generations (2017) - 0069 Super-Chochos Liebestaumel - S01E0069.webm" "fixture.webm",
[
SourceTrackSpec(TrackType.VIDEO, identity="video-0"),
SourceTrackSpec(TrackType.AUDIO, identity="audio-1", language="eng"),
SourceTrackSpec(
TrackType.SUBTITLE,
identity="subtitle-2",
language="eng",
subtitle_lines=("Lorem ipsum dolor sit amet.",),
),
],
duration_seconds=3,
video_encoder="libvpx-vp9",
video_encoder_options=("-b:v", "0", "-crf", "45"),
audio_encoder="libopus",
audio_encoder_options=("-b:a", "48k"),
subtitle_encoder="webvtt",
) )
file_properties = FileProperties(context, str(media_path)) file_properties = FileProperties(context, str(media_path))

View File

@@ -15,6 +15,7 @@ if str(SRC_ROOT) not in sys.path:
from ffx.logging_utils import get_ffx_logger # noqa: E402 from ffx.logging_utils import get_ffx_logger # noqa: E402
from ffx.helper import LogLevel # noqa: E402
from ffx.media_descriptor import MediaDescriptor # noqa: E402 from ffx.media_descriptor import MediaDescriptor # noqa: E402
from ffx.metadata_editor import ( # noqa: E402 from ffx.metadata_editor import ( # noqa: E402
apply_metadata_edits, apply_metadata_edits,
@@ -33,6 +34,16 @@ class StaticConfig:
return {} return {}
class NotificationCollector:
def __init__(self) -> None:
self.messages: list[str] = []
self.levels: list[LogLevel | None] = []
def __call__(self, message: str, level: LogLevel | None = None) -> None:
self.messages.append(message)
self.levels.append(level)
def make_context(*, dry_run: bool = False) -> dict: def make_context(*, dry_run: bool = False) -> dict:
return { return {
"logger": get_ffx_logger(), "logger": get_ffx_logger(),
@@ -151,7 +162,7 @@ class MetadataEditorTests(unittest.TestCase):
context = make_context(dry_run=True) context = make_context(dry_run=True)
baseline_descriptor = make_descriptor() baseline_descriptor = make_descriptor()
draft_descriptor = baseline_descriptor.clone(context=context) draft_descriptor = baseline_descriptor.clone(context=context)
notifications = [] notifications = NotificationCollector()
expected_command = build_metadata_edit_command( expected_command = build_metadata_edit_command(
build_metadata_edit_context(context), build_metadata_edit_context(context),
"/tmp/example.mkv", "/tmp/example.mkv",
@@ -170,12 +181,13 @@ class MetadataEditorTests(unittest.TestCase):
"/tmp/example.mkv", "/tmp/example.mkv",
baseline_descriptor, baseline_descriptor,
draft_descriptor, draft_descriptor,
loggingHandler = notifications.append, loggingHandler = notifications,
) )
mocked_execute.assert_not_called() mocked_execute.assert_not_called()
mocked_replace.assert_not_called() mocked_replace.assert_not_called()
self.assertEqual(["ffmpeg dry-run prepared."], notifications) self.assertEqual(["ffmpeg dry-run prepared."], notifications.messages)
self.assertEqual([None], notifications.levels)
self.assertEqual( self.assertEqual(
{ {
"applied": False, "applied": False,
@@ -204,7 +216,7 @@ class MetadataEditorTests(unittest.TestCase):
context["verbosity"] = 1 context["verbosity"] = 1
baseline_descriptor = make_descriptor() baseline_descriptor = make_descriptor()
draft_descriptor = baseline_descriptor.clone(context=context) draft_descriptor = baseline_descriptor.clone(context=context)
notifications = [] notifications = NotificationCollector()
with ( with (
patch("ffx.metadata_editor.create_temporary_output_path", return_value="/tmp/.edit.mkv"), patch("ffx.metadata_editor.create_temporary_output_path", return_value="/tmp/.edit.mkv"),
@@ -216,11 +228,12 @@ class MetadataEditorTests(unittest.TestCase):
"/tmp/example.mkv", "/tmp/example.mkv",
baseline_descriptor, baseline_descriptor,
draft_descriptor, draft_descriptor,
loggingHandler = notifications.append, loggingHandler = notifications,
) )
self.assertEqual(1, len(notifications)) self.assertEqual(1, len(notifications.messages))
self.assertTrue(notifications[0].startswith("ffmpeg: ffmpeg ")) self.assertTrue(notifications.messages[0].startswith("ffmpeg: ffmpeg "))
self.assertEqual([LogLevel.DEBUG], notifications.levels)
if __name__ == "__main__": if __name__ == "__main__":