mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Merge pull request #9374 from Shadowghost/fixup2
This commit is contained in:
commit
8c8972f0b5
@ -135,8 +135,8 @@ namespace Jellyfin.Server.Implementations.Devices
|
||||
{
|
||||
IEnumerable<Device> devices = _devices.Values
|
||||
.Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
|
||||
.Where(device => query.DeviceId == null || device.DeviceId == query.DeviceId)
|
||||
.Where(device => query.AccessToken == null || device.AccessToken == query.AccessToken)
|
||||
.Where(device => query.DeviceId is null || device.DeviceId == query.DeviceId)
|
||||
.Where(device => query.AccessToken is null || device.AccessToken == query.AccessToken)
|
||||
.OrderBy(d => d.Id)
|
||||
.ToList();
|
||||
var count = devices.Count();
|
||||
|
@ -1,74 +1,94 @@
|
||||
#nullable disable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
namespace MediaBrowser.Model.Dlna;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="CodecProfile"/>.
|
||||
/// </summary>
|
||||
public class CodecProfile
|
||||
{
|
||||
public class CodecProfile
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CodecProfile"/> class.
|
||||
/// </summary>
|
||||
public CodecProfile()
|
||||
{
|
||||
public CodecProfile()
|
||||
{
|
||||
Conditions = Array.Empty<ProfileCondition>();
|
||||
ApplyConditions = Array.Empty<ProfileCondition>();
|
||||
}
|
||||
Conditions = [];
|
||||
ApplyConditions = [];
|
||||
}
|
||||
|
||||
[XmlAttribute("type")]
|
||||
public CodecType Type { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="CodecType"/> which this container must meet.
|
||||
/// </summary>
|
||||
[XmlAttribute("type")]
|
||||
public CodecType Type { get; set; }
|
||||
|
||||
public ProfileCondition[] Conditions { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the list of <see cref="ProfileCondition"/> which this profile must meet.
|
||||
/// </summary>
|
||||
public ProfileCondition[] Conditions { get; set; }
|
||||
|
||||
public ProfileCondition[] ApplyConditions { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the list of <see cref="ProfileCondition"/> to apply if this profile is met.
|
||||
/// </summary>
|
||||
public ProfileCondition[] ApplyConditions { get; set; }
|
||||
|
||||
[XmlAttribute("codec")]
|
||||
public string Codec { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the codec(s) that this profile applies to.
|
||||
/// </summary>
|
||||
[XmlAttribute("codec")]
|
||||
public string? Codec { get; set; }
|
||||
|
||||
[XmlAttribute("container")]
|
||||
public string Container { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the container(s) which this profile will be applied to.
|
||||
/// </summary>
|
||||
[XmlAttribute("container")]
|
||||
public string? Container { get; set; }
|
||||
|
||||
[XmlAttribute("subcontainer")]
|
||||
public string SubContainer { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the sub-container(s) which this profile will be applied to.
|
||||
/// </summary>
|
||||
[XmlAttribute("subcontainer")]
|
||||
public string? SubContainer { get; set; }
|
||||
|
||||
public string[] GetCodecs()
|
||||
{
|
||||
return ContainerProfile.SplitValue(Codec);
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks to see whether the codecs and containers contain the given parameters.
|
||||
/// </summary>
|
||||
/// <param name="codecs">The codecs to match.</param>
|
||||
/// <param name="container">The container to match.</param>
|
||||
/// <param name="useSubContainer">Consider sub-containers.</param>
|
||||
/// <returns>True if both conditions are met.</returns>
|
||||
public bool ContainsAnyCodec(IReadOnlyList<string> codecs, string? container, bool useSubContainer = false)
|
||||
{
|
||||
var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
|
||||
return ContainerHelper.ContainsContainer(containerToCheck, container) && codecs.Any(c => ContainerHelper.ContainsContainer(Codec, false, c));
|
||||
}
|
||||
|
||||
private bool ContainsContainer(string container, bool useSubContainer = false)
|
||||
{
|
||||
var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
|
||||
return ContainerProfile.ContainsContainer(containerToCheck, container);
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks to see whether the codecs and containers contain the given parameters.
|
||||
/// </summary>
|
||||
/// <param name="codec">The codec to match.</param>
|
||||
/// <param name="container">The container to match.</param>
|
||||
/// <param name="useSubContainer">Consider sub-containers.</param>
|
||||
/// <returns>True if both conditions are met.</returns>
|
||||
public bool ContainsAnyCodec(string? codec, string? container, bool useSubContainer = false)
|
||||
{
|
||||
return ContainsAnyCodec(codec.AsSpan(), container, useSubContainer);
|
||||
}
|
||||
|
||||
public bool ContainsAnyCodec(string codec, string container, bool useSubContainer = false)
|
||||
{
|
||||
return ContainsAnyCodec(ContainerProfile.SplitValue(codec), container, useSubContainer);
|
||||
}
|
||||
|
||||
public bool ContainsAnyCodec(string[] codec, string container, bool useSubContainer = false)
|
||||
{
|
||||
if (!ContainsContainer(container, useSubContainer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var codecs = GetCodecs();
|
||||
if (codecs.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var val in codec)
|
||||
{
|
||||
if (codecs.Contains(val, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks to see whether the codecs and containers contain the given parameters.
|
||||
/// </summary>
|
||||
/// <param name="codec">The codec to match.</param>
|
||||
/// <param name="container">The container to match.</param>
|
||||
/// <param name="useSubContainer">Consider sub-containers.</param>
|
||||
/// <returns>True if both conditions are met.</returns>
|
||||
public bool ContainsAnyCodec(ReadOnlySpan<char> codec, string? container, bool useSubContainer = false)
|
||||
{
|
||||
var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
|
||||
return ContainerHelper.ContainsContainer(containerToCheck, container) && ContainerHelper.ContainsContainer(Codec, false, codec);
|
||||
}
|
||||
}
|
||||
|
@ -1,74 +1,49 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1819 // Properties should not return arrays
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
namespace MediaBrowser.Model.Dlna;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ContainerProfile"/>.
|
||||
/// </summary>
|
||||
public class ContainerProfile
|
||||
{
|
||||
public class ContainerProfile
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="DlnaProfileType"/> which this container must meet.
|
||||
/// </summary>
|
||||
[XmlAttribute("type")]
|
||||
public DlnaProfileType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of <see cref="ProfileCondition"/> which this container will be applied to.
|
||||
/// </summary>
|
||||
public ProfileCondition[] Conditions { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the container(s) which this container must meet.
|
||||
/// </summary>
|
||||
[XmlAttribute("container")]
|
||||
public string? Container { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sub container(s) which this container must meet.
|
||||
/// </summary>
|
||||
[XmlAttribute("subcontainer")]
|
||||
public string? SubContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if an item in <paramref name="container"/> appears in the <see cref="Container"/> property.
|
||||
/// </summary>
|
||||
/// <param name="container">The item to match.</param>
|
||||
/// <param name="useSubContainer">Consider subcontainers.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
public bool ContainsContainer(ReadOnlySpan<char> container, bool useSubContainer = false)
|
||||
{
|
||||
[XmlAttribute("type")]
|
||||
public DlnaProfileType Type { get; set; }
|
||||
|
||||
public ProfileCondition[] Conditions { get; set; } = Array.Empty<ProfileCondition>();
|
||||
|
||||
[XmlAttribute("container")]
|
||||
public string Container { get; set; } = string.Empty;
|
||||
|
||||
public static string[] SplitValue(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return value.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public bool ContainsContainer(string? container)
|
||||
{
|
||||
var containers = SplitValue(Container);
|
||||
|
||||
return ContainsContainer(containers, container);
|
||||
}
|
||||
|
||||
public static bool ContainsContainer(string? profileContainers, string? inputContainer)
|
||||
{
|
||||
var isNegativeList = false;
|
||||
if (profileContainers is not null && profileContainers.StartsWith('-'))
|
||||
{
|
||||
isNegativeList = true;
|
||||
profileContainers = profileContainers.Substring(1);
|
||||
}
|
||||
|
||||
return ContainsContainer(SplitValue(profileContainers), isNegativeList, inputContainer);
|
||||
}
|
||||
|
||||
public static bool ContainsContainer(string[]? profileContainers, string? inputContainer)
|
||||
{
|
||||
return ContainsContainer(profileContainers, false, inputContainer);
|
||||
}
|
||||
|
||||
public static bool ContainsContainer(string[]? profileContainers, bool isNegativeList, string? inputContainer)
|
||||
{
|
||||
if (profileContainers is null || profileContainers.Length == 0)
|
||||
{
|
||||
// Empty profiles always support all containers/codecs
|
||||
return true;
|
||||
}
|
||||
|
||||
var allInputContainers = SplitValue(inputContainer);
|
||||
|
||||
foreach (var container in allInputContainers)
|
||||
{
|
||||
if (profileContainers.Contains(container, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return !isNegativeList;
|
||||
}
|
||||
}
|
||||
|
||||
return isNegativeList;
|
||||
}
|
||||
var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
|
||||
return ContainerHelper.ContainsContainer(containerToCheck, container);
|
||||
}
|
||||
}
|
||||
|
@ -1,74 +1,71 @@
|
||||
#pragma warning disable CA1819 // Properties should not return arrays
|
||||
|
||||
using System;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
namespace MediaBrowser.Model.Dlna;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="DeviceProfile" /> represents a set of metadata which determines which content a certain device is able to play.
|
||||
/// <br/>
|
||||
/// Specifically, it defines the supported <see cref="ContainerProfiles">containers</see> and
|
||||
/// <see cref="CodecProfiles">codecs</see> (video and/or audio, including codec profiles and levels)
|
||||
/// the device is able to direct play (without transcoding or remuxing),
|
||||
/// as well as which <see cref="TranscodingProfiles">containers/codecs to transcode to</see> in case it isn't.
|
||||
/// </summary>
|
||||
public class DeviceProfile
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="DeviceProfile" /> represents a set of metadata which determines which content a certain device is able to play.
|
||||
/// <br/>
|
||||
/// Specifically, it defines the supported <see cref="ContainerProfiles">containers</see> and
|
||||
/// <see cref="CodecProfiles">codecs</see> (video and/or audio, including codec profiles and levels)
|
||||
/// the device is able to direct play (without transcoding or remuxing),
|
||||
/// as well as which <see cref="TranscodingProfiles">containers/codecs to transcode to</see> in case it isn't.
|
||||
/// Gets or sets the name of this device profile. User profiles must have a unique name.
|
||||
/// </summary>
|
||||
public class DeviceProfile
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of this device profile.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Id.
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
public string? Id { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the unique internal identifier.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed bitrate for all streamed content.
|
||||
/// </summary>
|
||||
public int? MaxStreamingBitrate { get; set; } = 8000000;
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed bitrate for all streamed content.
|
||||
/// </summary>
|
||||
public int? MaxStreamingBitrate { get; set; } = 8000000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed bitrate for statically streamed content (= direct played files).
|
||||
/// </summary>
|
||||
public int? MaxStaticBitrate { get; set; } = 8000000;
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed bitrate for statically streamed content (= direct played files).
|
||||
/// </summary>
|
||||
public int? MaxStaticBitrate { get; set; } = 8000000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed bitrate for transcoded music streams.
|
||||
/// </summary>
|
||||
public int? MusicStreamingTranscodingBitrate { get; set; } = 128000;
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed bitrate for transcoded music streams.
|
||||
/// </summary>
|
||||
public int? MusicStreamingTranscodingBitrate { get; set; } = 128000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed bitrate for statically streamed (= direct played) music files.
|
||||
/// </summary>
|
||||
public int? MaxStaticMusicBitrate { get; set; } = 8000000;
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed bitrate for statically streamed (= direct played) music files.
|
||||
/// </summary>
|
||||
public int? MaxStaticMusicBitrate { get; set; } = 8000000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the direct play profiles.
|
||||
/// </summary>
|
||||
public DirectPlayProfile[] DirectPlayProfiles { get; set; } = Array.Empty<DirectPlayProfile>();
|
||||
/// <summary>
|
||||
/// Gets or sets the direct play profiles.
|
||||
/// </summary>
|
||||
public DirectPlayProfile[] DirectPlayProfiles { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transcoding profiles.
|
||||
/// </summary>
|
||||
public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty<TranscodingProfile>();
|
||||
/// <summary>
|
||||
/// Gets or sets the transcoding profiles.
|
||||
/// </summary>
|
||||
public TranscodingProfile[] TranscodingProfiles { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the container profiles.
|
||||
/// </summary>
|
||||
public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty<ContainerProfile>();
|
||||
/// <summary>
|
||||
/// Gets or sets the container profiles. Failing to meet these optional conditions causes transcoding to occur.
|
||||
/// </summary>
|
||||
public ContainerProfile[] ContainerProfiles { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the codec profiles.
|
||||
/// </summary>
|
||||
public CodecProfile[] CodecProfiles { get; set; } = Array.Empty<CodecProfile>();
|
||||
/// <summary>
|
||||
/// Gets or sets the codec profiles.
|
||||
/// </summary>
|
||||
public CodecProfile[] CodecProfiles { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the subtitle profiles.
|
||||
/// </summary>
|
||||
public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty<SubtitleProfile>();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the subtitle profiles.
|
||||
/// </summary>
|
||||
public SubtitleProfile[] SubtitleProfiles { get; set; } = [];
|
||||
}
|
||||
|
@ -1,36 +1,65 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
namespace MediaBrowser.Model.Dlna;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="DirectPlayProfile"/>.
|
||||
/// </summary>
|
||||
public class DirectPlayProfile
|
||||
{
|
||||
public class DirectPlayProfile
|
||||
/// <summary>
|
||||
/// Gets or sets the container.
|
||||
/// </summary>
|
||||
[XmlAttribute("container")]
|
||||
public string Container { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio codec.
|
||||
/// </summary>
|
||||
[XmlAttribute("audioCodec")]
|
||||
public string? AudioCodec { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the video codec.
|
||||
/// </summary>
|
||||
[XmlAttribute("videoCodec")]
|
||||
public string? VideoCodec { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Dlna profile type.
|
||||
/// </summary>
|
||||
[XmlAttribute("type")]
|
||||
public DlnaProfileType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the <see cref="Container"/> supports the <paramref name="container"/>.
|
||||
/// </summary>
|
||||
/// <param name="container">The container to match against.</param>
|
||||
/// <returns>True if supported.</returns>
|
||||
public bool SupportsContainer(string? container)
|
||||
{
|
||||
[XmlAttribute("container")]
|
||||
public string? Container { get; set; }
|
||||
return ContainerHelper.ContainsContainer(Container, container);
|
||||
}
|
||||
|
||||
[XmlAttribute("audioCodec")]
|
||||
public string? AudioCodec { get; set; }
|
||||
/// <summary>
|
||||
/// Returns whether the <see cref="VideoCodec"/> supports the <paramref name="codec"/>.
|
||||
/// </summary>
|
||||
/// <param name="codec">The codec to match against.</param>
|
||||
/// <returns>True if supported.</returns>
|
||||
public bool SupportsVideoCodec(string? codec)
|
||||
{
|
||||
return Type == DlnaProfileType.Video && ContainerHelper.ContainsContainer(VideoCodec, codec);
|
||||
}
|
||||
|
||||
[XmlAttribute("videoCodec")]
|
||||
public string? VideoCodec { get; set; }
|
||||
|
||||
[XmlAttribute("type")]
|
||||
public DlnaProfileType Type { get; set; }
|
||||
|
||||
public bool SupportsContainer(string? container)
|
||||
{
|
||||
return ContainerProfile.ContainsContainer(Container, container);
|
||||
}
|
||||
|
||||
public bool SupportsVideoCodec(string? codec)
|
||||
{
|
||||
return Type == DlnaProfileType.Video && ContainerProfile.ContainsContainer(VideoCodec, codec);
|
||||
}
|
||||
|
||||
public bool SupportsAudioCodec(string? codec)
|
||||
{
|
||||
return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns whether the <see cref="AudioCodec"/> supports the <paramref name="codec"/>.
|
||||
/// </summary>
|
||||
/// <param name="codec">The codec to match against.</param>
|
||||
/// <returns>True if supported.</returns>
|
||||
public bool SupportsAudioCodec(string? codec)
|
||||
{
|
||||
// Video profiles can have audio codec restrictions too, therefore incude Video as valid type.
|
||||
return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerHelper.ContainsContainer(AudioCodec, codec);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -27,9 +28,9 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ITranscoderSupport _transcoderSupport;
|
||||
private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "vp9", "av1" };
|
||||
private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" };
|
||||
private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" };
|
||||
private static readonly string[] _supportedHlsVideoCodecs = ["h264", "hevc", "vp9", "av1"];
|
||||
private static readonly string[] _supportedHlsAudioCodecsTs = ["aac", "ac3", "eac3", "mp3"];
|
||||
private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd"];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamBuilder"/> class.
|
||||
@ -51,7 +52,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
ValidateMediaOptions(options, false);
|
||||
|
||||
var streams = new List<StreamInfo>();
|
||||
List<StreamInfo> streams = [];
|
||||
foreach (var mediaSource in options.MediaSources)
|
||||
{
|
||||
if (!(string.IsNullOrEmpty(options.MediaSourceId)
|
||||
@ -64,7 +65,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
if (streamInfo is not null)
|
||||
{
|
||||
streamInfo.DeviceId = options.DeviceId;
|
||||
streamInfo.DeviceProfileId = options.Profile.Id;
|
||||
streamInfo.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
streams.Add(streamInfo);
|
||||
}
|
||||
}
|
||||
@ -129,7 +130,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
if (directPlayMethod is PlayMethod.DirectStream)
|
||||
{
|
||||
var remuxContainer = item.TranscodingContainer ?? "ts";
|
||||
var supportedHlsContainers = new[] { "ts", "mp4" };
|
||||
string[] supportedHlsContainers = ["ts", "mp4"];
|
||||
// If the container specified for the profile is an HLS supported container, use that container instead, overriding the preference
|
||||
// The client should be responsible to ensure this container is compatible
|
||||
remuxContainer = Array.Exists(supportedHlsContainers, element => string.Equals(element, directPlayInfo.Profile?.Container, StringComparison.OrdinalIgnoreCase)) ? directPlayInfo.Profile?.Container : remuxContainer;
|
||||
@ -226,7 +227,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
? options.MediaSources
|
||||
: options.MediaSources.Where(x => string.Equals(x.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var streams = new List<StreamInfo>();
|
||||
List<StreamInfo> streams = [];
|
||||
foreach (var mediaSourceInfo in mediaSources)
|
||||
{
|
||||
var streamInfo = BuildVideoItem(mediaSourceInfo, options);
|
||||
@ -239,7 +240,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
stream.DeviceId = options.DeviceId;
|
||||
stream.DeviceProfileId = options.Profile.Id;
|
||||
stream.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
|
||||
@ -388,32 +389,32 @@ namespace MediaBrowser.Model.Dlna
|
||||
/// <param name="type">The <see cref="DlnaProfileType"/>.</param>
|
||||
/// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param>
|
||||
/// <returns>The normalized input container.</returns>
|
||||
public static string? NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null)
|
||||
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(inputContainer))
|
||||
if (profile is null || !inputContainer.Contains(',', StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
return inputContainer;
|
||||
}
|
||||
|
||||
var formats = ContainerProfile.SplitValue(inputContainer);
|
||||
|
||||
if (profile is not null)
|
||||
var formats = ContainerHelper.Split(inputContainer);
|
||||
var playProfiles = playProfile is null ? profile.DirectPlayProfiles : [playProfile];
|
||||
foreach (var format in formats)
|
||||
{
|
||||
var playProfiles = playProfile is null ? profile.DirectPlayProfiles : new[] { playProfile };
|
||||
foreach (var format in formats)
|
||||
foreach (var directPlayProfile in playProfiles)
|
||||
{
|
||||
foreach (var directPlayProfile in playProfiles)
|
||||
if (directPlayProfile.Type != type)
|
||||
{
|
||||
if (directPlayProfile.Type == type
|
||||
&& directPlayProfile.SupportsContainer(format))
|
||||
{
|
||||
return format;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (directPlayProfile.SupportsContainer(format))
|
||||
{
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formats[0];
|
||||
return inputContainer;
|
||||
}
|
||||
|
||||
private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options)
|
||||
@ -533,7 +534,6 @@ namespace MediaBrowser.Model.Dlna
|
||||
private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
|
||||
{
|
||||
int highestScore = -1;
|
||||
|
||||
foreach (var stream in item.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle
|
||||
@ -544,7 +544,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
}
|
||||
}
|
||||
|
||||
var topStreams = new List<MediaStream>();
|
||||
List<MediaStream> topStreams = [];
|
||||
foreach (var stream in item.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore)
|
||||
@ -623,8 +623,8 @@ namespace MediaBrowser.Model.Dlna
|
||||
playlistItem.Container = container;
|
||||
playlistItem.SubProtocol = protocol;
|
||||
|
||||
playlistItem.VideoCodecs = new[] { item.VideoStream.Codec };
|
||||
playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec);
|
||||
playlistItem.VideoCodecs = [item.VideoStream.Codec];
|
||||
playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec);
|
||||
}
|
||||
|
||||
private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options)
|
||||
@ -651,7 +651,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
}
|
||||
|
||||
// Collect candidate audio streams
|
||||
ICollection<MediaStream> candidateAudioStreams = audioStream is null ? Array.Empty<MediaStream>() : new[] { audioStream };
|
||||
ICollection<MediaStream> candidateAudioStreams = audioStream is null ? [] : [audioStream];
|
||||
if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0)
|
||||
{
|
||||
if (audioStream?.IsDefault == true)
|
||||
@ -702,7 +702,8 @@ namespace MediaBrowser.Model.Dlna
|
||||
directPlayProfile = directPlayInfo.Profile;
|
||||
playlistItem.PlayMethod = directPlay.Value;
|
||||
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile);
|
||||
playlistItem.VideoCodecs = new[] { videoStream.Codec };
|
||||
var videoCodec = videoStream?.Codec;
|
||||
playlistItem.VideoCodecs = videoCodec is null ? [] : [videoCodec];
|
||||
|
||||
if (directPlay == PlayMethod.DirectPlay)
|
||||
{
|
||||
@ -713,7 +714,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
playlistItem.AudioStreamIndex = audioStreamIndex;
|
||||
var audioCodec = item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec;
|
||||
playlistItem.AudioCodecs = audioCodec is null ? Array.Empty<string>() : new[] { audioCodec };
|
||||
playlistItem.AudioCodecs = audioCodec is null ? [] : [audioCodec];
|
||||
}
|
||||
}
|
||||
else if (directPlay == PlayMethod.DirectStream)
|
||||
@ -721,7 +722,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
playlistItem.AudioStreamIndex = audioStream?.Index;
|
||||
if (audioStream is not null)
|
||||
{
|
||||
playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec);
|
||||
playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec);
|
||||
}
|
||||
|
||||
SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile);
|
||||
@ -753,7 +754,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
// Can't direct play, find the transcoding profile
|
||||
// If we do this for direct-stream we will overwrite the info
|
||||
var (transcodingProfile, playMethod) = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem);
|
||||
var (transcodingProfile, playMethod) = GetVideoTranscodeProfile(item, options, videoStream, audioStream, playlistItem);
|
||||
|
||||
if (transcodingProfile is not null && playMethod.HasValue)
|
||||
{
|
||||
@ -781,7 +782,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
}
|
||||
|
||||
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
||||
playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
|
||||
playlistItem.SubtitleCodecs = [subtitleProfile.Format];
|
||||
}
|
||||
|
||||
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
|
||||
@ -810,8 +811,6 @@ namespace MediaBrowser.Model.Dlna
|
||||
MediaOptions options,
|
||||
MediaStream? videoStream,
|
||||
MediaStream? audioStream,
|
||||
IEnumerable<MediaStream> candidateAudioStreams,
|
||||
MediaStream? subtitleStream,
|
||||
StreamInfo playlistItem)
|
||||
{
|
||||
if (!(item.SupportsTranscoding || item.SupportsDirectStream))
|
||||
@ -849,9 +848,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
if (options.AllowVideoStreamCopy)
|
||||
{
|
||||
var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
|
||||
|
||||
if (ContainerProfile.ContainsContainer(videoCodecs, videoCodec))
|
||||
if (ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec))
|
||||
{
|
||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Video &&
|
||||
@ -868,9 +865,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
if (options.AllowAudioStreamCopy)
|
||||
{
|
||||
var audioCodecs = ContainerProfile.SplitValue(transcodingProfile.AudioCodec);
|
||||
|
||||
if (ContainerProfile.ContainsContainer(audioCodecs, audioCodec))
|
||||
if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec))
|
||||
{
|
||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.VideoAudio &&
|
||||
@ -913,20 +908,18 @@ namespace MediaBrowser.Model.Dlna
|
||||
string? audioCodec)
|
||||
{
|
||||
// Prefer matching video codecs
|
||||
var videoCodecs = ContainerProfile.SplitValue(videoCodec);
|
||||
var videoCodecs = ContainerHelper.Split(videoCodec).ToList();
|
||||
|
||||
if (videoCodecs.Count == 0 && videoStream is not null)
|
||||
{
|
||||
// Add the original codec if no codec is specified
|
||||
videoCodecs.Add(videoStream.Codec);
|
||||
}
|
||||
|
||||
// Enforce HLS video codec restrictions
|
||||
if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
|
||||
{
|
||||
videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray();
|
||||
}
|
||||
|
||||
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
|
||||
if (directVideoCodec is not null)
|
||||
{
|
||||
// merge directVideoCodec to videoCodecs
|
||||
Array.Resize(ref videoCodecs, videoCodecs.Length + 1);
|
||||
videoCodecs[^1] = directVideoCodec;
|
||||
videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToList();
|
||||
}
|
||||
|
||||
playlistItem.VideoCodecs = videoCodecs;
|
||||
@ -950,22 +943,28 @@ namespace MediaBrowser.Model.Dlna
|
||||
}
|
||||
|
||||
// Prefer matching audio codecs, could do better here
|
||||
var audioCodecs = ContainerProfile.SplitValue(audioCodec);
|
||||
var audioCodecs = ContainerHelper.Split(audioCodec).ToList();
|
||||
|
||||
if (audioCodecs.Count == 0 && audioStream is not null)
|
||||
{
|
||||
// Add the original codec if no codec is specified
|
||||
audioCodecs.Add(audioStream.Codec);
|
||||
}
|
||||
|
||||
// Enforce HLS audio codec restrictions
|
||||
if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
|
||||
{
|
||||
if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToArray();
|
||||
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToArray();
|
||||
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)).FirstOrDefault();
|
||||
var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerHelper.ContainsContainer(audioCodecs, false, stream.Codec)).FirstOrDefault();
|
||||
|
||||
var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec.Channels.Value <= (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue) ? audioStreamWithSupportedCodec : null;
|
||||
|
||||
@ -982,7 +981,8 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
audioStream = directAudioStream;
|
||||
playlistItem.AudioStreamIndex = audioStream.Index;
|
||||
playlistItem.AudioCodecs = audioCodecs = new[] { audioStream.Codec };
|
||||
audioCodecs = [audioStream.Codec];
|
||||
playlistItem.AudioCodecs = audioCodecs;
|
||||
|
||||
// Copy matching audio codec options
|
||||
playlistItem.AudioSampleRate = audioStream.SampleRate;
|
||||
@ -1023,18 +1023,17 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Video &&
|
||||
i.ContainsAnyCodec(videoCodecs, container, useSubContainer) &&
|
||||
i.ContainsAnyCodec(playlistItem.VideoCodecs, container, useSubContainer) &&
|
||||
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)))
|
||||
// Reverse codec profiles for backward compatibility - first codec profile has higher priority
|
||||
.Reverse();
|
||||
|
||||
foreach (var i in appliedVideoConditions)
|
||||
foreach (var condition in appliedVideoConditions)
|
||||
{
|
||||
foreach (var transcodingVideoCodec in videoCodecs)
|
||||
foreach (var transcodingVideoCodec in playlistItem.VideoCodecs)
|
||||
{
|
||||
if (i.ContainsAnyCodec(transcodingVideoCodec, container, useSubContainer))
|
||||
if (condition.ContainsAnyCodec(transcodingVideoCodec, container, useSubContainer))
|
||||
{
|
||||
ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, true);
|
||||
ApplyTranscodingConditions(playlistItem, condition.Conditions, transcodingVideoCodec, true, true);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -1055,14 +1054,14 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
var appliedAudioConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.VideoAudio &&
|
||||
i.ContainsAnyCodec(audioCodecs, container) &&
|
||||
i.ContainsAnyCodec(playlistItem.AudioCodecs, container) &&
|
||||
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)))
|
||||
// Reverse codec profiles for backward compatibility - first codec profile has higher priority
|
||||
.Reverse();
|
||||
|
||||
foreach (var codecProfile in appliedAudioConditions)
|
||||
{
|
||||
foreach (var transcodingAudioCodec in audioCodecs)
|
||||
foreach (var transcodingAudioCodec in playlistItem.AudioCodecs)
|
||||
{
|
||||
if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container))
|
||||
{
|
||||
@ -1132,9 +1131,9 @@ namespace MediaBrowser.Model.Dlna
|
||||
return 192000;
|
||||
}
|
||||
|
||||
private static int GetAudioBitrate(long maxTotalBitrate, string[] targetAudioCodecs, MediaStream? audioStream, StreamInfo item)
|
||||
private static int GetAudioBitrate(long maxTotalBitrate, IReadOnlyList<string> targetAudioCodecs, MediaStream? audioStream, StreamInfo item)
|
||||
{
|
||||
string? targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
|
||||
string? targetAudioCodec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0];
|
||||
|
||||
int? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec);
|
||||
|
||||
@ -1151,7 +1150,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
&& audioStream.Channels.HasValue
|
||||
&& audioStream.Channels.Value > targetAudioChannels.Value)
|
||||
{
|
||||
// Reduce the bitrate if we're downmixing.
|
||||
// Reduce the bitrate if we're down mixing.
|
||||
defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, targetAudioChannels);
|
||||
}
|
||||
else if (targetAudioChannels.HasValue
|
||||
@ -1159,8 +1158,8 @@ namespace MediaBrowser.Model.Dlna
|
||||
&& audioStream.Channels.Value <= targetAudioChannels.Value
|
||||
&& !string.IsNullOrEmpty(audioStream.Codec)
|
||||
&& targetAudioCodecs is not null
|
||||
&& targetAudioCodecs.Length > 0
|
||||
&& !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase)))
|
||||
&& targetAudioCodecs.Count > 0
|
||||
&& !targetAudioCodecs.Any(elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// Shift the bitrate if we're transcoding to a different audio codec.
|
||||
defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, audioStream.Channels.Value);
|
||||
@ -1299,7 +1298,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
!checkVideoConditions(codecProfile.ApplyConditions).Any())
|
||||
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
|
||||
|
||||
// Check audiocandidates profile conditions
|
||||
// Check audio candidates profile conditions
|
||||
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream));
|
||||
|
||||
TranscodeReason subtitleProfileReasons = 0;
|
||||
@ -1316,25 +1315,8 @@ namespace MediaBrowser.Model.Dlna
|
||||
}
|
||||
}
|
||||
|
||||
var rankings = new[] { TranscodeReason.VideoCodecNotSupported, VideoCodecReasons, TranscodeReason.AudioCodecNotSupported, AudioCodecReasons, ContainerReasons };
|
||||
var rank = (ref TranscodeReason a) =>
|
||||
{
|
||||
var index = 1;
|
||||
foreach (var flag in rankings)
|
||||
{
|
||||
var reason = a & flag;
|
||||
if (reason != 0)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return index;
|
||||
};
|
||||
|
||||
var containerSupported = false;
|
||||
TranscodeReason[] rankings = [TranscodeReason.VideoCodecNotSupported, VideoCodecReasons, TranscodeReason.AudioCodecNotSupported, AudioCodecReasons, ContainerReasons];
|
||||
|
||||
// Check DirectPlay profiles to see if it can be direct played
|
||||
var analyzedProfiles = profile.DirectPlayProfiles
|
||||
@ -1400,7 +1382,8 @@ namespace MediaBrowser.Model.Dlna
|
||||
playMethod = PlayMethod.DirectStream;
|
||||
}
|
||||
|
||||
var ranked = rank(ref failureReasons);
|
||||
var ranked = GetRank(ref failureReasons, rankings);
|
||||
|
||||
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
|
||||
})
|
||||
.OrderByDescending(analysis => analysis.Result.PlayMethod)
|
||||
@ -1475,7 +1458,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
/// <param name="playMethod">The <see cref="PlayMethod"/>.</param>
|
||||
/// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</param>
|
||||
/// <param name="outputContainer">The output container.</param>
|
||||
/// <param name="transcodingSubProtocol">The subtitle transoding protocol.</param>
|
||||
/// <param name="transcodingSubProtocol">The subtitle transcoding protocol.</param>
|
||||
/// <returns>The normalized input container.</returns>
|
||||
public static SubtitleProfile GetSubtitleProfile(
|
||||
MediaSourceInfo mediaSource,
|
||||
@ -1501,7 +1484,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer))
|
||||
if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -1530,7 +1513,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer))
|
||||
if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -1561,17 +1544,12 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
if (!string.IsNullOrEmpty(transcodingContainer))
|
||||
{
|
||||
string[] normalizedContainers = ContainerProfile.SplitValue(transcodingContainer);
|
||||
|
||||
if (ContainerProfile.ContainsContainer(normalizedContainers, "ts")
|
||||
|| ContainerProfile.ContainsContainer(normalizedContainers, "mpegts")
|
||||
|| ContainerProfile.ContainsContainer(normalizedContainers, "mp4"))
|
||||
if (ContainerHelper.ContainsContainer(transcodingContainer, "ts,mpegts,mp4"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv")
|
||||
|| ContainerProfile.ContainsContainer(normalizedContainers, "matroska"))
|
||||
if (ContainerHelper.ContainsContainer(transcodingContainer, "mkv,matroska"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -2274,5 +2252,22 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int GetRank(ref TranscodeReason a, TranscodeReason[] rankings)
|
||||
{
|
||||
var index = 1;
|
||||
foreach (var flag in rankings)
|
||||
{
|
||||
var reason = a & flag;
|
||||
if (reason != 0)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,48 +1,62 @@
|
||||
#nullable disable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Xml.Serialization;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
namespace MediaBrowser.Model.Dlna;
|
||||
|
||||
/// <summary>
|
||||
/// A class for subtitle profile information.
|
||||
/// </summary>
|
||||
public class SubtitleProfile
|
||||
{
|
||||
public class SubtitleProfile
|
||||
/// <summary>
|
||||
/// Gets or sets the format.
|
||||
/// </summary>
|
||||
[XmlAttribute("format")]
|
||||
public string Format { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delivery method.
|
||||
/// </summary>
|
||||
[XmlAttribute("method")]
|
||||
public SubtitleDeliveryMethod Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DIDL mode.
|
||||
/// </summary>
|
||||
[XmlAttribute("didlMode")]
|
||||
public string DidlMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the language.
|
||||
/// </summary>
|
||||
[XmlAttribute("language")]
|
||||
public string Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the container.
|
||||
/// </summary>
|
||||
[XmlAttribute("container")]
|
||||
public string Container { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a language is supported.
|
||||
/// </summary>
|
||||
/// <param name="subLanguage">The language to check for support.</param>
|
||||
/// <returns><c>true</c> if supported.</returns>
|
||||
public bool SupportsLanguage(string subLanguage)
|
||||
{
|
||||
[XmlAttribute("format")]
|
||||
public string Format { get; set; }
|
||||
|
||||
[XmlAttribute("method")]
|
||||
public SubtitleDeliveryMethod Method { get; set; }
|
||||
|
||||
[XmlAttribute("didlMode")]
|
||||
public string DidlMode { get; set; }
|
||||
|
||||
[XmlAttribute("language")]
|
||||
public string Language { get; set; }
|
||||
|
||||
[XmlAttribute("container")]
|
||||
public string Container { get; set; }
|
||||
|
||||
public string[] GetLanguages()
|
||||
if (string.IsNullOrEmpty(Language))
|
||||
{
|
||||
return ContainerProfile.SplitValue(Language);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SupportsLanguage(string subLanguage)
|
||||
if (string.IsNullOrEmpty(subLanguage))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Language))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(subLanguage))
|
||||
{
|
||||
subLanguage = "und";
|
||||
}
|
||||
|
||||
var languages = GetLanguages();
|
||||
return languages.Length == 0 || languages.Contains(subLanguage, StringComparison.OrdinalIgnoreCase);
|
||||
subLanguage = "und";
|
||||
}
|
||||
|
||||
return ContainerHelper.ContainsContainer(Language, subLanguage);
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +1,130 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Xml.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
namespace MediaBrowser.Model.Dlna;
|
||||
|
||||
/// <summary>
|
||||
/// A class for transcoding profile information.
|
||||
/// </summary>
|
||||
public class TranscodingProfile
|
||||
{
|
||||
public class TranscodingProfile
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TranscodingProfile" /> class.
|
||||
/// </summary>
|
||||
public TranscodingProfile()
|
||||
{
|
||||
public TranscodingProfile()
|
||||
{
|
||||
Conditions = Array.Empty<ProfileCondition>();
|
||||
}
|
||||
|
||||
[XmlAttribute("container")]
|
||||
public string Container { get; set; } = string.Empty;
|
||||
|
||||
[XmlAttribute("type")]
|
||||
public DlnaProfileType Type { get; set; }
|
||||
|
||||
[XmlAttribute("videoCodec")]
|
||||
public string VideoCodec { get; set; } = string.Empty;
|
||||
|
||||
[XmlAttribute("audioCodec")]
|
||||
public string AudioCodec { get; set; } = string.Empty;
|
||||
|
||||
[XmlAttribute("protocol")]
|
||||
public MediaStreamProtocol Protocol { get; set; } = MediaStreamProtocol.http;
|
||||
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("estimateContentLength")]
|
||||
public bool EstimateContentLength { get; set; }
|
||||
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("enableMpegtsM2TsMode")]
|
||||
public bool EnableMpegtsM2TsMode { get; set; }
|
||||
|
||||
[DefaultValue(TranscodeSeekInfo.Auto)]
|
||||
[XmlAttribute("transcodeSeekInfo")]
|
||||
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("copyTimestamps")]
|
||||
public bool CopyTimestamps { get; set; }
|
||||
|
||||
[DefaultValue(EncodingContext.Streaming)]
|
||||
[XmlAttribute("context")]
|
||||
public EncodingContext Context { get; set; }
|
||||
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("enableSubtitlesInManifest")]
|
||||
public bool EnableSubtitlesInManifest { get; set; }
|
||||
|
||||
[XmlAttribute("maxAudioChannels")]
|
||||
public string? MaxAudioChannels { get; set; }
|
||||
|
||||
[DefaultValue(0)]
|
||||
[XmlAttribute("minSegments")]
|
||||
public int MinSegments { get; set; }
|
||||
|
||||
[DefaultValue(0)]
|
||||
[XmlAttribute("segmentLength")]
|
||||
public int SegmentLength { get; set; }
|
||||
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("breakOnNonKeyFrames")]
|
||||
public bool BreakOnNonKeyFrames { get; set; }
|
||||
|
||||
public ProfileCondition[] Conditions { get; set; }
|
||||
|
||||
[DefaultValue(true)]
|
||||
[XmlAttribute("enableAudioVbrEncoding")]
|
||||
public bool EnableAudioVbrEncoding { get; set; } = true;
|
||||
|
||||
public string[] GetAudioCodecs()
|
||||
{
|
||||
return ContainerProfile.SplitValue(AudioCodec);
|
||||
}
|
||||
Conditions = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the container.
|
||||
/// </summary>
|
||||
[XmlAttribute("container")]
|
||||
public string Container { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DLNA profile type.
|
||||
/// </summary>
|
||||
[XmlAttribute("type")]
|
||||
public DlnaProfileType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the video codec.
|
||||
/// </summary>
|
||||
[XmlAttribute("videoCodec")]
|
||||
public string VideoCodec { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio codec.
|
||||
/// </summary>
|
||||
[XmlAttribute("audioCodec")]
|
||||
public string AudioCodec { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the protocol.
|
||||
/// </summary>
|
||||
[XmlAttribute("protocol")]
|
||||
public MediaStreamProtocol Protocol { get; set; } = MediaStreamProtocol.http;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the content length should be estimated.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("estimateContentLength")]
|
||||
public bool EstimateContentLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether M2TS mode is enabled.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("enableMpegtsM2TsMode")]
|
||||
public bool EnableMpegtsM2TsMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transcoding seek info mode.
|
||||
/// </summary>
|
||||
[DefaultValue(TranscodeSeekInfo.Auto)]
|
||||
[XmlAttribute("transcodeSeekInfo")]
|
||||
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether timestamps should be copied.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("copyTimestamps")]
|
||||
public bool CopyTimestamps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the encoding context.
|
||||
/// </summary>
|
||||
[DefaultValue(EncodingContext.Streaming)]
|
||||
[XmlAttribute("context")]
|
||||
public EncodingContext Context { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether subtitles are allowed in the manifest.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("enableSubtitlesInManifest")]
|
||||
public bool EnableSubtitlesInManifest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum audio channels.
|
||||
/// </summary>
|
||||
[XmlAttribute("maxAudioChannels")]
|
||||
public string? MaxAudioChannels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum amount of segments.
|
||||
/// </summary>
|
||||
[DefaultValue(0)]
|
||||
[XmlAttribute("minSegments")]
|
||||
public int MinSegments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the segment length.
|
||||
/// </summary>
|
||||
[DefaultValue(0)]
|
||||
[XmlAttribute("segmentLength")]
|
||||
public int SegmentLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether breaking the video stream on non-keyframes is supported.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[XmlAttribute("breakOnNonKeyFrames")]
|
||||
public bool BreakOnNonKeyFrames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the profile conditions.
|
||||
/// </summary>
|
||||
public ProfileCondition[] Conditions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether variable bitrate encoding is supported.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
[XmlAttribute("enableAudioVbrEncoding")]
|
||||
public bool EnableAudioVbrEncoding { get; set; } = true;
|
||||
}
|
||||
|
145
MediaBrowser.Model/Extensions/ContainerHelper.cs
Normal file
145
MediaBrowser.Model/Extensions/ContainerHelper.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace MediaBrowser.Model.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ContainerHelper"/> class.
|
||||
/// </summary>
|
||||
public static class ContainerHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares two containers, returning true if an item in <paramref name="inputContainer"/> exists
|
||||
/// in <paramref name="profileContainers"/>.
|
||||
/// </summary>
|
||||
/// <param name="profileContainers">The comma-delimited string being searched.
|
||||
/// If the parameter begins with the <c>-</c> character, the operation is reversed.</param>
|
||||
/// <param name="inputContainer">The comma-delimited string being matched.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
public static bool ContainsContainer(string? profileContainers, string? inputContainer)
|
||||
{
|
||||
var isNegativeList = false;
|
||||
if (profileContainers != null && profileContainers.StartsWith('-'))
|
||||
{
|
||||
isNegativeList = true;
|
||||
profileContainers = profileContainers[1..];
|
||||
}
|
||||
|
||||
return ContainsContainer(profileContainers, isNegativeList, inputContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two containers, returning true if an item in <paramref name="inputContainer"/> exists
|
||||
/// in <paramref name="profileContainers"/>.
|
||||
/// </summary>
|
||||
/// <param name="profileContainers">The comma-delimited string being searched.
|
||||
/// If the parameter begins with the <c>-</c> character, the operation is reversed.</param>
|
||||
/// <param name="inputContainer">The comma-delimited string being matched.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
public static bool ContainsContainer(string? profileContainers, ReadOnlySpan<char> inputContainer)
|
||||
{
|
||||
var isNegativeList = false;
|
||||
if (profileContainers != null && profileContainers.StartsWith('-'))
|
||||
{
|
||||
isNegativeList = true;
|
||||
profileContainers = profileContainers[1..];
|
||||
}
|
||||
|
||||
return ContainsContainer(profileContainers, isNegativeList, inputContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/>
|
||||
/// does not exist in <paramref name="profileContainers"/>.
|
||||
/// </summary>
|
||||
/// <param name="profileContainers">The comma-delimited string being searched.</param>
|
||||
/// <param name="isNegativeList">The boolean result to return if a match is not found.</param>
|
||||
/// <param name="inputContainer">The comma-delimited string being matched.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
public static bool ContainsContainer(string? profileContainers, bool isNegativeList, string? inputContainer)
|
||||
{
|
||||
if (string.IsNullOrEmpty(inputContainer))
|
||||
{
|
||||
return isNegativeList;
|
||||
}
|
||||
|
||||
return ContainsContainer(profileContainers, isNegativeList, inputContainer.AsSpan());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/>
|
||||
/// does not exist in <paramref name="profileContainers"/>.
|
||||
/// </summary>
|
||||
/// <param name="profileContainers">The comma-delimited string being searched.</param>
|
||||
/// <param name="isNegativeList">The boolean result to return if a match is not found.</param>
|
||||
/// <param name="inputContainer">The comma-delimited string being matched.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
public static bool ContainsContainer(string? profileContainers, bool isNegativeList, ReadOnlySpan<char> inputContainer)
|
||||
{
|
||||
if (string.IsNullOrEmpty(profileContainers))
|
||||
{
|
||||
// Empty profiles always support all containers/codecs.
|
||||
return true;
|
||||
}
|
||||
|
||||
var allInputContainers = inputContainer.Split(',');
|
||||
var allProfileContainers = profileContainers.SpanSplit(',');
|
||||
foreach (var container in allInputContainers)
|
||||
{
|
||||
if (!container.IsEmpty)
|
||||
{
|
||||
foreach (var profile in allProfileContainers)
|
||||
{
|
||||
if (!profile.IsEmpty && container.Equals(profile, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return !isNegativeList;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isNegativeList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/>
|
||||
/// does not exist in <paramref name="profileContainers"/>.
|
||||
/// </summary>
|
||||
/// <param name="profileContainers">The profile containers being matched searched.</param>
|
||||
/// <param name="isNegativeList">The boolean result to return if a match is not found.</param>
|
||||
/// <param name="inputContainer">The comma-delimited string being matched.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
public static bool ContainsContainer(IReadOnlyList<string>? profileContainers, bool isNegativeList, string inputContainer)
|
||||
{
|
||||
if (profileContainers is null)
|
||||
{
|
||||
// Empty profiles always support all containers/codecs.
|
||||
return true;
|
||||
}
|
||||
|
||||
var allInputContainers = Split(inputContainer);
|
||||
foreach (var container in allInputContainers)
|
||||
{
|
||||
foreach (var profile in profileContainers)
|
||||
{
|
||||
if (string.Equals(profile, container, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return !isNegativeList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isNegativeList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits and input string.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
public static string[] Split(string? input)
|
||||
{
|
||||
return input?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? [];
|
||||
}
|
||||
}
|
@ -316,7 +316,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray();
|
||||
}
|
||||
|
||||
audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0
|
||||
audio.Genres = options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0
|
||||
? genres
|
||||
: audio.Genres;
|
||||
}
|
||||
|
83
tests/Jellyfin.Model.Tests/Dlna/ContainerHelperTests.cs
Normal file
83
tests/Jellyfin.Model.Tests/Dlna/ContainerHelperTests.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Model.Tests.Dlna;
|
||||
|
||||
public class ContainerHelperTests
|
||||
{
|
||||
private readonly ContainerProfile _emptyContainerProfile = new ContainerProfile();
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData("mp4")]
|
||||
public void ContainsContainer_EmptyContainerProfile_ReturnsTrue(string? containers)
|
||||
{
|
||||
Assert.True(_emptyContainerProfile.ContainsContainer(containers));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("mp3,mpeg", "mp3")]
|
||||
[InlineData("mp3,mpeg,avi", "mp3,avi")]
|
||||
[InlineData("-mp3,mpeg", "avi")]
|
||||
[InlineData("-mp3,mpeg,avi", "mp4,jpg")]
|
||||
public void ContainsContainer_InList_ReturnsTrue(string container, string? extension)
|
||||
{
|
||||
Assert.True(ContainerHelper.ContainsContainer(container, extension));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("mp3,mpeg", "avi")]
|
||||
[InlineData("mp3,mpeg,avi", "mp4,jpg")]
|
||||
[InlineData("mp3,mpeg", null)]
|
||||
[InlineData("mp3,mpeg", "")]
|
||||
[InlineData("-mp3,mpeg", "mp3")]
|
||||
[InlineData("-mp3,mpeg,avi", "mpeg,avi")]
|
||||
[InlineData(",mp3,", ",avi,")] // Empty values should be discarded
|
||||
[InlineData("-,mp3,", ",mp3,")] // Empty values should be discarded
|
||||
public void ContainsContainer_NotInList_ReturnsFalse(string container, string? extension)
|
||||
{
|
||||
Assert.False(ContainerHelper.ContainsContainer(container, extension));
|
||||
|
||||
if (extension is not null)
|
||||
{
|
||||
Assert.False(ContainerHelper.ContainsContainer(container, extension.AsSpan()));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("mp3,mpeg", "mp3")]
|
||||
[InlineData("mp3,mpeg,avi", "mp3,avi")]
|
||||
[InlineData("-mp3,mpeg", "avi")]
|
||||
[InlineData("-mp3,mpeg,avi", "mp4,jpg")]
|
||||
public void ContainsContainer_InList_ReturnsTrue_SpanVersion(string container, string? extension)
|
||||
{
|
||||
Assert.True(ContainerHelper.ContainsContainer(container, extension.AsSpan()));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new string[] { "mp3", "mpeg" }, false, "mpeg")]
|
||||
[InlineData(new string[] { "mp3", "mpeg", "avi" }, false, "avi")]
|
||||
[InlineData(new string[] { "mp3", "", "avi" }, false, "mp3")]
|
||||
[InlineData(new string[] { "mp3", "mpeg" }, true, "avi")]
|
||||
[InlineData(new string[] { "mp3", "mpeg", "avi" }, true, "mkv")]
|
||||
[InlineData(new string[] { "mp3", "", "avi" }, true, "")]
|
||||
public void ContainsContainer_ThreeArgs_InList_ReturnsTrue(string[] containers, bool isNegativeList, string inputContainer)
|
||||
{
|
||||
Assert.True(ContainerHelper.ContainsContainer(containers, isNegativeList, inputContainer));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new string[] { "mp3", "mpeg" }, false, "avi")]
|
||||
[InlineData(new string[] { "mp3", "mpeg", "avi" }, false, "mkv")]
|
||||
[InlineData(new string[] { "mp3", "", "avi" }, false, "")]
|
||||
[InlineData(new string[] { "mp3", "mpeg" }, true, "mpeg")]
|
||||
[InlineData(new string[] { "mp3", "mpeg", "avi" }, true, "mp3")]
|
||||
[InlineData(new string[] { "mp3", "", "avi" }, true, "avi")]
|
||||
public void ContainsContainer_ThreeArgs_InList_ReturnsFalse(string[] containers, bool isNegativeList, string inputContainer)
|
||||
{
|
||||
Assert.False(ContainerHelper.ContainsContainer(containers, isNegativeList, inputContainer));
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Model.Tests.Dlna
|
||||
{
|
||||
public class ContainerProfileTests
|
||||
{
|
||||
private readonly ContainerProfile _emptyContainerProfile = new ContainerProfile();
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData("mp4")]
|
||||
public void ContainsContainer_EmptyContainerProfile_True(string? containers)
|
||||
{
|
||||
Assert.True(_emptyContainerProfile.ContainsContainer(containers));
|
||||
}
|
||||
}
|
||||
}
|
@ -389,18 +389,23 @@ namespace Jellyfin.Model.Tests
|
||||
if (playMethod == PlayMethod.DirectPlay)
|
||||
{
|
||||
// Check expected container
|
||||
var containers = ContainerProfile.SplitValue(mediaSource.Container);
|
||||
var containers = mediaSource.Container.Split(',');
|
||||
Assert.Contains(uri.Extension, containers);
|
||||
// TODO: Test transcode too
|
||||
// Assert.Contains(uri.Extension, containers);
|
||||
|
||||
// Check expected video codec (1)
|
||||
Assert.Contains(targetVideoStream?.Codec, streamInfo.TargetVideoCodec);
|
||||
Assert.Single(streamInfo.TargetVideoCodec);
|
||||
if (targetVideoStream?.Codec is not null)
|
||||
{
|
||||
Assert.Contains(targetVideoStream?.Codec, streamInfo.TargetVideoCodec);
|
||||
Assert.Single(streamInfo.TargetVideoCodec);
|
||||
}
|
||||
|
||||
// Check expected audio codecs (1)
|
||||
Assert.Contains(targetAudioStream?.Codec, streamInfo.TargetAudioCodec);
|
||||
Assert.Single(streamInfo.TargetAudioCodec);
|
||||
// Assert.Single(val.AudioCodecs);
|
||||
if (targetAudioStream?.Codec is not null)
|
||||
{
|
||||
// Check expected audio codecs (1)
|
||||
Assert.Contains(targetAudioStream?.Codec, streamInfo.TargetAudioCodec);
|
||||
Assert.Single(streamInfo.TargetAudioCodec);
|
||||
}
|
||||
|
||||
if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal))
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user