From 68de105dc21f4d1114a3b1db81f177caae747ef6 Mon Sep 17 00:00:00 2001 From: Jim Cartlidge Date: Sun, 13 Sep 2020 13:49:11 +0100 Subject: [PATCH 01/61] Comments part 1 --- Emby.Dlna/Common/Argument.cs | 20 +- Emby.Dlna/Common/DeviceIcon.cs | 32 +- Emby.Dlna/Common/DeviceService.cs | 33 +- Emby.Dlna/Common/ServiceAction.cs | 21 +- Emby.Dlna/Common/StateVariable.cs | 31 +- .../Dlna/DeviceIdentification.cs | 23 +- MediaBrowser.Model/Dlna/DeviceProfile.cs | 364 +++++++++++++----- MediaBrowser.Model/Dlna/XmlAttribute.cs | 9 + 8 files changed, 377 insertions(+), 156 deletions(-) diff --git a/Emby.Dlna/Common/Argument.cs b/Emby.Dlna/Common/Argument.cs index f375e6049c..430a3b47d5 100644 --- a/Emby.Dlna/Common/Argument.cs +++ b/Emby.Dlna/Common/Argument.cs @@ -1,13 +1,23 @@ -#pragma warning disable CS1591 - namespace Emby.Dlna.Common { + /// + /// DLNA Query parameter type, used when quering DLNA devices via SOAP. + /// public class Argument { - public string Name { get; set; } + /// + /// Gets or sets name of the DLNA argument. + /// + public string Name { get; set; } = string.Empty; - public string Direction { get; set; } + /// + /// Gets or sets the direction of the parameter. + /// + public string Direction { get; set; } = string.Empty; - public string RelatedStateVariable { get; set; } + /// + /// Gets or sets the related DLNA state variable for this argument. + /// + public string RelatedStateVariable { get; set; } = string.Empty; } } diff --git a/Emby.Dlna/Common/DeviceIcon.cs b/Emby.Dlna/Common/DeviceIcon.cs index c3f7fa8aaa..f9fd1dcec6 100644 --- a/Emby.Dlna/Common/DeviceIcon.cs +++ b/Emby.Dlna/Common/DeviceIcon.cs @@ -1,29 +1,41 @@ -#pragma warning disable CS1591 - using System.Globalization; namespace Emby.Dlna.Common { + /// + /// Defines the . + /// public class DeviceIcon { - public string Url { get; set; } + /// + /// Gets or sets the Url. + /// + public string Url { get; set; } = string.Empty; - public string MimeType { get; set; } + /// + /// Gets or sets the MimeType. + /// + public string MimeType { get; set; } = string.Empty; + /// + /// Gets or sets the Width. + /// public int Width { get; set; } + /// + /// Gets or sets the Height. + /// public int Height { get; set; } - public string Depth { get; set; } + /// + /// Gets or sets the Depth. + /// + public string Depth { get; set; } = string.Empty; /// public override string ToString() { - return string.Format( - CultureInfo.InvariantCulture, - "{0}x{1}", - Height, - Width); + return string.Format(CultureInfo.InvariantCulture, "{0}x{1}", Height, Width); } } } diff --git a/Emby.Dlna/Common/DeviceService.cs b/Emby.Dlna/Common/DeviceService.cs index 44c0a0412a..c1369558ec 100644 --- a/Emby.Dlna/Common/DeviceService.cs +++ b/Emby.Dlna/Common/DeviceService.cs @@ -1,21 +1,36 @@ -#pragma warning disable CS1591 - namespace Emby.Dlna.Common { + /// + /// Defines the . + /// public class DeviceService { - public string ServiceType { get; set; } + /// + /// Gets or sets the Service Type. + /// + public string ServiceType { get; set; } = string.Empty; - public string ServiceId { get; set; } + /// + /// Gets or sets the Service Id. + /// + public string ServiceId { get; set; } = string.Empty; - public string ScpdUrl { get; set; } + /// + /// Gets or sets the Scpd Url. + /// + public string ScpdUrl { get; set; } = string.Empty; - public string ControlUrl { get; set; } + /// + /// Gets or sets the Control Url. + /// + public string ControlUrl { get; set; } = string.Empty; - public string EventSubUrl { get; set; } + /// + /// Gets or sets the EventSubUrl. + /// + public string EventSubUrl { get; set; } = string.Empty; /// - public override string ToString() - => ServiceId; + public override string ToString() => ServiceId; } } diff --git a/Emby.Dlna/Common/ServiceAction.cs b/Emby.Dlna/Common/ServiceAction.cs index d458d7f3f6..02b81a0aa7 100644 --- a/Emby.Dlna/Common/ServiceAction.cs +++ b/Emby.Dlna/Common/ServiceAction.cs @@ -1,24 +1,31 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; namespace Emby.Dlna.Common { + /// + /// Defines the . + /// public class ServiceAction { + /// + /// Initializes a new instance of the class. + /// public ServiceAction() { ArgumentList = new List(); } - public string Name { get; set; } + /// + /// Gets or sets the name of the action. + /// + public string Name { get; set; } = string.Empty; + /// + /// Gets the ArgumentList. + /// public List ArgumentList { get; } /// - public override string ToString() - { - return Name; - } + public override string ToString() => Name; } } diff --git a/Emby.Dlna/Common/StateVariable.cs b/Emby.Dlna/Common/StateVariable.cs index 6daf7ab6b2..fd733e0853 100644 --- a/Emby.Dlna/Common/StateVariable.cs +++ b/Emby.Dlna/Common/StateVariable.cs @@ -1,27 +1,34 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; namespace Emby.Dlna.Common { + /// + /// Defines the . + /// public class StateVariable { - public StateVariable() - { - AllowedValues = Array.Empty(); - } + /// + /// Gets or sets the name of the state variable. + /// + public string Name { get; set; } = string.Empty; - public string Name { get; set; } - - public string DataType { get; set; } + /// + /// Gets or sets the data type of the state variable. + /// + public string DataType { get; set; } = string.Empty; + /// + /// Gets or sets a value indicating whether it sends events. + /// public bool SendsEvents { get; set; } - public IReadOnlyList AllowedValues { get; set; } + /// + /// Gets or sets the allowed values range. + /// + public IReadOnlyList AllowedValues { get; set; } = Array.Empty(); /// - public override string ToString() - => Name; + public override string ToString() => Name; } } diff --git a/MediaBrowser.Model/Dlna/DeviceIdentification.cs b/MediaBrowser.Model/Dlna/DeviceIdentification.cs index 43407383a8..c511801f4f 100644 --- a/MediaBrowser.Model/Dlna/DeviceIdentification.cs +++ b/MediaBrowser.Model/Dlna/DeviceIdentification.cs @@ -11,59 +11,54 @@ namespace MediaBrowser.Model.Dlna /// Gets or sets the name of the friendly. /// /// The name of the friendly. - public string FriendlyName { get; set; } + public string FriendlyName { get; set; } = string.Empty; /// /// Gets or sets the model number. /// /// The model number. - public string ModelNumber { get; set; } + public string ModelNumber { get; set; } = string.Empty; /// /// Gets or sets the serial number. /// /// The serial number. - public string SerialNumber { get; set; } + public string SerialNumber { get; set; } = string.Empty; /// /// Gets or sets the name of the model. /// /// The name of the model. - public string ModelName { get; set; } + public string ModelName { get; set; } = string.Empty; /// /// Gets or sets the model description. /// /// The model description. - public string ModelDescription { get; set; } + public string ModelDescription { get; set; } = string.Empty; /// /// Gets or sets the model URL. /// /// The model URL. - public string ModelUrl { get; set; } + public string ModelUrl { get; set; } = string.Empty; /// /// Gets or sets the manufacturer. /// /// The manufacturer. - public string Manufacturer { get; set; } + public string Manufacturer { get; set; } = string.Empty; /// /// Gets or sets the manufacturer URL. /// /// The manufacturer URL. - public string ManufacturerUrl { get; set; } + public string ManufacturerUrl { get; set; } = string.Empty; /// /// Gets or sets the headers. /// /// The headers. - public HttpHeaderInfo[] Headers { get; set; } - - public DeviceIdentification() - { - Headers = Array.Empty(); - } + public HttpHeaderInfo[] Headers { get; set; } = Array.Empty(); } } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 7e921b1fdf..8b2a94fd45 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,6 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - +#pragma warning disable CA1819 // Properties should not return arrays using System; using System.Linq; using System.Xml.Serialization; @@ -8,107 +7,15 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna { + /// + /// Defines the . + /// [XmlRoot("Profile")] public class DeviceProfile { /// - /// Gets or sets the name. + /// Initializes a new instance of the class. /// - /// The name. - public string Name { get; set; } - - [XmlIgnore] - public string Id { get; set; } - - /// - /// Gets or sets the identification. - /// - /// The identification. - public DeviceIdentification Identification { get; set; } - - public string FriendlyName { get; set; } - - public string Manufacturer { get; set; } - - public string ManufacturerUrl { get; set; } - - public string ModelName { get; set; } - - public string ModelDescription { get; set; } - - public string ModelNumber { get; set; } - - public string ModelUrl { get; set; } - - public string SerialNumber { get; set; } - - public bool EnableAlbumArtInDidl { get; set; } - - public bool EnableSingleAlbumArtLimit { get; set; } - - public bool EnableSingleSubtitleLimit { get; set; } - - public string SupportedMediaTypes { get; set; } - - public string UserId { get; set; } - - public string AlbumArtPn { get; set; } - - public int MaxAlbumArtWidth { get; set; } - - public int MaxAlbumArtHeight { get; set; } - - public int? MaxIconWidth { get; set; } - - public int? MaxIconHeight { get; set; } - - public long? MaxStreamingBitrate { get; set; } - - public long? MaxStaticBitrate { get; set; } - - public int? MusicStreamingTranscodingBitrate { get; set; } - - public int? MaxStaticMusicBitrate { get; set; } - - /// - /// Controls the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace. - /// - public string SonyAggregationFlags { get; set; } - - public string ProtocolInfo { get; set; } - - public int TimelineOffsetSeconds { get; set; } - - public bool RequiresPlainVideoItems { get; set; } - - public bool RequiresPlainFolders { get; set; } - - public bool EnableMSMediaReceiverRegistrar { get; set; } - - public bool IgnoreTranscodeByteRangeRequests { get; set; } - - public XmlAttribute[] XmlRootAttributes { get; set; } - - /// - /// Gets or sets the direct play profiles. - /// - /// The direct play profiles. - public DirectPlayProfile[] DirectPlayProfiles { get; set; } - - /// - /// Gets or sets the transcoding profiles. - /// - /// The transcoding profiles. - public TranscodingProfile[] TranscodingProfiles { get; set; } - - public ContainerProfile[] ContainerProfiles { get; set; } - - public CodecProfile[] CodecProfiles { get; set; } - - public ResponseProfile[] ResponseProfiles { get; set; } - - public SubtitleProfile[] SubtitleProfiles { get; set; } - public DeviceProfile() { DirectPlayProfiles = Array.Empty(); @@ -126,11 +33,217 @@ namespace MediaBrowser.Model.Dlna MusicStreamingTranscodingBitrate = 128000; } + /// + /// Gets or sets the Name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the Id. + /// + [XmlIgnore] + public string Id { get; set; } + + /// + /// Gets or sets the Identification. + /// + public DeviceIdentification Identification { get; set; } + + /// + /// Gets or sets the FriendlyName. + /// + public string FriendlyName { get; set; } + + /// + /// Gets or sets the Manufacturer. + /// + public string Manufacturer { get; set; } + + /// + /// Gets or sets the ManufacturerUrl. + /// + public string ManufacturerUrl { get; set; } + + /// + /// Gets or sets the ModelName. + /// + public string ModelName { get; set; } + + /// + /// Gets or sets the ModelDescription. + /// + public string ModelDescription { get; set; } + + /// + /// Gets or sets the ModelNumber. + /// + public string ModelNumber { get; set; } + + /// + /// Gets or sets the ModelUrl. + /// + public string ModelUrl { get; set; } + + /// + /// Gets or sets the SerialNumber. + /// + public string SerialNumber { get; set; } + + /// + /// Gets or sets a value indicating whether EnableAlbumArtInDidl. + /// + public bool EnableAlbumArtInDidl { get; set; } + + /// + /// Gets or sets a value indicating whether EnableSingleAlbumArtLimit. + /// + public bool EnableSingleAlbumArtLimit { get; set; } + + /// + /// Gets or sets a value indicating whether EnableSingleSubtitleLimit. + /// + public bool EnableSingleSubtitleLimit { get; set; } + + /// + /// Gets or sets the SupportedMediaTypes. + /// + public string SupportedMediaTypes { get; set; } + + /// + /// Gets or sets the UserId. + /// + public string UserId { get; set; } + + /// + /// Gets or sets the AlbumArtPn. + /// + public string AlbumArtPn { get; set; } + + /// + /// Gets or sets the MaxAlbumArtWidth. + /// + public int MaxAlbumArtWidth { get; set; } + + /// + /// Gets or sets the MaxAlbumArtHeight. + /// + public int MaxAlbumArtHeight { get; set; } + + /// + /// Gets or sets the MaxIconWidth. + /// + public int? MaxIconWidth { get; set; } + + /// + /// Gets or sets the MaxIconHeight. + /// + public int? MaxIconHeight { get; set; } + + /// + /// Gets or sets the MaxStreamingBitrate. + /// + public long? MaxStreamingBitrate { get; set; } + + /// + /// Gets or sets the MaxStaticBitrate. + /// + public long? MaxStaticBitrate { get; set; } + + /// + /// Gets or sets the MusicStreamingTranscodingBitrate. + /// + public int? MusicStreamingTranscodingBitrate { get; set; } + + /// + /// Gets or sets the MaxStaticMusicBitrate. + /// + public int? MaxStaticMusicBitrate { get; set; } + + /// + /// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace. + /// + public string SonyAggregationFlags { get; set; } + + /// + /// Gets or sets the ProtocolInfo. + /// + public string ProtocolInfo { get; set; } + + /// + /// Gets or sets the TimelineOffsetSeconds. + /// + public int TimelineOffsetSeconds { get; set; } + + /// + /// Gets or sets a value indicating whether RequiresPlainVideoItems. + /// + public bool RequiresPlainVideoItems { get; set; } + + /// + /// Gets or sets a value indicating whether RequiresPlainFolders. + /// + public bool RequiresPlainFolders { get; set; } + + /// + /// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar. + /// + public bool EnableMSMediaReceiverRegistrar { get; set; } + + /// + /// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests. + /// + public bool IgnoreTranscodeByteRangeRequests { get; set; } + + /// + /// Gets or sets the XmlRootAttributes. + /// + public XmlAttribute[] XmlRootAttributes { get; set; } + + /// + /// Gets or sets the direct play profiles. + /// + public DirectPlayProfile[] DirectPlayProfiles { get; set; } + + /// + /// Gets or sets the transcoding profiles. + /// + public TranscodingProfile[] TranscodingProfiles { get; set; } + + /// + /// Gets or sets the ContainerProfiles. + /// + public ContainerProfile[] ContainerProfiles { get; set; } + + /// + /// Gets or sets the CodecProfiles. + /// + public CodecProfile[] CodecProfiles { get; set; } + + /// + /// Gets or sets the ResponseProfiles. + /// + public ResponseProfile[] ResponseProfiles { get; set; } + + /// + /// Gets or sets the SubtitleProfiles. + /// + public SubtitleProfile[] SubtitleProfiles { get; set; } + + /// + /// The GetSupportedMediaTypes. + /// + /// The . public string[] GetSupportedMediaTypes() { return ContainerProfile.SplitValue(SupportedMediaTypes); } + /// + /// Gets the audio transcoding profile. + /// + /// The container. + /// The audio Codec. + /// A . public TranscodingProfile GetAudioTranscodingProfile(string container, string audioCodec) { container = (container ?? string.Empty).TrimStart('.'); @@ -158,6 +271,13 @@ namespace MediaBrowser.Model.Dlna return null; } + /// + /// Gets the video transcoding profile. + /// + /// The container. + /// The audio Codec. + /// The video Codec. + /// The . public TranscodingProfile GetVideoTranscodingProfile(string container, string audioCodec, string videoCodec) { container = (container ?? string.Empty).TrimStart('.'); @@ -190,6 +310,16 @@ namespace MediaBrowser.Model.Dlna return null; } + /// + /// Gets the audio media profile. + /// + /// The container. + /// The audio codec. + /// The audio channels. + /// The audio bitrate. + /// The audio sample rate. + /// The audio bit depth. + /// The . public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth) { foreach (var i in ResponseProfiles) @@ -231,6 +361,11 @@ namespace MediaBrowser.Model.Dlna return null; } + /// + /// Gets the model profile condition. + /// + /// The c. + /// The . private ProfileCondition GetModelProfileCondition(ProfileCondition c) { return new ProfileCondition @@ -242,6 +377,13 @@ namespace MediaBrowser.Model.Dlna }; } + /// + /// Gets the image media profile. + /// + /// The container. + /// The width. + /// The height. + /// The . public ResponseProfile GetImageMediaProfile(string container, int? width, int? height) { foreach (var i in ResponseProfiles) @@ -277,7 +419,31 @@ namespace MediaBrowser.Model.Dlna return null; } - public ResponseProfile GetVideoMediaProfile(string container, + /// + /// Gets the video media profile. + /// + /// The container. + /// The audio codec. + /// The video codec. + /// The width. + /// The height. + /// The bit depth. + /// The video bitrate. + /// The video profile. + /// The video level. + /// The video framerate. + /// The packet length. + /// The timestamp. + /// True if anamorphic. + /// True if interlaced. + /// The ref frames. + /// The number of video streams. + /// The number of audio streams. + /// The video Codec tag. + /// True if Avc. + /// The . + public ResponseProfile GetVideoMediaProfile( + string container, string audioCodec, string videoCodec, int? width, diff --git a/MediaBrowser.Model/Dlna/XmlAttribute.cs b/MediaBrowser.Model/Dlna/XmlAttribute.cs index 3a8939a797..03bb2e4b11 100644 --- a/MediaBrowser.Model/Dlna/XmlAttribute.cs +++ b/MediaBrowser.Model/Dlna/XmlAttribute.cs @@ -5,11 +5,20 @@ using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna { + /// + /// Defines the . + /// public class XmlAttribute { + /// + /// Gets or sets the name of the attribute. + /// [XmlAttribute("name")] public string Name { get; set; } + /// + /// Gets or sets the value of the attribute. + /// [XmlAttribute("value")] public string Value { get; set; } } From c1b3f2c136201cc5d260270a427333ca79b004f2 Mon Sep 17 00:00:00 2001 From: Jim Cartlidge Date: Sun, 13 Sep 2020 14:31:12 +0100 Subject: [PATCH 02/61] ContentDirectory --- .../ContentDirectoryService.cs | 39 +- .../ContentDirectoryXmlBuilder.cs | 229 ++++---- Emby.Dlna/ContentDirectory/ControlHandler.cs | 541 +++++++++++++++--- Emby.Dlna/ContentDirectory/ServerItem.cs | 13 + .../ServiceActionListBuilder.cs | 51 +- Emby.Dlna/ContentDirectory/StubType.cs | 3 + 6 files changed, 668 insertions(+), 208 deletions(-) diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs index 5760f260cf..2f3107450c 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs @@ -19,6 +19,9 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.ContentDirectory { + /// + /// Defines the . + /// public class ContentDirectoryService : BaseService, IContentDirectory { private readonly ILibraryManager _libraryManager; @@ -33,6 +36,22 @@ namespace Emby.Dlna.ContentDirectory private readonly IMediaEncoder _mediaEncoder; private readonly ITVSeriesManager _tvSeriesManager; + /// + /// Initializes a new instance of the class. + /// + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. + /// The to use in the instance. public ContentDirectoryService( IDlnaManager dlna, IUserDataManager userDataManager, @@ -62,7 +81,10 @@ namespace Emby.Dlna.ContentDirectory _tvSeriesManager = tvSeriesManager; } - private int SystemUpdateId + /// + /// Gets the system id. (A unique id which changes on when our definition changes.) + /// + private static int SystemUpdateId { get { @@ -75,14 +97,18 @@ namespace Emby.Dlna.ContentDirectory /// public string GetServiceXml() { - return new ContentDirectoryXmlBuilder().GetXml(); + return ContentDirectoryXmlBuilder.GetXml(); } /// public Task ProcessControlRequestAsync(ControlRequest request) { - var profile = _dlna.GetProfile(request.Headers) ?? - _dlna.GetDefaultProfile(); + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile(); var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase)); @@ -107,6 +133,11 @@ namespace Emby.Dlna.ContentDirectory .ProcessControlRequestAsync(request); } + /// + /// Get the user stored in the device profile. + /// + /// The . + /// The . private User GetUser(DeviceProfile profile) { if (!string.IsNullOrEmpty(profile.UserId)) diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs index 743dcc5161..3edaabb70e 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs @@ -6,143 +6,154 @@ using Emby.Dlna.Service; namespace Emby.Dlna.ContentDirectory { - public class ContentDirectoryXmlBuilder + /// + /// Defines the . + /// + public static class ContentDirectoryXmlBuilder { - public string GetXml() + /// + /// Gets the ContentDirectory:1 service template. + /// See http://upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf. + /// + /// An XML description of this service. + public static string GetXml() { - return new ServiceXmlBuilder().GetXml( - new ServiceActionListBuilder().GetActions(), - GetStateVariables()); + return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); } + /// + /// Get the list of state variables for this invocation. + /// + /// The . private static IEnumerable GetStateVariables() { - var list = new List(); - - list.Add(new StateVariable + var list = new List { - Name = "A_ARG_TYPE_Filter", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_Filter", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_SortCriteria", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_SortCriteria", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_Index", - DataType = "ui4", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_Index", + DataType = "ui4", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_Count", - DataType = "ui4", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_Count", + DataType = "ui4", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_UpdateID", - DataType = "ui4", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_UpdateID", + DataType = "ui4", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "SearchCapabilities", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "SearchCapabilities", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "SortCapabilities", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "SortCapabilities", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "SystemUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "SystemUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_SearchCriteria", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_SearchCriteria", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_Result", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_Result", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_ObjectID", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_ObjectID", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_BrowseFlag", - DataType = "string", - SendsEvents = false, + new StateVariable + { + Name = "A_ARG_TYPE_BrowseFlag", + DataType = "string", + SendsEvents = false, - AllowedValues = new[] + AllowedValues = new[] { "BrowseMetadata", "BrowseDirectChildren" } - }); + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_BrowseLetter", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_BrowseLetter", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_CategoryType", - DataType = "ui4", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_CategoryType", + DataType = "ui4", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_RID", - DataType = "ui4", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_RID", + DataType = "ui4", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_PosSec", - DataType = "ui4", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_PosSec", + DataType = "ui4", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_Featurelist", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_Featurelist", + DataType = "string", + SendsEvents = false + } + }; return list; } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 4b108b89ea..31f8ca7720 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -1,6 +1,5 @@ -#pragma warning disable CS1591 - using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -8,6 +7,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; +using Emby.Dlna.Configuration; using Emby.Dlna.Didl; using Emby.Dlna.Service; using Jellyfin.Data.Entities; @@ -38,6 +38,9 @@ using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Dlna.ContentDirectory { + /// + /// Defines the . + /// public class ControlHandler : BaseControlHandler { private const string NsDc = "http://purl.org/dc/elements/1.1/"; @@ -58,6 +61,24 @@ namespace Emby.Dlna.ContentDirectory private readonly DeviceProfile _profile; + /// + /// Initializes a new instance of the class. + /// + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. + /// The server address to use in this instance> for use with the instance. + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. + /// The system id for use with the instance. + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. public ControlHandler( ILogger logger, ILibraryManager libraryManager, @@ -102,6 +123,16 @@ namespace Emby.Dlna.ContentDirectory /// protected override void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter) { + if (xmlWriter == null) + { + throw new ArgumentNullException(nameof(xmlWriter)); + } + + if (methodParams == null) + { + throw new ArgumentNullException(nameof(methodParams)); + } + const string DeviceId = "test"; if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase)) @@ -167,6 +198,10 @@ namespace Emby.Dlna.ContentDirectory throw new ResourceNotFoundException("Unexpected control request name: " + methodName); } + /// + /// Adds a "XSetBookmark" element to the xml document. + /// + /// The . private void HandleXSetBookmark(IDictionary sparams) { var id = sparams["ObjectID"]; @@ -189,41 +224,69 @@ namespace Emby.Dlna.ContentDirectory CancellationToken.None); } - private void HandleGetSearchCapabilities(XmlWriter xmlWriter) + /// + /// Adds the "SearchCaps" element to the xml document. + /// + /// The . + private static void HandleGetSearchCapabilities(XmlWriter xmlWriter) { xmlWriter.WriteElementString( "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords"); } - private void HandleGetSortCapabilities(XmlWriter xmlWriter) + /// + /// Adds the "SortCaps" element to the xml document. + /// + /// The . + private static void HandleGetSortCapabilities(XmlWriter xmlWriter) { xmlWriter.WriteElementString( "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating"); } - private void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter) + /// + /// Adds the "SortExtensionCaps" element to the xml document. + /// + /// The . + private static void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter) { xmlWriter.WriteElementString( "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating"); } + /// + /// Adds the "Id" element to the xml document. + /// + /// The . private void HandleGetSystemUpdateID(XmlWriter xmlWriter) { xmlWriter.WriteElementString("Id", _systemUpdateId.ToString(CultureInfo.InvariantCulture)); } - private void HandleGetFeatureList(XmlWriter xmlWriter) + /// + /// Adds the "FeatureList" element to the xml document. + /// + /// The . + private static void HandleGetFeatureList(XmlWriter xmlWriter) { xmlWriter.WriteElementString("FeatureList", WriteFeatureListXml()); } - private void HandleXGetFeatureList(XmlWriter xmlWriter) + /// + /// Adds the "FeatureList" element to the xml document. + /// + /// The . + private static void HandleXGetFeatureList(XmlWriter xmlWriter) => HandleGetFeatureList(xmlWriter); - private string WriteFeatureListXml() + /// + /// Builds a static feature list. + /// + /// The xml feature list. + private static string WriteFeatureListXml() { // TODO: clean this up var builder = new StringBuilder(); @@ -242,9 +305,16 @@ namespace Emby.Dlna.ContentDirectory return builder.ToString(); } - public string GetValueOrDefault(IDictionary sparams, string key, string defaultValue) + /// + /// Returns the value in the key of the dictionary, or defaultValue if it doesn't exist. + /// + /// The . + /// The key. + /// The defaultValue. + /// The . + public static string GetValueOrDefault(IDictionary sparams, string key, string defaultValue) { - if (sparams.TryGetValue(key, out string val)) + if (sparams != null && sparams.TryGetValue(key, out string val)) { return val; } @@ -252,6 +322,12 @@ namespace Emby.Dlna.ContentDirectory return defaultValue; } + /// + /// Builds the "Browse" xml response. + /// + /// The . + /// The . + /// The device Id to use. private void HandleBrowse(XmlWriter xmlWriter, IDictionary sparams, string deviceId) { var id = sparams["ObjectID"]; @@ -313,7 +389,6 @@ namespace Emby.Dlna.ContentDirectory } else { - var dlnaOptions = _config.GetDlnaConfiguration(); _didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter); } @@ -326,7 +401,6 @@ namespace Emby.Dlna.ContentDirectory provided = childrenResult.Items.Count; - var dlnaOptions = _config.GetDlnaConfiguration(); foreach (var i in childrenResult.Items) { var childItem = i.Item; @@ -357,12 +431,24 @@ namespace Emby.Dlna.ContentDirectory xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture)); } + /// + /// Builds the response to the "X_BrowseByLetter request. + /// + /// The . + /// The . + /// The device id. private void HandleXBrowseByLetter(XmlWriter xmlWriter, IDictionary sparams, string deviceId) { // TODO: Implement this method HandleSearch(xmlWriter, sparams, deviceId); } + /// + /// Builds a response to the "Search" request. + /// + /// The xmlWriter. + /// The sparams. + /// The deviceId. private void HandleSearch(XmlWriter xmlWriter, IDictionary sparams, string deviceId) { var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", string.Empty)); @@ -442,7 +528,17 @@ namespace Emby.Dlna.ContentDirectory xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture)); } - private QueryResult GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) + /// + /// Returns the child items meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . + private static QueryResult GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) { var folder = (Folder)item; @@ -494,11 +590,25 @@ namespace Emby.Dlna.ContentDirectory }); } - private DtoOptions GetDtoOptions() + /// + /// Returns a new DtoOptions object. + /// + /// The . + private static DtoOptions GetDtoOptions() { return new DtoOptions(true); } + /// + /// Returns the User items meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit) { if (item is MusicGenre) @@ -568,6 +678,14 @@ namespace Emby.Dlna.ContentDirectory return ToResult(queryResult); } + /// + /// Returns the Live Tv Channels meeting the criteria. + /// + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) @@ -584,6 +702,16 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the music folders meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetMusicFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) @@ -643,57 +771,58 @@ namespace Emby.Dlna.ContentDirectory return GetMusicGenres(item, user, query); } - var list = new List(); - - list.Add(new ServerItem(item) + var list = new List { - StubType = StubType.Latest - }); + new ServerItem(item) + { + StubType = StubType.Latest + }, - list.Add(new ServerItem(item) - { - StubType = StubType.Playlists - }); + new ServerItem(item) + { + StubType = StubType.Playlists + }, - list.Add(new ServerItem(item) - { - StubType = StubType.Albums - }); + new ServerItem(item) + { + StubType = StubType.Albums + }, - list.Add(new ServerItem(item) - { - StubType = StubType.AlbumArtists - }); + new ServerItem(item) + { + StubType = StubType.AlbumArtists + }, - list.Add(new ServerItem(item) - { - StubType = StubType.Artists - }); + new ServerItem(item) + { + StubType = StubType.Artists + }, - list.Add(new ServerItem(item) - { - StubType = StubType.Songs - }); + new ServerItem(item) + { + StubType = StubType.Songs + }, - list.Add(new ServerItem(item) - { - StubType = StubType.Genres - }); + new ServerItem(item) + { + StubType = StubType.Genres + }, - list.Add(new ServerItem(item) - { - StubType = StubType.FavoriteArtists - }); + new ServerItem(item) + { + StubType = StubType.FavoriteArtists + }, - list.Add(new ServerItem(item) - { - StubType = StubType.FavoriteAlbums - }); + new ServerItem(item) + { + StubType = StubType.FavoriteAlbums + }, - list.Add(new ServerItem(item) - { - StubType = StubType.FavoriteSongs - }); + new ServerItem(item) + { + StubType = StubType.FavoriteSongs + } + }; return new QueryResult { @@ -702,6 +831,16 @@ namespace Emby.Dlna.ContentDirectory }; } + /// + /// Returns the movie folders meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetMovieFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) @@ -776,6 +915,13 @@ namespace Emby.Dlna.ContentDirectory }; } + /// + /// Returns the folders meeting the criteria. + /// + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetFolders(User user, int? startIndex, int? limit) { var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true) @@ -796,6 +942,16 @@ namespace Emby.Dlna.ContentDirectory limit); } + /// + /// Returns the TV folders meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) @@ -840,42 +996,43 @@ namespace Emby.Dlna.ContentDirectory return GetGenres(item, user, query); } - var list = new List(); - - list.Add(new ServerItem(item) + var list = new List { - StubType = StubType.ContinueWatching - }); + new ServerItem(item) + { + StubType = StubType.ContinueWatching + }, - list.Add(new ServerItem(item) - { - StubType = StubType.NextUp - }); + new ServerItem(item) + { + StubType = StubType.NextUp + }, - list.Add(new ServerItem(item) - { - StubType = StubType.Latest - }); + new ServerItem(item) + { + StubType = StubType.Latest + }, - list.Add(new ServerItem(item) - { - StubType = StubType.Series - }); + new ServerItem(item) + { + StubType = StubType.Series + }, - list.Add(new ServerItem(item) - { - StubType = StubType.FavoriteSeries - }); + new ServerItem(item) + { + StubType = StubType.FavoriteSeries + }, - list.Add(new ServerItem(item) - { - StubType = StubType.FavoriteEpisodes - }); + new ServerItem(item) + { + StubType = StubType.FavoriteEpisodes + }, - list.Add(new ServerItem(item) - { - StubType = StubType.Genres - }); + new ServerItem(item) + { + StubType = StubType.Genres + } + }; return new QueryResult { @@ -884,6 +1041,13 @@ namespace Emby.Dlna.ContentDirectory }; } + /// + /// Returns the Movies that are part watched that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMovieContinueWatching(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -904,6 +1068,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the series meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetSeries(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -917,6 +1088,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the Movie folders meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMovieMovies(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -930,6 +1108,12 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the Movie collections meeting the criteria. + /// + /// The see cref="User"/>. + /// The see cref="InternalItemsQuery"/>. + /// The . private QueryResult GetMovieCollections(User user, InternalItemsQuery query) { query.Recursive = true; @@ -943,6 +1127,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the Music albums meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMusicAlbums(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -956,6 +1147,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the Music songs meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMusicSongs(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -969,6 +1167,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the songs tagged as favourite that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetFavoriteSongs(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -982,6 +1187,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the series tagged as favourite that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetFavoriteSeries(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -995,6 +1207,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the episodes tagged as favourite that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetFavoriteEpisodes(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -1008,6 +1227,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the movies tagged as favourite that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMovieFavorites(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -1021,6 +1247,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// /// Returns the albums tagged as favourite that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetFavoriteAlbums(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; @@ -1034,6 +1267,14 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the genres meeting the criteria. + /// The GetGenres. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetGenres(BaseItem parent, User user, InternalItemsQuery query) { var genresResult = _libraryManager.GetGenres(new InternalItemsQuery(user) @@ -1052,6 +1293,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the music genres meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMusicGenres(BaseItem parent, User user, InternalItemsQuery query) { var genresResult = _libraryManager.GetMusicGenres(new InternalItemsQuery(user) @@ -1070,6 +1318,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the music albums by artist that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMusicAlbumArtists(BaseItem parent, User user, InternalItemsQuery query) { var artists = _libraryManager.GetAlbumArtists(new InternalItemsQuery(user) @@ -1088,6 +1343,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the music artists meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMusicArtists(BaseItem parent, User user, InternalItemsQuery query) { var artists = _libraryManager.GetArtists(new InternalItemsQuery(user) @@ -1106,6 +1368,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the artists tagged as favourite that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetFavoriteArtists(BaseItem parent, User user, InternalItemsQuery query) { var artists = _libraryManager.GetArtists(new InternalItemsQuery(user) @@ -1125,6 +1394,12 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the music playlists meeting the criteria. + /// + /// The user. + /// The query. + /// The . private QueryResult GetMusicPlaylists(User user, InternalItemsQuery query) { query.Parent = null; @@ -1137,6 +1412,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the latest music meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1155,6 +1437,12 @@ namespace Emby.Dlna.ContentDirectory return ToResult(items); } + /// + /// Returns the next up item meeting the criteria. + /// + /// The . + /// The . + /// The . private QueryResult GetNextUp(BaseItem parent, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1172,6 +1460,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the latest tv meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetTvLatest(BaseItem parent, User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1190,6 +1485,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(items); } + /// + /// Returns the latest movies meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . private QueryResult GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1208,6 +1510,16 @@ namespace Emby.Dlna.ContentDirectory return ToResult(items); } + /// + /// Returns music artist items that meet the criteria. + /// + /// The . + /// The . + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetMusicArtistItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) @@ -1228,6 +1540,16 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the genre items meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) @@ -1252,6 +1574,16 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } + /// + /// Returns the music genre items meeting the criteria. + /// + /// The . + /// The . + /// The . + /// The . + /// The start index. + /// The maximum number to return. + /// The . private QueryResult GetMusicGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) @@ -1272,7 +1604,12 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult ToResult(BaseItem[] result) + /// + /// Converts a array into a . + /// + /// An array of . + /// A . + private static QueryResult ToResult(BaseItem[] result) { var serverItems = result .Select(i => new ServerItem(i)) @@ -1285,7 +1622,12 @@ namespace Emby.Dlna.ContentDirectory }; } - private QueryResult ToResult(QueryResult result) + /// + /// Converts a to a . + /// + /// A . + /// The . + private static QueryResult ToResult(QueryResult result) { var serverItems = result .Items @@ -1299,7 +1641,13 @@ namespace Emby.Dlna.ContentDirectory }; } - private void SetSorting(InternalItemsQuery query, SortCriteria sort, bool isPreSorted) + /// + /// Sets the sorting method on a query. + /// + /// The . + /// The . + /// True if pre-sorted. + private static void SetSorting(InternalItemsQuery query, SortCriteria sort, bool isPreSorted) { if (isPreSorted) { @@ -1311,13 +1659,25 @@ namespace Emby.Dlna.ContentDirectory } } - private QueryResult ApplyPaging(QueryResult result, int? startIndex, int? limit) + /// + /// Apply paging to a query. + /// + /// The . + /// The start index. + /// The maximum number to return. + /// A . + private static QueryResult ApplyPaging(QueryResult result, int? startIndex, int? limit) { result.Items = result.Items.Skip(startIndex ?? 0).Take(limit ?? int.MaxValue).ToArray(); return result; } + /// + /// Retreives the ServerItem id. + /// + /// The id. + /// The . private ServerItem GetItemFromObjectId(string id) { return DidlBuilder.IsIdRoot(id) @@ -1326,6 +1686,11 @@ namespace Emby.Dlna.ContentDirectory : ParseItemId(id); } + /// + /// Parses the item id into a . + /// + /// The . + /// The corresponding . private ServerItem ParseItemId(string id) { StubType? stubType = null; diff --git a/Emby.Dlna/ContentDirectory/ServerItem.cs b/Emby.Dlna/ContentDirectory/ServerItem.cs index e406054149..34244000c1 100644 --- a/Emby.Dlna/ContentDirectory/ServerItem.cs +++ b/Emby.Dlna/ContentDirectory/ServerItem.cs @@ -4,8 +4,15 @@ using MediaBrowser.Controller.Entities; namespace Emby.Dlna.ContentDirectory { + /// + /// Defines the . + /// internal class ServerItem { + /// + /// Initializes a new instance of the class. + /// + /// The . public ServerItem(BaseItem item) { Item = item; @@ -16,8 +23,14 @@ namespace Emby.Dlna.ContentDirectory } } + /// + /// Gets or sets the underlying base item. + /// public BaseItem Item { get; set; } + /// + /// Gets or sets the DLNA item type. + /// public StubType? StubType { get; set; } } } diff --git a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs index 921b14e394..7e3db46519 100644 --- a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs +++ b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs @@ -1,13 +1,18 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Emby.Dlna.Common; namespace Emby.Dlna.ContentDirectory { - public class ServiceActionListBuilder + /// + /// Defines the . + /// + public static class ServiceActionListBuilder { - public IEnumerable GetActions() + /// + /// Returns a list of services that this instance provides. + /// + /// An . + public static IEnumerable GetActions() { return new[] { @@ -22,6 +27,10 @@ namespace Emby.Dlna.ContentDirectory }; } + /// + /// Returns the action details for "GetSystemUpdateID". + /// + /// The . private static ServiceAction GetGetSystemUpdateIDAction() { var action = new ServiceAction @@ -39,6 +48,10 @@ namespace Emby.Dlna.ContentDirectory return action; } + /// + /// Returns the action details for "GetSearchCapabilities". + /// + /// The . private static ServiceAction GetSearchCapabilitiesAction() { var action = new ServiceAction @@ -56,6 +69,10 @@ namespace Emby.Dlna.ContentDirectory return action; } + /// + /// Returns the action details for "GetSortCapabilities". + /// + /// The . private static ServiceAction GetSortCapabilitiesAction() { var action = new ServiceAction @@ -73,6 +90,10 @@ namespace Emby.Dlna.ContentDirectory return action; } + /// + /// Returns the action details for "X_GetFeatureList". + /// + /// The . private static ServiceAction GetX_GetFeatureListAction() { var action = new ServiceAction @@ -90,6 +111,10 @@ namespace Emby.Dlna.ContentDirectory return action; } + /// + /// Returns the action details for "Search". + /// + /// The . private static ServiceAction GetSearchAction() { var action = new ServiceAction @@ -170,7 +195,11 @@ namespace Emby.Dlna.ContentDirectory return action; } - private ServiceAction GetBrowseAction() + /// + /// Returns the action details for "Browse". + /// + /// The . + private static ServiceAction GetBrowseAction() { var action = new ServiceAction { @@ -250,7 +279,11 @@ namespace Emby.Dlna.ContentDirectory return action; } - private ServiceAction GetBrowseByLetterAction() + /// + /// Returns the action details for "X_BrowseByLetter". + /// + /// The . + private static ServiceAction GetBrowseByLetterAction() { var action = new ServiceAction { @@ -337,7 +370,11 @@ namespace Emby.Dlna.ContentDirectory return action; } - private ServiceAction GetXSetBookmarkAction() + /// + /// Returns the action details for "X_SetBookmark". + /// + /// The . + private static ServiceAction GetXSetBookmarkAction() { var action = new ServiceAction { diff --git a/Emby.Dlna/ContentDirectory/StubType.cs b/Emby.Dlna/ContentDirectory/StubType.cs index eee405d3e7..982ae5d68e 100644 --- a/Emby.Dlna/ContentDirectory/StubType.cs +++ b/Emby.Dlna/ContentDirectory/StubType.cs @@ -3,6 +3,9 @@ namespace Emby.Dlna.ContentDirectory { + /// + /// Defines the DLNA item types. + /// public enum StubType { Folder = 0, From 28ee4f0a7f7b7be954ac2dccc374bf72b1eb4f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Senart?= Date: Thu, 22 Oct 2020 11:09:59 +0200 Subject: [PATCH 03/61] [AudioTranscoding] Add FLAC as supported target audio format and be able to define the corresponding target sample rate --- .../MediaEncoding/EncodingJobInfo.cs | 5 +++- .../Encoder/EncoderValidator.cs | 2 ++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 26 +++++++++++++++++++ MediaBrowser.Model/Dlna/StreamInfo.cs | 9 +++++-- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 6cd0c70d2f..e99c48a704 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -404,7 +404,10 @@ namespace MediaBrowser.Controller.MediaEncoding { // Don't exceed what the encoder supports // Seeing issues of attempting to encode to 88200 - return Math.Min(44100, BaseRequest.AudioSampleRate.Value); + // return Math.Min(44100, BaseRequest.AudioSampleRate.Value); + + // I don't see any reason why limiting the sample rate to a maximum of 44100 ! + return BaseRequest.AudioSampleRate.Value; } return null; diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 3287f9814e..92f16ab95c 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "ac3", "aac", "mp3", + "flac", "h264_qsv", "hevc_qsv", "mpeg2_qsv", @@ -71,6 +72,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "libmp3lame", "libopus", "libvorbis", + "flac", "srt", "h264_amf", "hevc_amf", diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 4959a9b922..cf392ffcec 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1438,6 +1438,32 @@ namespace MediaBrowser.Model.Dlna break; } + case ProfileConditionValue.AudioSampleRate: + { + if (!enableNonQualifiedConditions) + { + continue; + } + + if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + { + if (condition.Condition == ProfileConditionType.Equals) + { + item.AudioSampleRate = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.AudioSampleRate = Math.Min(num, item.AudioSampleRate ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.AudioSampleRate = Math.Max(num, item.AudioSampleRate ?? num); + } + } + + break; + } + case ProfileConditionValue.AudioChannels: { if (string.IsNullOrEmpty(qualifier)) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 9399d21f16..20ca547735 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -110,6 +110,8 @@ namespace MediaBrowser.Model.Dlna public int? AudioBitrate { get; set; } + public int? AudioSampleRate { get; set; } + public int? VideoBitrate { get; set; } public int? MaxWidth { get; set; } @@ -184,7 +186,7 @@ namespace MediaBrowser.Model.Dlna } if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) && - string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase)) + string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase)) { continue; } @@ -250,6 +252,7 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); @@ -521,7 +524,9 @@ namespace MediaBrowser.Model.Dlna get { var stream = TargetAudioStream; - return stream == null ? null : stream.SampleRate; + return AudioSampleRate.HasValue && !IsDirectStream + ? AudioSampleRate + : stream == null ? null : stream.SampleRate; } } From 5979151f118ef48a966368530b5db68f2c72991a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Senart?= Date: Thu, 22 Oct 2020 12:22:31 +0200 Subject: [PATCH 04/61] [AudioTranscoding] Add FLAC as supported target audio format and be able to define the corresponding target sample rate --- Emby.Dlna/PlayTo/PlayToController.cs | 5 ++++- MediaBrowser.Model/Dlna/StreamInfo.cs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index a5b8e2b3ce..2aebc0f1db 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -945,7 +945,10 @@ namespace Emby.Dlna.PlayTo request.DeviceId = values.GetValueOrDefault("DeviceId"); request.MediaSourceId = values.GetValueOrDefault("MediaSourceId"); request.LiveStreamId = values.GetValueOrDefault("LiveStreamId"); - request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); + + // Be careful, IsDirectStream==true by default (Static != false or not in query). + // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true. + request.IsDirectStream = !string.Equals("false", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 20ca547735..93ea82c1c5 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -185,6 +185,8 @@ namespace MediaBrowser.Model.Dlna continue; } + // Be careful, IsDirectStream==true by default (Static != false or not in query). + // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true. if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) && string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase)) { From 95ecedbce7281e215b7c3c7403e648b0f66622e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Senart?= Date: Thu, 22 Oct 2020 12:49:26 +0200 Subject: [PATCH 05/61] [AudioTranscoding] Add FLAC as supported target audio format and be able to define the corresponding target sample rate --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7b4772730a..a97a4c7416 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -104,6 +104,7 @@ - [sorinyo2004](https://github.com/sorinyo2004) - [sparky8251](https://github.com/sparky8251) - [spookbits](https://github.com/spookbits) + - [ssenart] (https://github.com/ssenart) - [stanionascu](https://github.com/stanionascu) - [stevehayles](https://github.com/stevehayles) - [SuperSandro2000](https://github.com/SuperSandro2000) From 50558ffe3d5259b65fd726a629f3c8c11daec8e7 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 27 Oct 2020 17:57:52 -0600 Subject: [PATCH 06/61] Add BaseItemManager --- .../BaseItemManager/BaseItemManager.cs | 86 +++++++++++++++++++ .../BaseItemManager/IBaseItemManager.cs | 29 +++++++ 2 files changed, 115 insertions(+) create mode 100644 MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs create mode 100644 MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs new file mode 100644 index 0000000000..967c6fa154 --- /dev/null +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -0,0 +1,86 @@ +using System; +using System.Linq; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Controller.BaseItemManager +{ + /// + public class BaseItemManager : IBaseItemManager + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public BaseItemManager(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// + public bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name) + { + if (baseItem is Channel) + { + // hack alert + return true; + } + + if (baseItem.SourceType == SourceType.Channel) + { + // hack alert + return !baseItem.EnableMediaSourceDisplay; + } + + var typeOptions = libraryOptions.GetTypeOptions(GetType().Name); + if (typeOptions != null) + { + return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); + } + + if (!libraryOptions.EnableInternetProviders) + { + return false; + } + + var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase)); + + return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); + } + + /// + public bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name) + { + if (baseItem is Channel) + { + // hack alert + return true; + } + + if (baseItem.SourceType == SourceType.Channel) + { + // hack alert + return !baseItem.EnableMediaSourceDisplay; + } + + var typeOptions = libraryOptions.GetTypeOptions(GetType().Name); + if (typeOptions != null) + { + return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); + } + + if (!libraryOptions.EnableInternetProviders) + { + return false; + } + + var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase)); + + return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs new file mode 100644 index 0000000000..ee4d3dcdcc --- /dev/null +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -0,0 +1,29 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Controller.BaseItemManager +{ + /// + /// The BaseItem manager. + /// + public interface IBaseItemManager + { + /// + /// Is metadata fetcher enabled. + /// + /// The base item. + /// The library options. + /// The metadata fetcher name. + /// true if metadata fetcher is enabled, else false. + bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name); + + /// + /// Is image fetcher enabled. + /// + /// The base item. + /// The library options. + /// The image fetcher name. + /// true if image fetcher is enabled, else false. + bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name); + } +} \ No newline at end of file From 64b32d329023cdb4dc27f167115f399c376d084a Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 27 Oct 2020 18:01:52 -0600 Subject: [PATCH 07/61] Use BaseItemManager --- Jellyfin.Server/CoreAppHost.cs | 2 + MediaBrowser.Controller/Entities/BaseItem.cs | 54 ------------------- .../Manager/ProviderManager.cs | 11 ++-- 3 files changed, 10 insertions(+), 57 deletions(-) diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index c44736447f..cb8ae91f56 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -13,6 +13,7 @@ using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Common.Net; using MediaBrowser.Controller; +using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; @@ -76,6 +77,7 @@ namespace Jellyfin.Server options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")); ServiceCollection.AddEventServices(); + ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 2fc7d45c90..1d141c0e93 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -459,60 +459,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public string PrimaryImagePath => this.GetImagePath(ImageType.Primary); - public bool IsMetadataFetcherEnabled(LibraryOptions libraryOptions, string name) - { - if (SourceType == SourceType.Channel) - { - // hack alert - return !EnableMediaSourceDisplay; - } - - var typeOptions = libraryOptions.GetTypeOptions(GetType().Name); - if (typeOptions != null) - { - return typeOptions.MetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); - } - - if (!libraryOptions.EnableInternetProviders) - { - return false; - } - - var itemConfig = ConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase)); - - return itemConfig == null || !itemConfig.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); - } - - public bool IsImageFetcherEnabled(LibraryOptions libraryOptions, string name) - { - if (this is Channel) - { - // hack alert - return true; - } - - if (SourceType == SourceType.Channel) - { - // hack alert - return !EnableMediaSourceDisplay; - } - - var typeOptions = libraryOptions.GetTypeOptions(GetType().Name); - if (typeOptions != null) - { - return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); - } - - if (!libraryOptions.EnableInternetProviders) - { - return false; - } - - var itemConfig = ConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase)); - - return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); - } - public virtual bool CanDelete() { if (SourceType == SourceType.Channel) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index a0c7d4ad09..5cff4761c2 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -13,6 +13,7 @@ using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; +using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -51,6 +52,7 @@ namespace MediaBrowser.Providers.Manager private readonly ILibraryManager _libraryManager; private readonly ISubtitleManager _subtitleManager; private readonly IServerConfigurationManager _configurationManager; + private readonly IBaseItemManager _baseItemManager; private readonly ConcurrentDictionary _activeRefreshes = new ConcurrentDictionary(); private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private readonly SimplePriorityQueue> _refreshQueue = @@ -74,6 +76,7 @@ namespace MediaBrowser.Providers.Manager /// The filesystem. /// The server application paths. /// The library manager. + /// The BaseItem manager. public ProviderManager( IHttpClientFactory httpClientFactory, ISubtitleManager subtitleManager, @@ -82,7 +85,8 @@ namespace MediaBrowser.Providers.Manager ILogger logger, IFileSystem fileSystem, IServerApplicationPaths appPaths, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + IBaseItemManager baseItemManager) { _logger = logger; _httpClientFactory = httpClientFactory; @@ -92,6 +96,7 @@ namespace MediaBrowser.Providers.Manager _appPaths = appPaths; _libraryManager = libraryManager; _subtitleManager = subtitleManager; + _baseItemManager = baseItemManager; } /// @@ -398,7 +403,7 @@ namespace MediaBrowser.Providers.Manager if (provider is IRemoteMetadataProvider) { - if (!forceEnableInternetMetadata && !item.IsMetadataFetcherEnabled(libraryOptions, provider.Name)) + if (!forceEnableInternetMetadata && !_baseItemManager.IsMetadataFetcherEnabled(item, libraryOptions, provider.Name)) { return false; } @@ -442,7 +447,7 @@ namespace MediaBrowser.Providers.Manager if (provider is IRemoteImageProvider || provider is IDynamicImageProvider) { - if (!item.IsImageFetcherEnabled(libraryOptions, provider.Name)) + if (!_baseItemManager.IsImageFetcherEnabled(item, libraryOptions, provider.Name)) { return false; } From 59619b6ea74ab555977fd213f6ee5737897b0fbd Mon Sep 17 00:00:00 2001 From: Stepan Date: Sun, 1 Nov 2020 10:47:31 +0100 Subject: [PATCH 08/61] Enable nullable in Emby.Naming --- Emby.Naming/AudioBook/AudioBookFileInfo.cs | 19 +- Emby.Naming/AudioBook/AudioBookInfo.cs | 4 +- .../AudioBook/AudioBookListResolver.cs | 8 +- Emby.Naming/AudioBook/AudioBookResolver.cs | 14 +- Emby.Naming/Common/EpisodeExpression.cs | 4 +- Emby.Naming/Common/NamingOptions.cs | 515 ++++++++---------- Emby.Naming/Emby.Naming.csproj | 1 + Emby.Naming/Subtitles/SubtitleInfo.cs | 9 +- Emby.Naming/Subtitles/SubtitleParser.cs | 10 +- Emby.Naming/TV/EpisodeInfo.cs | 13 +- Emby.Naming/TV/EpisodePathParserResult.cs | 2 +- Emby.Naming/TV/EpisodeResolver.cs | 3 +- Emby.Naming/Video/ExtraResult.cs | 2 +- Emby.Naming/Video/ExtraRule.cs | 8 + Emby.Naming/Video/FileStack.cs | 2 +- Emby.Naming/Video/Format3DParser.cs | 2 +- Emby.Naming/Video/Format3DResult.cs | 2 +- Emby.Naming/Video/Format3DRule.cs | 8 +- Emby.Naming/Video/StubTypeRule.cs | 6 + Emby.Naming/Video/VideoFileInfo.cs | 12 +- Emby.Naming/Video/VideoInfo.cs | 4 +- Emby.Naming/Video/VideoListResolver.cs | 11 +- Emby.Naming/Video/VideoResolver.cs | 6 +- .../Library/LibraryManager.cs | 5 +- .../MediaBrowser.Common.csproj | 4 +- .../AudioBook/AudioBookFileInfoTests.cs | 10 +- .../AudioBook/AudioBookResolverTests.cs | 32 +- 27 files changed, 349 insertions(+), 367 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs index c4863b50ab..e5200416ee 100644 --- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs @@ -7,6 +7,23 @@ namespace Emby.Naming.AudioBook /// public class AudioBookFileInfo : IComparable { + /// + /// Initializes a new instance of the class. + /// + /// Path to audiobook file. + /// File type. + /// Number of part this file represents. + /// Number of chapter this file represents. + /// Indication if we are looking at file or directory. + public AudioBookFileInfo(string path, string container, int? partNumber = default, int? chapterNumber = default, bool isDirectory = default) + { + Path = path; + Container = container; + PartNumber = partNumber; + ChapterNumber = chapterNumber; + IsDirectory = isDirectory; + } + /// /// Gets or sets the path. /// @@ -38,7 +55,7 @@ namespace Emby.Naming.AudioBook public bool IsDirectory { get; set; } /// - public int CompareTo(AudioBookFileInfo other) + public int CompareTo(AudioBookFileInfo? other) { if (ReferenceEquals(this, other)) { diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs index b0b5bd881f..fba11ea726 100644 --- a/Emby.Naming/AudioBook/AudioBookInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookInfo.cs @@ -10,11 +10,13 @@ namespace Emby.Naming.AudioBook /// /// Initializes a new instance of the class. /// - public AudioBookInfo() + /// Name of audiobook. + public AudioBookInfo(string name) { Files = new List(); Extras = new List(); AlternateVersions = new List(); + Name = name; } /// diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index f4ba11a0d1..f350e5a4ae 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System; using System.Collections.Generic; using System.Linq; using Emby.Naming.Common; @@ -23,7 +24,7 @@ namespace Emby.Naming.AudioBook var audiobookFileInfos = files .Select(i => audioBookResolver.Resolve(i.FullName, i.IsDirectory)) - .Where(i => i != null) + .OfType() .ToList(); // Filter out all extras, otherwise they could cause stacks to not be resolved @@ -36,9 +37,10 @@ namespace Emby.Naming.AudioBook foreach (var stack in stackResult) { - var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).ToList(); + var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).OfType().ToList(); stackFiles.Sort(); - var info = new AudioBookInfo { Files = stackFiles, Name = stack.Name }; + // TODO nullable discover if name can be empty + var info = new AudioBookInfo(stack.Name ?? string.Empty) { Files = stackFiles }; yield return info; } diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index 5807d4688c..e76cfd744d 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -42,14 +42,12 @@ namespace Emby.Naming.AudioBook var parsingResult = new AudioBookFilePathParser(_options).Parse(path); - return new AudioBookFileInfo - { - Path = path, - Container = container, - ChapterNumber = parsingResult.ChapterNumber, - PartNumber = parsingResult.PartNumber, - IsDirectory = isDirectory - }; + return new AudioBookFileInfo( + path, + container, + chapterNumber: parsingResult.ChapterNumber, + partNumber: parsingResult.PartNumber, + isDirectory: isDirectory ); } } } diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs index ed6ba8881c..00b27541a6 100644 --- a/Emby.Naming/Common/EpisodeExpression.cs +++ b/Emby.Naming/Common/EpisodeExpression.cs @@ -8,11 +8,11 @@ namespace Emby.Naming.Common public class EpisodeExpression { private string _expression; - private Regex _regex; + private Regex? _regex; public EpisodeExpression(string expression, bool byDate) { - Expression = expression; + _expression = expression; IsByDate = byDate; DateTimeFormats = Array.Empty(); SupportsAbsoluteEpisodeNumbers = true; diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index fd4244f64d..78bb6242d6 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -75,56 +75,45 @@ namespace Emby.Naming.Common StubTypes = new[] { - new StubTypeRule - { - StubType = "dvd", - Token = "dvd" - }, - new StubTypeRule - { - StubType = "hddvd", - Token = "hddvd" - }, - new StubTypeRule - { - StubType = "bluray", - Token = "bluray" - }, - new StubTypeRule - { - StubType = "bluray", - Token = "brrip" - }, - new StubTypeRule - { - StubType = "bluray", - Token = "bd25" - }, - new StubTypeRule - { - StubType = "bluray", - Token = "bd50" - }, - new StubTypeRule - { - StubType = "vhs", - Token = "vhs" - }, - new StubTypeRule - { - StubType = "tv", - Token = "HDTV" - }, - new StubTypeRule - { - StubType = "tv", - Token = "PDTV" - }, - new StubTypeRule - { - StubType = "tv", - Token = "DSR" - } + new StubTypeRule( + stubType: "dvd", + token: "dvd"), + + new StubTypeRule( + stubType: "hddvd", + token: "hddvd"), + + new StubTypeRule( + stubType: "bluray", + token: "bluray"), + + new StubTypeRule( + stubType: "bluray", + token: "brrip"), + + new StubTypeRule( + stubType: "bluray", + token: "bd25"), + + new StubTypeRule( + stubType: "bluray", + token: "bd50"), + + new StubTypeRule( + stubType: "vhs", + token: "vhs"), + + new StubTypeRule( + stubType: "tv", + token: "HDTV"), + + new StubTypeRule( + stubType: "tv", + token: "PDTV"), + + new StubTypeRule( + stubType: "tv", + token: "DSR") }; VideoFileStackingExpressions = new[] @@ -381,247 +370,193 @@ namespace Emby.Naming.Common VideoExtraRules = new[] { - new ExtraRule - { - ExtraType = ExtraType.Trailer, - RuleType = ExtraRuleType.Filename, - Token = "trailer", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Trailer, - RuleType = ExtraRuleType.Suffix, - Token = "-trailer", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Trailer, - RuleType = ExtraRuleType.Suffix, - Token = ".trailer", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Trailer, - RuleType = ExtraRuleType.Suffix, - Token = "_trailer", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Trailer, - RuleType = ExtraRuleType.Suffix, - Token = " trailer", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Sample, - RuleType = ExtraRuleType.Filename, - Token = "sample", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Sample, - RuleType = ExtraRuleType.Suffix, - Token = "-sample", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Sample, - RuleType = ExtraRuleType.Suffix, - Token = ".sample", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Sample, - RuleType = ExtraRuleType.Suffix, - Token = "_sample", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Sample, - RuleType = ExtraRuleType.Suffix, - Token = " sample", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.ThemeSong, - RuleType = ExtraRuleType.Filename, - Token = "theme", - MediaType = MediaType.Audio - }, - new ExtraRule - { - ExtraType = ExtraType.Scene, - RuleType = ExtraRuleType.Suffix, - Token = "-scene", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Clip, - RuleType = ExtraRuleType.Suffix, - Token = "-clip", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Interview, - RuleType = ExtraRuleType.Suffix, - Token = "-interview", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.BehindTheScenes, - RuleType = ExtraRuleType.Suffix, - Token = "-behindthescenes", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.DeletedScene, - RuleType = ExtraRuleType.Suffix, - Token = "-deleted", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Clip, - RuleType = ExtraRuleType.Suffix, - Token = "-featurette", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.Clip, - RuleType = ExtraRuleType.Suffix, - Token = "-short", - MediaType = MediaType.Video - }, - new ExtraRule - { - ExtraType = ExtraType.BehindTheScenes, - RuleType = ExtraRuleType.DirectoryName, - Token = "behind the scenes", - MediaType = MediaType.Video, - }, - new ExtraRule - { - ExtraType = ExtraType.DeletedScene, - RuleType = ExtraRuleType.DirectoryName, - Token = "deleted scenes", - MediaType = MediaType.Video, - }, - new ExtraRule - { - ExtraType = ExtraType.Interview, - RuleType = ExtraRuleType.DirectoryName, - Token = "interviews", - MediaType = MediaType.Video, - }, - new ExtraRule - { - ExtraType = ExtraType.Scene, - RuleType = ExtraRuleType.DirectoryName, - Token = "scenes", - MediaType = MediaType.Video, - }, - new ExtraRule - { - ExtraType = ExtraType.Sample, - RuleType = ExtraRuleType.DirectoryName, - Token = "samples", - MediaType = MediaType.Video, - }, - new ExtraRule - { - ExtraType = ExtraType.Clip, - RuleType = ExtraRuleType.DirectoryName, - Token = "shorts", - MediaType = MediaType.Video, - }, - new ExtraRule - { - ExtraType = ExtraType.Clip, - RuleType = ExtraRuleType.DirectoryName, - Token = "featurettes", - MediaType = MediaType.Video, - }, - new ExtraRule - { - ExtraType = ExtraType.Unknown, - RuleType = ExtraRuleType.DirectoryName, - Token = "extras", - MediaType = MediaType.Video, - }, + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Filename, + "trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Suffix, + "-trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Suffix, + ".trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Suffix, + "_trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.Suffix, + " trailer", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Filename, + "sample", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Suffix, + "-sample", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Suffix, + ".sample", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Suffix, + "_sample", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.Suffix, + " sample", + MediaType.Video), + + new ExtraRule( + ExtraType.ThemeSong, + ExtraRuleType.Filename, + "theme", + MediaType.Audio), + + new ExtraRule( + ExtraType.Scene, + ExtraRuleType.Suffix, + "-scene", + MediaType.Video), + + new ExtraRule( + ExtraType.Clip, + ExtraRuleType.Suffix, + "-clip", + MediaType.Video), + + new ExtraRule( + ExtraType.Interview, + ExtraRuleType.Suffix, + "-interview", + MediaType.Video), + + new ExtraRule( + ExtraType.BehindTheScenes, + ExtraRuleType.Suffix, + "-behindthescenes", + MediaType.Video), + + new ExtraRule( + ExtraType.DeletedScene, + ExtraRuleType.Suffix, + "-deleted", + MediaType.Video), + + new ExtraRule( + ExtraType.Clip, + ExtraRuleType.Suffix, + "-featurette", + MediaType.Video), + + new ExtraRule( + ExtraType.Clip, + ExtraRuleType.Suffix, + "-short", + MediaType.Video), + + new ExtraRule( + ExtraType.BehindTheScenes, + ExtraRuleType.DirectoryName, + "behind the scenes", + MediaType.Video), + + new ExtraRule( + ExtraType.DeletedScene, + ExtraRuleType.DirectoryName, + "deleted scenes", + MediaType.Video), + + new ExtraRule( + ExtraType.Interview, + ExtraRuleType.DirectoryName, + "interviews", + MediaType.Video), + + new ExtraRule( + ExtraType.Scene, + ExtraRuleType.DirectoryName, + "scenes", + MediaType.Video), + + new ExtraRule( + ExtraType.Sample, + ExtraRuleType.DirectoryName, + "samples", + MediaType.Video), + + new ExtraRule( + ExtraType.Clip, + ExtraRuleType.DirectoryName, + "shorts", + MediaType.Video), + + new ExtraRule( + ExtraType.Clip, + ExtraRuleType.DirectoryName, + "featurettes", + MediaType.Video), + + new ExtraRule( + ExtraType.Unknown, + ExtraRuleType.DirectoryName, + "extras", + MediaType.Video), }; Format3DRules = new[] { // Kodi rules: - new Format3DRule - { - PreceedingToken = "3d", - Token = "hsbs" - }, - new Format3DRule - { - PreceedingToken = "3d", - Token = "sbs" - }, - new Format3DRule - { - PreceedingToken = "3d", - Token = "htab" - }, - new Format3DRule - { - PreceedingToken = "3d", - Token = "tab" - }, - // Media Browser rules: - new Format3DRule - { - Token = "fsbs" - }, - new Format3DRule - { - Token = "hsbs" - }, - new Format3DRule - { - Token = "sbs" - }, - new Format3DRule - { - Token = "ftab" - }, - new Format3DRule - { - Token = "htab" - }, - new Format3DRule - { - Token = "tab" - }, - new Format3DRule - { - Token = "sbs3d" - }, - new Format3DRule - { - Token = "mvc" - } + new Format3DRule( + preceedingToken: "3d", + token: "hsbs"), + + new Format3DRule( + preceedingToken: "3d", + token: "sbs"), + + new Format3DRule( + preceedingToken: "3d", + token: "htab"), + + new Format3DRule( + preceedingToken: "3d", + token: "tab"), + + // Media Browser rules: + new Format3DRule("fsbs"), + new Format3DRule("hsbs"), + new Format3DRule("sbs"), + new Format3DRule("ftab"), + new Format3DRule("htab"), + new Format3DRule("tab"), + new Format3DRule("sbs3d"), + new Format3DRule("mvc") }; + AudioBookPartsExpressions = new[] { // Detect specified chapters, like CH 01 @@ -737,15 +672,15 @@ namespace Emby.Naming.Common public ExtraRule[] VideoExtraRules { get; set; } - public Regex[] VideoFileStackingRegexes { get; private set; } + public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty(); - public Regex[] CleanDateTimeRegexes { get; private set; } + public Regex[] CleanDateTimeRegexes { get; private set; } = Array.Empty(); - public Regex[] CleanStringRegexes { get; private set; } + public Regex[] CleanStringRegexes { get; private set; } = Array.Empty(); - public Regex[] EpisodeWithoutSeasonRegexes { get; private set; } + public Regex[] EpisodeWithoutSeasonRegexes { get; private set; } = Array.Empty(); - public Regex[] EpisodeMultiPartRegexes { get; private set; } + public Regex[] EpisodeMultiPartRegexes { get; private set; } = Array.Empty(); public void Compile() { diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 6857f9952c..93770b1561 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -14,6 +14,7 @@ true true snupkg + enable diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs index f39c496b7a..2f16fb2df9 100644 --- a/Emby.Naming/Subtitles/SubtitleInfo.cs +++ b/Emby.Naming/Subtitles/SubtitleInfo.cs @@ -4,6 +4,13 @@ namespace Emby.Naming.Subtitles { public class SubtitleInfo { + public SubtitleInfo(string path, bool isDefault, bool isForced) + { + Path = path; + IsDefault = isDefault; + IsForced = isForced; + } + /// /// Gets or sets the path. /// @@ -14,7 +21,7 @@ namespace Emby.Naming.Subtitles /// Gets or sets the language. /// /// The language. - public string Language { get; set; } + public string? Language { get; set; } /// /// Gets or sets a value indicating whether this instance is default. diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs index 24e59f90a3..c8659e1b26 100644 --- a/Emby.Naming/Subtitles/SubtitleParser.cs +++ b/Emby.Naming/Subtitles/SubtitleParser.cs @@ -31,12 +31,10 @@ namespace Emby.Naming.Subtitles } var flags = GetFlags(path); - var info = new SubtitleInfo - { - Path = path, - IsDefault = _options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)), - IsForced = _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)) - }; + var info = new SubtitleInfo( + path, + _options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)), + _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase))); var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase)) diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs index 250df4e2d3..a9ee82da36 100644 --- a/Emby.Naming/TV/EpisodeInfo.cs +++ b/Emby.Naming/TV/EpisodeInfo.cs @@ -4,6 +4,11 @@ namespace Emby.Naming.TV { public class EpisodeInfo { + public EpisodeInfo(string path) + { + Path = path; + } + /// /// Gets or sets the path. /// @@ -14,19 +19,19 @@ namespace Emby.Naming.TV /// Gets or sets the container. /// /// The container. - public string Container { get; set; } + public string? Container { get; set; } /// /// Gets or sets the name of the series. /// /// The name of the series. - public string SeriesName { get; set; } + public string? SeriesName { get; set; } /// /// Gets or sets the format3 d. /// /// The format3 d. - public string Format3D { get; set; } + public string? Format3D { get; set; } /// /// Gets or sets a value indicating whether [is3 d]. @@ -44,7 +49,7 @@ namespace Emby.Naming.TV /// Gets or sets the type of the stub. /// /// The type of the stub. - public string StubType { get; set; } + public string? StubType { get; set; } public int? SeasonNumber { get; set; } diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs index 05f921edc9..9c48d07a3b 100644 --- a/Emby.Naming/TV/EpisodePathParserResult.cs +++ b/Emby.Naming/TV/EpisodePathParserResult.cs @@ -10,7 +10,7 @@ namespace Emby.Naming.TV public int? EndingEpsiodeNumber { get; set; } - public string SeriesName { get; set; } + public string? SeriesName { get; set; } public bool Success { get; set; } diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index 6994f69fc4..002de21175 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -54,9 +54,8 @@ namespace Emby.Naming.TV var parsingResult = new EpisodePathParser(_options) .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); - return new EpisodeInfo + return new EpisodeInfo(path) { - Path = path, Container = container, IsStub = isStub, EndingEpsiodeNumber = parsingResult.EndingEpsiodeNumber, diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs index 15db32e876..6be7e60525 100644 --- a/Emby.Naming/Video/ExtraResult.cs +++ b/Emby.Naming/Video/ExtraResult.cs @@ -16,6 +16,6 @@ namespace Emby.Naming.Video /// Gets or sets the rule. /// /// The rule. - public ExtraRule Rule { get; set; } + public ExtraRule? Rule { get; set; } } } diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs index 7c9702e244..c018894fdf 100644 --- a/Emby.Naming/Video/ExtraRule.cs +++ b/Emby.Naming/Video/ExtraRule.cs @@ -10,6 +10,14 @@ namespace Emby.Naming.Video /// public class ExtraRule { + public ExtraRule(ExtraType extraType, ExtraRuleType ruleType, string token, MediaType mediaType) + { + Token = token; + ExtraType = extraType; + RuleType = ruleType; + MediaType = mediaType; + } + /// /// Gets or sets the token to use for matching against the file path. /// diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index 3ef190b865..b0a22b18b0 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -13,7 +13,7 @@ namespace Emby.Naming.Video Files = new List(); } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public List Files { get; set; } diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs index 51c26af863..3a9eaa1a14 100644 --- a/Emby.Naming/Video/Format3DParser.cs +++ b/Emby.Naming/Video/Format3DParser.cs @@ -57,7 +57,7 @@ namespace Emby.Naming.Video else { var foundPrefix = false; - string format = null; + string? format = null; foreach (var flag in videoFlags) { diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs index fa0e9d3b80..36dc1c12b2 100644 --- a/Emby.Naming/Video/Format3DResult.cs +++ b/Emby.Naming/Video/Format3DResult.cs @@ -21,7 +21,7 @@ namespace Emby.Naming.Video /// Gets or sets the format3 d. /// /// The format3 d. - public string Format3D { get; set; } + public string? Format3D { get; set; } /// /// Gets or sets the tokens. diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs index 310ec84e8f..a35f0d9d97 100644 --- a/Emby.Naming/Video/Format3DRule.cs +++ b/Emby.Naming/Video/Format3DRule.cs @@ -4,6 +4,12 @@ namespace Emby.Naming.Video { public class Format3DRule { + public Format3DRule(string token, string? preceedingToken = null) + { + Token = token; + PreceedingToken = preceedingToken; + } + /// /// Gets or sets the token. /// @@ -14,6 +20,6 @@ namespace Emby.Naming.Video /// Gets or sets the preceeding token. /// /// The preceeding token. - public string PreceedingToken { get; set; } + public string? PreceedingToken { get; set; } } } diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs index 8285cb51a3..fa42af6049 100644 --- a/Emby.Naming/Video/StubTypeRule.cs +++ b/Emby.Naming/Video/StubTypeRule.cs @@ -4,6 +4,12 @@ namespace Emby.Naming.Video { public class StubTypeRule { + public StubTypeRule(string token, string stubType) + { + Token = token; + StubType = stubType; + } + /// /// Gets or sets the token. /// diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index 11e789b663..12bd8c4364 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -11,19 +11,19 @@ namespace Emby.Naming.Video /// Gets or sets the path. /// /// The path. - public string Path { get; set; } + public string? Path { get; set; } /// /// Gets or sets the container. /// /// The container. - public string Container { get; set; } + public string? Container { get; set; } /// /// Gets or sets the name. /// /// The name. - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the year. @@ -41,13 +41,13 @@ namespace Emby.Naming.Video /// Gets or sets the extra rule. /// /// The extra rule. - public ExtraRule ExtraRule { get; set; } + public ExtraRule? ExtraRule { get; set; } /// /// Gets or sets the format3 d. /// /// The format3 d. - public string Format3D { get; set; } + public string? Format3D { get; set; } /// /// Gets or sets a value indicating whether [is3 d]. @@ -65,7 +65,7 @@ namespace Emby.Naming.Video /// Gets or sets the type of the stub. /// /// The type of the stub. - public string StubType { get; set; } + public string? StubType { get; set; } /// /// Gets or sets a value indicating whether this instance is a directory. diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs index ea74c40e2a..930fdb33f8 100644 --- a/Emby.Naming/Video/VideoInfo.cs +++ b/Emby.Naming/Video/VideoInfo.cs @@ -12,7 +12,7 @@ namespace Emby.Naming.Video /// Initializes a new instance of the class. /// /// The name. - public VideoInfo(string name) + public VideoInfo(string? name) { Name = name; @@ -25,7 +25,7 @@ namespace Emby.Naming.Video /// Gets or sets the name. /// /// The name. - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the year. diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 948fe037b5..601c6c0b6d 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -26,7 +26,8 @@ namespace Emby.Naming.Video var videoInfos = files .Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory)) - .Where(i => i != null) + // .Where(i => i != null) + .OfType() .ToList(); // Filter out all extras, otherwise they could cause stacks to not be resolved @@ -39,7 +40,7 @@ namespace Emby.Naming.Video .Resolve(nonExtras).ToList(); var remainingFiles = videoInfos - .Where(i => !stackResult.Any(s => s.ContainsFile(i.Path, i.IsDirectory))) + .Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory))) .ToList(); var list = new List(); @@ -48,7 +49,9 @@ namespace Emby.Naming.Video { var info = new VideoInfo(stack.Name) { - Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList() + Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)) + .OfType() + .ToList() }; info.Year = info.Files[0].Year; @@ -203,7 +206,7 @@ namespace Emby.Naming.Video return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2; } - private bool IsEligibleForMultiVersion(string folderName, string testFilename) + private bool IsEligibleForMultiVersion(string folderName, string? testFilename) { testFilename = Path.GetFileNameWithoutExtension(testFilename) ?? string.Empty; diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index b4aee614b0..b9ff90179f 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -22,7 +22,7 @@ namespace Emby.Naming.Video /// /// The path. /// VideoFileInfo. - public VideoFileInfo? ResolveDirectory(string path) + public VideoFileInfo? ResolveDirectory(string? path) { return Resolve(path, true); } @@ -32,7 +32,7 @@ namespace Emby.Naming.Video /// /// The path. /// VideoFileInfo. - public VideoFileInfo? ResolveFile(string path) + public VideoFileInfo? ResolveFile(string? path) { return Resolve(path, false); } @@ -45,7 +45,7 @@ namespace Emby.Naming.Video /// Whether or not the name should be parsed for info. /// VideoFileInfo. /// path is null. - public VideoFileInfo? Resolve(string path, bool isDirectory, bool parseName = true) + public VideoFileInfo? Resolve(string? path, bool isDirectory, bool parseName = true) { if (string.IsNullOrEmpty(path)) { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 00282b71a5..e121e9eaf1 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2470,9 +2470,10 @@ namespace Emby.Server.Implementations.Library var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; + // TODO nullable - what are we trying to do there with empty episodeInfo? var episodeInfo = episode.IsFileProtocol - ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo() - : new Naming.TV.EpisodeInfo(); + ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path) + : new Naming.TV.EpisodeInfo(episode.Path); try { diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index e716a6610f..777136f8bf 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -1,4 +1,4 @@ - + @@ -20,7 +20,7 @@ - + diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookFileInfoTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookFileInfoTests.cs index a214bc57c4..cf21f964e2 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookFileInfoTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookFileInfoTests.cs @@ -1,4 +1,4 @@ -using Emby.Naming.AudioBook; +using Emby.Naming.AudioBook; using Xunit; namespace Jellyfin.Naming.Tests.AudioBook @@ -8,22 +8,22 @@ namespace Jellyfin.Naming.Tests.AudioBook [Fact] public void CompareTo_Same_Success() { - var info = new AudioBookFileInfo(); + var info = new AudioBookFileInfo(string.Empty, string.Empty); Assert.Equal(0, info.CompareTo(info)); } [Fact] public void CompareTo_Null_Success() { - var info = new AudioBookFileInfo(); + var info = new AudioBookFileInfo(string.Empty, string.Empty); Assert.Equal(1, info.CompareTo(null)); } [Fact] public void CompareTo_Empty_Success() { - var info1 = new AudioBookFileInfo(); - var info2 = new AudioBookFileInfo(); + var info1 = new AudioBookFileInfo(string.Empty, string.Empty); + var info2 = new AudioBookFileInfo(string.Empty, string.Empty); Assert.Equal(0, info1.CompareTo(info2)); } } diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index 673289436d..2708d80bb6 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Emby.Naming.AudioBook; using Emby.Naming.Common; @@ -14,30 +14,24 @@ namespace Jellyfin.Naming.Tests.AudioBook { yield return new object[] { - new AudioBookFileInfo() - { - Path = @"/server/AudioBooks/Larry Potter/Larry Potter.mp3", - Container = "mp3", - } + new AudioBookFileInfo( + @"/server/AudioBooks/Larry Potter/Larry Potter.mp3", + "mp3") }; yield return new object[] { - new AudioBookFileInfo() - { - Path = @"/server/AudioBooks/Berry Potter/Chapter 1 .ogg", - Container = "ogg", - ChapterNumber = 1 - } + new AudioBookFileInfo( + @"/server/AudioBooks/Berry Potter/Chapter 1 .ogg", + "ogg", + chapterNumber: 1) }; yield return new object[] { - new AudioBookFileInfo() - { - Path = @"/server/AudioBooks/Nerry Potter/Part 3 - Chapter 2.mp3", - Container = "mp3", - ChapterNumber = 2, - PartNumber = 3 - } + new AudioBookFileInfo( + @"/server/AudioBooks/Nerry Potter/Part 3 - Chapter 2.mp3", + "mp3", + chapterNumber: 2, + partNumber: 3) }; } From 60b49e67eafd356d1276f43de1a3f1f2fe52fe3f Mon Sep 17 00:00:00 2001 From: Stepan Date: Sun, 1 Nov 2020 11:19:22 +0100 Subject: [PATCH 09/61] Re-Sharper inspection issues --- .../AudioBook/AudioBookListResolver.cs | 1 - Emby.Naming/AudioBook/AudioBookResolver.cs | 2 +- Emby.Naming/Common/NamingOptions.cs | 12 +++++++----- Emby.Naming/Emby.Naming.csproj | 2 +- Emby.Naming/TV/EpisodeInfo.cs | 2 +- Emby.Naming/TV/EpisodePathParser.cs | 8 ++++---- Emby.Naming/TV/EpisodePathParserResult.cs | 2 +- Emby.Naming/TV/EpisodeResolver.cs | 2 +- Emby.Naming/TV/SeasonPathParser.cs | 12 ++++++------ Emby.Naming/Video/ExtraRuleType.cs | 2 +- Emby.Naming/Video/FlagParser.cs | 4 ++-- Emby.Naming/Video/Format3DParser.cs | 14 +++++++------- Emby.Naming/Video/Format3DRule.cs | 10 +++++----- Emby.Naming/Video/StubResult.cs | 19 ------------------- Emby.Naming/Video/VideoListResolver.cs | 2 +- .../Library/LibraryManager.cs | 4 ++-- .../TV/MultiEpisodeTests.cs | 2 +- 17 files changed, 41 insertions(+), 59 deletions(-) delete mode 100644 Emby.Naming/Video/StubResult.cs diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index f350e5a4ae..179a3bc073 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System; using System.Collections.Generic; using System.Linq; using Emby.Naming.Common; diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index e76cfd744d..56442fc4ee 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -47,7 +47,7 @@ namespace Emby.Naming.AudioBook container, chapterNumber: parsingResult.ChapterNumber, partNumber: parsingResult.PartNumber, - isDirectory: isDirectory ); + isDirectory: isDirectory); } } } diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 78bb6242d6..537de63d55 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -6,6 +6,8 @@ using System.Text.RegularExpressions; using Emby.Naming.Video; using MediaBrowser.Model.Entities; +// ReSharper disable StringLiteralTypo + namespace Emby.Naming.Common { public class NamingOptions @@ -531,19 +533,19 @@ namespace Emby.Naming.Common { // Kodi rules: new Format3DRule( - preceedingToken: "3d", + precedingToken: "3d", token: "hsbs"), new Format3DRule( - preceedingToken: "3d", + precedingToken: "3d", token: "sbs"), new Format3DRule( - preceedingToken: "3d", + precedingToken: "3d", token: "htab"), new Format3DRule( - preceedingToken: "3d", + precedingToken: "3d", token: "tab"), // Media Browser rules: @@ -608,7 +610,7 @@ namespace Emby.Naming.Common ".mxf" }); - MultipleEpisodeExpressions = new string[] + MultipleEpisodeExpressions = new[] { @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?[0-9]{1,3}))+[^\\\/]*$", diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 93770b1561..b7fd0c5455 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -39,7 +39,7 @@ - + diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs index a9ee82da36..e01c810628 100644 --- a/Emby.Naming/TV/EpisodeInfo.cs +++ b/Emby.Naming/TV/EpisodeInfo.cs @@ -55,7 +55,7 @@ namespace Emby.Naming.TV public int? EpisodeNumber { get; set; } - public int? EndingEpsiodeNumber { get; set; } + public int? EndingEpisodeNumber { get; set; } public int? Year { get; set; } diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index a6af689c72..866d8adc00 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -146,7 +146,7 @@ namespace Emby.Naming.TV { if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) { - result.EndingEpsiodeNumber = num; + result.EndingEpisodeNumber = num; } } } @@ -217,13 +217,13 @@ namespace Emby.Naming.TV info.SeriesName = result.SeriesName; } - if (!info.EndingEpsiodeNumber.HasValue && info.EpisodeNumber.HasValue) + if (!info.EndingEpisodeNumber.HasValue && info.EpisodeNumber.HasValue) { - info.EndingEpsiodeNumber = result.EndingEpsiodeNumber; + info.EndingEpisodeNumber = result.EndingEpisodeNumber; } if (!string.IsNullOrEmpty(info.SeriesName) - && (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue)) + && (!info.EpisodeNumber.HasValue || info.EndingEpisodeNumber.HasValue)) { break; } diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs index 9c48d07a3b..5fa0b6f0b4 100644 --- a/Emby.Naming/TV/EpisodePathParserResult.cs +++ b/Emby.Naming/TV/EpisodePathParserResult.cs @@ -8,7 +8,7 @@ namespace Emby.Naming.TV public int? EpisodeNumber { get; set; } - public int? EndingEpsiodeNumber { get; set; } + public int? EndingEpisodeNumber { get; set; } public string? SeriesName { get; set; } diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index 002de21175..5f02c553de 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -58,7 +58,7 @@ namespace Emby.Naming.TV { Container = container, IsStub = isStub, - EndingEpsiodeNumber = parsingResult.EndingEpsiodeNumber, + EndingEpisodeNumber = parsingResult.EndingEpisodeNumber, EpisodeNumber = parsingResult.EpisodeNumber, SeasonNumber = parsingResult.SeasonNumber, SeriesName = parsingResult.SeriesName, diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs index d2e324dda5..142680f0cd 100644 --- a/Emby.Naming/TV/SeasonPathParser.cs +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -101,9 +101,9 @@ namespace Emby.Naming.TV } var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries); - for (int i = 0; i < parts.Length; i++) + foreach (var part in parts) { - if (TryGetSeasonNumberFromPart(parts[i], out int seasonNumber)) + if (TryGetSeasonNumberFromPart(part, out int seasonNumber)) { return (seasonNumber, true); } @@ -139,7 +139,7 @@ namespace Emby.Naming.TV var numericStart = -1; var length = 0; - var hasOpenParenth = false; + var hasOpenParenthesis = false; var isSeasonFolder = true; // Find out where the numbers start, and then keep going until they end @@ -147,7 +147,7 @@ namespace Emby.Naming.TV { if (char.IsNumber(path[i])) { - if (!hasOpenParenth) + if (!hasOpenParenthesis) { if (numericStart == -1) { @@ -167,11 +167,11 @@ namespace Emby.Naming.TV var currentChar = path[i]; if (currentChar == '(') { - hasOpenParenth = true; + hasOpenParenthesis = true; } else if (currentChar == ')') { - hasOpenParenth = false; + hasOpenParenthesis = false; } } diff --git a/Emby.Naming/Video/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs index e89876f4ae..98114c7e8b 100644 --- a/Emby.Naming/Video/ExtraRuleType.cs +++ b/Emby.Naming/Video/ExtraRuleType.cs @@ -22,6 +22,6 @@ namespace Emby.Naming.Video /// /// Match against the name of the directory containing the file. /// - DirectoryName = 3, + DirectoryName = 3 } } diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs index a8bd9d5c5d..27ca1abf1a 100644 --- a/Emby.Naming/Video/FlagParser.cs +++ b/Emby.Naming/Video/FlagParser.cs @@ -20,7 +20,7 @@ namespace Emby.Naming.Video return GetFlags(path, _options.VideoFlagDelimiters); } - public string[] GetFlags(string path, char[] delimeters) + public string[] GetFlags(string path, char[] delimiters) { if (string.IsNullOrEmpty(path)) { @@ -31,7 +31,7 @@ namespace Emby.Naming.Video var file = Path.GetFileName(path); - return file.Split(delimeters, StringSplitOptions.RemoveEmptyEntries); + return file.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); } } } diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs index 3a9eaa1a14..fb881f978f 100644 --- a/Emby.Naming/Video/Format3DParser.cs +++ b/Emby.Naming/Video/Format3DParser.cs @@ -18,11 +18,11 @@ namespace Emby.Naming.Video public Format3DResult Parse(string path) { int oldLen = _options.VideoFlagDelimiters.Length; - var delimeters = new char[oldLen + 1]; - _options.VideoFlagDelimiters.CopyTo(delimeters, 0); - delimeters[oldLen] = ' '; + var delimiters = new char[oldLen + 1]; + _options.VideoFlagDelimiters.CopyTo(delimiters, 0); + delimiters[oldLen] = ' '; - return Parse(new FlagParser(_options).GetFlags(path, delimeters)); + return Parse(new FlagParser(_options).GetFlags(path, delimiters)); } internal Format3DResult Parse(string[] videoFlags) @@ -44,7 +44,7 @@ namespace Emby.Naming.Video { var result = new Format3DResult(); - if (string.IsNullOrEmpty(rule.PreceedingToken)) + if (string.IsNullOrEmpty(rule.PrecedingToken)) { result.Format3D = new[] { rule.Token }.FirstOrDefault(i => videoFlags.Contains(i, StringComparer.OrdinalIgnoreCase)); result.Is3D = !string.IsNullOrEmpty(result.Format3D); @@ -63,7 +63,7 @@ namespace Emby.Naming.Video { if (foundPrefix) { - result.Tokens.Add(rule.PreceedingToken); + result.Tokens.Add(rule.PrecedingToken); if (string.Equals(rule.Token, flag, StringComparison.OrdinalIgnoreCase)) { @@ -74,7 +74,7 @@ namespace Emby.Naming.Video break; } - foundPrefix = string.Equals(flag, rule.PreceedingToken, StringComparison.OrdinalIgnoreCase); + foundPrefix = string.Equals(flag, rule.PrecedingToken, StringComparison.OrdinalIgnoreCase); } result.Is3D = foundPrefix && !string.IsNullOrEmpty(format); diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs index a35f0d9d97..7679164b30 100644 --- a/Emby.Naming/Video/Format3DRule.cs +++ b/Emby.Naming/Video/Format3DRule.cs @@ -4,10 +4,10 @@ namespace Emby.Naming.Video { public class Format3DRule { - public Format3DRule(string token, string? preceedingToken = null) + public Format3DRule(string token, string? precedingToken = null) { Token = token; - PreceedingToken = preceedingToken; + PrecedingToken = precedingToken; } /// @@ -17,9 +17,9 @@ namespace Emby.Naming.Video public string Token { get; set; } /// - /// Gets or sets the preceeding token. + /// Gets or sets the preceding token. /// - /// The preceeding token. - public string? PreceedingToken { get; set; } + /// The preceding token. + public string? PrecedingToken { get; set; } } } diff --git a/Emby.Naming/Video/StubResult.cs b/Emby.Naming/Video/StubResult.cs deleted file mode 100644 index 1b8e99b0dc..0000000000 --- a/Emby.Naming/Video/StubResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -#pragma warning disable CS1591 - -namespace Emby.Naming.Video -{ - public struct StubResult - { - /// - /// Gets or sets a value indicating whether this instance is stub. - /// - /// true if this instance is stub; otherwise, false. - public bool IsStub { get; set; } - - /// - /// Gets or sets the type of the stub. - /// - /// The type of the stub. - public string StubType { get; set; } - } -} diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 601c6c0b6d..190562cfcd 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -136,7 +136,7 @@ namespace Emby.Naming.Video } // If there's only one video, accept all trailers - // Be lenient because people use all kinds of mish mash conventions with trailers + // Be lenient because people use all kinds of mishmash conventions with trailers if (list.Count == 1) { var trailers = remainingFiles diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index e121e9eaf1..6f85a24089 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2562,12 +2562,12 @@ namespace Emby.Server.Implementations.Library if (!episode.IndexNumberEnd.HasValue || forceRefresh) { - if (episode.IndexNumberEnd != episodeInfo.EndingEpsiodeNumber) + if (episode.IndexNumberEnd != episodeInfo.EndingEpisodeNumber) { changed = true; } - episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber; + episode.IndexNumberEnd = episodeInfo.EndingEpisodeNumber; } if (!episode.ParentIndexNumber.HasValue || forceRefresh) diff --git a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs index 3513050b64..58ea0bec59 100644 --- a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs @@ -74,7 +74,7 @@ namespace Jellyfin.Naming.Tests.TV var result = new EpisodePathParser(options) .Parse(filename, false); - Assert.Equal(result.EndingEpsiodeNumber, endingEpisodeNumber); + Assert.Equal(result.EndingEpisodeNumber, endingEpisodeNumber); } } } From 6437cf69508076bab2d62bbe887f12b72d02f7b3 Mon Sep 17 00:00:00 2001 From: Stepan Date: Sun, 1 Nov 2020 13:28:43 +0100 Subject: [PATCH 10/61] Removed Success property from AudioBookFilePathParserResult, since it was unused and consider only audiobooks that have chapter/page number in name makes no sense --- Emby.Naming/AudioBook/AudioBookFilePathParser.cs | 2 -- Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs index 14edd64926..56580f194b 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs @@ -52,8 +52,6 @@ namespace Emby.Naming.AudioBook } } - result.Success = result.ChapterNumber.HasValue || result.PartNumber.HasValue; - return result; } } diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs index 7bfc4479d2..b65d231dfd 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs @@ -8,7 +8,5 @@ namespace Emby.Naming.AudioBook public int? PartNumber { get; set; } public int? ChapterNumber { get; set; } - - public bool Success { get; set; } } } From e7a37bedfca159ab6a305833395aead07ccd872f Mon Sep 17 00:00:00 2001 From: Stepan Date: Sun, 1 Nov 2020 13:42:56 +0100 Subject: [PATCH 11/61] Simplify AudioBookResolver since there is no option of passing directories into it (AudioResolver.cs:179) and handling directories were not implemented anyway --- Emby.Naming/AudioBook/AudioBookFileInfo.cs | 10 +--------- Emby.Naming/AudioBook/AudioBookListResolver.cs | 8 ++++---- Emby.Naming/AudioBook/AudioBookResolver.cs | 11 ++--------- Emby.Naming/Video/StackResolver.cs | 15 ++++----------- 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs index e5200416ee..862e396677 100644 --- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs @@ -14,14 +14,12 @@ namespace Emby.Naming.AudioBook /// File type. /// Number of part this file represents. /// Number of chapter this file represents. - /// Indication if we are looking at file or directory. - public AudioBookFileInfo(string path, string container, int? partNumber = default, int? chapterNumber = default, bool isDirectory = default) + public AudioBookFileInfo(string path, string container, int? partNumber = default, int? chapterNumber = default) { Path = path; Container = container; PartNumber = partNumber; ChapterNumber = chapterNumber; - IsDirectory = isDirectory; } /// @@ -48,12 +46,6 @@ namespace Emby.Naming.AudioBook /// The chapter number. public int? ChapterNumber { get; set; } - /// - /// Gets or sets a value indicating whether this instance is a directory. - /// - /// The type. - public bool IsDirectory { get; set; } - /// public int CompareTo(AudioBookFileInfo? other) { diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 179a3bc073..0d190c172e 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -22,21 +22,21 @@ namespace Emby.Naming.AudioBook var audioBookResolver = new AudioBookResolver(_options); var audiobookFileInfos = files - .Select(i => audioBookResolver.Resolve(i.FullName, i.IsDirectory)) + .Select(i => audioBookResolver.Resolve(i.FullName)) .OfType() .ToList(); // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer var metadata = audiobookFileInfos - .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); + .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = false }); var stackResult = new StackResolver(_options) - .ResolveAudioBooks(metadata); + .ResolveAudioBooks(audiobookFileInfos); foreach (var stack in stackResult) { - var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).OfType().ToList(); + var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i)).OfType().ToList(); stackFiles.Sort(); // TODO nullable discover if name can be empty var info = new AudioBookInfo(stack.Name ?? string.Empty) { Files = stackFiles }; diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index 56442fc4ee..c9e8c76cbc 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -17,19 +17,13 @@ namespace Emby.Naming.AudioBook _options = options; } - public AudioBookFileInfo? Resolve(string path, bool isDirectory = false) + public AudioBookFileInfo? Resolve(string path) { if (path.Length == 0) { throw new ArgumentException("String can't be empty.", nameof(path)); } - // TODO - if (isDirectory) - { - return null; - } - var extension = Path.GetExtension(path); // Check supported extensions @@ -46,8 +40,7 @@ namespace Emby.Naming.AudioBook path, container, chapterNumber: parsingResult.ChapterNumber, - partNumber: parsingResult.PartNumber, - isDirectory: isDirectory); + partNumber: parsingResult.PartNumber); } } } diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index f733cd2620..ce3152739b 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Emby.Naming.AudioBook; using Emby.Naming.Common; using MediaBrowser.Model.IO; @@ -29,24 +30,16 @@ namespace Emby.Naming.Video return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false })); } - public IEnumerable ResolveAudioBooks(IEnumerable files) + public IEnumerable ResolveAudioBooks(IEnumerable files) { - var groupedDirectoryFiles = files.GroupBy(file => - file.IsDirectory - ? file.FullName - : Path.GetDirectoryName(file.FullName)); + var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path)); foreach (var directory in groupedDirectoryFiles) { var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false }; foreach (var file in directory) { - if (file.IsDirectory) - { - continue; - } - - stack.Files.Add(file.FullName); + stack.Files.Add(file.Path); } yield return stack; From f39775dc3a813609454655c31dd5c6a1413cc890 Mon Sep 17 00:00:00 2001 From: Stepan Date: Sun, 1 Nov 2020 17:10:48 +0100 Subject: [PATCH 12/61] Written test to finish coverage for AudioBookListResolver & AudioBookResolver and corrected some logical erros / unhandled exception --- .../AudioBook/AudioBookListResolver.cs | 20 ++++++---- Emby.Naming/AudioBook/AudioBookResolver.cs | 3 +- .../AudioBook/AudioBookListResolverTests.cs | 38 ++++++++++++++++++- .../AudioBook/AudioBookResolverTests.cs | 21 ++++++++-- 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 0d190c172e..795065a6c9 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -1,6 +1,8 @@ #pragma warning disable CS1591 +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; @@ -21,25 +23,27 @@ namespace Emby.Naming.AudioBook { var audioBookResolver = new AudioBookResolver(_options); + // File with empty fullname will be sorted out here var audiobookFileInfos = files .Select(i => audioBookResolver.Resolve(i.FullName)) .OfType() .ToList(); - // Filter out all extras, otherwise they could cause stacks to not be resolved - // See the unit test TestStackedWithTrailer - var metadata = audiobookFileInfos - .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = false }); - var stackResult = new StackResolver(_options) .ResolveAudioBooks(audiobookFileInfos); foreach (var stack in stackResult) { - var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i)).OfType().ToList(); + var stackFiles = stack.Files + .Select(i => audioBookResolver.Resolve(i)) + .OfType() + .ToList(); + stackFiles.Sort(); - // TODO nullable discover if name can be empty - var info = new AudioBookInfo(stack.Name ?? string.Empty) { Files = stackFiles }; + + // stack.Name can be empty when we have file without folder, but always have some files + var name = string.IsNullOrEmpty(stack.Name) ? stack.Files[0] : stack.Name; + var info = new AudioBookInfo(name) { Files = stackFiles }; yield return info; } diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index c9e8c76cbc..542d6fee51 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -21,7 +21,8 @@ namespace Emby.Naming.AudioBook { if (path.Length == 0) { - throw new ArgumentException("String can't be empty.", nameof(path)); + // Return null to indicate this path will not be used, instead of stopping whole process with exception + return null; } var extension = Path.GetExtension(path); diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs index 1084e20bda..d912b0e907 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Emby.Naming.AudioBook; using Emby.Naming.Common; using MediaBrowser.Model.IO; @@ -82,6 +83,41 @@ namespace Jellyfin.Naming.Tests.AudioBook Assert.Single(result); } + [Fact] + public void TestWithoutFolder() + { + var files = new[] + { + "Harry Potter and the Deathly Hallows trailer.mp3" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + })).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestEmpty() + { + var files = Array.Empty(); + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + })).ToList(); + + Assert.Empty(result); + } + private AudioBookListResolver GetResolver() { return new AudioBookListResolver(_namingOptions); diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index 2708d80bb6..282da2b93a 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -35,6 +35,11 @@ namespace Jellyfin.Naming.Tests.AudioBook }; } + public static IEnumerable GetPathsWithInvalidExtensions() + { + yield return new object[] { @"/server/AudioBooks/Larry Potter/Larry Potter.mp9" }; + } + [Theory] [MemberData(nameof(GetResolveFileTestData))] public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult) @@ -46,13 +51,23 @@ namespace Jellyfin.Naming.Tests.AudioBook Assert.Equal(result!.Container, expectedResult.Container); Assert.Equal(result!.ChapterNumber, expectedResult.ChapterNumber); Assert.Equal(result!.PartNumber, expectedResult.PartNumber); - Assert.Equal(result!.IsDirectory, expectedResult.IsDirectory); + } + + [Theory] + [MemberData(nameof(GetPathsWithInvalidExtensions))] + public void Resolve_InvalidExtension(string path) + { + var result = new AudioBookResolver(_namingOptions).Resolve(path); + + Assert.Null(result); } [Fact] - public void Resolve_EmptyFileName_ArgumentException() + public void Resolve_EmptyFileName() { - Assert.Throws(() => new AudioBookResolver(_namingOptions).Resolve(string.Empty)); + var result = new AudioBookResolver(_namingOptions).Resolve(string.Empty); + + Assert.Null(result); } } } From 50a2ef9d8aaf92e8b69ced5ea2fcc8fa185fe675 Mon Sep 17 00:00:00 2001 From: Stepan Date: Sun, 1 Nov 2020 17:41:47 +0100 Subject: [PATCH 13/61] Simplify Resolve_InvalidExtension Test and created tests for Alternative Versions parsing & Year Extraction for audiobooks --- .../AudioBook/AudioBookListResolverTests.cs | 58 +++++++++++++++++++ .../AudioBook/AudioBookResolverTests.cs | 11 +--- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs index d912b0e907..c4b061b4e9 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs @@ -43,6 +43,64 @@ namespace Jellyfin.Naming.Tests.AudioBook Assert.Equal("Batman", result[1].Name); } + [Fact] + public void TestAlternativeVersions() + { + var files = new[] + { + "Harry Potter and the Deathly Hallows/Chapter 1.ogg", + "Harry Potter and the Deathly Hallows/Chapter 1.mp3", + + "Deadpool.ogg", + "Deadpool.mp3", + + "Batman/Chapter 1.mp3" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + })).ToList(); + + Assert.Equal(3, result[0].Files.Count); + Assert.NotEmpty(result[0].AlternateVersions); + Assert.NotEmpty(result[1].AlternateVersions); + Assert.Empty(result[2].AlternateVersions); + } + + [Fact] + public void TestYearExtraction() + { + var files = new[] + { + "Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg", + "Harry Potter and the Deathly Hallows (2007)/Chapter 2.mp3", + + "Batman (2020).ogg", + + "Batman(2021).mp3", + + "Batman.mp3" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + })).ToList(); + + Assert.Equal(3, result[0].Files.Count); + Assert.Equal(2007, result[0].Year); + Assert.Equal(2020, result[1].Year); + Assert.Equal(2021, result[2].Year); + Assert.Null(result[2].Year); + } + [Fact] public void TestWithMetadata() { diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index 282da2b93a..5e9d12970a 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -35,10 +35,6 @@ namespace Jellyfin.Naming.Tests.AudioBook }; } - public static IEnumerable GetPathsWithInvalidExtensions() - { - yield return new object[] { @"/server/AudioBooks/Larry Potter/Larry Potter.mp9" }; - } [Theory] [MemberData(nameof(GetResolveFileTestData))] @@ -53,11 +49,10 @@ namespace Jellyfin.Naming.Tests.AudioBook Assert.Equal(result!.PartNumber, expectedResult.PartNumber); } - [Theory] - [MemberData(nameof(GetPathsWithInvalidExtensions))] - public void Resolve_InvalidExtension(string path) + [Fact] + public void Resolve_InvalidExtension() { - var result = new AudioBookResolver(_namingOptions).Resolve(path); + var result = new AudioBookResolver(_namingOptions).Resolve(@"/server/AudioBooks/Larry Potter/Larry Potter.mp9"); Assert.Null(result); } From 1e7177568887d0f808660454e5eb7ca7ebcd6998 Mon Sep 17 00:00:00 2001 From: Stepan Date: Mon, 2 Nov 2020 20:03:12 +0100 Subject: [PATCH 14/61] Add Name and Year parsing for audiobooks --- Emby.Naming/AudioBook/AudioBookInfo.cs | 4 +- .../AudioBook/AudioBookListResolver.cs | 6 +- Emby.Naming/AudioBook/AudioBookNameParser.cs | 59 ++++++++++++++ .../AudioBook/AudioBookNameParserResult.cs | 12 +++ Emby.Naming/Common/NamingOptions.cs | 9 +++ Emby.Naming/Video/StackResolver.cs | 20 ++++- .../AudioBook/AudioBookListResolverTests.cs | 78 +++++++++++++++---- .../AudioBook/AudioBookResolverTests.cs | 1 - 8 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 Emby.Naming/AudioBook/AudioBookNameParser.cs create mode 100644 Emby.Naming/AudioBook/AudioBookNameParserResult.cs diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs index fba11ea726..353a0f4a01 100644 --- a/Emby.Naming/AudioBook/AudioBookInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookInfo.cs @@ -11,12 +11,14 @@ namespace Emby.Naming.AudioBook /// Initializes a new instance of the class. /// /// Name of audiobook. - public AudioBookInfo(string name) + /// Year of audiobook release. + public AudioBookInfo(string name, int? year) { Files = new List(); Extras = new List(); AlternateVersions = new List(); Name = name; + Year = year; } /// diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 795065a6c9..86ba2eeeaf 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -41,9 +41,9 @@ namespace Emby.Naming.AudioBook stackFiles.Sort(); - // stack.Name can be empty when we have file without folder, but always have some files - var name = string.IsNullOrEmpty(stack.Name) ? stack.Files[0] : stack.Name; - var info = new AudioBookInfo(name) { Files = stackFiles }; + var result = new AudioBookNameParser(_options).Parse(stack.Name); + + var info = new AudioBookInfo(result.Name, result.Year) { Files = stackFiles }; yield return info; } diff --git a/Emby.Naming/AudioBook/AudioBookNameParser.cs b/Emby.Naming/AudioBook/AudioBookNameParser.cs new file mode 100644 index 0000000000..c48db93b37 --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookNameParser.cs @@ -0,0 +1,59 @@ +#nullable enable +#pragma warning disable CS1591 + +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; +using Emby.Naming.Common; + +namespace Emby.Naming.AudioBook +{ + public class AudioBookNameParser + { + private readonly NamingOptions _options; + + public AudioBookNameParser(NamingOptions options) + { + _options = options; + } + + public AudioBookNameParserResult Parse(string name) + { + AudioBookNameParserResult result = default; + foreach (var expression in _options.AudioBookNamesExpressions) + { + var match = new Regex(expression, RegexOptions.IgnoreCase).Match(name); + if (match.Success) + { + if (result.Name == null) + { + var value = match.Groups["name"]; + if (value.Success) + { + result.Name = value.Value; + } + } + + if (!result.Year.HasValue) + { + var value = match.Groups["year"]; + if (value.Success) + { + if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + { + result.Year = intValue; + } + } + } + } + } + + if (string.IsNullOrEmpty(result.Name)) + { + result.Name = name; + } + + return result; + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookNameParserResult.cs b/Emby.Naming/AudioBook/AudioBookNameParserResult.cs new file mode 100644 index 0000000000..b28e259dda --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookNameParserResult.cs @@ -0,0 +1,12 @@ +#nullable enable +#pragma warning disable CS1591 + +namespace Emby.Naming.AudioBook +{ + public struct AudioBookNameParserResult + { + public string Name { get; set; } + + public int? Year { get; set; } + } +} diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 537de63d55..5bf232451b 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -575,6 +575,13 @@ namespace Emby.Naming.Common @"dis(?:c|k)[\s_-]?(?[0-9]+)" }; + AudioBookNamesExpressions = new[] + { + // Detect year usually in brackets after name Batman (2020) + @"^(?.+?)\s*\(\s*(?\d{4})\s*\)\s*$", + @"^\s*(?.+?)\s*$" + }; + var extensions = VideoFileExtensions.ToList(); extensions.AddRange(new[] @@ -658,6 +665,8 @@ namespace Emby.Naming.Common public string[] AudioBookPartsExpressions { get; set; } + public string[] AudioBookNamesExpressions { get; set; } + public StubTypeRule[] StubTypes { get; set; } public char[] VideoFlagDelimiters { get; set; } diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index ce3152739b..e11b4063ce 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -36,13 +36,25 @@ namespace Emby.Naming.Video foreach (var directory in groupedDirectoryFiles) { - var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false }; - foreach (var file in directory) + if (string.IsNullOrEmpty(directory.Key)) { - stack.Files.Add(file.Path); + foreach (var file in directory) + { + var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false }; + stack.Files.Add(file.Path); + yield return stack; + } } + else + { + var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false }; + foreach (var file in directory) + { + stack.Files.Add(file.Path); + } - yield return stack; + yield return stack; + } } } diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs index c4b061b4e9..91492d46c9 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Emby.Naming.AudioBook; using Emby.Naming.Common; @@ -72,33 +73,69 @@ namespace Jellyfin.Naming.Tests.AudioBook } [Fact] - public void TestYearExtraction() + public void TestNameYearExtraction() { - var files = new[] + var data = new[] { - "Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg", - "Harry Potter and the Deathly Hallows (2007)/Chapter 2.mp3", - - "Batman (2020).ogg", - - "Batman(2021).mp3", - - "Batman.mp3" + new NameYearPath + { + Name = "Harry Potter and the Deathly Hallows", + Path = "Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg", + Year = 2007 + }, + new NameYearPath + { + Name = "Batman", + Path = "Batman (2020).ogg", + Year = 2020 + }, + new NameYearPath + { + Name = "Batman", + Path = "Batman( 2021 ).mp3", + Year = 2021 + }, + new NameYearPath + { + Name = "Batman(*2021*)", + Path = "Batman(*2021*).mp3", + Year = null + }, + new NameYearPath + { + Name = "Batman", + Path = "Batman.mp3", + Year = null + }, + new NameYearPath + { + Name = "+ Batman .", + Path = " + Batman . .mp3", + Year = null + }, + new NameYearPath + { + Name = " ", + Path = " .mp3", + Year = null + } }; var resolver = GetResolver(); - var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + var result = resolver.Resolve(data.Select(i => new FileSystemMetadata { IsDirectory = false, - FullName = i + FullName = i.Path })).ToList(); - Assert.Equal(3, result[0].Files.Count); - Assert.Equal(2007, result[0].Year); - Assert.Equal(2020, result[1].Year); - Assert.Equal(2021, result[2].Year); - Assert.Null(result[2].Year); + Assert.Equal(data.Length, result.Count); + + for (int i = 0; i < data.Length; i++) + { + Assert.Equal(data[i].Name, result[i].Name); + Assert.Equal(data[i].Year, result[i].Year); + } } [Fact] @@ -180,5 +217,12 @@ namespace Jellyfin.Naming.Tests.AudioBook { return new AudioBookListResolver(_namingOptions); } + + internal struct NameYearPath + { + public string Name; + public string Path; + public int? Year; + } } } diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index 5e9d12970a..b3257ace3b 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -35,7 +35,6 @@ namespace Jellyfin.Naming.Tests.AudioBook }; } - [Theory] [MemberData(nameof(GetResolveFileTestData))] public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult) From 7b6363b09a3aabba3dcd285fd70d2eda8f1ea889 Mon Sep 17 00:00:00 2001 From: Stepan Date: Mon, 2 Nov 2020 23:07:46 +0100 Subject: [PATCH 15/61] Update test for detecting audiobooks extras and alternative files --- .../AudioBook/AudioBookListResolverTests.cs | 61 ++++++++++++++++--- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs index 91492d46c9..a246999628 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs @@ -20,11 +20,20 @@ namespace Jellyfin.Naming.Tests.AudioBook { "Harry Potter and the Deathly Hallows/Part 1.mp3", "Harry Potter and the Deathly Hallows/Part 2.mp3", - "Harry Potter and the Deathly Hallows/book.nfo", + "Harry Potter and the Deathly Hallows/Extra.mp3", "Batman/Chapter 1.mp3", "Batman/Chapter 2.mp3", "Batman/Chapter 3.mp3", + + "Ready Player One (2020)/Ready Player One.mp3", + "Ready Player One (2020)/extra.mp3", + + "Badman/audiobook.mp3", + "Badman/extra.mp3", + + "Superman (2020)/book.mp3", + "Superman (2020)/extra.mp3" }; var resolver = GetResolver(); @@ -35,13 +44,21 @@ namespace Jellyfin.Naming.Tests.AudioBook FullName = i })).ToList(); - Assert.Equal(2, result[0].Files.Count); - // Assert.Empty(result[0].Extras); FIXME: AudioBookListResolver should resolve extra files properly + Assert.Equal(4, result[0].Files.Count); + Assert.Single(result[0].Extras); Assert.Equal("Harry Potter and the Deathly Hallows", result[0].Name); Assert.Equal(3, result[1].Files.Count); Assert.Empty(result[1].Extras); Assert.Equal("Batman", result[1].Name); + + Assert.Equal(2, result[2].Files.Count); + Assert.Single(result[2].Extras); + Assert.Equal("Badman", result[2].Name); + + Assert.Equal(2, result[3].Files.Count); + Assert.Single(result[3].Extras); + Assert.Equal("Superman", result[3].Name); } [Fact] @@ -52,10 +69,19 @@ namespace Jellyfin.Naming.Tests.AudioBook "Harry Potter and the Deathly Hallows/Chapter 1.ogg", "Harry Potter and the Deathly Hallows/Chapter 1.mp3", - "Deadpool.ogg", - "Deadpool.mp3", + "Aqua-man/book.mp3", - "Batman/Chapter 1.mp3" + "Deadpool.mp3", + "Deadpool [HQ].mp3", + + "Superman/book.mp3", + "Superman/audiobook.mp3", + "Superman/Superman.mp3", + "Superman/Superman [HQ].mp3", + "Superman/extra.mp3", + + "Batman/ Chapter 1 .mp3", + "Batman/Chapter 1[loss-less].mp3" }; var resolver = GetResolver(); @@ -66,10 +92,27 @@ namespace Jellyfin.Naming.Tests.AudioBook FullName = i })).ToList(); - Assert.Equal(3, result[0].Files.Count); - Assert.NotEmpty(result[0].AlternateVersions); - Assert.NotEmpty(result[1].AlternateVersions); + Assert.Equal(6, result[0].Files.Count); + // HP - Same name so we don't care which file is alternative + Assert.Single(result[0].AlternateVersions); + // Aqua-man + Assert.Empty(result[1].AlternateVersions); + // DP Assert.Empty(result[2].AlternateVersions); + // DP HQ (directory missing so we do not group deadpools together) + Assert.Empty(result[3].AlternateVersions); + // Superman + // Priority: + // 1. Name + // 2. audiobook + // 3. book + // 4. Names with modifiers + Assert.Equal(3, result[4].AlternateVersions.Count); + Assert.Equal("Superman/audiobook.mp3", result[4].AlternateVersions[0].Path); + Assert.Equal("Superman/book.mp3", result[4].AlternateVersions[1].Path); + Assert.Equal("Superman/Superman [HQ].mp3", result[4].AlternateVersions[2].Path); + // Batman + Assert.Single(result[5].AlternateVersions); } [Fact] From c060ed1a1853eaa28ae2f88f6b301c23cf326725 Mon Sep 17 00:00:00 2001 From: Stepan Date: Tue, 3 Nov 2020 16:24:04 +0100 Subject: [PATCH 16/61] Added resolving of alternative files and extras for audibooks. --- Emby.Naming/AudioBook/AudioBookInfo.cs | 11 ++- .../AudioBook/AudioBookListResolver.cs | 93 ++++++++++++++++++- Emby.Naming/AudioBook/AudioBookNameParser.cs | 1 - Emby.Naming/AudioBook/AudioBookResolver.cs | 2 +- Emby.Naming/Common/NamingOptions.cs | 4 +- .../AudioBook/AudioBookListResolverTests.cs | 48 +++++----- 6 files changed, 126 insertions(+), 33 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs index 353a0f4a01..adf403ab6d 100644 --- a/Emby.Naming/AudioBook/AudioBookInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookInfo.cs @@ -12,13 +12,16 @@ namespace Emby.Naming.AudioBook /// /// Name of audiobook. /// Year of audiobook release. - public AudioBookInfo(string name, int? year) + /// List of files composing the actual audiobook. + /// List of extra files. + /// Alternative version of files. + public AudioBookInfo(string name, int? year, List? files, List? extras, List? alternateVersions) { - Files = new List(); - Extras = new List(); - AlternateVersions = new List(); Name = name; Year = year; + Files = files ?? new List(); + Extras = extras ?? new List(); + AlternateVersions = alternateVersions ?? new List(); } /// diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 86ba2eeeaf..e8908aa37c 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -41,12 +41,101 @@ namespace Emby.Naming.AudioBook stackFiles.Sort(); - var result = new AudioBookNameParser(_options).Parse(stack.Name); + var nameParserResult = new AudioBookNameParser(_options).Parse(stack.Name); - var info = new AudioBookInfo(result.Name, result.Year) { Files = stackFiles }; + FindExtraAndAlternativeFiles(ref stackFiles, out var extras, out var alternativeVersions, nameParserResult); + + var info = new AudioBookInfo( + nameParserResult.Name, + nameParserResult.Year, + stackFiles, + extras, + alternativeVersions); yield return info; } } + + private void FindExtraAndAlternativeFiles(ref List stackFiles, out List extras, out List alternativeVersions, AudioBookNameParserResult nameParserResult) + { + extras = new List(); + alternativeVersions = new List(); + + var haveChaptersOrPages = stackFiles.Any(x => x.ChapterNumber != null || x.PartNumber != null); + var groupedBy = stackFiles.GroupBy(file => new { file.ChapterNumber, file.PartNumber }); + + foreach (var group in groupedBy) + { + if (group.Key.ChapterNumber == null && group.Key.PartNumber == null) + { + if (group.Count() > 1 || haveChaptersOrPages) + { + var ex = new List(); + var alt = new List(); + + foreach (var audioFile in group) + { + var name = Path.GetFileNameWithoutExtension(audioFile.Path); + if (name == "audiobook" || + name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) || + name.Contains(nameParserResult.Name.Replace(" ", "."), StringComparison.OrdinalIgnoreCase)) + { + alt.Add(audioFile); + } + else + { + ex.Add(audioFile); + } + } + + if (ex.Count > 0) + { + var extra = ex + .OrderBy(x => x.Container) + .ThenBy(x => x.Path) + .ToList(); + + stackFiles = stackFiles.Except(extra).ToList(); + extras.AddRange(extra); + } + + if (alt.Count > 0) + { + var alternatives = alt + .OrderBy(x => x.Container) + .ThenBy(x => x.Path) + .ToList(); + + var main = FindMainAudioBookFile(alternatives, nameParserResult.Name); + alternatives.Remove(main); + stackFiles = stackFiles.Except(alternatives).ToList(); + alternativeVersions.AddRange(alternatives); + } + } + } + else if (group.Count() > 1) + { + var alternatives = group + .OrderBy(x => x.Container) + .ThenBy(x => x.Path) + .Skip(1) + .ToList(); + + stackFiles = stackFiles.Except(alternatives).ToList(); + alternativeVersions.AddRange(alternatives); + } + } + } + + private AudioBookFileInfo FindMainAudioBookFile(List files, string name) + { + var main = files.Find(x => Path.GetFileNameWithoutExtension(x.Path) == name); + main ??= files.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x.Path) == "audiobook"); + main ??= files.OrderBy(x => x.Container) + .ThenBy(x => x.Path) + .First(); + + return main; + } } } diff --git a/Emby.Naming/AudioBook/AudioBookNameParser.cs b/Emby.Naming/AudioBook/AudioBookNameParser.cs index c48db93b37..7c86161241 100644 --- a/Emby.Naming/AudioBook/AudioBookNameParser.cs +++ b/Emby.Naming/AudioBook/AudioBookNameParser.cs @@ -2,7 +2,6 @@ #pragma warning disable CS1591 using System.Globalization; -using System.IO; using System.Text.RegularExpressions; using Emby.Naming.Common; diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index 542d6fee51..c7b3b2d2d1 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -19,7 +19,7 @@ namespace Emby.Naming.AudioBook public AudioBookFileInfo? Resolve(string path) { - if (path.Length == 0) + if (path.Length == 0 || Path.GetFileNameWithoutExtension(path).Length == 0) { // Return null to indicate this path will not be used, instead of stopping whole process with exception return null; diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 5bf232451b..d2f07817a6 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -568,7 +568,7 @@ namespace Emby.Naming.Common // Chapter is often beginning of filename "^(?[0-9]+)", // Part if often ending of filename - "(?[0-9]+)$", + @"(?[0-9]+)$", // Sometimes named as 0001_005 (chapter_part) "(?[0-9]+)_(?[0-9]+)", // Some audiobooks are ripped from cd's, and will be named by disk number. @@ -579,7 +579,7 @@ namespace Emby.Naming.Common { // Detect year usually in brackets after name Batman (2020) @"^(?.+?)\s*\(\s*(?\d{4})\s*\)\s*$", - @"^\s*(?.+?)\s*$" + @"^\s*(?[^ ].*?)\s*$" }; var extensions = VideoFileExtensions.ToList(); diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs index a246999628..e5768b6209 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs @@ -26,14 +26,16 @@ namespace Jellyfin.Naming.Tests.AudioBook "Batman/Chapter 2.mp3", "Batman/Chapter 3.mp3", - "Ready Player One (2020)/Ready Player One.mp3", - "Ready Player One (2020)/extra.mp3", - "Badman/audiobook.mp3", "Badman/extra.mp3", - "Superman (2020)/book.mp3", - "Superman (2020)/extra.mp3" + "Superman (2020)/Part 1.mp3", + "Superman (2020)/extra.mp3", + + "Ready Player One (2020)/audiobook.mp3", + "Ready Player One (2020)/extra.mp3", + + ".mp3" }; var resolver = GetResolver(); @@ -44,7 +46,9 @@ namespace Jellyfin.Naming.Tests.AudioBook FullName = i })).ToList(); - Assert.Equal(4, result[0].Files.Count); + Assert.Equal(5, result.Count); + + Assert.Equal(2, result[0].Files.Count); Assert.Single(result[0].Extras); Assert.Equal("Harry Potter and the Deathly Hallows", result[0].Name); @@ -52,13 +56,17 @@ namespace Jellyfin.Naming.Tests.AudioBook Assert.Empty(result[1].Extras); Assert.Equal("Batman", result[1].Name); - Assert.Equal(2, result[2].Files.Count); + Assert.Single(result[2].Files); Assert.Single(result[2].Extras); Assert.Equal("Badman", result[2].Name); - Assert.Equal(2, result[3].Files.Count); + Assert.Single(result[3].Files); Assert.Single(result[3].Extras); Assert.Equal("Superman", result[3].Name); + + Assert.Single(result[4].Files); + Assert.Single(result[4].Extras); + Assert.Equal("Ready Player One", result[4].Name); } [Fact] @@ -69,12 +77,9 @@ namespace Jellyfin.Naming.Tests.AudioBook "Harry Potter and the Deathly Hallows/Chapter 1.ogg", "Harry Potter and the Deathly Hallows/Chapter 1.mp3", - "Aqua-man/book.mp3", - "Deadpool.mp3", "Deadpool [HQ].mp3", - "Superman/book.mp3", "Superman/audiobook.mp3", "Superman/Superman.mp3", "Superman/Superman [HQ].mp3", @@ -92,27 +97,24 @@ namespace Jellyfin.Naming.Tests.AudioBook FullName = i })).ToList(); - Assert.Equal(6, result[0].Files.Count); + Assert.Equal(5, result.Count); // HP - Same name so we don't care which file is alternative Assert.Single(result[0].AlternateVersions); - // Aqua-man - Assert.Empty(result[1].AlternateVersions); // DP - Assert.Empty(result[2].AlternateVersions); + Assert.Empty(result[1].AlternateVersions); // DP HQ (directory missing so we do not group deadpools together) - Assert.Empty(result[3].AlternateVersions); + Assert.Empty(result[2].AlternateVersions); // Superman // Priority: // 1. Name // 2. audiobook - // 3. book - // 4. Names with modifiers - Assert.Equal(3, result[4].AlternateVersions.Count); - Assert.Equal("Superman/audiobook.mp3", result[4].AlternateVersions[0].Path); - Assert.Equal("Superman/book.mp3", result[4].AlternateVersions[1].Path); - Assert.Equal("Superman/Superman [HQ].mp3", result[4].AlternateVersions[2].Path); + // 3. Names with modifiers + Assert.Equal(2, result[3].AlternateVersions.Count); + var paths = result[3].AlternateVersions.Select(x => x.Path).ToList(); + Assert.Contains("Superman/audiobook.mp3", paths); + Assert.Contains("Superman/Superman [HQ].mp3", paths); // Batman - Assert.Single(result[5].AlternateVersions); + Assert.Single(result[4].AlternateVersions); } [Fact] From aef1fe62c216612f3f42b2e19496730b56b155ce Mon Sep 17 00:00:00 2001 From: Stepan Date: Tue, 3 Nov 2020 16:25:33 +0100 Subject: [PATCH 17/61] Complete test coverage for Emby.Naming.Subtitles --- tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs index d11809de11..5152098908 100644 --- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs +++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Emby.Naming.Common; using Emby.Naming.Subtitles; using Xunit; @@ -26,6 +26,7 @@ namespace Jellyfin.Naming.Tests.Subtitles Assert.Equal(language, result?.Language, true); Assert.Equal(isDefault, result?.IsDefault); Assert.Equal(isForced, result?.IsForced); + Assert.Equal(input, result?.Path); } [Theory] From c96aa0551db48063dba74196816ffe51a839b44d Mon Sep 17 00:00:00 2001 From: Stepan Date: Thu, 5 Nov 2020 13:25:29 +0100 Subject: [PATCH 18/61] Added NamingOptions tests --- .../Common/NamingOptionsTest.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs diff --git a/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs b/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs new file mode 100644 index 0000000000..3892d00f61 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs @@ -0,0 +1,36 @@ +using Emby.Naming.Common; +using Xunit; + +namespace Jellyfin.Naming.Tests.Common +{ + public class NamingOptionsTest + { + [Fact] + public void TestNamingOptionsCompile() + { + var options = new NamingOptions(); + + Assert.NotEmpty(options.VideoFileStackingRegexes); + Assert.NotEmpty(options.CleanDateTimeRegexes); + Assert.NotEmpty(options.CleanStringRegexes); + Assert.NotEmpty(options.EpisodeWithoutSeasonRegexes); + Assert.NotEmpty(options.EpisodeMultiPartRegexes); + } + + [Fact] + public void TestNamingOptionsEpisodeExpressions() + { + var exp = new EpisodeExpression(string.Empty); + + Assert.False(exp.IsOptimistic); + exp.IsOptimistic = true; + Assert.True(exp.IsOptimistic); + + Assert.Equal(string.Empty, exp.Expression); + Assert.NotNull(exp.Regex); + exp.Expression = "test"; + Assert.Equal("test", exp.Expression); + Assert.NotNull(exp.Regex); + } + } +} From 57411503673c07f4130b73c578c06ccfecc4c612 Mon Sep 17 00:00:00 2001 From: Stepan Date: Thu, 5 Nov 2020 14:51:27 +0100 Subject: [PATCH 19/61] Enable MultiVersion video tests and added support for naming based on tests 11 & 8 --- Emby.Naming/Common/NamingOptions.cs | 2 +- Emby.Naming/Video/VideoListResolver.cs | 6 ++ .../Video/MultiVersionTests.cs | 79 ++++++++++--------- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index d2f07817a6..3a7bcb7d72 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -133,7 +133,7 @@ namespace Emby.Naming.Common CleanStrings = new[] { - @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)", + @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"(\[.*\])" }; diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 190562cfcd..be9b4959a7 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -212,9 +212,15 @@ namespace Emby.Naming.Video if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) { + if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) + { + testFilename = cleanName.ToString(); + } + testFilename = testFilename.Substring(folderName.Length).Trim(); return string.IsNullOrEmpty(testFilename) || testFilename[0] == '-' + || testFilename[0] == '_' || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); } diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index 4198d69ff8..9df6904ef6 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Model.IO; @@ -11,8 +12,8 @@ namespace Jellyfin.Naming.Tests.Video private readonly NamingOptions _namingOptions = new NamingOptions(); // FIXME - // [Fact] - private void TestMultiEdition1() + [Fact] + public void TestMultiEdition1() { var files = new[] { @@ -35,8 +36,8 @@ namespace Jellyfin.Naming.Tests.Video } // FIXME - // [Fact] - private void TestMultiEdition2() + [Fact] + public void TestMultiEdition2() { var files = new[] { @@ -81,8 +82,8 @@ namespace Jellyfin.Naming.Tests.Video } // FIXME - // [Fact] - private void TestLetterFolders() + [Fact] + public void TestLetterFolders() { var files = new[] { @@ -109,8 +110,8 @@ namespace Jellyfin.Naming.Tests.Video } // FIXME - // [Fact] - private void TestMultiVersionLimit() + [Fact] + public void TestMultiVersionLimit() { var files = new[] { @@ -138,8 +139,8 @@ namespace Jellyfin.Naming.Tests.Video } // FIXME - // [Fact] - private void TestMultiVersionLimit2() + [Fact] + public void TestMultiVersionLimit2() { var files = new[] { @@ -168,8 +169,8 @@ namespace Jellyfin.Naming.Tests.Video } // FIXME - // [Fact] - private void TestMultiVersion3() + [Fact] + public void TestMultiVersion3() { var files = new[] { @@ -194,8 +195,8 @@ namespace Jellyfin.Naming.Tests.Video } // FIXME - // [Fact] - private void TestMultiVersion4() + [Fact] + public void TestMultiVersion4() { // Test for false positive @@ -221,9 +222,8 @@ namespace Jellyfin.Naming.Tests.Video Assert.Empty(result[0].AlternateVersions); } - // FIXME - // [Fact] - private void TestMultiVersion5() + [Fact] + public void TestMultiVersion5() { var files = new[] { @@ -254,8 +254,8 @@ namespace Jellyfin.Naming.Tests.Video } // FIXME - // [Fact] - private void TestMultiVersion6() + [Fact] + public void TestMultiVersion6() { var files = new[] { @@ -285,9 +285,8 @@ namespace Jellyfin.Naming.Tests.Video Assert.True(result[0].AlternateVersions[5].Is3D); } - // FIXME - // [Fact] - private void TestMultiVersion7() + [Fact] + public void TestMultiVersion7() { var files = new[] { @@ -306,12 +305,9 @@ namespace Jellyfin.Naming.Tests.Video Assert.Equal(2, result.Count); } - // FIXME - // [Fact] - private void TestMultiVersion8() + [Fact] + public void TestMultiVersion8() { - // This is not actually supported yet - var files = new[] { @"/movies/Iron Man/Iron Man.mkv", @@ -339,9 +335,8 @@ namespace Jellyfin.Naming.Tests.Video Assert.True(result[0].AlternateVersions[4].Is3D); } - // FIXME - // [Fact] - private void TestMultiVersion9() + [Fact] + public void TestMultiVersion9() { // Test for false positive @@ -367,9 +362,8 @@ namespace Jellyfin.Naming.Tests.Video Assert.Empty(result[0].AlternateVersions); } - // FIXME - // [Fact] - private void TestMultiVersion10() + [Fact] + public void TestMultiVersion10() { var files = new[] { @@ -390,12 +384,9 @@ namespace Jellyfin.Naming.Tests.Video Assert.Single(result[0].AlternateVersions); } - // FIXME - // [Fact] - private void TestMultiVersion11() + [Fact] + public void TestMultiVersion11() { - // Currently not supported but we should probably handle this. - var files = new[] { @"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [1080p] Blu-ray.x264.DTS.mkv", @@ -415,6 +406,16 @@ namespace Jellyfin.Naming.Tests.Video Assert.Single(result[0].AlternateVersions); } + [Fact] + public void TestEmptyList() + { + var resolver = GetResolver(); + + var result = resolver.Resolve(new List()).ToList(); + + Assert.Empty(result); + } + private VideoListResolver GetResolver() { return new VideoListResolver(_namingOptions); From 3466dc558186f435eafae9b9d3fd80621fdbd79f Mon Sep 17 00:00:00 2001 From: Stepan Date: Thu, 5 Nov 2020 16:59:15 +0100 Subject: [PATCH 20/61] Finish coverage for Emby.Naming.Video --- Emby.Naming/Common/NamingOptions.cs | 6 +- Emby.Naming/Video/ExtraResolver.cs | 7 +- Emby.Naming/Video/FlagParser.cs | 2 +- Emby.Naming/Video/StackResolver.cs | 8 +- Emby.Naming/Video/StubResolver.cs | 2 +- Emby.Naming/Video/VideoFileInfo.cs | 34 ++- Emby.Naming/Video/VideoResolver.cs | 28 +-- .../Jellyfin.Naming.Tests/Video/ExtraTests.cs | 25 +- .../Jellyfin.Naming.Tests/Video/StubTests.cs | 3 +- .../Video/VideoListResolverTests.cs | 29 ++- .../Video/VideoResolverTests.cs | 235 +++++++++--------- 11 files changed, 230 insertions(+), 149 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 3a7bcb7d72..a1b95954ec 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -120,9 +120,9 @@ namespace Emby.Naming.Common VideoFileStackingExpressions = new[] { - "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(.*?)(\\.[^.]+)$", - "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(.*?)(\\.[^.]+)$", - "(.*?)([ ._-]*[a-d])(.*?)(\\.[^.]+)$" + "(?.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$", + "(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$", + "(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$" }; CleanDateTimes = new[] diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index fc0424faab..bd78299dcd 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -45,7 +45,8 @@ namespace Emby.Naming.Video } else { - return result; + // Currently unreachable code if new rule.MediaType is desired add if clause with proper tests + throw new InvalidOperationException(); } if (rule.RuleType == ExtraRuleType.Filename) @@ -70,6 +71,9 @@ namespace Emby.Naming.Video } else if (rule.RuleType == ExtraRuleType.Regex) { + // Currently unreachable code if new rule.MediaType is desired add if clause with proper tests + throw new InvalidOperationException(); + /* var filename = Path.GetFileName(path); var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); @@ -79,6 +83,7 @@ namespace Emby.Naming.Video result.ExtraType = rule.ExtraType; result.Rule = rule; } + */ } else if (rule.RuleType == ExtraRuleType.DirectoryName) { diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs index 27ca1abf1a..6015c41a0c 100644 --- a/Emby.Naming/Video/FlagParser.cs +++ b/Emby.Naming/Video/FlagParser.cs @@ -24,7 +24,7 @@ namespace Emby.Naming.Video { if (string.IsNullOrEmpty(path)) { - throw new ArgumentNullException(nameof(path)); + return Array.Empty<string>(); } // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _. diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index e11b4063ce..30b812e21d 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -86,10 +86,10 @@ namespace Emby.Naming.Video if (match1.Success) { - var title1 = match1.Groups[1].Value; - var volume1 = match1.Groups[2].Value; - var ignore1 = match1.Groups[3].Value; - var extension1 = match1.Groups[4].Value; + var title1 = match1.Groups["title"].Value; + var volume1 = match1.Groups["volume"].Value; + var ignore1 = match1.Groups["ignore"].Value; + var extension1 = match1.Groups["extension"].Value; var j = i + 1; while (j < list.Count) diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index f1b5d7bcca..b0eb92e53e 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -14,7 +14,7 @@ namespace Emby.Naming.Video { stubType = default; - if (path == null) + if (string.IsNullOrEmpty(path)) { return false; } diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index 12bd8c4364..7d7411a563 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -7,6 +7,35 @@ namespace Emby.Naming.Video /// </summary> public class VideoFileInfo { + /// <summary> + /// Initializes a new instance of the <see cref="VideoFileInfo"/> class. + /// </summary> + /// <param name="name">Name of file.</param> + /// <param name="path">Path to the file.</param> + /// <param name="container">Container type.</param> + /// <param name="year">Year of release.</param> + /// <param name="extraType">Extra type.</param> + /// <param name="extraRule">Extra rule.</param> + /// <param name="format3D">Format 3D.</param> + /// <param name="is3D">Is 3D.</param> + /// <param name="isStub">Is Stub.</param> + /// <param name="stubType">Stub type.</param> + /// <param name="isDirectory">Is directory.</param> + public VideoFileInfo(string name, string? path, string? container, int? year = default, ExtraType? extraType = default, ExtraRule? extraRule = default, string? format3D = default, bool is3D = default, bool isStub = default, string? stubType = default, bool isDirectory = default) + { + Path = path; + Container = container; + Name = name; + Year = year; + ExtraType = extraType; + ExtraRule = extraRule; + Format3D = format3D; + Is3D = is3D; + IsStub = isStub; + StubType = stubType; + IsDirectory = isDirectory; + } + /// <summary> /// Gets or sets the path. /// </summary> @@ -23,7 +52,7 @@ namespace Emby.Naming.Video /// Gets or sets the name. /// </summary> /// <value>The name.</value> - public string? Name { get; set; } + public string Name { get; set; } /// <summary> /// Gets or sets the year. @@ -84,8 +113,7 @@ namespace Emby.Naming.Video /// <inheritdoc /> public override string ToString() { - // Makes debugging easier - return Name ?? base.ToString(); + return "VideoFileInfo(Name: '" + Name + "')"; } } } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index b9ff90179f..fed567d03d 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -49,7 +49,7 @@ namespace Emby.Naming.Video { if (string.IsNullOrEmpty(path)) { - throw new ArgumentNullException(nameof(path)); + return null; } bool isStub = false; @@ -99,20 +99,18 @@ namespace Emby.Naming.Video } } - return new VideoFileInfo - { - Path = path, - Container = container, - IsStub = isStub, - Name = name, - Year = year, - StubType = stubType, - Is3D = format3DResult.Is3D, - Format3D = format3DResult.Format3D, - ExtraType = extraResult.ExtraType, - IsDirectory = isDirectory, - ExtraRule = extraResult.Rule - }; + return new VideoFileInfo( + path: path, + container: container, + isStub: isStub, + name: name, + year: year, + stubType: stubType, + is3D: format3DResult.Is3D, + format3D: format3DResult.Format3D, + extraType: extraResult.ExtraType, + isDirectory: isDirectory, + extraRule: extraResult.Rule); } public bool IsVideoFile(string path) diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index 8dfb8f8591..12a9b023bf 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -1,7 +1,9 @@ -using Emby.Naming.Common; +using System; +using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Model.Entities; using Xunit; +using MediaType = Emby.Naming.Common.MediaType; namespace Jellyfin.Naming.Tests.Video { @@ -93,6 +95,27 @@ namespace Jellyfin.Naming.Tests.Video } } + [Fact] + public void TestExtraInfo_InvalidRuleMediaType() + { + var options = new NamingOptions { VideoExtraRules = new[] { new ExtraRule(ExtraType.Unknown, ExtraRuleType.DirectoryName, " ", MediaType.Photo) } }; + Assert.Throws<InvalidOperationException>(() => GetExtraTypeParser(options).GetExtraInfo("sample.jpg")); + } + + [Fact] + public void TestExtraInfo_InvalidRuleType() + { + var options = new NamingOptions { VideoExtraRules = new[] { new ExtraRule(ExtraType.Unknown, ExtraRuleType.Regex, " ", MediaType.Video) } }; + Assert.Throws<InvalidOperationException>(() => GetExtraTypeParser(options).GetExtraInfo("sample.mp4")); + } + + [Fact] + public void TestFlagsParser() + { + var flags = new FlagParser(_videoOptions).GetFlags(string.Empty); + Assert.Empty(flags); + } + private ExtraResolver GetExtraTypeParser(NamingOptions videoOptions) { return new ExtraResolver(videoOptions); diff --git a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs index 30ba941365..6e759c6d6b 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs @@ -1,4 +1,4 @@ -using Emby.Naming.Common; +using Emby.Naming.Common; using Emby.Naming.Video; using Xunit; @@ -23,6 +23,7 @@ namespace Jellyfin.Naming.Tests.Video Test("video.hdtv.disc", true, "tv"); Test("video.pdtv.disc", true, "tv"); Test("video.dsr.disc", true, "tv"); + Test(string.Empty, false, "tv"); } [Fact] diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index 12c4a50fe3..215c7e5405 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Model.IO; @@ -369,6 +369,26 @@ namespace Jellyfin.Naming.Tests.Video Assert.Single(result); } + [Fact] + public void TestFourRooms() + { + var files = new[] + { + @"Four Rooms - A.avi", + @"Four Rooms - A.mp4" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + }).ToList()).ToList(); + + Assert.Equal(2, result.Count); + } + [Fact] public void TestMovieTrailer() { @@ -431,6 +451,13 @@ namespace Jellyfin.Naming.Tests.Video Assert.Single(result); } + [Fact] + public void TestDirectoryStack() + { + var stack = new FileStack(); + Assert.False(stack.ContainsFile("XX", true)); + } + private VideoListResolver GetResolver() { return new VideoListResolver(_namingOptions); diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index 99828b2eb7..3bdafa84d9 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Model.Entities; @@ -14,165 +15,135 @@ namespace Jellyfin.Naming.Tests.Video { yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/7 Psychos.mkv/7 Psychos.mkv", - Container = "mkv", - Name = "7 Psychos" - } + new VideoFileInfo( + path: @"/server/Movies/7 Psychos.mkv/7 Psychos.mkv", + container: "mkv", + name: "7 Psychos") }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv", - Container = "mkv", - Name = "3 days to kill", - Year = 2005 - } + new VideoFileInfo( + path: @"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv", + container: "mkv", + name: "3 days to kill", + year: 2005) }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/American Psycho/American.Psycho.mkv", - Container = "mkv", - Name = "American.Psycho", - } + new VideoFileInfo( + path: @"/server/Movies/American Psycho/American.Psycho.mkv", + container: "mkv", + name: "American.Psycho") }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv", - Container = "mkv", - Name = "brave", - Year = 2006, - Is3D = true, - Format3D = "sbs", - } + new VideoFileInfo( + path: @"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv", + container: "mkv", + name: "brave", + year: 2006, + is3D: true, + format3D: "sbs") }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv", - Container = "mkv", - Name = "300", - Year = 2006 - } + new VideoFileInfo( + path: @"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv", + container: "mkv", + name: "300", + year: 2006) }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv", - Container = "mkv", - Name = "300", - Year = 2006, - Is3D = true, - Format3D = "sbs", - } + new VideoFileInfo( + path: @"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv", + container: "mkv", + name: "300", + year: 2006, + is3D: true, + format3D: "sbs") }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc", - Container = "disc", - Name = "brave", - Year = 2006, - IsStub = true, - StubType = "bluray", - } + new VideoFileInfo( + path: @"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc", + container: "disc", + name: "brave", + year: 2006, + isStub: true, + stubType: "bluray") }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc", - Container = "disc", - Name = "300", - Year = 2006, - IsStub = true, - StubType = "bluray", - } + new VideoFileInfo( + path: @"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc", + container: "disc", + name: "300", + year: 2006, + isStub: true, + stubType: "bluray") }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/Brave (2007)/Brave (2006).bluray.disc", - Container = "disc", - Name = "Brave", - Year = 2006, - IsStub = true, - StubType = "bluray", - } + new VideoFileInfo( + path: @"/server/Movies/Brave (2007)/Brave (2006).bluray.disc", + container: "disc", + name: "Brave", + year: 2006, + isStub: true, + stubType: "bluray") }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/300 (2007)/300 (2006).bluray.disc", - Container = "disc", - Name = "300", - Year = 2006, - IsStub = true, - StubType = "bluray", - } + new VideoFileInfo( + path: @"/server/Movies/300 (2007)/300 (2006).bluray.disc", + container: "disc", + name: "300", + year: 2006, + isStub: true, + stubType: "bluray") }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/300 (2007)/300 (2006)-trailer.mkv", - Container = "mkv", - Name = "300", - Year = 2006, - ExtraType = ExtraType.Trailer, - } + new VideoFileInfo( + path: @"/server/Movies/300 (2007)/300 (2006)-trailer.mkv", + container: "mkv", + name: "300", + year: 2006, + extraType: ExtraType.Trailer) }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv", - Container = "mkv", - Name = "Brave", - Year = 2006, - ExtraType = ExtraType.Trailer, - } + new VideoFileInfo( + path: @"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv", + container: "mkv", + name: "Brave", + year: 2006, + extraType: ExtraType.Trailer) }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/300 (2007)/300 (2006).mkv", - Container = "mkv", - Name = "300", - Year = 2006 - } + new VideoFileInfo( + path: @"/server/Movies/300 (2007)/300 (2006).mkv", + container: "mkv", + name: "300", + year: 2006) }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv", - Container = "mkv", - Name = "Bad Boys", - Year = 1995, - } + new VideoFileInfo( + path: @"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv", + container: "mkv", + name: "Bad Boys", + year: 1995) }; yield return new object[] { - new VideoFileInfo() - { - Path = @"/server/Movies/Brave (2007)/Brave (2006).mkv", - Container = "mkv", - Name = "Brave", - Year = 2006, - } + new VideoFileInfo( + path: @"/server/Movies/Brave (2007)/Brave (2006).mkv", + container: "mkv", + name: "Brave", + year: 2006) }; } @@ -194,6 +165,34 @@ namespace Jellyfin.Naming.Tests.Video Assert.Equal(result?.StubType, expectedResult.StubType); Assert.Equal(result?.IsDirectory, expectedResult.IsDirectory); Assert.Equal(result?.FileNameWithoutExtension, expectedResult.FileNameWithoutExtension); + Assert.Equal(result?.ToString(), expectedResult.ToString()); + } + + [Fact] + public void ResolveFile_EmptyPath() + { + var result = new VideoResolver(_namingOptions).ResolveFile(string.Empty); + + Assert.Null(result); + } + + [Fact] + public void ResolveDirectoryTest() + { + var paths = new[] + { + @"/Server/Iron Man", + @"Batman", + string.Empty + }; + + var resolver = new VideoResolver(_namingOptions); + var results = paths.Select(path => resolver.ResolveDirectory(path)).ToList(); + + Assert.Equal(3, results.Count); + Assert.NotNull(results[0]); + Assert.NotNull(results[1]); + Assert.Null(results[2]); } } } From e96e480f01632ef785e7ead3cde908e57aba738c Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Fri, 6 Nov 2020 15:52:01 +0100 Subject: [PATCH 21/61] Add comment with match cases for weir EpisodeExpression and named group for some date EpisodeExpressions --- Emby.Naming/Common/NamingOptions.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index a1b95954ec..4325cf2369 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -246,7 +246,7 @@ namespace Emby.Naming.Common }, // <!-- foo.ep01, foo.EP_01 --> new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), - new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true) + new EpisodeExpression("(?<year>[0-9]{4})[\\.-](?<month>[0-9]{2})[\\.-](?<day>[0-9]{2})", true) { DateTimeFormats = new[] { @@ -255,7 +255,7 @@ namespace Emby.Naming.Common "yyyy_MM_dd" } }, - new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true) + new EpisodeExpression(@"(?<day>[0-9]{2})[.-](?<month>[0-9]{2})[.-](?<year>[0-9]{4})", true) { DateTimeFormats = new[] { @@ -277,6 +277,11 @@ namespace Emby.Naming.Common { SupportsAbsoluteEpisodeNumbers = true }, + + // Case Closed (1996-2007)/Case Closed - 317.mkv + // /server/anything_102.mp4 + // /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv + // /server/anything_1996.11.14.mp4 new EpisodeExpression(@"[\\\\/\\._ -](?<seriesname>(?![0-9]+[0-9][0-9])([^\\\/])*)[\\\\/\\._ -](?<seasonnumber>[0-9]+)(?<epnumber>[0-9][0-9](?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([\\._ -][^\\\\/]*)$") { IsOptimistic = true, From f22e0800e272b9f0aac198243a44196421af5dff Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Sat, 7 Nov 2020 11:02:12 +0100 Subject: [PATCH 22/61] Episode parsing coverage --- Emby.Naming/Common/NamingOptions.cs | 2 +- Emby.Naming/TV/EpisodePathParser.cs | 7 +- .../TV/EpisodePathParserTest.cs | 99 ++++++++++++++----- 3 files changed, 77 insertions(+), 31 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 4325cf2369..471491d22c 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -282,7 +282,7 @@ namespace Emby.Naming.Common // /server/anything_102.mp4 // /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv // /server/anything_1996.11.14.mp4 - new EpisodeExpression(@"[\\\\/\\._ -](?<seriesname>(?![0-9]+[0-9][0-9])([^\\\/])*)[\\\\/\\._ -](?<seasonnumber>[0-9]+)(?<epnumber>[0-9][0-9](?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([\\._ -][^\\\\/]*)$") + new EpisodeExpression(@"[\\/._ -](?<seriesname>(?![0-9]+[0-9][0-9])([^\\\/_])*)[\\\/._ -](?<seasonnumber>[0-9]+)(?<epnumber>[0-9][0-9](?:(?:[a-i]|\.[1-9])(?![0-9]))?)([._ -][^\\\/]*)$") { IsOptimistic = true, IsNamed = true, diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index 866d8adc00..d9cc8172bd 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -186,7 +186,7 @@ namespace Emby.Naming.TV private void FillAdditional(string path, EpisodePathParserResult info) { - var expressions = _options.MultipleEpisodeExpressions.ToList(); + var expressions = _options.MultipleEpisodeExpressions.Where(i => i.IsNamed).ToList(); if (string.IsNullOrEmpty(info.SeriesName)) { @@ -200,11 +200,6 @@ namespace Emby.Naming.TV { foreach (var i in expressions) { - if (!i.IsNamed) - { - continue; - } - var result = Parse(path, i); if (!result.Success) diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs index 03aeb7f76b..57f382b382 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs @@ -1,4 +1,4 @@ -using Emby.Naming.Common; +using Emby.Naming.Common; using Emby.Naming.TV; using Xunit; @@ -7,43 +7,94 @@ namespace Jellyfin.Naming.Tests.TV public class EpisodePathParserTest { [Theory] - [InlineData("/media/Foo/Foo-S01E01", "Foo", 1, 1)] - [InlineData("/media/Foo - S04E011", "Foo", 4, 11)] - [InlineData("/media/Foo/Foo s01x01", "Foo", 1, 1)] - [InlineData("/media/Foo (2019)/Season 4/Foo (2019).S04E03", "Foo (2019)", 4, 3)] - [InlineData("D:\\media\\Foo\\Foo-S01E01", "Foo", 1, 1)] - [InlineData("D:\\media\\Foo - S04E011", "Foo", 4, 11)] - [InlineData("D:\\media\\Foo\\Foo s01x01", "Foo", 1, 1)] - [InlineData("D:\\media\\Foo (2019)\\Season 4\\Foo (2019).S04E03", "Foo (2019)", 4, 3)] - [InlineData("/Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", "Elementary", 2, 3)] - [InlineData("/Season 1/seriesname S01E02 blah.avi", "seriesname", 1, 2)] - [InlineData("/Running Man/Running Man S2017E368.mkv", "Running Man", 2017, 368)] - [InlineData("/Season 1/seriesname 01x02 blah.avi", "seriesname", 1, 2)] - [InlineData("/Season 25/The Simpsons.S25E09.Steal this episode.mp4", "The Simpsons", 25, 9)] - [InlineData("/Season 1/seriesname S01x02 blah.avi", "seriesname", 1, 2)] - [InlineData("/Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", "Elementary", 2, 3)] - [InlineData("/Season 1/seriesname S01xE02 blah.avi", "seriesname", 1, 2)] - [InlineData("/Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", "Elementary", 2, 3)] - [InlineData("/Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", "Elementary", 2, 3)] - [InlineData("/Season 02/Elementary - 02x03-E15 - Ep Name.mp4", "Elementary", 2, 3)] - [InlineData("/Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", "Elementary", 1, 23)] - [InlineData("/The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", "The Wonder Years", 4, 7)] + [InlineData("/media/Foo/Foo-S01E01", true, "Foo", 1, 1)] + [InlineData("/media/Foo - S04E011", true, "Foo", 4, 11)] + [InlineData("/media/Foo/Foo s01x01", true, "Foo", 1, 1)] + [InlineData("/media/Foo (2019)/Season 4/Foo (2019).S04E03", true, "Foo (2019)", 4, 3)] + [InlineData("D:\\media\\Foo\\Foo-S01E01", true, "Foo", 1, 1)] + [InlineData("D:\\media\\Foo - S04E011", true, "Foo", 4, 11)] + [InlineData("D:\\media\\Foo\\Foo s01x01", true, "Foo", 1, 1)] + [InlineData("D:\\media\\Foo (2019)\\Season 4\\Foo (2019).S04E03", true, "Foo (2019)", 4, 3)] + [InlineData("/Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", false, "Elementary", 2, 3)] + [InlineData("/Season 1/seriesname S01E02 blah.avi", false, "seriesname", 1, 2)] + [InlineData("/Running Man/Running Man S2017E368.mkv", false, "Running Man", 2017, 368)] + [InlineData("/Season 1/seriesname 01x02 blah.avi", false, "seriesname", 1, 2)] + [InlineData("/Season 25/The Simpsons.S25E09.Steal this episode.mp4", false, "The Simpsons", 25, 9)] + [InlineData("/Season 1/seriesname S01x02 blah.avi", false, "seriesname", 1, 2)] + [InlineData("/Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", false, "Elementary", 2, 3)] + [InlineData("/Season 1/seriesname S01xE02 blah.avi", false, "seriesname", 1, 2)] + [InlineData("/Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", false, "Elementary", 2, 3)] + [InlineData("/Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", false, "Elementary", 2, 3)] + [InlineData("/Season 02/Elementary - 02x03-E15 - Ep Name.mp4", false, "Elementary", 2, 3)] + [InlineData("/Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", false, "Elementary", 1, 23)] + [InlineData("/The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", false, "The Wonder Years", 4, 7)] // TODO: [InlineData("/Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", "Castle Rock", 2, 1)] // TODO: [InlineData("/After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", "After Life", 1, 6)] // TODO: [InlineData("/Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", "Uchuu Senkan Yamoto 2199", 4, 3)] // TODO: [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", "The Daily Show", 25, 22)] // TODO: [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", "Watchmen (2019)", 1, 3)] // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)] - public void ParseEpisodesCorrectly(string path, string name, int season, int episode) + public void ParseEpisodesCorrectly(string path, bool isDirectory, string name, int season, int episode) { NamingOptions o = new NamingOptions(); EpisodePathParser p = new EpisodePathParser(o); - var res = p.Parse(path, false); + var res = p.Parse(path, isDirectory); Assert.True(res.Success); Assert.Equal(name, res.SeriesName); Assert.Equal(season, res.SeasonNumber); Assert.Equal(episode, res.EpisodeNumber); } + + [Theory] + [InlineData("/test/01-03.avi", true, true)] + public void EpisodePathParserTest_DifferentExpressionsParameters(string path, bool? isNamed, bool? isOptimistic) + { + NamingOptions o = new NamingOptions(); + EpisodePathParser p = new EpisodePathParser(o); + var res = p.Parse(path, false, isNamed, isOptimistic); + + Assert.True(res.Success); + } + + [Fact] + public void EpisodePathParserTest_FalsePositivePixelRate() + { + NamingOptions o = new NamingOptions(); + EpisodePathParser p = new EpisodePathParser(o); + var res = p.Parse("Series Special (1920x1080).mkv", false); + + Assert.False(res.Success); + } + + [Fact] + public void EpisodeResolverTest_WrongExtension() + { + var res = new EpisodeResolver(new NamingOptions()).Resolve("test.mp3", false); + Assert.Null(res); + } + + [Fact] + public void EpisodeResolverTest_WrongExtensionStub() + { + var res = new EpisodeResolver(new NamingOptions()).Resolve("dvd.disc", false); + Assert.NotNull(res); + Assert.True(res!.IsStub); + } + + [Fact] + public void EpisodePathParserTest_EmptyDateParsers() + { + NamingOptions o = new NamingOptions() + { + EpisodeExpressions = new[] { new EpisodeExpression("(([0-9]{4})-([0-9]{2})-([0-9]{2}) [0-9]{2}:[0-9]{2}:[0-9]{2})", true) } + }; + o.Compile(); + + EpisodePathParser p = new EpisodePathParser(o); + var res = p.Parse("ABC_2019_10_21 11:00:00", false); + + Assert.True(res.Success); + } } } From 3d1076ae42433314835f4277ff42cd5ef1e6c016 Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Sat, 7 Nov 2020 12:30:22 +0100 Subject: [PATCH 23/61] Rest of tests for Emby.Naming code coverage --- .../TV/SeasonFolderTests.cs | 38 ++++++++++--------- .../TV/SimpleEpisodeTests.cs | 27 +++++++++++-- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs index 078f940b29..b7b5b54ec8 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs @@ -1,4 +1,4 @@ -using Emby.Naming.TV; +using Emby.Naming.TV; using Xunit; namespace Jellyfin.Naming.Tests.TV @@ -6,26 +6,30 @@ namespace Jellyfin.Naming.Tests.TV public class SeasonFolderTests { [Theory] - [InlineData(@"/Drive/Season 1", 1)] - [InlineData(@"/Drive/Season 2", 2)] - [InlineData(@"/Drive/Season 02", 2)] - [InlineData(@"/Drive/Seinfeld/S02", 2)] - [InlineData(@"/Drive/Seinfeld/2", 2)] - [InlineData(@"/Drive/Season 2009", 2009)] - [InlineData(@"/Drive/Season1", 1)] - [InlineData(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH", 4)] - [InlineData(@"/Drive/Season 7 (2016)", 7)] - [InlineData(@"/Drive/Staffel 7 (2016)", 7)] - [InlineData(@"/Drive/Stagione 7 (2016)", 7)] - [InlineData(@"/Drive/Season (8)", null)] - [InlineData(@"/Drive/3.Staffel", 3)] - [InlineData(@"/Drive/s06e05", null)] - [InlineData(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv", null)] - public void GetSeasonNumberFromPathTest(string path, int? seasonNumber) + [InlineData(@"/Drive/Season 1", 1, true)] + [InlineData(@"/Drive/Season 2", 2, true)] + [InlineData(@"/Drive/Season 02", 2, true)] + [InlineData(@"/Drive/Seinfeld/S02", 2, true)] + [InlineData(@"/Drive/Seinfeld/2", 2, true)] + [InlineData(@"/Drive/Season 2009", 2009, true)] + [InlineData(@"/Drive/Season1", 1, true)] + [InlineData(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH", 4, true)] + [InlineData(@"/Drive/Season 7 (2016)", 7, false)] + [InlineData(@"/Drive/Staffel 7 (2016)", 7, false)] + [InlineData(@"/Drive/Stagione 7 (2016)", 7, false)] + [InlineData(@"/Drive/Season (8)", null, false)] + [InlineData(@"/Drive/3.Staffel", 3, false)] + [InlineData(@"/Drive/s06e05", null, false)] + [InlineData(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv", null, false)] + [InlineData(@"/Drive/extras", 0, true)] + [InlineData(@"/Drive/specials", 0, true)] + public void GetSeasonNumberFromPathTest(string path, int? seasonNumber, bool isSeasonDirectory) { var result = SeasonPathParser.Parse(path, true, true); + Assert.Equal(result.SeasonNumber != null, result.Success); Assert.Equal(result.SeasonNumber, seasonNumber); + Assert.Equal(isSeasonDirectory, result.IsSeasonFolder); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs index 40b41b9f3d..89579c0376 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs @@ -1,4 +1,5 @@ -using Emby.Naming.Common; +using System.IO; +using Emby.Naming.Common; using Emby.Naming.TV; using Xunit; @@ -15,7 +16,6 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1)] [InlineData("/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1)] [InlineData("/server/Temp/S01E02 foo.mp4", "", 1, 2)] - [InlineData("Series/4-12 - The Woman.mp4", "", 4, 12)] [InlineData("Series/4x12 - The Woman.mp4", "", 4, 12)] [InlineData("Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32)] [InlineData("[Baz-Bar]Foo - [1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)] @@ -24,16 +24,37 @@ namespace Jellyfin.Naming.Tests.TV // TODO: [InlineData("[Baz-Bar]Foo - 01 - 12[1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)] // TODO: [InlineData("E:\\Anime\\Yahari Ore no Seishun Love Comedy wa Machigatteiru\\Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku\\Oregairu Zoku 11 - Hayama Hayato Always Renconds to Everyone's Expectations..mkv", "Yahari Ore no Seishun Love Comedy wa Machigatteiru", null, 11)] // TODO: [InlineData(@"/Library/Series/The Grand Tour (2016)/Season 1/S01E01 The Holy Trinity.mkv", "The Grand Tour", 1, 1)] - public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) + public void TestSimple(string path, string seriesName, int? seasonNumber, int? episodeNumber) + { + Test(path, seriesName, seasonNumber, episodeNumber, null); + } + + [Theory] + [InlineData("Series/4-12 - The Woman.mp4", "", 4, 12, 12)] + public void TestWithPossibleEpisodeEnd(string path, string seriesName, int? seasonNumber, int? episodeNumber, int? episodeEndNumber) + { + Test(path, seriesName, seasonNumber, episodeNumber, episodeEndNumber); + } + + private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber, int? episodeEndNumber) { var options = new NamingOptions(); var result = new EpisodeResolver(options) .Resolve(path, false); + Assert.NotNull(result); Assert.Equal(seasonNumber, result?.SeasonNumber); Assert.Equal(episodeNumber, result?.EpisodeNumber); Assert.Equal(seriesName, result?.SeriesName, true); + Assert.Equal(path, result?.Path); + Assert.Equal(Path.GetExtension(path).Substring(1), result?.Container); + Assert.Null(result?.Format3D); + Assert.False(result?.Is3D); + Assert.False(result?.IsStub); + Assert.Null(result?.StubType); + Assert.Equal(episodeEndNumber, result?.EndingEpisodeNumber); + Assert.False(result?.IsByDate); } } } From e8b832ea185a912f9546ae749bc65e7c3584e21f Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sun, 8 Nov 2020 06:42:33 -0700 Subject: [PATCH 24/61] Update MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs Co-authored-by: BaronGreenback <jimcartlidge@yahoo.co.uk> --- MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 967c6fa154..763c56fa6c 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.BaseItemManager if (baseItem.SourceType == SourceType.Channel) { - // hack alert + // Hack alert. return !baseItem.EnableMediaSourceDisplay; } @@ -83,4 +83,4 @@ namespace MediaBrowser.Controller.BaseItemManager return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); } } -} \ No newline at end of file +} From e06b33af83e0555ddc2461bcfa72eaeb7a9bc973 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sun, 8 Nov 2020 06:42:40 -0700 Subject: [PATCH 25/61] Update MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs Co-authored-by: BaronGreenback <jimcartlidge@yahoo.co.uk> --- MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 763c56fa6c..218920b044 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Controller.BaseItemManager { if (baseItem is Channel) { - // hack alert + // Hack alert. return true; } From 1996e08dd12ee92b3cc2ef4d1b9932a6a0b7b27d Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sun, 8 Nov 2020 06:42:49 -0700 Subject: [PATCH 26/61] Update MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs Co-authored-by: BaronGreenback <jimcartlidge@yahoo.co.uk> --- MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 218920b044..5d9b851d36 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.BaseItemManager if (baseItem.SourceType == SourceType.Channel) { - // hack alert + // Hack alert. return !baseItem.EnableMediaSourceDisplay; } From ce88815b3cd0c3be5aba07daf0af0e73e043d91b Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sun, 8 Nov 2020 06:42:54 -0700 Subject: [PATCH 27/61] Update MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs Co-authored-by: BaronGreenback <jimcartlidge@yahoo.co.uk> --- MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 5d9b851d36..67aa7f3383 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.BaseItemManager { if (baseItem is Channel) { - // hack alert + // Hack alert. return true; } From 693760e38ae51b9267f9383c3957df742bb136a6 Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Tue, 10 Nov 2020 17:11:48 +0100 Subject: [PATCH 28/61] Xml-doc part1 --- Emby.Naming/Audio/AlbumParser.cs | 15 ++++++-- Emby.Naming/Audio/AudioFileParser.cs | 12 ++++-- .../AudioBook/AudioBookFilePathParser.cs | 15 ++++++-- .../AudioBookFilePathParserResult.cs | 12 ++++-- .../AudioBook/AudioBookListResolver.cs | 14 ++++++- Emby.Naming/AudioBook/AudioBookNameParser.cs | 15 ++++++-- .../AudioBook/AudioBookNameParserResult.cs | 12 ++++-- Emby.Naming/AudioBook/AudioBookResolver.cs | 15 ++++++-- Emby.Naming/Common/EpisodeExpression.cs | 38 +++++++++++++++---- Emby.Naming/Common/MediaType.cs | 2 - Emby.Naming/Common/NamingOptions.cs | 2 - Emby.Naming/Subtitles/SubtitleInfo.cs | 2 - Emby.Naming/Subtitles/SubtitleParser.cs | 3 -- Emby.Naming/TV/EpisodeInfo.cs | 30 ++++++++++++++- Emby.Naming/TV/EpisodePathParser.cs | 20 ++++++++-- Emby.Naming/TV/EpisodePathParserResult.cs | 33 +++++++++++++++- Emby.Naming/TV/EpisodeResolver.cs | 3 -- Emby.Naming/TV/SeasonPathParser.cs | 2 - Emby.Naming/TV/SeasonPathParserResult.cs | 2 - Emby.Naming/Video/CleanDateTimeParser.cs | 9 +++-- Emby.Naming/Video/CleanDateTimeResult.cs | 19 +++++----- Emby.Naming/Video/CleanStringParser.cs | 10 +++-- Emby.Naming/Video/ExtraResolver.cs | 3 -- Emby.Naming/Video/ExtraResult.cs | 2 - Emby.Naming/Video/ExtraRule.cs | 2 - Emby.Naming/Video/ExtraRuleType.cs | 5 ++- Emby.Naming/Video/FileStack.cs | 2 - Emby.Naming/Video/FlagParser.cs | 2 - Emby.Naming/Video/Format3DParser.cs | 2 - Emby.Naming/Video/Format3DResult.cs | 2 - Emby.Naming/Video/Format3DRule.cs | 2 - Emby.Naming/Video/StackResolver.cs | 2 - Emby.Naming/Video/StubResolver.cs | 3 -- Emby.Naming/Video/StubTypeRule.cs | 2 - Emby.Naming/Video/VideoListResolver.cs | 2 - Emby.Naming/Video/VideoResolver.cs | 3 -- 36 files changed, 218 insertions(+), 101 deletions(-) diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs index b63be3a647..bbfdccc902 100644 --- a/Emby.Naming/Audio/AlbumParser.cs +++ b/Emby.Naming/Audio/AlbumParser.cs @@ -1,6 +1,3 @@ -#nullable enable -#pragma warning disable CS1591 - using System; using System.Globalization; using System.IO; @@ -9,15 +6,27 @@ using Emby.Naming.Common; namespace Emby.Naming.Audio { + /// <summary> + /// Helper class to determine if Album is multipart. + /// </summary> public class AlbumParser { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="AlbumParser"/> class. + /// </summary> + /// <param name="options">Naming options containing AlbumStackingPrefixes.</param> public AlbumParser(NamingOptions options) { _options = options; } + /// <summary> + /// Function that determines if album is multipart. + /// </summary> + /// <param name="path">Path to file.</param> + /// <returns>True if album is multipart.</returns> public bool IsMultiPart(string path) { var filename = Path.GetFileName(path); diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs index 6b2f4be93e..8b47dd12e4 100644 --- a/Emby.Naming/Audio/AudioFileParser.cs +++ b/Emby.Naming/Audio/AudioFileParser.cs @@ -1,6 +1,3 @@ -#nullable enable -#pragma warning disable CS1591 - using System; using System.IO; using System.Linq; @@ -8,8 +5,17 @@ using Emby.Naming.Common; namespace Emby.Naming.Audio { + /// <summary> + /// Static helper class to determine if file at path is audio file. + /// </summary> public static class AudioFileParser { + /// <summary> + /// Static helper method to determine if file at path is audio file. + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="options"><see cref="NamingOptions"/> containing AudioFileExtensions.</param> + /// <returns>True if file at path is audio file.</returns> public static bool IsAudioFile(string path, NamingOptions options) { var extension = Path.GetExtension(path); diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs index 56580f194b..7b4429ab15 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs @@ -1,6 +1,3 @@ -#nullable enable -#pragma warning disable CS1591 - using System.Globalization; using System.IO; using System.Text.RegularExpressions; @@ -8,15 +5,27 @@ using Emby.Naming.Common; namespace Emby.Naming.AudioBook { + /// <summary> + /// Parser class to extract part and/or chapter number from audiobook filename. + /// </summary> public class AudioBookFilePathParser { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="AudioBookFilePathParser"/> class. + /// </summary> + /// <param name="options">Naming options containing AudioBookPartsExpressions.</param> public AudioBookFilePathParser(NamingOptions options) { _options = options; } + /// <summary> + /// Based on regex determines if filename includes part/chapter number. + /// </summary> + /// <param name="path">Path to audiobook file.</param> + /// <returns>Returns <see cref="AudioBookFilePathParser"/> object.</returns> public AudioBookFilePathParserResult Parse(string path) { AudioBookFilePathParserResult result = default; diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs index b65d231dfd..48ab8b57dc 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs @@ -1,12 +1,18 @@ -#nullable enable -#pragma warning disable CS1591 - namespace Emby.Naming.AudioBook { + /// <summary> + /// Data object for passing result of audiobook part/chapter extraction. + /// </summary> public struct AudioBookFilePathParserResult { + /// <summary> + /// Gets or sets optional number of path extracted from audiobook filename. + /// </summary> public int? PartNumber { get; set; } + /// <summary> + /// Gets or sets optional number of chapter extracted from audiobook filename. + /// </summary> public int? ChapterNumber { get; set; } } } diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index e8908aa37c..b203f99022 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.IO; @@ -10,15 +8,27 @@ using MediaBrowser.Model.IO; namespace Emby.Naming.AudioBook { + /// <summary> + /// Class used to resolve Name, Year, alternative files and extras from stack of files. + /// </summary> public class AudioBookListResolver { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="AudioBookListResolver"/> class. + /// </summary> + /// <param name="options">Naming options passed along to <see cref="AudioBookResolver"/> and <see cref="AudioBookNameParser"/>.</param> public AudioBookListResolver(NamingOptions options) { _options = options; } + /// <summary> + /// Resolves Name, Year and differentiate alternative files and extras from regular audiobook files. + /// </summary> + /// <param name="files">List of files related to audiobook.</param> + /// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns> public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files) { var audioBookResolver = new AudioBookResolver(_options); diff --git a/Emby.Naming/AudioBook/AudioBookNameParser.cs b/Emby.Naming/AudioBook/AudioBookNameParser.cs index 7c86161241..120482bc2c 100644 --- a/Emby.Naming/AudioBook/AudioBookNameParser.cs +++ b/Emby.Naming/AudioBook/AudioBookNameParser.cs @@ -1,21 +1,30 @@ -#nullable enable -#pragma warning disable CS1591 - using System.Globalization; using System.Text.RegularExpressions; using Emby.Naming.Common; namespace Emby.Naming.AudioBook { + /// <summary> + /// Helper class to retrieve name and year from audiobook previously retrieved name. + /// </summary> public class AudioBookNameParser { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="AudioBookNameParser"/> class. + /// </summary> + /// <param name="options">Naming options containing AudioBookNamesExpressions.</param> public AudioBookNameParser(NamingOptions options) { _options = options; } + /// <summary> + /// Parse name and year from previously determined name of audiobook. + /// </summary> + /// <param name="name">Name of the audiobook.</param> + /// <returns>Returns <see cref="AudioBookNameParserResult"/> object.</returns> public AudioBookNameParserResult Parse(string name) { AudioBookNameParserResult result = default; diff --git a/Emby.Naming/AudioBook/AudioBookNameParserResult.cs b/Emby.Naming/AudioBook/AudioBookNameParserResult.cs index b28e259dda..3f2d7b2b0b 100644 --- a/Emby.Naming/AudioBook/AudioBookNameParserResult.cs +++ b/Emby.Naming/AudioBook/AudioBookNameParserResult.cs @@ -1,12 +1,18 @@ -#nullable enable -#pragma warning disable CS1591 - namespace Emby.Naming.AudioBook { + /// <summary> + /// Data object used to pass result of name and year parsing. + /// </summary> public struct AudioBookNameParserResult { + /// <summary> + /// Gets or sets name of audiobook. + /// </summary> public string Name { get; set; } + /// <summary> + /// Gets or sets optional year of release. + /// </summary> public int? Year { get; set; } } } diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index c7b3b2d2d1..f6ad3601d7 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -1,6 +1,3 @@ -#nullable enable -#pragma warning disable CS1591 - using System; using System.IO; using System.Linq; @@ -8,15 +5,27 @@ using Emby.Naming.Common; namespace Emby.Naming.AudioBook { + /// <summary> + /// Resolve specifics (path, container, partNumber, chapterNumber) about audiobook file. + /// </summary> public class AudioBookResolver { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="AudioBookResolver"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> containing AudioFileExtensions and also used to pass to AudioBookFilePathParser.</param> public AudioBookResolver(NamingOptions options) { _options = options; } + /// <summary> + /// Resolve specifics (path, container, partNumber, chapterNumber) about audiobook file. + /// </summary> + /// <param name="path">Path to audiobook file.</param> + /// <returns>Returns <see cref="AudioBookResolver"/> object.</returns> public AudioBookFileInfo? Resolve(string path) { if (path.Length == 0 || Path.GetFileNameWithoutExtension(path).Length == 0) diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs index 00b27541a6..19d3c7aab0 100644 --- a/Emby.Naming/Common/EpisodeExpression.cs +++ b/Emby.Naming/Common/EpisodeExpression.cs @@ -1,16 +1,22 @@ -#pragma warning disable CS1591 - using System; using System.Text.RegularExpressions; namespace Emby.Naming.Common { + /// <summary> + /// Regular expressions for parsing TV Episodes. + /// </summary> public class EpisodeExpression { private string _expression; private Regex? _regex; - public EpisodeExpression(string expression, bool byDate) + /// <summary> + /// Initializes a new instance of the <see cref="EpisodeExpression"/> class. + /// </summary> + /// <param name="expression">Regular expressions.</param> + /// <param name="byDate">True if date is expected.</param> + public EpisodeExpression(string expression, bool byDate = false) { _expression = expression; IsByDate = byDate; @@ -18,11 +24,9 @@ namespace Emby.Naming.Common SupportsAbsoluteEpisodeNumbers = true; } - public EpisodeExpression(string expression) - : this(expression, false) - { - } - + /// <summary> + /// Gets or sets raw expressions string. + /// </summary> public string Expression { get => _expression; @@ -33,16 +37,34 @@ namespace Emby.Naming.Common } } + /// <summary> + /// Gets or sets a value indicating whether gets or sets property indicating if date can be find in expression. + /// </summary> public bool IsByDate { get; set; } + /// <summary> + /// Gets or sets a value indicating whether gets or sets property indicating if expression is optimistic. + /// </summary> public bool IsOptimistic { get; set; } + /// <summary> + /// Gets or sets a value indicating whether gets or sets property indicating if expression is named. + /// </summary> public bool IsNamed { get; set; } + /// <summary> + /// Gets or sets a value indicating whether gets or sets property indicating if expression supports episodes with absolute numbers. + /// </summary> public bool SupportsAbsoluteEpisodeNumbers { get; set; } + /// <summary> + /// Gets or sets optional list of date formats used for date parsing. + /// </summary> public string[] DateTimeFormats { get; set; } + /// <summary> + /// Gets a <see cref="Regex"/> expressions objects (creates it if null). + /// </summary> public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled); } } diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs index 148833765f..1231b18871 100644 --- a/Emby.Naming/Common/MediaType.cs +++ b/Emby.Naming/Common/MediaType.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - namespace Emby.Naming.Common { public enum MediaType diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 471491d22c..0f02c03cbb 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Linq; using System.Text.RegularExpressions; diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs index 2f16fb2df9..62cc3ead17 100644 --- a/Emby.Naming/Subtitles/SubtitleInfo.cs +++ b/Emby.Naming/Subtitles/SubtitleInfo.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - namespace Emby.Naming.Subtitles { public class SubtitleInfo diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs index c8659e1b26..476a83cf35 100644 --- a/Emby.Naming/Subtitles/SubtitleParser.cs +++ b/Emby.Naming/Subtitles/SubtitleParser.cs @@ -1,6 +1,3 @@ -#nullable enable -#pragma warning disable CS1591 - using System; using System.IO; using System.Linq; diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs index e01c810628..a8920b36ae 100644 --- a/Emby.Naming/TV/EpisodeInfo.cs +++ b/Emby.Naming/TV/EpisodeInfo.cs @@ -1,9 +1,14 @@ -#pragma warning disable CS1591 - namespace Emby.Naming.TV { + /// <summary> + /// Holder object for Episode information. + /// </summary> public class EpisodeInfo { + /// <summary> + /// Initializes a new instance of the <see cref="EpisodeInfo"/> class. + /// </summary> + /// <param name="path">Path to the file.</param> public EpisodeInfo(string path) { Path = path; @@ -51,18 +56,39 @@ namespace Emby.Naming.TV /// <value>The type of the stub.</value> public string? StubType { get; set; } + /// <summary> + /// Gets or sets optional season number. + /// </summary> public int? SeasonNumber { get; set; } + /// <summary> + /// Gets or sets optional episode number. + /// </summary> public int? EpisodeNumber { get; set; } + /// <summary> + /// Gets or sets optional ending episode number. For multi-episode files 1-13. + /// </summary> public int? EndingEpisodeNumber { get; set; } + /// <summary> + /// Gets or sets optional year of release. + /// </summary> public int? Year { get; set; } + /// <summary> + /// Gets or sets optional year of release. + /// </summary> public int? Month { get; set; } + /// <summary> + /// Gets or sets optional day of release. + /// </summary> public int? Day { get; set; } + /// <summary> + /// Gets or sets a value indicating whether by date expression was used. + /// </summary> public bool IsByDate { get; set; } } } diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index d9cc8172bd..6d0597356b 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 -#nullable enable - using System; using System.Collections.Generic; using System.Globalization; @@ -9,15 +6,32 @@ using Emby.Naming.Common; namespace Emby.Naming.TV { + /// <summary> + /// Used to parse information about episode from path. + /// </summary> public class EpisodePathParser { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="EpisodePathParser"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing EpisodeExpressions and MultipleEpisodeExpressions.</param> public EpisodePathParser(NamingOptions options) { _options = options; } + /// <summary> + /// Parses information about episode from path. + /// </summary> + /// <param name="path">Path.</param> + /// <param name="isDirectory">Is path for a directory or file.</param> + /// <param name="isNamed">Do we want to use IsNamed expressions.</param> + /// <param name="isOptimistic">Do we want to use Optimistic expressions.</param> + /// <param name="supportsAbsoluteNumbers">Do we want to use expressions supporting absolute episode numbers.</param> + /// <param name="fillExtendedInfo">Should we attempt to retrieve extended information.</param> + /// <returns>Returns <see cref="EpisodePathParserResult"/> object.</returns> public EpisodePathParserResult Parse( string path, bool isDirectory, diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs index 5fa0b6f0b4..233d5a4f6c 100644 --- a/Emby.Naming/TV/EpisodePathParserResult.cs +++ b/Emby.Naming/TV/EpisodePathParserResult.cs @@ -1,25 +1,54 @@ -#pragma warning disable CS1591 - namespace Emby.Naming.TV { + /// <summary> + /// Holder object for <see cref="EpisodePathParser"/> result. + /// </summary> public class EpisodePathParserResult { + /// <summary> + /// Gets or sets optional season number. + /// </summary> public int? SeasonNumber { get; set; } + /// <summary> + /// Gets or sets optional episode number. + /// </summary> public int? EpisodeNumber { get; set; } + /// <summary> + /// Gets or sets optional ending episode number. For multi-episode files 1-13. + /// </summary> public int? EndingEpisodeNumber { get; set; } + /// <summary> + /// Gets or sets the name of the series. + /// </summary> + /// <value>The name of the series.</value> public string? SeriesName { get; set; } + /// <summary> + /// Gets or sets a value indicating whether parsing was successful. + /// </summary> public bool Success { get; set; } + /// <summary> + /// Gets or sets a value indicating whether by date expression was used. + /// </summary> public bool IsByDate { get; set; } + /// <summary> + /// Gets or sets optional year of release. + /// </summary> public int? Year { get; set; } + /// <summary> + /// Gets or sets optional year of release. + /// </summary> public int? Month { get; set; } + /// <summary> + /// Gets or sets optional day of release. + /// </summary> public int? Day { get; set; } } } diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index 5f02c553de..26dd6915b9 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 -#nullable enable - using System; using System.IO; using System.Linq; diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs index 142680f0cd..cf99097bc4 100644 --- a/Emby.Naming/TV/SeasonPathParser.cs +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Globalization; using System.IO; diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs index a142fafea0..f52f941a7c 100644 --- a/Emby.Naming/TV/SeasonPathParserResult.cs +++ b/Emby.Naming/TV/SeasonPathParserResult.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - namespace Emby.Naming.TV { public class SeasonPathParserResult diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs index f05d540f8b..0ee633dcc6 100644 --- a/Emby.Naming/Video/CleanDateTimeParser.cs +++ b/Emby.Naming/Video/CleanDateTimeParser.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 -#nullable enable - using System.Collections.Generic; using System.Globalization; using System.Text.RegularExpressions; @@ -12,6 +9,12 @@ namespace Emby.Naming.Video /// </summary> public static class CleanDateTimeParser { + /// <summary> + /// Attempts to clean the name. + /// </summary> + /// <param name="name">Name of video.</param> + /// <param name="cleanDateTimeRegexes">Optional list of regexes to clean the name.</param> + /// <returns>Returns <see cref="CleanDateTimeResult"/> object.</returns> public static CleanDateTimeResult Clean(string name, IReadOnlyList<Regex> cleanDateTimeRegexes) { CleanDateTimeResult result = new CleanDateTimeResult(name); diff --git a/Emby.Naming/Video/CleanDateTimeResult.cs b/Emby.Naming/Video/CleanDateTimeResult.cs index 57eeaa7e32..c675a19d0f 100644 --- a/Emby.Naming/Video/CleanDateTimeResult.cs +++ b/Emby.Naming/Video/CleanDateTimeResult.cs @@ -1,22 +1,21 @@ -#pragma warning disable CS1591 -#nullable enable - namespace Emby.Naming.Video { + /// <summary> + /// Holder structure for name and year. + /// </summary> public readonly struct CleanDateTimeResult { - public CleanDateTimeResult(string name, int? year) + /// <summary> + /// Initializes a new instance of the <see cref="CleanDateTimeResult"/> struct. + /// </summary> + /// <param name="name">Name of video.</param> + /// <param name="year">Year of release.</param> + public CleanDateTimeResult(string name, int? year = null) { Name = name; Year = year; } - public CleanDateTimeResult(string name) - { - Name = name; - Year = null; - } - /// <summary> /// Gets the name. /// </summary> diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index 3f584d5847..09a0cd1893 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 -#nullable enable - using System; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -12,6 +9,13 @@ namespace Emby.Naming.Video /// </summary> public static class CleanStringParser { + /// <summary> + /// Attempts to extract clean name with regular expressions. + /// </summary> + /// <param name="name">Name of file.</param> + /// <param name="expressions">List of regex to parse name and year from.</param> + /// <param name="newName">Parsing result string.</param> + /// <returns>True if parsing was successful.</returns> public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName) { var len = expressions.Count; diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index bd78299dcd..98ea342acc 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -1,9 +1,6 @@ -#pragma warning disable CS1591 - using System; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using Emby.Naming.Audio; using Emby.Naming.Common; diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs index 6be7e60525..f3b8d2a2fc 100644 --- a/Emby.Naming/Video/ExtraResult.cs +++ b/Emby.Naming/Video/ExtraResult.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using MediaBrowser.Model.Entities; namespace Emby.Naming.Video diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs index c018894fdf..a93474bc69 100644 --- a/Emby.Naming/Video/ExtraRule.cs +++ b/Emby.Naming/Video/ExtraRule.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using MediaBrowser.Model.Entities; using MediaType = Emby.Naming.Common.MediaType; diff --git a/Emby.Naming/Video/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs index 98114c7e8b..3243195057 100644 --- a/Emby.Naming/Video/ExtraRuleType.cs +++ b/Emby.Naming/Video/ExtraRuleType.cs @@ -1,7 +1,8 @@ -#pragma warning disable CS1591 - namespace Emby.Naming.Video { + /// <summary> + /// Extra rules type to determine against what <see cref="ExtraRule.Token"/> should be matched. + /// </summary> public enum ExtraRuleType { /// <summary> diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index b0a22b18b0..75620e9610 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs index 6015c41a0c..cd15b4666f 100644 --- a/Emby.Naming/Video/FlagParser.cs +++ b/Emby.Naming/Video/FlagParser.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.IO; using Emby.Naming.Common; diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs index fb881f978f..73ad36af46 100644 --- a/Emby.Naming/Video/Format3DParser.cs +++ b/Emby.Naming/Video/Format3DParser.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Linq; using Emby.Naming.Common; diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs index 36dc1c12b2..539060c982 100644 --- a/Emby.Naming/Video/Format3DResult.cs +++ b/Emby.Naming/Video/Format3DResult.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; namespace Emby.Naming.Video diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs index 7679164b30..bee5c109e1 100644 --- a/Emby.Naming/Video/Format3DRule.cs +++ b/Emby.Naming/Video/Format3DRule.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - namespace Emby.Naming.Video { public class Format3DRule diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index 30b812e21d..d6de468cc9 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index b0eb92e53e..6241a46b03 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 -#nullable enable - using System; using System.IO; using System.Linq; diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs index fa42af6049..df2d3c7d22 100644 --- a/Emby.Naming/Video/StubTypeRule.cs +++ b/Emby.Naming/Video/StubTypeRule.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - namespace Emby.Naming.Video { public class StubTypeRule diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index be9b4959a7..dda3225217 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index fed567d03d..31b47cdf15 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 -#nullable enable - using System; using System.IO; using System.Linq; From 158eff62d75db2d5e6e6f09fbe6e03eac607fc56 Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Tue, 10 Nov 2020 19:23:10 +0100 Subject: [PATCH 29/61] Xml-doc part2 --- Emby.Naming/Common/MediaType.cs | 3 + Emby.Naming/Common/NamingOptions.cs | 87 +++++++++++++++++++ Emby.Naming/Subtitles/SubtitleInfo.cs | 9 ++ Emby.Naming/Subtitles/SubtitleParser.cs | 14 ++- Emby.Naming/TV/EpisodeResolver.cs | 17 ++++ Emby.Naming/TV/SeasonPathParser.cs | 10 +++ Emby.Naming/TV/SeasonPathParserResult.cs | 7 ++ Emby.Naming/Video/ExtraResolver.cs | 12 +++ Emby.Naming/Video/ExtraResult.cs | 3 + Emby.Naming/Video/ExtraRule.cs | 7 ++ Emby.Naming/Video/FileStack.cs | 21 +++++ Emby.Naming/Video/FlagParser.cs | 18 ++++ Emby.Naming/Video/Format3DParser.cs | 12 +++ Emby.Naming/Video/Format3DResult.cs | 6 ++ Emby.Naming/Video/Format3DRule.cs | 8 ++ Emby.Naming/Video/StackResolver.cs | 27 ++++++ Emby.Naming/Video/StubResolver.cs | 10 +++ Emby.Naming/Video/StubTypeRule.cs | 8 ++ Emby.Naming/Video/VideoListResolver.cs | 13 +++ Emby.Naming/Video/VideoResolver.cs | 29 +++++++ .../Subtitles/SubtitleParserTests.cs | 7 +- 21 files changed, 321 insertions(+), 7 deletions(-) diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs index 1231b18871..dc9784c6da 100644 --- a/Emby.Naming/Common/MediaType.cs +++ b/Emby.Naming/Common/MediaType.cs @@ -1,5 +1,8 @@ namespace Emby.Naming.Common { + /// <summary> + /// Type of audiovisual media. + /// </summary> public enum MediaType { /// <summary> diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 0f02c03cbb..035d1b2280 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -8,8 +8,14 @@ using MediaBrowser.Model.Entities; namespace Emby.Naming.Common { + /// <summary> + /// Big ugly class containing lot of different naming options that should be split and injected instead of passes everywhere. + /// </summary> public class NamingOptions { + /// <summary> + /// Initializes a new instance of the <see cref="NamingOptions"/> class. + /// </summary> public NamingOptions() { VideoFileExtensions = new[] @@ -644,58 +650,139 @@ namespace Emby.Naming.Common Compile(); } + /// <summary> + /// Gets or sets list of audio file extensions. + /// </summary> public string[] AudioFileExtensions { get; set; } + /// <summary> + /// Gets or sets list of album stacking prefixes. + /// </summary> public string[] AlbumStackingPrefixes { get; set; } + /// <summary> + /// Gets or sets list of subtitle file extensions. + /// </summary> public string[] SubtitleFileExtensions { get; set; } + /// <summary> + /// Gets or sets list of subtitles flag delimiters. + /// </summary> public char[] SubtitleFlagDelimiters { get; set; } + /// <summary> + /// Gets or sets list of subtitle forced flags. + /// </summary> public string[] SubtitleForcedFlags { get; set; } + /// <summary> + /// Gets or sets list of subtitle default flags. + /// </summary> public string[] SubtitleDefaultFlags { get; set; } + /// <summary> + /// Gets or sets list of episode regular expressions. + /// </summary> public EpisodeExpression[] EpisodeExpressions { get; set; } + /// <summary> + /// Gets or sets list of raw episode without season regular expressions strings. + /// </summary> public string[] EpisodeWithoutSeasonExpressions { get; set; } + /// <summary> + /// Gets or sets list of raw multi-part episodes regular expressions strings. + /// </summary> public string[] EpisodeMultiPartExpressions { get; set; } + /// <summary> + /// Gets or sets list of video file extensions. + /// </summary> public string[] VideoFileExtensions { get; set; } + /// <summary> + /// Gets or sets list of video stub file extensions. + /// </summary> public string[] StubFileExtensions { get; set; } + /// <summary> + /// Gets or sets list of raw audiobook parts regular expressions strings. + /// </summary> public string[] AudioBookPartsExpressions { get; set; } + /// <summary> + /// Gets or sets list of raw audiobook names regular expressions strings. + /// </summary> public string[] AudioBookNamesExpressions { get; set; } + /// <summary> + /// Gets or sets list of stub type rules. + /// </summary> public StubTypeRule[] StubTypes { get; set; } + /// <summary> + /// Gets or sets list of video flag delimiters. + /// </summary> public char[] VideoFlagDelimiters { get; set; } + /// <summary> + /// Gets or sets list of 3D Format rules. + /// </summary> public Format3DRule[] Format3DRules { get; set; } + /// <summary> + /// Gets or sets list of raw video file-stacking expressions strings. + /// </summary> public string[] VideoFileStackingExpressions { get; set; } + /// <summary> + /// Gets or sets list of raw clean DateTimes regular expressions strings. + /// </summary> public string[] CleanDateTimes { get; set; } + /// <summary> + /// Gets or sets list of raw clean strings regular expressions strings. + /// </summary> public string[] CleanStrings { get; set; } + /// <summary> + /// Gets or sets list of multi-episode regular expressions. + /// </summary> public EpisodeExpression[] MultipleEpisodeExpressions { get; set; } + /// <summary> + /// Gets or sets list of extra rules for videos. + /// </summary> public ExtraRule[] VideoExtraRules { get; set; } + /// <summary> + /// Gets list of video file-stack regular expressions. + /// </summary> public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty<Regex>(); + /// <summary> + /// Gets list of clean datetime regular expressions. + /// </summary> public Regex[] CleanDateTimeRegexes { get; private set; } = Array.Empty<Regex>(); + /// <summary> + /// Gets list of clean string regular expressions. + /// </summary> public Regex[] CleanStringRegexes { get; private set; } = Array.Empty<Regex>(); + /// <summary> + /// Gets list of episode without season regular expressions. + /// </summary> public Regex[] EpisodeWithoutSeasonRegexes { get; private set; } = Array.Empty<Regex>(); + /// <summary> + /// Gets list of multi-part episode regular expressions. + /// </summary> public Regex[] EpisodeMultiPartRegexes { get; private set; } = Array.Empty<Regex>(); + /// <summary> + /// Compiles raw regex strings into regexes. + /// </summary> public void Compile() { VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray(); diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs index 62cc3ead17..1fb2e0dc89 100644 --- a/Emby.Naming/Subtitles/SubtitleInfo.cs +++ b/Emby.Naming/Subtitles/SubtitleInfo.cs @@ -1,7 +1,16 @@ namespace Emby.Naming.Subtitles { + /// <summary> + /// Class holding information about subtitle. + /// </summary> public class SubtitleInfo { + /// <summary> + /// Initializes a new instance of the <see cref="SubtitleInfo"/> class. + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="isDefault">Is subtitle default.</param> + /// <param name="isForced">Is subtitle forced.</param> public SubtitleInfo(string path, bool isDefault, bool isForced) { Path = path; diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs index 476a83cf35..e872452519 100644 --- a/Emby.Naming/Subtitles/SubtitleParser.cs +++ b/Emby.Naming/Subtitles/SubtitleParser.cs @@ -5,20 +5,32 @@ using Emby.Naming.Common; namespace Emby.Naming.Subtitles { + /// <summary> + /// Subtitle Parser class. + /// </summary> public class SubtitleParser { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="SubtitleParser"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing SubtitleFileExtensions, SubtitleDefaultFlags, SubtitleForcedFlags and SubtitleFlagDelimiters.</param> public SubtitleParser(NamingOptions options) { _options = options; } + /// <summary> + /// Parse file to determine if is subtitle and <see cref="SubtitleInfo"/>. + /// </summary> + /// <param name="path">Path to file.</param> + /// <returns>Returns null or <see cref="SubtitleInfo"/> object if parsing is successful.</returns> public SubtitleInfo? ParseFile(string path) { if (path.Length == 0) { - throw new ArgumentException("File path can't be empty.", nameof(path)); + return null; } var extension = Path.GetExtension(path); diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index 26dd6915b9..f7df587864 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -6,15 +6,32 @@ using Emby.Naming.Video; namespace Emby.Naming.TV { + /// <summary> + /// Used to resolve information about episode from path. + /// </summary> public class EpisodeResolver { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="EpisodeResolver"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing VideoFileExtensions and passed to <see cref="StubResolver"/>, <see cref="FlagParser"/>, <see cref="Format3DParser"/> and <see cref="EpisodePathParser"/>.</param> public EpisodeResolver(NamingOptions options) { _options = options; } + /// <summary> + /// Resolve information about episode from path. + /// </summary> + /// <param name="path">Path.</param> + /// <param name="isDirectory">Is path for a directory or file.</param> + /// <param name="isNamed">Do we want to use IsNamed expressions.</param> + /// <param name="isOptimistic">Do we want to use Optimistic expressions.</param> + /// <param name="supportsAbsoluteNumbers">Do we want to use expressions supporting absolute episode numbers.</param> + /// <param name="fillExtendedInfo">Should we attempt to retrieve extended information.</param> + /// <returns>Returns null or <see cref="EpisodeInfo"/> object if successful.</returns> public EpisodeInfo? Resolve( string path, bool isDirectory, diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs index cf99097bc4..d11c7c99e8 100644 --- a/Emby.Naming/TV/SeasonPathParser.cs +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -4,6 +4,9 @@ using System.IO; namespace Emby.Naming.TV { + /// <summary> + /// Class to parse season paths. + /// </summary> public static class SeasonPathParser { /// <summary> @@ -21,6 +24,13 @@ namespace Emby.Naming.TV "stagione" }; + /// <summary> + /// Attempts to parse season number from path. + /// </summary> + /// <param name="path">Path to season.</param> + /// <param name="supportSpecialAliases">Support special aliases when parsing.</param> + /// <param name="supportNumericSeasonFolders">Support numeric season folders when parsing.</param> + /// <returns>Returns <see cref="SeasonPathParserResult"/> object.</returns> public static SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders) { var result = new SeasonPathParserResult(); diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs index f52f941a7c..b4b6f236a7 100644 --- a/Emby.Naming/TV/SeasonPathParserResult.cs +++ b/Emby.Naming/TV/SeasonPathParserResult.cs @@ -1,5 +1,8 @@ namespace Emby.Naming.TV { + /// <summary> + /// Data object to pass result of <see cref="SeasonPathParser"/>. + /// </summary> public class SeasonPathParserResult { /// <summary> @@ -14,6 +17,10 @@ namespace Emby.Naming.TV /// <value><c>true</c> if success; otherwise, <c>false</c>.</value> public bool Success { get; set; } + /// <summary> + /// Gets or sets a value indicating whether "Is season folder". + /// Seems redundant and barely used. + /// </summary> public bool IsSeasonFolder { get; set; } } } diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index 98ea342acc..dd934d91b0 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -6,15 +6,27 @@ using Emby.Naming.Common; namespace Emby.Naming.Video { + /// <summary> + /// Resolve if file is extra for video. + /// </summary> public class ExtraResolver { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="ExtraResolver"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param> public ExtraResolver(NamingOptions options) { _options = options; } + /// <summary> + /// Attempts to resolve if file is extra. + /// </summary> + /// <param name="path">Path to file.</param> + /// <returns>Returns <see cref="ExtraResult"/> object.</returns> public ExtraResult GetExtraInfo(string path) { return _options.VideoExtraRules diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs index f3b8d2a2fc..243fc2b415 100644 --- a/Emby.Naming/Video/ExtraResult.cs +++ b/Emby.Naming/Video/ExtraResult.cs @@ -2,6 +2,9 @@ using MediaBrowser.Model.Entities; namespace Emby.Naming.Video { + /// <summary> + /// Holder object for passing results from ExtraResolver. + /// </summary> public class ExtraResult { /// <summary> diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs index a93474bc69..e267ac55fc 100644 --- a/Emby.Naming/Video/ExtraRule.cs +++ b/Emby.Naming/Video/ExtraRule.cs @@ -8,6 +8,13 @@ namespace Emby.Naming.Video /// </summary> public class ExtraRule { + /// <summary> + /// Initializes a new instance of the <see cref="ExtraRule"/> class. + /// </summary> + /// <param name="extraType">Type of extra.</param> + /// <param name="ruleType">Type of rule.</param> + /// <param name="token">Token.</param> + /// <param name="mediaType">Media type.</param> public ExtraRule(ExtraType extraType, ExtraRuleType ruleType, string token, MediaType mediaType) { Token = token; diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index 75620e9610..6519db57c3 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -4,19 +4,40 @@ using System.Linq; namespace Emby.Naming.Video { + /// <summary> + /// Object holding list of files paths with additional information. + /// </summary> public class FileStack { + /// <summary> + /// Initializes a new instance of the <see cref="FileStack"/> class. + /// </summary> public FileStack() { Files = new List<string>(); } + /// <summary> + /// Gets or sets name of file stack. + /// </summary> public string Name { get; set; } = string.Empty; + /// <summary> + /// Gets or sets list of paths in stack. + /// </summary> public List<string> Files { get; set; } + /// <summary> + /// Gets or sets a value indicating whether stack is directory stack. + /// </summary> public bool IsDirectoryStack { get; set; } + /// <summary> + /// Helper function to determine if path is in the stack. + /// </summary> + /// <param name="file">Path of desired file.</param> + /// <param name="isDirectory">Requested type of stack.</param> + /// <returns>True if file is in the stack.</returns> public bool ContainsFile(string file, bool isDirectory) { if (IsDirectoryStack == isDirectory) diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs index cd15b4666f..439de18138 100644 --- a/Emby.Naming/Video/FlagParser.cs +++ b/Emby.Naming/Video/FlagParser.cs @@ -4,20 +4,38 @@ using Emby.Naming.Common; namespace Emby.Naming.Video { + /// <summary> + /// Parses list of flags from filename based on delimiters. + /// </summary> public class FlagParser { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="FlagParser"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing VideoFlagDelimiters.</param> public FlagParser(NamingOptions options) { _options = options; } + /// <summary> + /// Calls GetFlags function with _options.VideoFlagDelimiters parameter. + /// </summary> + /// <param name="path">Path to file.</param> + /// <returns>List of found flags.</returns> public string[] GetFlags(string path) { return GetFlags(path, _options.VideoFlagDelimiters); } + /// <summary> + /// Parses flags from filename based on delimiters. + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="delimiters">Delimiters used to extract flags.</param> + /// <returns>List of found flags.</returns> public string[] GetFlags(string path, char[] delimiters) { if (string.IsNullOrEmpty(path)) diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs index 73ad36af46..4fd5d78ba7 100644 --- a/Emby.Naming/Video/Format3DParser.cs +++ b/Emby.Naming/Video/Format3DParser.cs @@ -4,15 +4,27 @@ using Emby.Naming.Common; namespace Emby.Naming.Video { + /// <summary> + /// Parste 3D format related flags. + /// </summary> public class Format3DParser { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="Format3DParser"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing VideoFlagDelimiters and passes options to <see cref="FlagParser"/>.</param> public Format3DParser(NamingOptions options) { _options = options; } + /// <summary> + /// Parse 3D format related flags. + /// </summary> + /// <param name="path">Path to file.</param> + /// <returns>Returns <see cref="Format3DResult"/> object.</returns> public Format3DResult Parse(string path) { int oldLen = _options.VideoFlagDelimiters.Length; diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs index 539060c982..ac935f2030 100644 --- a/Emby.Naming/Video/Format3DResult.cs +++ b/Emby.Naming/Video/Format3DResult.cs @@ -2,8 +2,14 @@ using System.Collections.Generic; namespace Emby.Naming.Video { + /// <summary> + /// Helper object to return data from <see cref="Format3DParser"/>. + /// </summary> public class Format3DResult { + /// <summary> + /// Initializes a new instance of the <see cref="Format3DResult"/> class. + /// </summary> public Format3DResult() { Tokens = new List<string>(); diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs index bee5c109e1..e562691df9 100644 --- a/Emby.Naming/Video/Format3DRule.cs +++ b/Emby.Naming/Video/Format3DRule.cs @@ -1,7 +1,15 @@ namespace Emby.Naming.Video { + /// <summary> + /// Data holder class for 3D format rule. + /// </summary> public class Format3DRule { + /// <summary> + /// Initializes a new instance of the <see cref="Format3DRule"/> class. + /// </summary> + /// <param name="token">Token.</param> + /// <param name="precedingToken">Token present before current token.</param> public Format3DRule(string token, string? precedingToken = null) { Token = token; diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index d6de468cc9..550c429614 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -9,25 +9,47 @@ using MediaBrowser.Model.IO; namespace Emby.Naming.Video { + /// <summary> + /// Resolve <see cref="FileStack"/> from list of paths. + /// </summary> public class StackResolver { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="StackResolver"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param> public StackResolver(NamingOptions options) { _options = options; } + /// <summary> + /// Resolves only directories from paths. + /// </summary> + /// <param name="files">List of paths.</param> + /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files) { return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true })); } + /// <summary> + /// Resolves only files from paths. + /// </summary> + /// <param name="files">List of paths.</param> + /// <returns>Enumerable <see cref="FileStack"/> of files.</returns> public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files) { return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false })); } + /// <summary> + /// Resolves audiobooks from paths. + /// </summary> + /// <param name="files">List of paths.</param> + /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files) { var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path)); @@ -56,6 +78,11 @@ namespace Emby.Naming.Video } } + /// <summary> + /// Resolves videos from paths. + /// </summary> + /// <param name="files">List of paths.</param> + /// <returns>Enumerable <see cref="FileStack"/> of videos.</returns> public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files) { var resolver = new VideoResolver(_options); diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index 6241a46b03..079987fe8a 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -5,8 +5,18 @@ using Emby.Naming.Common; namespace Emby.Naming.Video { + /// <summary> + /// Resolve if file is stub (.disc). + /// </summary> public static class StubResolver { + /// <summary> + /// Tries to resolve if file is stub (.disc). + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="options">NamingOptions containing StubFileExtensions and StubTypes.</param> + /// <param name="stubType">Stub type.</param> + /// <returns>True if file is a stub.</returns> public static bool TryResolveFile(string path, NamingOptions options, out string? stubType) { stubType = default; diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs index df2d3c7d22..dfb3ac013d 100644 --- a/Emby.Naming/Video/StubTypeRule.cs +++ b/Emby.Naming/Video/StubTypeRule.cs @@ -1,7 +1,15 @@ namespace Emby.Naming.Video { + /// <summary> + /// Data class holding information about Stub type rule. + /// </summary> public class StubTypeRule { + /// <summary> + /// Initializes a new instance of the <see cref="StubTypeRule"/> class. + /// </summary> + /// <param name="token">Token.</param> + /// <param name="stubType">Stub type.</param> public StubTypeRule(string token, string stubType) { Token = token; diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index dda3225217..ee0e4d4659 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -9,15 +9,28 @@ using MediaBrowser.Model.IO; namespace Emby.Naming.Video { + /// <summary> + /// Resolves alternative versions and extras from list of video files. + /// </summary> public class VideoListResolver { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="VideoListResolver"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing CleanStringRegexes and VideoFlagDelimiters and passes options to <see cref="StackResolver"/> and <see cref="VideoResolver"/>.</param> public VideoListResolver(NamingOptions options) { _options = options; } + /// <summary> + /// Resolves alternative versions and extras from list of video files. + /// </summary> + /// <param name="files">List of related video files.</param> + /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> + /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files togeather when related.</returns> public IEnumerable<VideoInfo> Resolve(List<FileSystemMetadata> files, bool supportMultiVersion = true) { var videoResolver = new VideoResolver(_options); diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 31b47cdf15..d7165d8d7f 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -5,10 +5,18 @@ using Emby.Naming.Common; namespace Emby.Naming.Video { + /// <summary> + /// Resolves <see cref="VideoFileInfo"/> from file path. + /// </summary> public class VideoResolver { private readonly NamingOptions _options; + /// <summary> + /// Initializes a new instance of the <see cref="VideoResolver"/> class. + /// </summary> + /// <param name="options"><see cref="NamingOptions"/> object containing VideoFileExtensions, StubFileExtensions, CleanStringRegexes and CleanDateTimeRegexes + /// and passes options in <see cref="StubResolver"/>, <see cref="FlagParser"/>, <see cref="Format3DParser"/> and <see cref="ExtraResolver"/>.</param> public VideoResolver(NamingOptions options) { _options = options; @@ -110,23 +118,44 @@ namespace Emby.Naming.Video extraRule: extraResult.Rule); } + /// <summary> + /// Determines if path is video file based on extension. + /// </summary> + /// <param name="path">Path to file.</param> + /// <returns>True if is video file.</returns> public bool IsVideoFile(string path) { var extension = Path.GetExtension(path) ?? string.Empty; return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); } + /// <summary> + /// Determines if path is video file stub based on extension. + /// </summary> + /// <param name="path">Path to file.</param> + /// <returns>True if is video file stub.</returns> public bool IsStubFile(string path) { var extension = Path.GetExtension(path) ?? string.Empty; return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); } + /// <summary> + /// Tries to clean name of clutter. + /// </summary> + /// <param name="name">Raw name.</param> + /// <param name="newName">Clean name.</param> + /// <returns>True if cleaning of name was successful.</returns> public bool TryCleanString(string name, out ReadOnlySpan<char> newName) { return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName); } + /// <summary> + /// Tries to get name and year from raw name. + /// </summary> + /// <param name="name">Raw name.</param> + /// <returns>Returns <see cref="CleanDateTimeResult"/> with name and optional year.</returns> public CleanDateTimeResult CleanDateTime(string name) { return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes); diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs index 5152098908..f3abacb4f9 100644 --- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs +++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs @@ -31,17 +31,12 @@ namespace Jellyfin.Naming.Tests.Subtitles [Theory] [InlineData("The Skin I Live In (2011).mp4")] + [InlineData("")] public void SubtitleParser_InvalidFileName_ReturnsNull(string input) { var parser = new SubtitleParser(_namingOptions); Assert.Null(parser.ParseFile(input)); } - - [Fact] - public void SubtitleParser_EmptyFileName_ThrowsArgumentException() - { - Assert.Throws<ArgumentException>(() => new SubtitleParser(_namingOptions).ParseFile(string.Empty)); - } } } From 195a6261c49f2f1376794a149ddec0a56cbe230a Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Tue, 10 Nov 2020 19:28:03 +0100 Subject: [PATCH 30/61] Dummy test case explanation. --- tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs index 57f382b382..12fc19bc48 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs @@ -82,6 +82,10 @@ namespace Jellyfin.Naming.Tests.TV Assert.True(res!.IsStub); } + /* + * EpisodePathParser.cs:130 is currently unreachable, but the piece of code is useful and could be reached with addition of new EpisodeExpressions. + * In order to preserve it but achieve 100% code coverage the test case below with made up expressions and filename is used. + */ [Fact] public void EpisodePathParserTest_EmptyDateParsers() { From 7104a37be1a9bea0106c8b291b6120de0b71dfa8 Mon Sep 17 00:00:00 2001 From: yodatak <mryodatak@gmail.com> Date: Tue, 10 Nov 2020 22:52:20 +0100 Subject: [PATCH 31/61] Bump dependencies to Fedora 33 Fedora 33 is out so Fedora 31 is unmaintened so its time to migrate the build to Fedora 33 ! --- deployment/Dockerfile.fedora.amd64 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index 01b99deb6d..f881890348 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -1,4 +1,4 @@ -FROM fedora:31 +FROM fedora:33 # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist From 496923719c405cd9b0386f7d31fcb204cdaeb9c4 Mon Sep 17 00:00:00 2001 From: martinek-stepan <68327580+martinek-stepan@users.noreply.github.com> Date: Thu, 12 Nov 2020 12:54:55 +0100 Subject: [PATCH 32/61] Apply suggestions from code review Co-authored-by: BaronGreenback <jimcartlidge@yahoo.co.uk> --- Emby.Naming/AudioBook/AudioBookListResolver.cs | 6 +++--- Emby.Naming/Video/VideoListResolver.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index b203f99022..54c1fe5bd3 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -33,7 +33,7 @@ namespace Emby.Naming.AudioBook { var audioBookResolver = new AudioBookResolver(_options); - // File with empty fullname will be sorted out here + // File with empty fullname will be sorted out here. var audiobookFileInfos = files .Select(i => audioBookResolver.Resolve(i.FullName)) .OfType<AudioBookFileInfo>() @@ -139,8 +139,8 @@ namespace Emby.Naming.AudioBook private AudioBookFileInfo FindMainAudioBookFile(List<AudioBookFileInfo> files, string name) { - var main = files.Find(x => Path.GetFileNameWithoutExtension(x.Path) == name); - main ??= files.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x.Path) == "audiobook"); + var main = files.Find(x => Path.GetFileNameWithoutExtension(x.Path).Equals(name)); + main ??= files.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x.Path).Equals("audiobook")); main ??= files.OrderBy(x => x.Container) .ThenBy(x => x.Path) .First(); diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index ee0e4d4659..1e18c4452d 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -147,7 +147,7 @@ namespace Emby.Naming.Video } // If there's only one video, accept all trailers - // Be lenient because people use all kinds of mishmash conventions with trailers + // Be lenient because people use all kinds of mishmash conventions with trailers. if (list.Count == 1) { var trailers = remainingFiles @@ -231,7 +231,7 @@ namespace Emby.Naming.Video testFilename = testFilename.Substring(folderName.Length).Trim(); return string.IsNullOrEmpty(testFilename) || testFilename[0] == '-' - || testFilename[0] == '_' + || testFilename[0].Equals( '_') || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); } From 3bca1181b3c91115e693dc83ba1f5fdd6ee38f7c Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Thu, 12 Nov 2020 13:16:33 +0100 Subject: [PATCH 33/61] Taken suggestions from code review and created test for ExtraRuleType.Regex instead of throwing exception there. --- Emby.Naming/AudioBook/AudioBookListResolver.cs | 5 +++-- Emby.Naming/Video/ExtraResolver.cs | 10 +--------- Emby.Naming/Video/VideoListResolver.cs | 4 ++-- .../Video/CleanDateTimeTests.cs | 4 +++- tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs | 14 +++++--------- 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 54c1fe5bd3..95817efc3e 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -73,6 +73,7 @@ namespace Emby.Naming.AudioBook var haveChaptersOrPages = stackFiles.Any(x => x.ChapterNumber != null || x.PartNumber != null); var groupedBy = stackFiles.GroupBy(file => new { file.ChapterNumber, file.PartNumber }); + var nameWithReplacedDots = nameParserResult.Name.Replace(" ", "."); foreach (var group in groupedBy) { @@ -86,9 +87,9 @@ namespace Emby.Naming.AudioBook foreach (var audioFile in group) { var name = Path.GetFileNameWithoutExtension(audioFile.Path); - if (name == "audiobook" || + if (name.Equals("audiobook") || name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) || - name.Contains(nameParserResult.Name.Replace(" ", "."), StringComparison.OrdinalIgnoreCase)) + name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase)) { alt.Add(audioFile); } diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index dd934d91b0..1d3b36a1ad 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using Emby.Naming.Audio; using Emby.Naming.Common; @@ -52,11 +53,6 @@ namespace Emby.Naming.Video return result; } } - else - { - // Currently unreachable code if new rule.MediaType is desired add if clause with proper tests - throw new InvalidOperationException(); - } if (rule.RuleType == ExtraRuleType.Filename) { @@ -80,9 +76,6 @@ namespace Emby.Naming.Video } else if (rule.RuleType == ExtraRuleType.Regex) { - // Currently unreachable code if new rule.MediaType is desired add if clause with proper tests - throw new InvalidOperationException(); - /* var filename = Path.GetFileName(path); var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); @@ -92,7 +85,6 @@ namespace Emby.Naming.Video result.ExtraType = rule.ExtraType; result.Rule = rule; } - */ } else if (rule.RuleType == ExtraRuleType.DirectoryName) { diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 1e18c4452d..2fd2d3e8b2 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -230,8 +230,8 @@ namespace Emby.Naming.Video testFilename = testFilename.Substring(folderName.Length).Trim(); return string.IsNullOrEmpty(testFilename) - || testFilename[0] == '-' - || testFilename[0].Equals( '_') + || testFilename[0].Equals('-') + || testFilename[0].Equals('_') || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); } diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs index ed971eed7b..15cb5c72fa 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using Emby.Naming.Common; using Emby.Naming.Video; using Xunit; @@ -51,6 +51,8 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("My Movie 2013-12-09", "My Movie 2013-12-09", null)] [InlineData("My Movie 20131209", "My Movie 20131209", null)] [InlineData("My Movie 2013-12-09 2013", "My Movie 2013-12-09", 2013)] + [InlineData(null, null, null)] + [InlineData("", "", null)] public void CleanDateTimeTest(string input, string expectedName, int? expectedYear) { input = Path.GetFileName(input); diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index 12a9b023bf..d34f65409f 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -95,18 +95,14 @@ namespace Jellyfin.Naming.Tests.Video } } - [Fact] - public void TestExtraInfo_InvalidRuleMediaType() - { - var options = new NamingOptions { VideoExtraRules = new[] { new ExtraRule(ExtraType.Unknown, ExtraRuleType.DirectoryName, " ", MediaType.Photo) } }; - Assert.Throws<InvalidOperationException>(() => GetExtraTypeParser(options).GetExtraInfo("sample.jpg")); - } - [Fact] public void TestExtraInfo_InvalidRuleType() { - var options = new NamingOptions { VideoExtraRules = new[] { new ExtraRule(ExtraType.Unknown, ExtraRuleType.Regex, " ", MediaType.Video) } }; - Assert.Throws<InvalidOperationException>(() => GetExtraTypeParser(options).GetExtraInfo("sample.mp4")); + var rule = new ExtraRule(ExtraType.Unknown, ExtraRuleType.Regex, @"([eE]x(tra)?\.\w+)", MediaType.Video); + var options = new NamingOptions { VideoExtraRules = new[] { rule } }; + var res = GetExtraTypeParser(options).GetExtraInfo("extra.mp4"); + + Assert.Equal(rule, res.Rule); } [Fact] From b66239fd521150078d96314e58c61d09ea2888ae Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Thu, 12 Nov 2020 13:18:22 +0100 Subject: [PATCH 34/61] One more missed suggestions (removing commented out code) --- Emby.Naming/Video/VideoListResolver.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 2fd2d3e8b2..19cc491cfb 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -37,7 +37,6 @@ namespace Emby.Naming.Video var videoInfos = files .Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory)) - // .Where(i => i != null) .OfType<VideoFileInfo>() .ToList(); From 4bfcc8b0d15a76d9d33e038cc4e5590fc1016750 Mon Sep 17 00:00:00 2001 From: martinek-stepan <68327580+martinek-stepan@users.noreply.github.com> Date: Thu, 12 Nov 2020 16:51:52 +0100 Subject: [PATCH 35/61] Update Emby.Naming/AudioBook/AudioBookListResolver.cs Use StringComparison.OrdinalIgnoreCase when comparing names Co-authored-by: BaronGreenback <jimcartlidge@yahoo.co.uk> --- Emby.Naming/AudioBook/AudioBookListResolver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 95817efc3e..e9ea9b7a5d 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -140,8 +140,8 @@ namespace Emby.Naming.AudioBook private AudioBookFileInfo FindMainAudioBookFile(List<AudioBookFileInfo> files, string name) { - var main = files.Find(x => Path.GetFileNameWithoutExtension(x.Path).Equals(name)); - main ??= files.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x.Path).Equals("audiobook")); + var main = files.Find(x => Path.GetFileNameWithoutExtension(x.Path).Equals(name, StringComparison.OrdinalIgnoreCase)); + main ??= files.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x.Path).Equals("audiobook", StringComparison.OrdinalIgnoreCase)); main ??= files.OrderBy(x => x.Container) .ThenBy(x => x.Path) .First(); From 83ac7d267428c74e4fe5161991feb2a6ca089578 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Fri, 13 Nov 2020 08:02:45 -0700 Subject: [PATCH 36/61] Fix ci condition --- .ci/azure-pipelines-api-client.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index 03102121ff..1c447fd977 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -9,6 +9,7 @@ jobs: - job: GenerateApiClients displayName: 'Generate Api Clients' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') dependsOn: Test pool: @@ -37,7 +38,6 @@ jobs: ## Generate npm api client - task: CmdLine@2 displayName: 'Build stable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') inputs: script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)" @@ -51,7 +51,6 @@ jobs: ## Publish npm packages - task: Npm@1 displayName: 'Publish stable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') inputs: command: publish publishRegistry: useExternalRegistry From cfb7523ea8461ee4cb19ea8bb8511196d336d0e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Nov 2020 20:47:21 +0000 Subject: [PATCH 37/61] Bump Moq from 4.14.7 to 4.15.1 Bumps [Moq](https://github.com/moq/moq4) from 4.14.7 to 4.15.1. - [Release notes](https://github.com/moq/moq4/releases) - [Changelog](https://github.com/moq/moq4/blob/master/CHANGELOG.md) - [Commits](https://github.com/moq/moq4/commits) Signed-off-by: dependabot[bot] <support@github.com> --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 5bf322f071..14eed30e03 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -22,7 +22,7 @@ <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="1.3.0" /> - <PackageReference Include="Moq" Version="4.14.7" /> + <PackageReference Include="Moq" Version="4.15.1" /> </ItemGroup> <!-- Code Analyzers --> diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index b960fda723..547f80ed96 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -17,7 +17,7 @@ <PackageReference Include="AutoFixture" Version="4.14.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.14.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" /> - <PackageReference Include="Moq" Version="4.14.7" /> + <PackageReference Include="Moq" Version="4.15.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="1.3.0" /> From 809556eb98d3d57ac2e5b4c50ccdccdc70389e07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Nov 2020 20:47:32 +0000 Subject: [PATCH 38/61] Bump PlaylistsNET from 1.1.2 to 1.1.3 Bumps [PlaylistsNET](https://github.com/tmk907/PlaylistsNET) from 1.1.2 to 1.1.3. - [Release notes](https://github.com/tmk907/PlaylistsNET/releases) - [Commits](https://github.com/tmk907/PlaylistsNET/commits) Signed-off-by: dependabot[bot] <support@github.com> --- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index fd3f9f4c7e..accdea36e4 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -20,7 +20,7 @@ <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" /> - <PackageReference Include="PlaylistsNET" Version="1.1.2" /> + <PackageReference Include="PlaylistsNET" Version="1.1.3" /> <PackageReference Include="TMDbLib" Version="1.7.3-alpha" /> <PackageReference Include="TvDbSharper" Version="3.2.2" /> </ItemGroup> From 24839e68909d7064718344de07ccefa1ab4d0167 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Nov 2020 20:47:33 +0000 Subject: [PATCH 39/61] Bump Mono.Nat from 3.0.0 to 3.0.1 Bumps [Mono.Nat](https://github.com/mono/Mono.Nat) from 3.0.0 to 3.0.1. - [Release notes](https://github.com/mono/Mono.Nat/releases) - [Commits](https://github.com/mono/Mono.Nat/compare/release-v3.0.0...release-v3.0.1) Signed-off-by: dependabot[bot] <support@github.com> --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index f3052f5445..d360bb00f2 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -36,7 +36,7 @@ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> - <PackageReference Include="Mono.Nat" Version="3.0.0" /> + <PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.10.0" /> <PackageReference Include="sharpcompress" Version="0.26.0" /> From e2e10e672fb3a8f343f62b6b6ef8d5a2f1628fee Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Sun, 15 Nov 2020 21:08:02 -0700 Subject: [PATCH 40/61] User buster-slim instead of non existent buster --- deployment/Dockerfile.docker.amd64 | 2 +- deployment/Dockerfile.docker.arm64 | 2 +- deployment/Dockerfile.docker.armhf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64 index b61185f8c0..e1787acadd 100644 --- a/deployment/Dockerfile.docker.amd64 +++ b/deployment/Dockerfile.docker.amd64 @@ -1,6 +1,6 @@ ARG DOTNET_VERSION=5.0 -FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64 index 7420b2137a..08240111af 100644 --- a/deployment/Dockerfile.docker.arm64 +++ b/deployment/Dockerfile.docker.arm64 @@ -1,6 +1,6 @@ ARG DOTNET_VERSION=5.0 -FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf index 38e72ad850..68c7b7d656 100644 --- a/deployment/Dockerfile.docker.armhf +++ b/deployment/Dockerfile.docker.armhf @@ -1,6 +1,6 @@ ARG DOTNET_VERSION=5.0 -FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin From 7b42b7e8bd40ef7bd6bb6a533cb3c9dfcd00c63f Mon Sep 17 00:00:00 2001 From: Ygor Lhano <ygor.lhano@gmail.com> Date: Mon, 16 Nov 2020 23:35:05 +0000 Subject: [PATCH 41/61] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 5e49ca702e..8d25e27f6b 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -113,5 +113,7 @@ "TasksChannelsCategory": "Canais da Internet", "TasksApplicationCategory": "Aplicativo", "TasksLibraryCategory": "Biblioteca", - "TasksMaintenanceCategory": "Manutenção" + "TasksMaintenanceCategory": "Manutenção", + "TaskCleanActivityLogDescription": "Apaga o registro de atividades mais antigo que a idade configurada.", + "TaskCleanActivityLog": "Limpar Registro de Atividades" } From 1d96167e8d58ff59859ae5b9afc1f9e0f6bed74f Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Mon, 16 Nov 2020 17:05:31 -0700 Subject: [PATCH 42/61] Fix builders --- deployment/Dockerfile.docker.amd64 | 2 +- deployment/Dockerfile.docker.arm64 | 2 +- deployment/Dockerfile.docker.armhf | 2 +- fedora/jellyfin.spec | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64 index b61185f8c0..926dd0cae7 100644 --- a/deployment/Dockerfile.docker.amd64 +++ b/deployment/Dockerfile.docker.amd64 @@ -1,6 +1,6 @@ ARG DOTNET_VERSION=5.0 -FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64 index 7420b2137a..8fb03fcd06 100644 --- a/deployment/Dockerfile.docker.arm64 +++ b/deployment/Dockerfile.docker.arm64 @@ -1,6 +1,6 @@ ARG DOTNET_VERSION=5.0 -FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf index 38e72ad850..d2c7fa3ff9 100644 --- a/deployment/Dockerfile.docker.armhf +++ b/deployment/Dockerfile.docker.armhf @@ -1,6 +1,6 @@ ARG DOTNET_VERSION=5.0 -FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 22f5949aee..13305488e2 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -82,7 +82,6 @@ EOF %{_libdir}/jellyfin/* # Needs 755 else only root can run it since binary build by dotnet is 722 %attr(755,root,root) %{_libdir}/jellyfin/jellyfin -%{_libdir}/jellyfin/SOS_README.md %{_unitdir}/jellyfin.service %{_libexecdir}/jellyfin/restart.sh %{_prefix}/lib/firewalld/services/jellyfin.xml From 001bf95d105e9ad849b0d4fc06acc23e7d12c7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no> Date: Tue, 17 Nov 2020 03:56:17 +0100 Subject: [PATCH 43/61] Fedora: install systemd as build dependency --- deployment/Dockerfile.fedora.amd64 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index f881890348..ffb08f9e2b 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -10,7 +10,7 @@ ENV IS_DOCKER=YES # Prepare Fedora environment RUN dnf update -y \ - && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel + && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd # Install DotNET SDK RUN rpm --import https://packages.microsoft.com/keys/microsoft.asc \ From c770b9e0c68661b5e8f4ef8289a6ef089799d6ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no> Date: Tue, 17 Nov 2020 05:27:14 +0100 Subject: [PATCH 44/61] Use .NET 5.0 in the Nuget pipeline --- .ci/azure-pipelines-package.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 0dc604a794..d478516b83 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -188,6 +188,12 @@ jobs: vmImage: 'ubuntu-latest' steps: + - task: UseDotNet@2 + displayName: 'Use .NET 5.0 sdk' + inputs: + packageType: 'sdk' + version: '5.0.x' + - task: DotNetCoreCLI@2 displayName: 'Build Stable Nuget packages' condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') From bb46f24bb3301869fcefa5dd7892d0e303181050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= <lukas@kucharczyk.xyz> Date: Tue, 17 Nov 2020 09:43:46 +0000 Subject: [PATCH 45/61] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index fb31b01ff8..7752671837 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -115,5 +115,8 @@ "TasksLibraryCategory": "Knihovna", "TasksMaintenanceCategory": "Údržba", "TaskCleanActivityLogDescription": "Smazat záznamy o aktivitě, které jsou starší než zadaná doba.", - "TaskCleanActivityLog": "Smazat záznam aktivity" + "TaskCleanActivityLog": "Smazat záznam aktivity", + "Undefined": "Nedefinované", + "Forced": "Vynucené", + "Default": "Výchozí" } From 7cbcce06382e8314ac778095dcfe9212ab083358 Mon Sep 17 00:00:00 2001 From: Sverre <sverre@sverrecraft.com> Date: Tue, 17 Nov 2020 09:48:52 +0000 Subject: [PATCH 46/61] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Translate-?= =?UTF-8?q?URL:=20https://translate.jellyfin.org/projects/jellyfin/jellyfi?= =?UTF-8?q?n-core/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/Core/nb.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 245c3cd636..3b016fe62a 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -113,5 +113,9 @@ "TaskRefreshPeople": "Oppfrisk personer", "TaskCleanLogsDescription": "Sletter loggfiler som er eldre enn {0} dager gamle.", "TaskCleanLogs": "Tøm loggmappe", - "TaskRefreshLibraryDescription": "Skanner mediebibliotekene dine for nye filer og oppdaterer metadata." + "TaskRefreshLibraryDescription": "Skanner mediebibliotekene dine for nye filer og oppdaterer metadata.", + "TaskCleanActivityLog": "Tøm aktivitetslogg", + "Undefined": "Udefinert", + "Forced": "Tvungen", + "Default": "Standard" } From fc3d6278be89b695aa79dc61d7f1226d4d6e441b Mon Sep 17 00:00:00 2001 From: Thomas Schwery <thomas@inf3.ch> Date: Tue, 17 Nov 2020 10:16:24 +0000 Subject: [PATCH 47/61] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index cc9243f37b..3d5d69f36a 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -115,5 +115,8 @@ "TasksLibraryCategory": "Bibliothèque", "TasksMaintenanceCategory": "Maintenance", "TaskCleanActivityLogDescription": "Supprime les entrées du journal d'activité antérieures à l'âge configuré.", - "TaskCleanActivityLog": "Nettoyer le journal d'activité" + "TaskCleanActivityLog": "Nettoyer le journal d'activité", + "Undefined": "Non défini", + "Forced": "Forcé", + "Default": "Par défaut" } From d481c35cad8f4ed7444744f051620c5b3ed9fd2c Mon Sep 17 00:00:00 2001 From: Stepan <ste.martinek+git@gmail.com> Date: Tue, 17 Nov 2020 13:06:56 +0100 Subject: [PATCH 48/61] Merge fix --- Emby.Naming/Video/VideoFileInfo.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index 7d7411a563..eb2353440a 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -1,3 +1,4 @@ +#nullable enable using MediaBrowser.Model.Entities; namespace Emby.Naming.Video @@ -21,7 +22,7 @@ namespace Emby.Naming.Video /// <param name="isStub">Is Stub.</param> /// <param name="stubType">Stub type.</param> /// <param name="isDirectory">Is directory.</param> - public VideoFileInfo(string name, string? path, string? container, int? year = default, ExtraType? extraType = default, ExtraRule? extraRule = default, string? format3D = default, bool is3D = default, bool isStub = default, string? stubType = default, bool isDirectory = default) + public VideoFileInfo(string name, string path, string? container, int? year = default, ExtraType? extraType = default, ExtraRule? extraRule = default, string? format3D = default, bool is3D = default, bool isStub = default, string? stubType = default, bool isDirectory = default) { Path = path; Container = container; @@ -40,7 +41,7 @@ namespace Emby.Naming.Video /// Gets or sets the path. /// </summary> /// <value>The path.</value> - public string? Path { get; set; } + public string Path { get; set; } /// <summary> /// Gets or sets the container. From 5fa4cce4cce2bcc6ac2a2cca884af5e659c836f0 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Tue, 17 Nov 2020 06:57:25 -0700 Subject: [PATCH 49/61] Use ALL the decompression methods. --- MediaBrowser.Common/Net/DefaultHttpClientHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs b/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs index e189d6e706..f1c5f24772 100644 --- a/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs +++ b/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs @@ -13,8 +13,7 @@ namespace MediaBrowser.Common.Net /// </summary> public DefaultHttpClientHandler() { - // TODO change to DecompressionMethods.All with .NET5 - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + AutomaticDecompression = DecompressionMethods.All; } } } From 08279e91badebf34e34735b8f512ba585fb336d6 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Tue, 17 Nov 2020 07:08:22 -0700 Subject: [PATCH 50/61] Update MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs --- MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index e99c48a704..2c5abe933f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -404,9 +404,6 @@ namespace MediaBrowser.Controller.MediaEncoding { // Don't exceed what the encoder supports // Seeing issues of attempting to encode to 88200 - // return Math.Min(44100, BaseRequest.AudioSampleRate.Value); - - // I don't see any reason why limiting the sample rate to a maximum of 44100 ! return BaseRequest.AudioSampleRate.Value; } From 04a712ab1d56a29a31e2c4991c16d338348762f4 Mon Sep 17 00:00:00 2001 From: martinek-stepan <68327580+martinek-stepan@users.noreply.github.com> Date: Tue, 17 Nov 2020 15:11:32 +0100 Subject: [PATCH 51/61] Update Emby.Naming/Video/VideoFileInfo.cs Co-authored-by: Cody Robibero <cody@robibe.ro> --- Emby.Naming/Video/VideoFileInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index eb2353440a..1457db7378 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -1,4 +1,3 @@ -#nullable enable using MediaBrowser.Model.Entities; namespace Emby.Naming.Video From 4b1c9dc9eaa120a30a7820257a83dd5aa3ecd9f4 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Tue, 17 Nov 2020 19:43:00 +0100 Subject: [PATCH 52/61] Pass cancellation where possible --- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 4 ++-- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Data/SqliteExtensions.cs | 14 -------------- .../LiveTv/EmbyTV/DirectRecorder.cs | 7 ++++--- .../LiveTv/Listings/SchedulesDirect.cs | 18 +++++++++--------- .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 +- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 6 +++--- .../LiveTv/TunerHosts/M3uParser.cs | 2 +- .../LiveTv/TunerHosts/SharedHttpStream.cs | 2 +- .../Updates/InstallationManager.cs | 7 ++++--- .../Helpers/FileStreamResponseHelpers.cs | 6 ++++-- .../Subtitles/SubtitleEncoder.cs | 2 +- .../Manager/ItemImageProvider.cs | 4 ++-- .../Manager/ProviderManager.cs | 2 +- .../Plugins/AudioDb/AlbumProvider.cs | 2 +- .../Plugins/AudioDb/ArtistProvider.cs | 2 +- .../Plugins/MusicBrainz/ArtistProvider.cs | 6 +++--- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 10 +++++----- .../Plugins/Omdb/OmdbItemProvider.cs | 2 +- .../Plugins/Omdb/OmdbProvider.cs | 4 ++-- 20 files changed, 47 insertions(+), 57 deletions(-) diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index c8c36fc972..f4d7937907 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -45,7 +45,7 @@ namespace Emby.Dlna.PlayTo header, cancellationToken) .ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var reader = new StreamReader(stream, Encoding.UTF8); return XDocument.Parse( await reader.ReadToEndAsync().ConfigureAwait(false), @@ -94,7 +94,7 @@ namespace Emby.Dlna.PlayTo options.Headers.UserAgent.ParseAdd(USERAGENT); options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var reader = new StreamReader(stream, Encoding.UTF8); return XDocument.Parse( await reader.ReadToEndAsync().ConfigureAwait(false), diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ad3c196189..17b99c858f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1378,7 +1378,7 @@ namespace Emby.Server.Implementations using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var result = await System.Text.Json.JsonSerializer.DeserializeAsync<string>(stream, JsonDefaults.GetOptions(), cancellationToken).ConfigureAwait(false); var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 70a6df977f..1af301ceb0 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -107,20 +107,6 @@ namespace Emby.Server.Implementations.Data return null; } - public static void Attach(SQLiteDatabaseConnection db, string path, string alias) - { - var commandText = string.Format( - CultureInfo.InvariantCulture, - "attach @path as {0};", - alias); - - using (var statement = db.PrepareStatement(commandText)) - { - statement.TryBind("@path", path); - statement.MoveNext(); - } - } - public static bool IsDBNull(this IReadOnlyList<IResultSetValue> result, int index) { return result[index].SQLiteType == SQLiteType.Null; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 44560d1e21..341194f239 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -77,11 +77,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logger.LogInformation("Copying recording stream to file {0}", targetFile); // The media source if infinite so we need to handle stopping ourselves - var durationToken = new CancellationTokenSource(duration); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + using var durationToken = new CancellationTokenSource(duration); + using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); + cancellationToken = linkedCancellationToken.Token; await _streamHelper.CopyUntilCancelled( - await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), output, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 43128c60d7..91f7c79315 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json); options.Headers.TryAddWithoutValidation("token", token); using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); - await using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(responseStream).ConfigureAwait(false); _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); @@ -123,7 +123,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json); using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); - await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream).ConfigureAwait(false); var programDict = programDetails.ToDictionary(p => p.programID, y => y); @@ -480,9 +480,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings try { using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false); - await using var response = await innerResponse2.Content.ReadAsStreamAsync().ConfigureAwait(false); - return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>( - response).ConfigureAwait(false); + await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>(response).ConfigureAwait(false); } catch (Exception ex) { @@ -509,7 +508,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings try { using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false); - await using var response = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(response).ConfigureAwait(false); @@ -542,6 +541,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); private DateTime _lastErrorResponse; + private async Task<string> GetToken(ListingsProviderInfo info, CancellationToken cancellationToken) { var username = info.Username; @@ -651,7 +651,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + hashedPassword + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(stream).ConfigureAwait(false); if (root.message == "OK") { @@ -705,7 +705,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings try { using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false); - await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var response = httpResponse.Content; var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(stream).ConfigureAwait(false); @@ -780,7 +780,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings var list = new List<ChannelInfo>(); using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false); - await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(stream).ConfigureAwait(false); _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count); _logger.LogInformation("Mapping Stations to Channel"); diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 2d6f453bd4..76c8757370 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(path, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew)) { await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 9fdbad63c2..c0a4d12285 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, cancellationToken: cancellationToken) .ConfigureAwait(false) ?? new List<Channels>(); @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var sr = new StreamReader(stream, System.Text.Encoding.UTF8); var tuners = new List<LiveTvTunerInfo>(); while (!sr.EndOfStream) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 7c13d45e95..6ea1e1dd77 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts .SendAsync(requestMessage, cancellationToken) .ConfigureAwait(false); response.EnsureSuccessStatusCode(); - return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); } return File.OpenRead(info.Url); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 2e1b895096..2de447ad9a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); using var message = response; - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read); await StreamHelper.CopyToAsync( stream, diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 6b6b8c4fe5..851e7bd68b 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.Updates { using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(manifest, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); try { @@ -241,7 +241,8 @@ namespace Emby.Server.Implementations.Updates _currentInstallations.Add(tuple); } - var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token; + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token); + var linkedToken = linkedTokenSource.Token; await _eventManager.PublishAsync(new PluginInstallingEventArgs(package)).ConfigureAwait(false); @@ -333,7 +334,7 @@ namespace Emby.Server.Implementations.Updates using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(package.SourceUrl, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); // CA5351: Do Not Use Broken Cryptographic Algorithms #pragma warning disable CA5351 diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 20c94cddad..cfa2c1229a 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -24,12 +24,14 @@ namespace Jellyfin.Api.Helpers /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param> /// <param name="httpClient">The <see cref="HttpClient"/> making the remote request.</param> /// <param name="httpContext">The current http context.</param> + /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param> /// <returns>A <see cref="Task{ActionResult}"/> containing the API response.</returns> public static async Task<ActionResult> GetStaticRemoteStreamResult( StreamState state, bool isHeadRequest, HttpClient httpClient, - HttpContext httpContext) + HttpContext httpContext, + CancellationToken cancellationToken = default) { if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent)) { @@ -47,7 +49,7 @@ namespace Jellyfin.Api.Helpers return new FileContentResult(Array.Empty<byte>(), contentType); } - return new FileStreamResult(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType); + return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType); } /// <summary> diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 8b3c6b2e63..b61b8a0e04 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -760,7 +760,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(new Uri(path), cancellationToken) .ConfigureAwait(false); - return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); } case MediaProtocol.File: diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 39748171af..ffc6889fa2 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -469,7 +469,7 @@ namespace MediaBrowser.Providers.Manager try { using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await _providerManager.SaveImage( item, @@ -586,7 +586,7 @@ namespace MediaBrowser.Providers.Manager } } - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await _providerManager.SaveImage( item, stream, diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 7a1b7bb2c7..58ca88a5be 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -181,7 +181,7 @@ namespace MediaBrowser.Providers.Manager throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound); } - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await SaveImage( item, stream, diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index e6d89e6880..6536b303fe 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -175,7 +175,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Directory.CreateDirectory(Path.GetDirectoryName(path)); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 72dad8a25a..85c92fa7b9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(path)); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs index f27da7ce61..dc755b600b 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.Music var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture); using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); return GetResultsFromResponse(stream); } else @@ -49,7 +49,7 @@ namespace MediaBrowser.Providers.Music var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch)); using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - await using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + await using (var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)) { var results = GetResultsFromResponse(stream).ToList(); @@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Music url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch)); using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); return GetResultsFromResponse(stream); } } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 31f0123dcf..93178d64a8 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Music if (!string.IsNullOrWhiteSpace(url)) { using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); return GetResultsFromResponse(stream); } @@ -284,7 +284,7 @@ namespace MediaBrowser.Providers.Music artistId); using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var oReader = new StreamReader(stream, Encoding.UTF8); var settings = new XmlReaderSettings { @@ -307,7 +307,7 @@ namespace MediaBrowser.Providers.Music WebUtility.UrlEncode(artistName)); using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var oReader = new StreamReader(stream, Encoding.UTF8); var settings = new XmlReaderSettings() { @@ -622,7 +622,7 @@ namespace MediaBrowser.Providers.Music var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var oReader = new StreamReader(stream, Encoding.UTF8); var settings = new XmlReaderSettings { @@ -649,7 +649,7 @@ namespace MediaBrowser.Providers.Music var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture); using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var oReader = new StreamReader(stream, Encoding.UTF8); var settings = new XmlReaderSettings { diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 705359d2c7..43d8af75f0 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -133,7 +133,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var url = OmdbProvider.GetOmdbUrl(urlQuery); using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var resultList = new List<SearchResult>(); if (isSearch) diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 9eed6172dd..6af52b591c 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -298,7 +298,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb imdbParam)); using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(path)); _jsonSerializer.SerializeToFile(rootObject, path); @@ -336,7 +336,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb seasonId)); using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(path)); _jsonSerializer.SerializeToFile(rootObject, path); From f6c6ee20085b16ca24079b102306b49f27d70751 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Tue, 17 Nov 2020 13:44:51 -0700 Subject: [PATCH 53/61] Fix Environment authorization policy --- Jellyfin.Api/Controllers/EnvironmentController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs index 6dd5362547..b0b4b5af51 100644 --- a/Jellyfin.Api/Controllers/EnvironmentController.cs +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -17,7 +17,7 @@ namespace Jellyfin.Api.Controllers /// <summary> /// Environment Controller. /// </summary> - [Authorize(Policy = Policies.RequiresElevation)] + [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] public class EnvironmentController : BaseJellyfinApiController { private const char UncSeparator = '\\'; From b91afdb56fb9295c81f40665db5084958c630ed8 Mon Sep 17 00:00:00 2001 From: Moritz <moritz.leick@googlemail.com> Date: Tue, 17 Nov 2020 19:47:16 +0000 Subject: [PATCH 54/61] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/ --- Emby.Server.Implementations/Localization/Core/de.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index c81de8218f..6ab22b8a4b 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -115,5 +115,8 @@ "TasksLibraryCategory": "Bibliothek", "TasksMaintenanceCategory": "Wartung", "TaskCleanActivityLogDescription": "Löscht Aktivitätsprotokolleinträge, die älter als das konfigurierte Alter sind.", - "TaskCleanActivityLog": "Aktivitätsprotokoll aufräumen" + "TaskCleanActivityLog": "Aktivitätsprotokoll aufräumen", + "Undefined": "Undefiniert", + "Forced": "Erzwungen", + "Default": "Standard" } From 316b18e8f20963ff15c1e5e76aef09870aa601b7 Mon Sep 17 00:00:00 2001 From: danielxb-ar <danielxb@gmail.com> Date: Tue, 17 Nov 2020 12:39:57 +0000 Subject: [PATCH 55/61] Translated using Weblate (Spanish (Argentina)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_AR/ --- Emby.Server.Implementations/Localization/Core/es-AR.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index 390074cdd7..0d4a14be00 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -113,5 +113,10 @@ "TasksChannelsCategory": "Canales de internet", "TasksApplicationCategory": "Aplicación", "TasksLibraryCategory": "Biblioteca", - "TasksMaintenanceCategory": "Mantenimiento" + "TasksMaintenanceCategory": "Mantenimiento", + "TaskCleanActivityLogDescription": "Borrar log de actividades anteriores a la fecha establecida.", + "TaskCleanActivityLog": "Borrar log de actividades", + "Undefined": "Indefinido", + "Forced": "Forzado", + "Default": "Por Defecto" } From a8524be6be3ff7c527d31cbe3c4e0edc7ffa9d56 Mon Sep 17 00:00:00 2001 From: HDmaniac <HDmaniac@protonmail.com> Date: Tue, 17 Nov 2020 21:14:05 +0000 Subject: [PATCH 56/61] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/ --- Emby.Server.Implementations/Localization/Core/es.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index d6af40c409..fe674cf366 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -115,5 +115,8 @@ "TaskDownloadMissingSubtitles": "Descargar los subtítulos que faltan", "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten en el contenido de tus bibliotecas, basándose en la configuración de los metadatos.", "TaskCleanActivityLogDescription": "Elimina todos los registros de actividad anteriores a la fecha configurada.", - "TaskCleanActivityLog": "Limpiar registro de actividad" + "TaskCleanActivityLog": "Limpiar registro de actividad", + "Undefined": "Indefinido", + "Forced": "Forzado", + "Default": "Predeterminado" } From fbcf3b750dd4a9d07e80a5455a315518ceb9aa3b Mon Sep 17 00:00:00 2001 From: Nyanmisaka <799610810@qq.com> Date: Wed, 18 Nov 2020 13:28:26 +0000 Subject: [PATCH 57/61] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 3ae0fe5e77..12803456e3 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -115,5 +115,8 @@ "TasksApplicationCategory": "应用程序", "TasksMaintenanceCategory": "维护", "TaskCleanActivityLog": "清理程序日志", - "TaskCleanActivityLogDescription": "删除早于设置时间的活动日志条目。" + "TaskCleanActivityLogDescription": "删除早于设置时间的活动日志条目。", + "Undefined": "未定义", + "Forced": "强制的", + "Default": "默认" } From d608ab8042c03d6b84fd4d17c1ad9fb5f0e18192 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Wed, 18 Nov 2020 09:29:18 -0700 Subject: [PATCH 58/61] Fix null reference when saving plugin configuration --- MediaBrowser.Common/Plugins/BasePlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index e21d8c7d1b..e271bc03e1 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -276,7 +276,7 @@ namespace MediaBrowser.Common.Plugins SaveConfiguration(); - ConfigurationChanged.Invoke(this, configuration); + ConfigurationChanged?.Invoke(this, configuration); } /// <inheritdoc /> From f8dd50fc1ae434be61f0ffb9519a40d5d6448df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no> Date: Thu, 19 Nov 2020 15:41:58 +0100 Subject: [PATCH 59/61] Fix plugin old version cleanup --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 17b99c858f..aa2ec158fa 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1070,7 +1070,6 @@ namespace Emby.Server.Implementations if (!string.IsNullOrEmpty(lastName) && cleanup) { // Attempt a cleanup of old folders. - versions.RemoveAt(x); try { Logger.LogDebug("Deleting {Path}", versions[x].Path); @@ -1080,6 +1079,8 @@ namespace Emby.Server.Implementations { Logger.LogWarning(e, "Unable to delete {Path}", versions[x].Path); } + + versions.RemoveAt(x); } } From 28f8cb6ea54d330341afd7a0f3f96dc50dfb91ec Mon Sep 17 00:00:00 2001 From: Oatavandi <oatavandi@gmail.com> Date: Thu, 19 Nov 2020 15:26:36 +0000 Subject: [PATCH 60/61] Translated using Weblate (Tamil) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/ --- Emby.Server.Implementations/Localization/Core/ta.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index e8cd23d5d1..5fcdb1f748 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -114,5 +114,8 @@ "UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது", "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது", "TaskCleanActivityLogDescription": "உள்ளமைக்கப்பட்ட வயதை விட பழைய செயல்பாட்டு பதிவு உள்ளீடுகளை நீக்குகிறது.", - "TaskCleanActivityLog": "செயல்பாட்டு பதிவை அழி" + "TaskCleanActivityLog": "செயல்பாட்டு பதிவை அழி", + "Undefined": "வரையறுக்கப்படாத", + "Forced": "கட்டாயப்படுத்தப்பட்டது", + "Default": "இயல்புநிலை" } From e71ab2afb1fda7521c61f860654fd94e3bd5e3e8 Mon Sep 17 00:00:00 2001 From: hoanghuy309 <hoanghuy309@gmail.com> Date: Thu, 19 Nov 2020 15:00:19 +0000 Subject: [PATCH 61/61] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index ba58e4bebf..0549995c8c 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -114,5 +114,8 @@ "Application": "Ứng Dụng", "AppDeviceValues": "Ứng Dụng: {0}, Thiết Bị: {1}", "TaskCleanActivityLogDescription": "Xóa các mục nhật ký hoạt động cũ hơn độ tuổi đã cài đặt.", - "TaskCleanActivityLog": "Xóa Nhật Ký Hoạt Động" + "TaskCleanActivityLog": "Xóa Nhật Ký Hoạt Động", + "Undefined": "Không Xác Định", + "Forced": "Bắt Buộc", + "Default": "Mặc Định" }