fix(mux): strip encoding dates from MP4Box and shaka-packager output

MP4Box gets -for-test (non-MP4 inputs keep their tkhd flags) and shaka decrypts get --use_fake_clock_for_muxer, so MP4 and --no-mux files carry no download time.
This commit is contained in:
imSp4rky
2026-11-12 13:05:14 -07:00
parent e29a523907
commit b48da2a16a
8 changed files with 34 additions and 14 deletions
+2 -2
View File
@@ -482,11 +482,11 @@ Set `default_subtitle_type` to give the subtitle default to a different type. `d
A few muxing behaviors are automatic and not configurable: A few muxing behaviors are automatic and not configurable:
- `--no-date` is always passed for privacy (no timestamps embedded). - The output has no encoding date: mkvmerge gets `--no-date` and MP4Box gets `-for-test`.
- Descriptive audio gets the visually-impaired flag. SDH subtitles get the hearing-impaired flag. Forced subtitles get the forced flag. - Descriptive audio gets the visually-impaired flag. SDH subtitles get the hearing-impaired flag. Forced subtitles get the forced flag.
- unshackle writes the chapters from the title's chapter list (see [Chapters](#chapters-naming) below). - unshackle writes the chapters from the title's chapter list (see [Chapters](#chapters-naming) below).
The `--no-mux` flag skips muxing entirely and writes the individual track files, each with a track-type suffix, into the same output/folder structure. The `--no-mux` flag skips muxing entirely and writes the individual track files, each with a track-type suffix, into the same output/folder structure. A track that shaka-packager decrypts has its date set to 1970-01-01; other tracks keep the date of the source file.
## Sample files ## Sample files
+2 -2
View File
@@ -96,7 +96,7 @@ def test_dts_uhd_audio_routes_the_mux_to_mp4(tmp_path: Path, monkeypatch: pytest
assert out.suffix == ".mp4", "a DTS-UHD title must not be delivered as Matroska" assert out.suffix == ".mp4", "a DTS-UHD title must not be delivered as Matroska"
assert returncode == 0 and not errors assert returncode == 0 and not errors
command = captured[0].args command = captured[0].args
assert command[0] == "MP4Box" assert command[:2] == ["MP4Box", "-for-test"]
assert command[-2:] == ["-new", str(out)] assert command[-2:] == ["-new", str(out)]
assert f"{tmp_path / 'a.mp4'}:lang=en" in command assert f"{tmp_path / 'a.mp4'}:lang=en" in command
# the child must not inherit the terminal that the live progress tree is drawing on # the child must not inherit the terminal that the live progress tree is drawing on
@@ -170,7 +170,7 @@ def test_mp4_subtitles_get_the_script_their_region_implies(tmp_path: Path, monke
tracks.mux("Title", delete=False, output_path=tmp_path / "out.muxed.mkv") tracks.mux("Title", delete=False, output_path=tmp_path / "out.muxed.mkv")
command = captured[0].args command = captured[0].args
assert f"{tmp_path / 's.vtt'}:lang=zh-Hant-TW" in command assert f"{tmp_path / 's.vtt'}:lang=zh-Hant-TW:tkhd=enable,movie,preview" in command
assert any(arg.endswith("=Chinese (Traditional)") for arg in command) assert any(arg.endswith("=Chinese (Traditional)") for arg in command)
+15 -1
View File
@@ -123,7 +123,12 @@ def mp4box_commands(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> list[lis
monkeypatch.setattr(binaries, "MP4Box", Path("/bin/MP4Box"), raising=False) monkeypatch.setattr(binaries, "MP4Box", Path("/bin/MP4Box"), raising=False)
monkeypatch.setattr(tracks_module.subprocess, "Popen", FakePopen) monkeypatch.setattr(tracks_module.subprocess, "Popen", FakePopen)
monkeypatch.setattr(tracks_module, "run_step", lambda command, **kwargs: commands.append([str(c) for c in command]))
def run_step(command: list[Any], output: Path, **kwargs: Any) -> None:
commands.append([str(c) for c in command])
output.write_bytes(b"cut")
monkeypatch.setattr(tracks_module, "run_step", run_step)
monkeypatch.setattr(config, "sample", None) monkeypatch.setattr(config, "sample", None)
monkeypatch.setattr(config.directories, "temp", tmp_path) monkeypatch.setattr(config.directories, "temp", tmp_path)
return commands return commands
@@ -136,6 +141,15 @@ def test_a_3d_mp4_mux_keeps_the_sample_whole(
Tracks(stereo_video(tmp_path))._mux_mp4(title="Film", output_path=tmp_path / "out.mkv") Tracks(stereo_video(tmp_path))._mux_mp4(title="Film", output_path=tmp_path / "out.mkv")
assert len(mp4box_commands) == 1 assert len(mp4box_commands) == 1
assert "-splitx" not in mp4box_commands[0] assert "-splitx" not in mp4box_commands[0]
assert "-for-test" in mp4box_commands[0]
def test_a_2d_mp4_sample_cut_keeps_no_date(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, mp4box_commands: list[list[str]]
) -> None:
monkeypatch.setattr(config, "sample", (120.0, 180.0))
Tracks(stereo_video(tmp_path, stereo=False))._mux_mp4(title="Film", output_path=tmp_path / "out.mkv")
assert mp4box_commands[1][1:3] == ["-for-test", "-splitx"]
def test_a_3d_mp4_mux_writes_quicktime_chapters(tmp_path: Path, mp4box_commands: list[list[str]]) -> None: def test_a_3d_mp4_mux_writes_quicktime_chapters(tmp_path: Path, mp4box_commands: list[list[str]]) -> None:
+1
View File
@@ -122,6 +122,7 @@ def test_shaka_decrypt_passes_the_built_keys(monkeypatch: pytest.MonkeyPatch, tm
keys = seen[0][seen[0].index("--keys") + 1] keys = seen[0][seen[0].index("--keys") + 1]
assert _shaka_zero_entries(keys) == [VIDEO_KEY] assert _shaka_zero_entries(keys) == [VIDEO_KEY]
assert "--use_fake_clock_for_muxer" in seen[0]
def test_a_rebuilt_widevine_does_not_take_the_first_pssh_kid_as_its_own() -> None: def test_a_rebuilt_widevine_does_not_take_the_first_pssh_kid_as_its_own() -> None:
+1
View File
@@ -289,6 +289,7 @@ class ClearKeyCENC:
"label={}:key_id={}:key={}".format(i, kid.hex, key.lower()) "label={}:key_id={}:key={}".format(i, kid.hex, key.lower())
for i, (kid, key) in enumerate(self.content_keys.items()) for i, (kid, key) in enumerate(self.content_keys.items())
), ),
"--use_fake_clock_for_muxer",
"--temp_dir", "--temp_dir",
config.directories.temp, config.directories.temp,
] ]
+1
View File
@@ -654,6 +654,7 @@ class PlayReady:
"--enable_raw_key_decryption", "--enable_raw_key_decryption",
"--keys", "--keys",
drm_key_args.shaka_keys(self.content_keys, self.own_kids()), drm_key_args.shaka_keys(self.content_keys, self.own_kids()),
"--use_fake_clock_for_muxer",
"--temp_dir", "--temp_dir",
config.directories.temp, config.directories.temp,
] ]
+1
View File
@@ -404,6 +404,7 @@ class Widevine:
"--enable_raw_key_decryption", "--enable_raw_key_decryption",
"--keys", "--keys",
drm_key_args.shaka_keys(self.content_keys, self.own_kids()), drm_key_args.shaka_keys(self.content_keys, self.own_kids()),
"--use_fake_clock_for_muxer",
"--temp_dir", "--temp_dir",
config.directories.temp, config.directories.temp,
] ]
+11 -9
View File
@@ -69,10 +69,9 @@ def file_track_ids(path: Path) -> dict[str, int]:
return ids or {"video": 0, "audio": 1} return ids or {"video": 0, "audio": 1}
def mp4_track_count(path: Path) -> int: def mp4_track_count(moov: Optional[bytes]) -> int:
"""How many tracks ``MP4Box -add`` takes from a file: each ``trak`` of an MP4, or 1 for any other file.""" """How many tracks ``MP4Box -add`` takes from a file: each ``trak`` of its moov, or 1 for a file with none."""
moov = read_top_level_box(path, b"moov") or b"" return sum(1 for kind, _, _ in children(moov or b"", 8) if kind == b"trak") or 1
return sum(1 for kind, _, _ in children(moov, 8) if kind == b"trak") or 1
class Tracks: class Tracks:
@@ -677,7 +676,8 @@ class Tracks:
(MV-HEVC) video, whose second view mkvmerge drops. MP4 carries far less: it drops the (MV-HEVC) video, whose second view mkvmerge drops. MP4 carries far less: it drops the
forced, hearing-impaired and original flags, holds no attachments at all, and leaves forced, hearing-impaired and original flags, holds no attachments at all, and leaves
out a subtitle it has no format for rather than mangling it. Losing those beats out a subtitle it has no format for rather than mangling it. Losing those beats
shipping audio no player can name or a 3D film as 2D. shipping audio no player can name or a 3D film as 2D. ``-for-test`` drops the dates and
the GPAC version tag; ``tkhd`` restores the flags it clears on a non-MP4 input.
Returns the same (path, returncode, errors) shape as the Matroska mux. Returns the same (path, returncode, errors) shape as the Matroska mux.
""" """
@@ -688,7 +688,7 @@ class Tracks:
progress = progress or partial(lambda **kwargs: None) progress = progress or partial(lambda **kwargs: None)
stereo = any(v.stereo for v in self.videos) stereo = any(v.stereo for v in self.videos)
cl = [str(mp4box)] cl = [str(mp4box), "-for-test"]
sample_base = self.sample_base sample_base = self.sample_base
names: list[str] = [] names: list[str] = []
cleaned: list[Path] = [] cleaned: list[Path] = []
@@ -723,11 +723,13 @@ class Tracks:
candidate.unlink() candidate.unlink()
delay = round((track.sample_start - sample_base) * 1000) if config.sample else 0 delay = round((track.sample_start - sample_base) * 1000) if config.sample else 0
cl.extend(["-add", f"{source}:lang={track.mux_language}" + (f":delay={delay}" if delay else "")]) moov = read_top_level_box(source, b"moov")
flags = "" if moov else ":tkhd=enable,movie,preview"
cl.extend(["-add", f"{source}:lang={track.mux_language}{flags}" + (f":delay={delay}" if delay else "")])
name = "" if isinstance(track, Video) else (track.get_track_name() or "") name = "" if isinstance(track, Video) else (track.get_track_name() or "")
if name: if name:
names.extend(["-name", f"{track_id + 1}={name}"]) names.extend(["-name", f"{track_id + 1}={name}"])
track_id += mp4_track_count(source) track_id += mp4_track_count(moov)
if not track_id: if not track_id:
raise ValueError("No tracks provided, at least one track must be provided.") raise ValueError("No tracks provided, at least one track must be provided.")
@@ -806,7 +808,7 @@ class Tracks:
cut_path = output_path.with_suffix(".cut.mp4") cut_path = output_path.with_suffix(".cut.mp4")
try: try:
run_step( run_step(
[mp4box, "-splitx", f"{start:.3f}:{end:.3f}", output_path, "-out", cut_path], [mp4box, "-for-test", "-splitx", f"{start:.3f}:{end:.3f}", output_path, "-out", cut_path],
output=cut_path, output=cut_path,
label="MP4Box sample cut", label="MP4Box sample cut",
) )