Add video stream copy remuxing

This commit is contained in:
ajp_anton
2026-07-22 22:51:00 +00:00
parent 49296d6f45
commit d60ce51b14
7 changed files with 404 additions and 68 deletions
+31 -11
View File
@@ -232,6 +232,30 @@ class VideoEncodeEndToEndTests(unittest.TestCase):
self.assertFalse(list((WORKSPACE / ".source").rglob("*.frames.csv")))
self.assertFalse(list((WORKSPACE / ".source").rglob("*.runner.log")))
def test_mp4_stream_copy_remuxes_video_and_audio(self) -> None:
with tempfile.TemporaryDirectory(prefix="mbt-video-e2e-") as temp_dir:
source = self.copy_fixture("20260602_222842.mp4", Path(temp_dir))
self.create_workspace(source)
self.run_video_encode(
source,
encode=True,
extra_env={
"MBT_VIDEO_CONTAINER": "mp4",
"MBT_VIDEO_CODEC": "copy",
"MBT_VIDEO_AUDIO": "copy",
},
)
output = WORKSPACE / "20260602_222842.mp4.mp4"
self.assertTrue(output.is_file(), output)
self.assert_video_stream_frames(output, 170)
streams = ffprobe_json(output, self.env)["streams"]
self.assertEqual(
next(stream["codec_name"] for stream in streams if stream["codec_type"] == "video"),
"hevc",
)
self.assert_audio_codec(output, "aac")
def test_vfr_mp4_trim_preserves_location_and_adjusts_end_timestamp(self) -> None:
with tempfile.TemporaryDirectory(prefix="mbt-video-e2e-") as temp_dir:
source = self.copy_fixture("20260602_222842.mp4", Path(temp_dir))
@@ -359,21 +383,17 @@ last
self.assertEqual(completed.returncode, 0, completed.stderr)
self.assertIn(expected, completed.stdout)
def test_simple_trim_can_copy_audio(self) -> None:
def test_incompatible_mp4_audio_copy_is_rejected(self) -> None:
with tempfile.TemporaryDirectory(prefix="mbt-video-e2e-") as temp_dir:
source = self.copy_fixture("C0011.MP4", Path(temp_dir))
script = self.create_workspace(source)
self.replace_user_script_body(script, "Trim(last, 10, 49)\nlast")
self.run_video_encode(
source,
encode=True,
extra_env={"MBT_VIDEO_CONTAINER": "mp4", "MBT_VIDEO_AUDIO": "copy"},
)
output = WORKSPACE / "C0011.MP4.mp4"
self.assertTrue(output.is_file(), output)
self.assert_video_stream_frames(output, 40)
self.assert_audio_codec(output, "pcm_s16be")
with self.assertRaises(subprocess.CalledProcessError):
self.run_video_encode(
source,
encode=True,
extra_env={"MBT_VIDEO_CONTAINER": "mp4", "MBT_VIDEO_AUDIO": "copy"},
)
def test_mkv_opus_output(self) -> None:
with tempfile.TemporaryDirectory(prefix="mbt-video-e2e-") as temp_dir:
+87
View File
@@ -31,6 +31,7 @@ from tools.video_timestamp_encode import write_timecode_v2, x264_command
from tools.video_encode_plan import (
missing_source_frames_between_kept,
normal_deleted_frame_count,
preserves_entire_source_timeline,
y4m_command_for_timeline,
)
from tools.video_codec_constraints import (
@@ -46,11 +47,14 @@ from tools.video_inputs import VideoInput
from tools.video_options import OutputNamingOptions
from tools.video_outputs import output_begin_timestamp, planned_output_path
from tools.video_batch_encode import (
EncodingOutputSpec,
_default_output_timezone,
_default_answer,
_encoding_steps,
_parse_audio,
_parse_answer,
_question_for_key,
_stream_copy_warnings,
)
from tools.video_probe import AudioStreamProbe, VideoProbe, _video_timezone, summarize_frame_timing
from tools.video_reporting import _format_probe_timestamp, _format_vfr_timing
@@ -109,6 +113,89 @@ def make_probe(
class VideoCodecConstraintTests(unittest.TestCase):
def test_video_copy_is_only_available_for_a_full_source_timeline(self) -> None:
source = OutputTimeline(
frames=[OutputFrameTiming(index, 1, index, float(index), 1.0) for index in range(3)],
audio_segments=[(0.0, 3.0)],
consumed_source_frames=[0, 1, 2],
duration_seconds=3.0,
timing_kind="cfr",
beginning_trimmed=False,
ending_trimmed=False,
dropped_frame_count=0,
source_frame_count=3,
matrix=None,
)
trimmed = OutputTimeline(
frames=source.frames[:2],
audio_segments=[(0.0, 2.0)],
consumed_source_frames=[0, 1],
duration_seconds=2.0,
timing_kind="cfr",
beginning_trimmed=False,
ending_trimmed=True,
dropped_frame_count=0,
source_frame_count=3,
matrix=None,
)
self.assertTrue(preserves_entire_source_timeline(source))
self.assertFalse(preserves_entire_source_timeline(trimmed))
def test_mp4_stream_copy_warnings_name_incompatible_streams(self) -> None:
specs = [
EncodingOutputSpec(
label="clip.mkv",
width=1920,
height=1080,
bit_depth=8,
chroma="420",
fps=30.0,
begin_utc="unknown",
summary_lines=(),
video_codec="prores",
audio_codecs=("pcm_s16le",),
)
]
warnings = _stream_copy_warnings(
extension=".mp4",
video_codec="copy",
audio_mode="copy",
specs=specs,
)
self.assertEqual(
warnings,
["does not support prores video", "does not support pcm_s16le audio"],
)
def test_mp4_allows_opus_and_flac_audio(self) -> None:
self.assertEqual(_parse_audio("opus"), "opus")
self.assertEqual(_parse_audio("flac"), "flac")
spec = EncodingOutputSpec(
label="clip.mp4",
width=1920,
height=1080,
bit_depth=8,
chroma="420",
fps=30.0,
begin_utc="unknown",
summary_lines=(),
video_codec="h264",
audio_codecs=("flac",),
)
self.assertEqual(
_stream_copy_warnings(
extension=".mp4",
video_codec="libx265",
audio_mode="copy",
specs=[spec],
),
[],
)
def test_pixel_format_preserves_ten_bit_output(self) -> None:
formats = [
(8, "yuv420p"),