From 7061cbd3544d9c0ca3a4cd87aaa8246bc2f07b24 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 13 Jul 2026 23:37:57 +0200 Subject: [PATCH] Added even more unit tests for the subsonic transcoding decision engine --- .../transcoding/TranscodeDecision.cpp | 4 ++ src/libs/subsonic/test/TranscodeDecision.cpp | 69 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/libs/subsonic/impl/endpoints/transcoding/TranscodeDecision.cpp b/src/libs/subsonic/impl/endpoints/transcoding/TranscodeDecision.cpp index f98306ea..6a94e610 100644 --- a/src/libs/subsonic/impl/endpoints/transcoding/TranscodeDecision.cpp +++ b/src/libs/subsonic/impl/endpoints/transcoding/TranscodeDecision.cpp @@ -308,6 +308,7 @@ namespace lms::api::subsonic::detail throw InternalErrorGenericError{ "Unhandled limitation comparison operator" }; } + // A non-required limitation is a preference only: it must never force a transcode away from direct play bool isStreamCompatibleWithLimitation(const audio::AudioProperties& source, const Limitation& limitation) { if (!limitation.required) @@ -397,6 +398,9 @@ namespace lms::api::subsonic::detail return std::nullopt; } + // Unlike isStreamCompatibleWithLimitation, this ignores `required` entirely: + // once transcoding is already happening for any reason, + // every limitation of the matched target codec profile still shapes the output AdjustResult applyLimitation(const audio::AudioProperties& source, const Limitation& limitation, StreamDetails& transcodedStream) { switch (limitation.name) diff --git a/src/libs/subsonic/test/TranscodeDecision.cpp b/src/libs/subsonic/test/TranscodeDecision.cpp index ae7272c2..a98a8a5b 100644 --- a/src/libs/subsonic/test/TranscodeDecision.cpp +++ b/src/libs/subsonic/test/TranscodeDecision.cpp @@ -837,6 +837,75 @@ namespace lms::api::subsonic processTests(testCases); } + TEST(TranscodeDecision, optionalLimitation) + { + const TestCase testCases[]{ + // A non-required limitation is a preference only: it never blocks direct play, even when clearly violated (source has 6 channels vs the allowed 1 or 2) + { + .clientInfo = { + .name = "TestClient", + .platform = "TestPlatform", + .maxAudioBitrate = 1'000'000, + .maxTranscodingAudioBitrate = 1'000'000, + .directPlayProfiles = { + { .containers = { "flac" }, .audioCodecs = { "flac" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt }, + }, + .transcodingProfiles = { + { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 }, + }, + .codecProfiles = { { .type = "AudioCodec", .name = "flac", .limitations = { + { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "192000" }, .required = false }, + { .name = Limitation::Type::AudioChannels, .comparison = Limitation::ComparisonOperator::Equals, .values = { "1", "2" }, .required = false }, + } } }, + }, + .source = { + .container = core::media::Container::FLAC, + .codec = core::media::Codec::FLAC, + .duration = std::chrono::seconds{ 60 }, + .bitrate = 900'000, + .channelCount = 6, + .sampleRate = 96'000, + .bitsPerSample = 24, + }, + + .expected = { detail::DirectPlayResult{} }, + }, + + // A non-required limitation still shapes the transcoded target once a transcode is already happening for an unrelated reason (container mismatch): applyLimitation never checks `required` + { + .clientInfo = { + .name = "TestClient", + .platform = "TestPlatform", + .maxAudioBitrate = 1'000'000, + .maxTranscodingAudioBitrate = 1'000'000, + .directPlayProfiles = { + { .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt }, + }, + .transcodingProfiles = { + { .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = std::nullopt }, + }, + .codecProfiles = { { .type = "AudioCodec", .name = "flac", .limitations = { + { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "192000" }, .required = false }, + { .name = Limitation::Type::AudioChannels, .comparison = Limitation::ComparisonOperator::Equals, .values = { "1", "2" }, .required = false }, + } } }, + }, + .source = { + .container = core::media::Container::FLAC, + .codec = core::media::Codec::FLAC, + .duration = std::chrono::seconds{ 60 }, + .bitrate = 900'000, + .channelCount = 6, + .sampleRate = 96'000, + .bitsPerSample = 16, + }, + + .expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = 2, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } }, + }, + }; + + processTests(testCases); + } + TEST(TranscodeDecision, comparisonOperators) { const TestCase testCases[]{