From 0728ece4b8e1c90420a0db4421dd49ff5a3ff8d1 Mon Sep 17 00:00:00 2001 From: Javanaut Date: Wed, 15 Apr 2026 00:03:17 +0200 Subject: [PATCH] Fix h265 subtrack unmux --- src/ffx/cli.py | 13 ++-- tests/unit/test_cli_unmux_sequence.py | 91 +++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 tests/unit/test_cli_unmux_sequence.py diff --git a/src/ffx/cli.py b/src/ffx/cli.py index bd8f8f7..9b380fc 100755 --- a/src/ffx/cli.py +++ b/src/ffx/cli.py @@ -631,21 +631,24 @@ def rename(ctx, paths, prefix, season, suffix, dry_run): def getUnmuxSequence(trackDescriptor: TrackDescriptor, sourcePath, targetPrefix, targetDirectory = ''): + from ffx.track_codec import TrackCodec + from ffx.track_type import TrackType # executable and input file commandTokens = list(FFMPEG_COMMAND_TOKENS) + ['-i', sourcePath] trackType = trackDescriptor.getType() + trackCodec = trackDescriptor.getCodec() targetPathBase = os.path.join(targetDirectory, targetPrefix) if targetDirectory else targetPrefix # mapping - commandTokens += ['-map', - f"0:{trackType.indicator()}:{trackDescriptor.getSubIndex()}", - '-c', - 'copy'] + commandTokens += ['-map', f"0:{trackType.indicator()}:{trackDescriptor.getSubIndex()}"] - trackCodec = trackDescriptor.getCodec() + if trackType == TrackType.VIDEO and trackCodec == TrackCodec.H265: + commandTokens += ['-c:v', 'copy', '-bsf:v', 'hevc_mp4toannexb'] + else: + commandTokens += ['-c', 'copy'] # output format codecFormat = trackCodec.format() diff --git a/tests/unit/test_cli_unmux_sequence.py b/tests/unit/test_cli_unmux_sequence.py new file mode 100644 index 0000000..b858755 --- /dev/null +++ b/tests/unit/test_cli_unmux_sequence.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +from pathlib import Path +import sys +import unittest + + +SRC_ROOT = Path(__file__).resolve().parents[2] / "src" + +if str(SRC_ROOT) not in sys.path: + sys.path.insert(0, str(SRC_ROOT)) + + +from ffx import cli # noqa: E402 +from ffx.track_codec import TrackCodec # noqa: E402 +from ffx.track_descriptor import TrackDescriptor # noqa: E402 +from ffx.track_type import TrackType # noqa: E402 + + +class UnmuxSequenceTests(unittest.TestCase): + def test_h265_video_unmux_uses_annex_b_bitstream_filter(self): + track_descriptor = TrackDescriptor( + index=0, + sub_index=0, + track_type=TrackType.VIDEO, + codec_name=TrackCodec.H265, + tags={}, + disposition_set=set(), + ) + + sequence = cli.getUnmuxSequence( + track_descriptor, + "input.mp4", + "episode_0_eng", + ) + + self.assertEqual( + [ + "ffmpeg", + "-y", + "-i", + "input.mp4", + "-map", + "0:v:0", + "-c:v", + "copy", + "-bsf:v", + "hevc_mp4toannexb", + "-f", + "h265", + "episode_0_eng.h265", + ], + sequence, + ) + + def test_non_h265_unmux_keeps_generic_copy_behavior(self): + track_descriptor = TrackDescriptor( + index=1, + sub_index=0, + track_type=TrackType.SUBTITLE, + codec_name=TrackCodec.SRT, + tags={}, + disposition_set=set(), + ) + + sequence = cli.getUnmuxSequence( + track_descriptor, + "input.mkv", + "episode_1_eng", + ) + + self.assertEqual( + [ + "ffmpeg", + "-y", + "-i", + "input.mkv", + "-map", + "0:s:0", + "-c", + "copy", + "-f", + "srt", + "episode_1_eng.srt", + ], + sequence, + ) + + +if __name__ == "__main__": + unittest.main()