aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsaac Gordezky <eye.zak@gmail.com>2022-01-23 16:37:52 +0000
committerCody Robibero <cody@robibe.ro>2022-03-06 18:13:54 -0700
commitd871dded9fc7b704f778764a73830ae6a481f3ff (patch)
tree508f8d267052557eaa2353cdb41cab521bc67c05
parenta3057afde82f314d01e5af72acb75beb4e08f778 (diff)
Convert TranscodeReason to Flags
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs5
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs23
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs120
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs5
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs2
-rw-r--r--MediaBrowser.Model/Session/TranscodeReason.cs67
-rw-r--r--MediaBrowser.Model/Session/TranscodeReasonExtensions.cs22
-rw-r--r--MediaBrowser.Model/Session/TranscodingInfo.cs12
-rw-r--r--tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs363
10 files changed, 412 insertions, 209 deletions
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index bc9527a0b..962f637d4 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -16,6 +16,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -223,7 +224,7 @@ namespace Jellyfin.Api.Controllers
DeInterlace = false,
RequireNonAnamorphic = false,
EnableMpegtsM2TsMode = false,
- TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
+ TranscodeReasons = mediaSource.TranscodeReasons == MediaBrowser.Model.Session.TranscodeReason.None ? null : mediaSource.TranscodeReasons.Serialize(),
Context = EncodingContext.Static,
StreamOptions = new Dictionary<string, string>(),
EnableAdaptiveBitrateStreaming = true
@@ -254,7 +255,7 @@ namespace Jellyfin.Api.Controllers
CopyTimestamps = true,
StartTimeTicks = startTimeTicks,
SubtitleMethod = SubtitleDeliveryMethod.Embed,
- TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
+ TranscodeReasons = mediaSource.TranscodeReasons == MediaBrowser.Model.Session.TranscodeReason.None ? null : mediaSource.TranscodeReasons.Serialize(),
Context = EncodingContext.Static
};
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index c8762b7c5..49a394868 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -479,7 +479,7 @@ namespace Jellyfin.Api.Helpers
IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
HardwareAccelerationType = hardwareAccelerationType,
- TranscodeReasons = state.TranscodeReasons
+ TranscodeReason = state.TranscodeReason
});
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index c4affa567..0f5fdcc3c 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
@@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public int? OutputAudioBitrate;
public int? OutputAudioChannels;
- private TranscodeReason[] _transcodeReasons = null;
+ private TranscodeReason? _transcodeReasons = null;
public EncodingJobInfo(TranscodingJobType jobType)
{
@@ -34,25 +35,27 @@ namespace MediaBrowser.Controller.MediaEncoding
SupportedSubtitleCodecs = Array.Empty<string>();
}
- public TranscodeReason[] TranscodeReasons
+ public TranscodeReason[] TranscodeReasons { get => TranscodeReason.ToArray(); }
+
+ [JsonIgnore]
+ public TranscodeReason TranscodeReason
{
get
{
- if (_transcodeReasons == null)
+ if (!_transcodeReasons.HasValue)
{
if (BaseRequest.TranscodeReasons == null)
{
- return Array.Empty<TranscodeReason>();
+ _transcodeReasons = TranscodeReason.None;
+ return TranscodeReason.None;
}
- _transcodeReasons = BaseRequest.TranscodeReasons
- .Split(',')
- .Where(i => !string.IsNullOrEmpty(i))
- .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
- .ToArray();
+ TranscodeReason reason = TranscodeReason.None;
+ Enum.TryParse<TranscodeReason>(BaseRequest.TranscodeReasons, out reason);
+ _transcodeReasons = reason;
}
- return _transcodeReasons;
+ return _transcodeReasons.Value;
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index d2ca21150..7654337e7 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -143,7 +143,7 @@ namespace MediaBrowser.Model.Dlna
}).ThenBy(streams.IndexOf);
}
- private static TranscodeReason? GetTranscodeReasonForFailedCondition(ProfileCondition condition)
+ private static TranscodeReason GetTranscodeReasonForFailedCondition(ProfileCondition condition)
{
switch (condition.Property)
{
@@ -161,7 +161,7 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.Has64BitOffsets:
// TODO
- return null;
+ return TranscodeReason.None;
case ProfileConditionValue.Height:
return TranscodeReason.VideoResolutionNotSupported;
@@ -171,7 +171,7 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.IsAvc:
// TODO
- return null;
+ return TranscodeReason.None;
case ProfileConditionValue.IsInterlaced:
return TranscodeReason.InterlacedVideoNotSupported;
@@ -181,15 +181,15 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.NumAudioStreams:
// TODO
- return null;
+ return TranscodeReason.None;
case ProfileConditionValue.NumVideoStreams:
// TODO
- return null;
+ return TranscodeReason.None;
case ProfileConditionValue.PacketLength:
// TODO
- return null;
+ return TranscodeReason.None;
case ProfileConditionValue.RefFrames:
return TranscodeReason.RefFramesNotSupported;
@@ -217,13 +217,13 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.VideoTimestamp:
// TODO
- return null;
+ return TranscodeReason.None;
case ProfileConditionValue.Width:
return TranscodeReason.VideoResolutionNotSupported;
default:
- return null;
+ return TranscodeReason.None;
}
}
@@ -290,7 +290,7 @@ namespace MediaBrowser.Model.Dlna
var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options);
var directPlayMethods = directPlayInfo.PlayMethods;
- var transcodeReasons = directPlayInfo.TranscodeReasons.ToList();
+ var transcodeReasons = directPlayInfo.TranscodeReasons;
int? inputAudioChannels = audioStream?.Channels;
int? inputAudioBitrate = audioStream?.BitDepth;
@@ -331,11 +331,7 @@ namespace MediaBrowser.Model.Dlna
if (!ConditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
- var transcodeReason = GetTranscodeReasonForFailedCondition(c);
- if (transcodeReason.HasValue)
- {
- transcodeReasons.Add(transcodeReason.Value);
- }
+ transcodeReasons |= GetTranscodeReasonForFailedCondition(c);
all = false;
break;
@@ -434,7 +430,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
}
- playlistItem.TranscodeReasons = transcodeReasons.ToArray();
+ playlistItem.TranscodeReasons = transcodeReasons;
return playlistItem;
}
@@ -448,7 +444,7 @@ namespace MediaBrowser.Model.Dlna
return options.GetMaxBitrate(isAudio);
}
- private (IEnumerable<PlayMethod> PlayMethods, IEnumerable<TranscodeReason> TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
+ private (IEnumerable<PlayMethod> PlayMethods, TranscodeReason TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{
DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles
.FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream));
@@ -465,12 +461,12 @@ namespace MediaBrowser.Model.Dlna
}
var playMethods = new List<PlayMethod>();
- var transcodeReasons = new List<TranscodeReason>();
+ var transcodeReasons = TranscodeReason.None;
// While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream)
{
- if (IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
+ if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
{
if (options.EnableDirectStream)
{
@@ -479,7 +475,7 @@ namespace MediaBrowser.Model.Dlna
}
else
{
- transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
+ transcodeReasons |= TranscodeReason.ContainerBitrateExceedsLimit;
}
}
@@ -487,7 +483,7 @@ namespace MediaBrowser.Model.Dlna
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay)
{
- if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
+ if (IsItemBitrateEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
{
if (options.EnableDirectPlay)
{
@@ -496,29 +492,26 @@ namespace MediaBrowser.Model.Dlna
}
else
{
- transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
+ transcodeReasons |= TranscodeReason.ContainerBitrateExceedsLimit;
}
}
if (playMethods.Count > 0)
{
- transcodeReasons.Clear();
- }
- else
- {
- transcodeReasons = transcodeReasons.Distinct().ToList();
+ transcodeReasons = TranscodeReason.None;
}
return (playMethods, transcodeReasons);
}
- private static List<TranscodeReason> GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles)
+ private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles)
{
var mediaType = videoStream == null ? DlnaProfileType.Audio : DlnaProfileType.Video;
var containerSupported = false;
var audioSupported = false;
var videoSupported = false;
+ var reasons = TranscodeReason.None;
foreach (var profile in directPlayProfiles)
{
@@ -541,20 +534,20 @@ namespace MediaBrowser.Model.Dlna
var list = new List<TranscodeReason>();
if (!containerSupported)
{
- list.Add(TranscodeReason.ContainerNotSupported);
+ reasons |= TranscodeReason.ContainerNotSupported;
}
if (videoStream != null && !videoSupported)
{
- list.Add(TranscodeReason.VideoCodecNotSupported);
+ reasons |= TranscodeReason.VideoCodecNotSupported;
}
if (audioStream != null && !audioSupported)
{
- list.Add(TranscodeReason.AudioCodecNotSupported);
+ reasons |= TranscodeReason.AudioCodecNotSupported;
}
- return list;
+ return reasons;
}
private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
@@ -679,8 +672,9 @@ namespace MediaBrowser.Model.Dlna
// TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough
var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectPlay);
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectStream);
- bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.DirectPlay);
- bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.DirectPlay);
+ bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == TranscodeReason.None);
+ bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == TranscodeReason.None);
+ var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult;
_logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@@ -689,8 +683,6 @@ namespace MediaBrowser.Model.Dlna
isEligibleForDirectPlay,
isEligibleForDirectStream);
- var transcodeReasons = new List<TranscodeReason>();
-
if (isEligibleForDirectPlay || isEligibleForDirectStream)
{
// See if it can be direct played
@@ -713,17 +705,13 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
- transcodeReasons.AddRange(directPlayInfo.TranscodeReasons);
- }
-
- if (directPlayEligibilityResult.Reason.HasValue)
- {
- transcodeReasons.Add(directPlayEligibilityResult.Reason.Value);
+ transcodeReasons |= directPlayInfo.TranscodeReasons;
}
- if (directStreamEligibilityResult.Reason.HasValue)
+ if (playlistItem.PlayMethod != PlayMethod.Transcode)
{
- transcodeReasons.Add(directStreamEligibilityResult.Reason.Value);
+ playlistItem.TranscodeReasons = transcodeReasons;
+ return playlistItem;
}
// Can't direct play, find the transcoding profile
@@ -869,7 +857,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- playlistItem.TranscodeReasons = transcodeReasons.ToArray();
+ playlistItem.TranscodeReasons = transcodeReasons;
return playlistItem;
}
@@ -1000,7 +988,7 @@ namespace MediaBrowser.Model.Dlna
return 7168000;
}
- private (PlayMethod? PlayMethod, List<TranscodeReason> TranscodeReasons) GetVideoDirectPlayProfile(
+ private (PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile(
VideoOptions options,
MediaSourceInfo mediaSource,
MediaStream videoStream,
@@ -1009,12 +997,12 @@ namespace MediaBrowser.Model.Dlna
{
if (options.ForceDirectPlay)
{
- return (PlayMethod.DirectPlay, new List<TranscodeReason>());
+ return (PlayMethod.DirectPlay, TranscodeReason.None);
}
if (options.ForceDirectStream)
{
- return (PlayMethod.DirectStream, new List<TranscodeReason>());
+ return (PlayMethod.DirectStream, TranscodeReason.None);
}
DeviceProfile profile = options.Profile;
@@ -1089,11 +1077,7 @@ namespace MediaBrowser.Model.Dlna
{
LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
- var transcodeReason = GetTranscodeReasonForFailedCondition(i);
- var transcodeReasons = transcodeReason.HasValue
- ? new List<TranscodeReason> { transcodeReason.Value }
- : new List<TranscodeReason>();
-
+ var transcodeReasons = GetTranscodeReasonForFailedCondition(i);
return (null, transcodeReasons);
}
}
@@ -1133,11 +1117,7 @@ namespace MediaBrowser.Model.Dlna
LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
- var transcodeReasons = transcodeReason.HasValue
- ? new List<TranscodeReason> { transcodeReason.Value }
- : new List<TranscodeReason>();
-
- return (null, transcodeReasons);
+ return (null, transcodeReason);
}
}
@@ -1178,11 +1158,7 @@ namespace MediaBrowser.Model.Dlna
{
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
- var transcodeReason = GetTranscodeReasonForFailedCondition(i);
- var transcodeReasons = transcodeReason.HasValue
- ? new List<TranscodeReason> { transcodeReason.Value }
- : new List<TranscodeReason>();
-
+ var transcodeReasons = GetTranscodeReasonForFailedCondition(i);
return (null, transcodeReasons);
}
}
@@ -1190,10 +1166,10 @@ namespace MediaBrowser.Model.Dlna
if (isEligibleForDirectStream && mediaSource.SupportsDirectStream)
{
- return (PlayMethod.DirectStream, new List<TranscodeReason>());
+ return (PlayMethod.DirectStream, TranscodeReason.None);
}
- return (null, new List<TranscodeReason> { TranscodeReason.ContainerBitrateExceedsLimit });
+ return (null, TranscodeReason.ContainerBitrateExceedsLimit);
}
private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource)
@@ -1209,7 +1185,7 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path");
}
- private (bool DirectPlay, TranscodeReason? Reason) IsEligibleForDirectPlay(
+ private TranscodeReason IsEligibleForDirectPlay(
MediaSourceInfo item,
long maxBitrate,
MediaStream subtitleStream,
@@ -1217,6 +1193,7 @@ namespace MediaBrowser.Model.Dlna
VideoOptions options,
PlayMethod playMethod)
{
+ var reason = TranscodeReason.None;
if (subtitleStream != null)
{
var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, playMethod, _transcoderSupport, item.Container, null);
@@ -1226,22 +1203,23 @@ namespace MediaBrowser.Model.Dlna
&& subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
{
_logger.LogDebug("Not eligible for {0} due to unsupported subtitles", playMethod);
- return (false, TranscodeReason.SubtitleCodecNotSupported);
+ reason |= TranscodeReason.SubtitleCodecNotSupported;
}
}
- bool result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
+ bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod);
if (!result)
{
- return (false, TranscodeReason.ContainerBitrateExceedsLimit);
+ reason |= TranscodeReason.ContainerBitrateExceedsLimit;
}
+ // TODO:6450 support external audio in DirectStream?
if (audioStream?.IsExternal == true)
{
- return (false, TranscodeReason.AudioIsExternal);
+ reason |= TranscodeReason.AudioIsExternal;
}
- return (true, null);
+ return reason;
}
public static SubtitleProfile GetSubtitleProfile(
@@ -1401,7 +1379,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
+ private bool IsItemBitrateEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
{
// Don't restrict by bitrate if coming from an external domain
if (item.IsRemote)
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index a678c54e7..3b86d5f42 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -23,7 +23,6 @@ namespace MediaBrowser.Model.Dlna
AudioCodecs = Array.Empty<string>();
VideoCodecs = Array.Empty<string>();
SubtitleCodecs = Array.Empty<string>();
- TranscodeReasons = Array.Empty<TranscodeReason>();
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
@@ -103,7 +102,7 @@ namespace MediaBrowser.Model.Dlna
public string PlaySessionId { get; set; }
- public TranscodeReason[] TranscodeReasons { get; set; }
+ public TranscodeReason TranscodeReasons { get; set; }
public Dictionary<string, string> StreamOptions { get; private set; }
@@ -799,7 +798,7 @@ namespace MediaBrowser.Model.Dlna
if (!item.IsDirectStream)
{
- list.Add(new NameValuePair("TranscodeReasons", string.Join(',', item.TranscodeReasons.Distinct())));
+ list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.Serialize()));
}
return list;
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index 049e14333..2281e6ae5 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -109,7 +109,7 @@ namespace MediaBrowser.Model.Dto
public int? AnalyzeDurationMs { get; set; }
[JsonIgnore]
- public TranscodeReason[] TranscodeReasons { get; set; }
+ public TranscodeReason TranscodeReasons { get; set; }
public int? DefaultAudioStreamIndex { get; set; }
diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs
index 3c95df66d..c3570840f 100644
--- a/MediaBrowser.Model/Session/TranscodeReason.cs
+++ b/MediaBrowser.Model/Session/TranscodeReason.cs
@@ -1,32 +1,51 @@
#pragma warning disable CS1591
+using System;
+
namespace MediaBrowser.Model.Session
{
+ [Flags]
public enum TranscodeReason
{
- ContainerNotSupported = 0,
- VideoCodecNotSupported = 1,
- AudioCodecNotSupported = 2,
- ContainerBitrateExceedsLimit = 3,
- AudioBitrateNotSupported = 4,
- AudioChannelsNotSupported = 5,
- VideoResolutionNotSupported = 6,
- UnknownVideoStreamInfo = 7,
- UnknownAudioStreamInfo = 8,
- AudioProfileNotSupported = 9,
- AudioSampleRateNotSupported = 10,
- AnamorphicVideoNotSupported = 11,
- InterlacedVideoNotSupported = 12,
- SecondaryAudioNotSupported = 13,
- RefFramesNotSupported = 14,
- VideoBitDepthNotSupported = 15,
- VideoBitrateNotSupported = 16,
- VideoFramerateNotSupported = 17,
- VideoLevelNotSupported = 18,
- VideoProfileNotSupported = 19,
- AudioBitDepthNotSupported = 20,
- SubtitleCodecNotSupported = 21,
- DirectPlayError = 22,
- AudioIsExternal = 23
+ None = 0,
+
+ // Primary
+ ContainerNotSupported = 1 << 0,
+ VideoCodecNotSupported = 1 << 1,
+ AudioCodecNotSupported = 1 << 2,
+ SubtitleCodecNotSupported = 1 << 3,
+ AudioIsExternal = 1 << 4,
+ SecondaryAudioNotSupported = 1 << 5,
+
+ // Video Constraints
+ VideoProfileNotSupported = 1 << 6,
+ VideoLevelNotSupported = 1 << 7,
+ VideoResolutionNotSupported = 1 << 8,
+ VideoBitDepthNotSupported = 1 << 9,
+ VideoFramerateNotSupported = 1 << 10,
+ RefFramesNotSupported = 1 << 11,
+ AnamorphicVideoNotSupported = 1 << 12,
+ InterlacedVideoNotSupported = 1 << 13,
+
+ // Audio Constraints
+ AudioChannelsNotSupported = 1 << 14,
+ AudioProfileNotSupported = 1 << 15,
+ AudioSampleRateNotSupported = 1 << 16,
+ AudioBitDepthNotSupported = 1 << 20,
+
+ // Bitrate Constraints
+ ContainerBitrateExceedsLimit = 1 << 17,
+ VideoBitrateNotSupported = 1 << 18,
+ AudioBitrateNotSupported = 1 << 19,
+
+ // Errors
+ UnknownVideoStreamInfo = 1 << 20,
+ UnknownAudioStreamInfo = 1 << 21,
+ DirectPlayError = 1 << 22,
+
+ // Aliases
+ ContainerReasons = ContainerNotSupported | ContainerBitrateExceedsLimit,
+ AudioReasons = AudioCodecNotSupported | AudioBitrateNotSupported | AudioChannelsNotSupported | AudioProfileNotSupported | AudioSampleRateNotSupported | SecondaryAudioNotSupported | AudioBitDepthNotSupported | AudioIsExternal,
+ VideoReasons = VideoCodecNotSupported | VideoResolutionNotSupported | AnamorphicVideoNotSupported | InterlacedVideoNotSupported | VideoBitDepthNotSupported | VideoBitrateNotSupported | VideoFramerateNotSupported | VideoLevelNotSupported | RefFramesNotSupported,
}
}
diff --git a/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs b/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs
new file mode 100644
index 000000000..c7a5095f7
--- /dev/null
+++ b/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs
@@ -0,0 +1,22 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Linq;
+
+namespace MediaBrowser.Model.Session
+{
+ public static class TranscodeReasonExtensions
+ {
+ private static TranscodeReason[] values = Enum.GetValues<TranscodeReason>();
+
+ public static string Serialize(this MediaBrowser.Model.Session.TranscodeReason reasons, string sep = ",")
+ {
+ return string.Join(sep, reasons.ToArray());
+ }
+
+ public static TranscodeReason[] ToArray(this MediaBrowser.Model.Session.TranscodeReason reasons)
+ {
+ return values.Where(r => r != 0 && reasons.HasFlag(r)).ToArray();
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs
index 68ab691f8..78e5baad7 100644
--- a/MediaBrowser.Model/Session/TranscodingInfo.cs
+++ b/MediaBrowser.Model/Session/TranscodingInfo.cs
@@ -1,17 +1,12 @@
#nullable disable
#pragma warning disable CS1591
-using System;
+using System.Text.Json.Serialization;
namespace MediaBrowser.Model.Session
{
public class TranscodingInfo
{
- public TranscodingInfo()
- {
- TranscodeReasons = Array.Empty<TranscodeReason>();
- }
-
public string AudioCodec { get; set; }
public string VideoCodec { get; set; }
@@ -36,6 +31,9 @@ namespace MediaBrowser.Model.Session
public HardwareEncodingType? HardwareAccelerationType { get; set; }
- public TranscodeReason[] TranscodeReasons { get; set; }
+ public TranscodeReason[] TranscodeReasons { get => TranscodeReason.ToArray(); }
+
+ [JsonIgnore]
+ public TranscodeReason TranscodeReason { get; set; }
}
}
diff --git a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs
index abb853b2a..ccd95f750 100644
--- a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text.Json;
@@ -19,65 +20,69 @@ namespace Jellyfin.MediaBrowser.Model.Tests
[Theory]
// Chrome
[InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream
- [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)]
- [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)]
- [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false'
- [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false'
+ [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+ [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+ [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
+ [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
+ [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
// Firefox
[InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream
- [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)]
- [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)]
- [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false'
- [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false'
+ [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+ [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+ [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
+ [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
+ [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
// Safari
[InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay
[InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay
// AndroidPixel
[InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)]
- [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)]
+ [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+ [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
// Yatse
[InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, true)]
+ [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")]
[InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+ [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
// RokuSSPlus
[InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream
+ [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
[InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectStream
+ [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectStream
// JellyfinMediaPlayer
[InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
- [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
[InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
// TranscodeMedia
- [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+ [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
// DirectMedia
[InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
@@ -88,27 +93,120 @@ namespace Jellyfin.MediaBrowser.Model.Tests
[InlineData("DirectMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
[InlineData("DirectMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
// LowBandwidth
- [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
- [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
- [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+ [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
+ [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
+ [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
+ [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
// Null
- [InlineData("Null", "mp4-h264-aac-vtt-2600k", null)] // #6450 should be DirectPlay
- [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null)] // #6450 should be DirectPlay
- [InlineData("Null", "mp4-h264-ac3-srt-2600k", null)] // #6450 should be DirectPlay
- [InlineData("Null", "mp4-hevc-aac-srt-15200k", null)] // #6450 should be DirectPlay
- [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null)] // #6450 should be DirectPlay
- [InlineData("Null", "mkv-vp9-aac-srt-2600k", null)] // #6450 should be DirectPlay
- [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null)] // #6450 should be DirectPlay
- [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null)] // #6450 should be DirectPlay
- public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, bool fullTranscode = false)
+ [InlineData("Null", "mp4-h264-aac-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Null", "mp4-h264-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Null", "mp4-hevc-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+ public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{
- var builder = GetStreamBuilder();
var options = await GetVideoOptions(deviceName, mediaSource);
+ BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+ }
+
+ [Theory]
+ // Chrome
+ [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+ [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+ [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
+ [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
+ [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
+ [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ // Firefox
+ [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+ [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+ [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
+ [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
+ [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
+ [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ // Safari
+ [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay
+ [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay
+ // AndroidPixel
+ [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+ [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+ // Yatse
+ [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")]
+ [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+ // RokuSSPlus
+ [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+ [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+ [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+ [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectStream
+ // JellyfinMediaPlayer
+ [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod?playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
+ {
+ var options = await GetVideoOptions(deviceName, mediaSource);
+ options.AudioStreamIndex = 1;
+ options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count() - 1;
+ BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+ }
+
+ [Theory]
+ // Chrome
+ [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450 should have container & profile video reasons?
+ // Firefox
+ [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450 should have container & profile video reasons?
+ // Yatse
+ [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
+ [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported, "Transcode")] // #6450 should be DirectPlay
+ // RokuSSPlus
+ [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+ public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
+ {
+ var options = await GetVideoOptions(deviceName, mediaSource);
+ var streamCount = options.MediaSources[0].MediaStreams.Count();
+ options.AudioStreamIndex = streamCount - 2;
+ options.SubtitleStreamIndex = streamCount - 1;
+ BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+ }
+
+ private void BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol)
+ {
+ if (string.IsNullOrEmpty(transcodeProtocol))
+ {
+ transcodeProtocol = playMethod == PlayMethod.DirectStream ? "http" : "hls";
+ }
+
+ var builder = GetStreamBuilder();
var val = builder.BuildVideoItem(options);
Assert.NotNull(val);
@@ -118,63 +216,130 @@ namespace Jellyfin.MediaBrowser.Model.Tests
Assert.Equal(playMethod, val.PlayMethod);
}
- var videoStreams = options.MediaSources.SelectMany(source => source.MediaStreams).Where(stream => stream.Type == MediaStreamType.Video);
- var audioStreams = options.MediaSources.SelectMany(source => source.MediaStreams).Where(stream => stream.Type == MediaStreamType.Audio);
+ Assert.Equal(why, val.TranscodeReasons);
+
+ var audioStreamIndexInput = options.AudioStreamIndex;
+ var targetVideoStream = val.TargetVideoStream;
+ var targetAudioStream = val.TargetAudioStream;
- var url = new UriBuilder(val.ToUrl("https://server/", "ACCESSTOKEN"));
- var query = System.Web.HttpUtility.ParseQueryString(url.Query);
+ var mediaSource = options.MediaSources.First(source => source.Id == val.MediaSourceId);
+ Assert.NotNull(mediaSource);
+ var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
+ var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
+ // TODO: check AudioStreamIndex vs options.AudioStreamIndex
+ var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
+
+ var uri = ParseUri(val);
if (playMethod == PlayMethod.DirectPlay)
{
- // Assert.Contains(query.Get("VidoeCodec"), videoStreams.Select(stream => stream.Codec));
- // Assert.Contains(query.Get("AudioCodec"), audioStreams.Select(stream => stream.Codec));
- Assert.Contains(
- videoStreams,
- stream => val.TargetVideoCodec.Contains(stream.Codec));
- Assert.Contains(
- audioStreams,
- stream => val.TargetAudioCodec.Contains(stream.Codec));
- }
+ // check expected container
+ var containers = ContainerProfile.SplitValue(mediaSource.Container);
+ Assert.Contains(uri.Extension, containers);
- if (playMethod == PlayMethod.DirectStream)
- {
- Assert.Matches("stream[.][^.]+$", url.Path);
- }
+ // check expected video codec (1)
+ Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
+ Assert.Single(val.TargetVideoCodec);
+
+ // check expected audio codecs (1)
+ Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
+ Assert.Single(val.AudioCodecs);
- if (playMethod == PlayMethod.Transcode)
+ // TODO: validate transcoding options as well
+ }
+ else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode)
{
- if (fullTranscode)
+ Assert.NotNull(val.Container);
+ // Assert.NotEmpty(val.VideoCodecs);
+ // Assert.NotEmpty(val.AudioCodecs);
+
+ // check expected container (todo: this could be a test param)
+ if (transcodeProtocol == "http")
{
+ // Assert.Equal("webm", val.Container);
+ Assert.Equal(val.Container, uri.Extension);
+ Assert.Equal("stream", uri.Filename);
+ // Assert.Equal("http", val.SubProtocol);
+ }
+ else
+ {
+ Assert.Equal("ts", val.Container);
+ Assert.Equal("m3u8", uri.Extension);
+ Assert.Equal("master", uri.Filename);
Assert.Equal("hls", val.SubProtocol);
- Assert.EndsWith("master.m3u8", url.Path, StringComparison.InvariantCulture);
+ }
+
+ // Full transcode
+ if (transcodeMode == "Transcode")
+ {
+ // TODO: what else to validate here
+ if ((val.TranscodeReasons & TranscodeReason.ContainerReasons) == TranscodeReason.None)
+ {
+ // Assert.All(
+ // videoStreams,
+ // stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
+ }
- // Assert.All(
- // videoStreams,
- // stream => Assert.DoesNotContain(stream.Codec, val.TargetVideoCodec));
+ // todo: fill out tests here
}
+
+ // DirectStream and Remux
else
{
- Assert.Equal("hls", val.SubProtocol);
- Assert.EndsWith("master.m3u8", url.Path, StringComparison.InvariantCulture);
+ // check expected video codec (1)
+ Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
+ Assert.Single(val.TargetVideoCodec);
- Assert.Contains(
- videoStreams,
- stream => val.TargetVideoCodec.Contains(stream.Codec));
- // Assert.All(
- // audioStreams,
- // stream => Assert.DoesNotContain(stream.Codec, val.TargetAudioCodec));
+ if (transcodeMode == "DirectStream")
+ {
+ if (!targetAudioStream.IsExternal)
+ {
+ // check expected audio codecs (1)
+ // Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+ }
+ }
+ else if (transcodeMode == "Remux")
+ {
+ // check expected audio codecs (1)
+ Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
+ Assert.Single(val.AudioCodecs);
+ }
+ // video details
+ var videoStream = targetVideoStream;
Assert.False(val.EstimateContentLength);
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
- // Assert.True(val.CopyTimestamps);
-
- var videoStream = videoStreams.First(stream => val.TargetVideoCodec.Contains(stream.Codec));
-
- Assert.Contains(videoStream.Codec, val.TargetVideoCodec);
- // Assert.Contains(videoStream.Profile.ToLowerInvariant(), val.TargetVideoProfile.Split(","));
+ // Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? new string[0]);
// Assert.Equal(videoStream.Level, val.TargetVideoLevel);
// Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
- // Assert.Equal(videoStream.BitRate, val.VideoBitrate);
+ // Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
+
+ // audio codec not supported
+ if ((why & TranscodeReason.AudioCodecNotSupported) != TranscodeReason.None)
+ {
+ // audio stream specified
+ if (options.AudioStreamIndex >= 0)
+ {
+ // TODO:fixme
+ if (!targetAudioStream.IsExternal)
+ {
+ Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+ }
+ }
+
+ // audio stream not specified
+ else
+ {
+ // TODO:fixme
+ Assert.All(audioStreams, stream =>
+ {
+ if (!stream.IsExternal)
+ {
+ // Assert.DoesNotContain(stream.Codec, val.AudioCodecs);
+ }
+ });
+ }
+ }
}
}
@@ -182,7 +347,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests
{
// what should the actual result be here?
Assert.Null(val.SubProtocol);
- Assert.EndsWith("/stream", url.Path, StringComparison.InvariantCulture);
+ Assert.EndsWith("/stream", uri.Path, StringComparison.InvariantCulture);
Assert.False(val.EstimateContentLength);
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
@@ -231,5 +396,23 @@ namespace Jellyfin.MediaBrowser.Model.Tests
Profile = dp,
};
}
+
+ private static (string Path, NameValueCollection Query, string Filename, string Extension) ParseUri(StreamInfo val)
+ {
+ var href = val.ToUrl("media:", "ACCESSTOKEN").Split("?", 2);
+ var path = href[0];
+
+ var queryString = href.ElementAtOrDefault(1);
+ var query = string.IsNullOrEmpty(queryString) ? System.Web.HttpUtility.ParseQueryString(queryString ?? string.Empty) : new NameValueCollection();
+
+ var filename = System.IO.Path.GetFileNameWithoutExtension(path);
+ var extension = System.IO.Path.GetExtension(path);
+ if (extension.Length > 0)
+ {
+ extension = extension.Substring(1);
+ }
+
+ return (path, query, filename, extension);
+ }
}
}