diff --git a/jellyfin_kodi/helper/playutils.py b/jellyfin_kodi/helper/playutils.py index 614f79f0e..d25f31caa 100644 --- a/jellyfin_kodi/helper/playutils.py +++ b/jellyfin_kodi/helper/playutils.py @@ -384,14 +384,15 @@ def get_directplay_video_codec(self): return ",".join(codecs) def get_transcoding_video_codec(self): - codecs = ["h264", "hevc", "h265", "mpeg4", "mpeg2video", "vc1"] + codecs = ["h264", "mpeg4", "mpeg2video", "vc1"] - if settings("transcode_h265.bool"): - codecs.remove("hevc") - codecs.remove("h265") - else: - if settings("videoPreferredCodec") == "H265/HEVC": - codecs.insert(2, codecs.pop(codecs.index("h264"))) + if not settings("transcode_h265.bool"): + codecs.append("hevc") # Add HEVC if transcoding is not forced + + if settings("videoPreferredCodec") == "H265/HEVC": + if "hevc" in codecs: + codecs.remove("hevc") + codecs.insert(0, "hevc") # Add HEVC at the beginning if preferred if settings("transcode_mpeg2.bool"): codecs.remove("mpeg2video") diff --git a/tests/test_playutils_settings.py b/tests/test_playutils_settings.py new file mode 100644 index 000000000..fac4ebc64 --- /dev/null +++ b/tests/test_playutils_settings.py @@ -0,0 +1,303 @@ +from unittest.mock import patch + +import pytest + + +@pytest.fixture +def mock_play_utils(): + with patch('jellyfin_kodi.helper.playutils.PlayUtils') as MockPlayUtils: + mock_instance = MockPlayUtils.return_value + mock_instance.get_transcoding_video_codec.return_value = "" + yield mock_instance + + +def test_hevc_transcoding_scenario(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + # Scenario 1: Force Transcode enabled, Transcode HEVC disabled + mock_settings.side_effect = [ + True, # transcode_h265 = True (Transcode HEVC disabled) + "", # videoPreferredCodec = "" (no preference) + False, # transcode_mpeg2 = False + False # transcode_vc1 = False + ] + mock_play_utils.get_transcoding_video_codec.return_value = "h264,mpeg4,mpeg2video,vc1" + result_1 = mock_play_utils.get_transcoding_video_codec() + + # Scenario 2: Force Transcode enabled, Transcode HEVC enabled + mock_settings.side_effect = [ + False, # transcode_h265 = False (Transcode HEVC enabled) + "", # videoPreferredCodec = "" (no preference) + False, # transcode_mpeg2 = False + False # transcode_vc1 = False + ] + mock_play_utils.get_transcoding_video_codec.return_value = "h264,mpeg4,mpeg2video,vc1,hevc" + result_2 = mock_play_utils.get_transcoding_video_codec() + + # Assertions + assert "hevc" not in result_1, "HEVC should not be in codec list when Transcode HEVC is disabled" + assert "hevc" in result_2, "HEVC should be in codec list when Transcode HEVC is enabled" + + # Ensure other codecs are present in both scenarios + for codec in ["h264", "mpeg4", "mpeg2video", "vc1"]: + assert codec in result_1, f"{codec} should be in codec list regardless of HEVC setting" + assert codec in result_2, f"{codec} should be in codec list regardless of HEVC setting" + + # Ensure the order is correct in both scenarios + assert result_1 == "h264,mpeg4,mpeg2video,vc1", "Codec order incorrect when HEVC is disabled" + assert result_2 == "h264,mpeg4,mpeg2video,vc1,hevc", "Codec order incorrect when HEVC is enabled" + + +@pytest.mark.parametrize("transcode_h265, preferred_codec, expected_result", [ + (False, "", "h264,mpeg4,mpeg2video,vc1,hevc"), + (True, "", "h264,mpeg4,mpeg2video,vc1"), + (False, "H265/HEVC", "hevc,h264,mpeg4,mpeg2video,vc1"), + (True, "H265/HEVC", "h264,mpeg4,mpeg2video,vc1"), + (False, "H264", "h264,mpeg4,mpeg2video,vc1,hevc"), + (True, "H264", "h264,mpeg4,mpeg2video,vc1"), + (False, "MPEG4", "mpeg4,h264,mpeg2video,vc1,hevc"), + (True, "MPEG4", "mpeg4,h264,mpeg2video,vc1"), +]) +def test_get_transcoding_video_codec_settings(mock_play_utils, transcode_h265, preferred_codec, expected_result): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [transcode_h265, preferred_codec] + mock_play_utils.get_transcoding_video_codec.return_value = expected_result + result = mock_play_utils.get_transcoding_video_codec() + assert result == expected_result + + +@pytest.mark.parametrize("transcode_mpeg2, transcode_vc1, expected_result", [ + (False, False, "h264,mpeg4,mpeg2video,vc1,hevc"), + (True, False, "h264,mpeg4,vc1,hevc"), + (False, True, "h264,mpeg4,mpeg2video,hevc"), + (True, True, "h264,mpeg4,hevc"), +]) +def test_get_transcoding_video_codec_transcode_options(mock_play_utils, transcode_mpeg2, transcode_vc1, expected_result): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [False, "", transcode_mpeg2, transcode_vc1] + mock_play_utils.get_transcoding_video_codec.return_value = expected_result + result = mock_play_utils.get_transcoding_video_codec() + assert result == expected_result + + +@pytest.mark.parametrize("preferred_codec, expected_first, expected_second", [ + ("H265/HEVC", "hevc", "h264"), + ("H264", "h264", "hevc"), + ("MPEG4", "mpeg4", "h264"), +]) +def test_get_transcoding_video_codec_order(mock_play_utils, preferred_codec, expected_first, expected_second): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [False, preferred_codec] + mock_play_utils.get_transcoding_video_codec.return_value = f"{expected_first},{expected_second},mpeg2video,vc1" + result = mock_play_utils.get_transcoding_video_codec() + assert result.startswith(expected_first) + assert expected_second in result + assert result.index(expected_first) < result.index(expected_second) + + +def test_get_transcoding_video_codec_no_duplicates(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [False, ""] + mock_play_utils.get_transcoding_video_codec.return_value = "h264,mpeg4,mpeg2video,vc1,hevc" + result = mock_play_utils.get_transcoding_video_codec() + assert result.count("hevc") == 1 + assert result.count("h264") == 1 + assert result.count("mpeg4") == 1 + assert result.count("mpeg2video") == 1 + assert result.count("vc1") == 1 + + +def test_get_transcoding_video_codec_empty_result(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [True, ""] + mock_play_utils.get_transcoding_video_codec.return_value = "" + result = mock_play_utils.get_transcoding_video_codec() + assert result == "" + + +def test_get_transcoding_video_codec_single_codec(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [True, "H264"] + mock_play_utils.get_transcoding_video_codec.return_value = "h264" + result = mock_play_utils.get_transcoding_video_codec() + assert result == "h264" + + +def test_get_transcoding_video_codec_unknown_preferred(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [False, "UNKNOWN"] + mock_play_utils.get_transcoding_video_codec.return_value = "h264,mpeg4,mpeg2video,vc1,hevc" + result = mock_play_utils.get_transcoding_video_codec() + assert result == "h264,mpeg4,mpeg2video,vc1,hevc" + + +@pytest.mark.parametrize("transcode_h265, preferred_codec, transcode_mpeg2, transcode_vc1, expected_result", [ + (True, "H264", True, True, "h264,mpeg4"), + (False, "H265/HEVC", False, False, "hevc,h264,mpeg4,mpeg2video,vc1"), + (True, "MPEG4", True, False, "mpeg4,h264,vc1"), +]) +def test_get_transcoding_video_codec_combined_settings(mock_play_utils, transcode_h265, preferred_codec, transcode_mpeg2, transcode_vc1, expected_result): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [transcode_h265, preferred_codec, transcode_mpeg2, transcode_vc1] + mock_play_utils.get_transcoding_video_codec.return_value = expected_result + result = mock_play_utils.get_transcoding_video_codec() + assert result == expected_result + + +@pytest.mark.parametrize("transcode_h265, preferred_codec, expected_result", [ + (False, "", "h264,mpeg4,mpeg2video,vc1,hevc"), + (True, "", "h264,mpeg4,mpeg2video,vc1"), + (False, "H265/HEVC", "hevc,h264,mpeg4,mpeg2video,vc1"), + (True, "H265/HEVC", "h264,mpeg4,mpeg2video,vc1"), + (False, "H264", "h264,mpeg4,mpeg2video,vc1,hevc"), + (True, "H264", "h264,mpeg4,mpeg2video,vc1"), + (False, "MPEG4", "mpeg4,h264,mpeg2video,vc1,hevc"), + (True, "MPEG4", "mpeg4,h264,mpeg2video,vc1"), +]) +def test_get_directplay_video_codec(mock_play_utils, transcode_h265, preferred_codec, expected_result): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [transcode_h265, preferred_codec] + mock_play_utils.get_directplay_video_codec.return_value = expected_result + result = mock_play_utils.get_directplay_video_codec() + assert result == expected_result + + +@pytest.mark.parametrize("transcode_mpeg2, transcode_vc1, expected_result", [ + (False, False, "h264,mpeg4,mpeg2video,vc1,hevc"), + (True, False, "h264,mpeg4,vc1,hevc"), + (False, True, "h264,mpeg4,mpeg2video,hevc"), + (True, True, "h264,mpeg4,hevc"), +]) +def test_get_directplay_video_codec_transcode_options(mock_play_utils, transcode_mpeg2, transcode_vc1, expected_result): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [False, "", transcode_mpeg2, transcode_vc1] + mock_play_utils.get_directplay_video_codec.return_value = expected_result + result = mock_play_utils.get_directplay_video_codec() + assert result == expected_result + + +def test_get_directplay_video_codec_no_duplicates(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [False, ""] + mock_play_utils.get_directplay_video_codec.return_value = "h264,mpeg4,mpeg2video,vc1,hevc" + result = mock_play_utils.get_directplay_video_codec() + assert result.count("hevc") == 1 + assert result.count("h264") == 1 + assert result.count("mpeg4") == 1 + assert result.count("mpeg2video") == 1 + assert result.count("vc1") == 1 + + +def test_get_directplay_video_codec_empty_result(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [True, ""] + mock_play_utils.get_directplay_video_codec.return_value = "" + result = mock_play_utils.get_directplay_video_codec() + assert result == "" + + +def test_get_directplay_video_codec_single_codec(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [True, "H264"] + mock_play_utils.get_directplay_video_codec.return_value = "h264" + result = mock_play_utils.get_directplay_video_codec() + assert result == "h264" + + +def test_get_directplay_video_codec_unknown_preferred(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [False, "UNKNOWN"] + mock_play_utils.get_directplay_video_codec.return_value = "h264,mpeg4,mpeg2video,vc1,hevc" + result = mock_play_utils.get_directplay_video_codec() + assert result == "h264,mpeg4,mpeg2video,vc1,hevc" + + +@pytest.mark.parametrize("transcode_h265, preferred_codec, transcode_mpeg2, transcode_vc1, expected_result", [ + (True, "H264", True, True, "h264,mpeg4"), + (False, "H265/HEVC", False, False, "hevc,h264,mpeg4,mpeg2video,vc1"), + (True, "MPEG4", True, False, "mpeg4,h264,vc1"), +]) +def test_get_directplay_video_codec_combined_settings(mock_play_utils, transcode_h265, preferred_codec, transcode_mpeg2, transcode_vc1, expected_result): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = [transcode_h265, preferred_codec, transcode_mpeg2, transcode_vc1] + mock_play_utils.get_directplay_video_codec.return_value = expected_result + result = mock_play_utils.get_directplay_video_codec() + assert result == expected_result + + +@pytest.mark.parametrize("preferred_codec, expected_result", [ + ("", "aac,mp3,ac3,opus,flac,vorbis"), + ("AAC", "aac,mp3,ac3,opus,flac,vorbis"), + ("MP3", "mp3,aac,ac3,opus,flac,vorbis"), + ("AC3", "ac3,aac,mp3,opus,flac,vorbis"), + ("OPUS", "opus,aac,mp3,ac3,flac,vorbis"), + ("FLAC", "flac,aac,mp3,ac3,opus,vorbis"), + ("VORBIS", "vorbis,aac,mp3,ac3,opus,flac"), + ("UNKNOWN", "aac,mp3,ac3,opus,flac,vorbis"), +]) +def test_get_transcoding_audio_codec(mock_play_utils, preferred_codec, expected_result): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.return_value = preferred_codec + mock_play_utils.get_transcoding_audio_codec.return_value = expected_result + result = mock_play_utils.get_transcoding_audio_codec() + assert result == expected_result + + +def test_get_transcoding_audio_codec_case_insensitive(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.return_value = "aAc" + mock_play_utils.get_transcoding_audio_codec.return_value = "aac,mp3,ac3,opus,flac,vorbis" + result = mock_play_utils.get_transcoding_audio_codec() + assert result == "aac,mp3,ac3,opus,flac,vorbis" + + +def test_get_transcoding_audio_codec_no_duplicates(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.return_value = "" + mock_play_utils.get_transcoding_audio_codec.return_value = "aac,mp3,ac3,opus,flac,vorbis" + result = mock_play_utils.get_transcoding_audio_codec() + assert result.count("aac") == 1 + assert result.count("mp3") == 1 + assert result.count("ac3") == 1 + assert result.count("opus") == 1 + assert result.count("flac") == 1 + assert result.count("vorbis") == 1 + + +def test_get_transcoding_audio_codec_empty_result(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.return_value = "" + mock_play_utils.get_transcoding_audio_codec.return_value = "" + result = mock_play_utils.get_transcoding_audio_codec() + assert result == "" + + +def test_get_transcoding_audio_codec_single_codec(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.return_value = "AAC" + mock_play_utils.get_transcoding_audio_codec.return_value = "aac" + result = mock_play_utils.get_transcoding_audio_codec() + assert result == "aac" + + +def test_get_transcoding_audio_codec_preserve_order(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.return_value = "" + mock_play_utils.get_transcoding_audio_codec.return_value = "aac,mp3,ac3,opus,flac,vorbis" + result = mock_play_utils.get_transcoding_audio_codec() + assert result == "aac,mp3,ac3,opus,flac,vorbis" + + +def test_get_transcoding_audio_codec_multiple_calls(mock_play_utils): + with patch('jellyfin_kodi.helper.playutils.settings') as mock_settings: + mock_settings.side_effect = ["AAC", "MP3", "FLAC"] + mock_play_utils.get_transcoding_audio_codec.side_effect = [ + "aac,mp3,ac3,opus,flac,vorbis", + "mp3,aac,ac3,opus,flac,vorbis", + "flac,aac,mp3,ac3,opus,vorbis" + ] + result1 = mock_play_utils.get_transcoding_audio_codec() + result2 = mock_play_utils.get_transcoding_audio_codec() + result3 = mock_play_utils.get_transcoding_audio_codec() + assert result1 == "aac,mp3,ac3,opus,flac,vorbis" + assert result2 == "mp3,aac,ac3,opus,flac,vorbis" + assert result3 == "flac,aac,mp3,ac3,opus,vorbis"