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)
diff --git a/Emby.Dlna/Common/Argument.cs b/Emby.Dlna/Common/Argument.cs
index 430a3b47d5..e4e9c55e0d 100644
--- a/Emby.Dlna/Common/Argument.cs
+++ b/Emby.Dlna/Common/Argument.cs
@@ -1,7 +1,7 @@
namespace Emby.Dlna.Common
{
///
- /// DLNA Query parameter type, used when quering DLNA devices via SOAP.
+ /// DLNA Query parameter type, used when querying DLNA devices via SOAP.
///
public class Argument
{
diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs
index 6dd9a445a8..e63a858605 100644
--- a/Emby.Dlna/Configuration/DlnaOptions.cs
+++ b/Emby.Dlna/Configuration/DlnaOptions.cs
@@ -2,8 +2,14 @@
namespace Emby.Dlna.Configuration
{
+ ///
+ /// The DlnaOptions class contains the user definable parameters for the dlna subsystems.
+ ///
public class DlnaOptions
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public DlnaOptions()
{
EnablePlayTo = true;
@@ -11,23 +17,76 @@ namespace Emby.Dlna.Configuration
BlastAliveMessages = true;
SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
- BlastAliveMessageIntervalSeconds = 1800;
+ AliveMessageIntervalSeconds = 1800;
}
+ ///
+ /// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna playTo subsystem.
+ ///
public bool EnablePlayTo { get; set; }
+ ///
+ /// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna server subsystem.
+ ///
public bool EnableServer { get; set; }
+ ///
+ /// Gets or sets a value indicating whether detailed dlna server logs are sent to the console/log.
+ /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work.
+ ///
public bool EnableDebugLog { get; set; }
- public bool BlastAliveMessages { get; set; }
-
- public bool SendOnlyMatchedHost { get; set; }
+ ///
+ /// Gets or sets a value indicating whether whether detailed playTo debug logs are sent to the console/log.
+ /// If the setting "Emby.Dlna.PlayTo": "Debug" msut be set in logging.default.json for this property to work.
+ ///
+ public bool EnablePlayToTracing { get; set; }
+ ///
+ /// Gets or sets the ssdp client discovery interval time (in seconds).
+ /// This is the time after which the server will send a ssdp search request.
+ ///
public int ClientDiscoveryIntervalSeconds { get; set; }
- public int BlastAliveMessageIntervalSeconds { get; set; }
+ ///
+ /// Gets or sets the frequency at which ssdp alive notifications are transmitted.
+ ///
+ public int AliveMessageIntervalSeconds { get; set; }
+ ///
+ /// Gets or sets the frequency at which ssdp alive notifications are transmitted. MIGRATING - TO BE REMOVED ONCE WEB HAS BEEN ALTERED.
+ ///
+ public int BlastAliveMessageIntervalSeconds
+ {
+ get
+ {
+ return AliveMessageIntervalSeconds;
+ }
+
+ set
+ {
+ AliveMessageIntervalSeconds = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the default user account that the dlna server uses.
+ ///
public string DefaultUserId { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether playTo device profiles should be created.
+ ///
+ public bool AutoCreatePlayToProfiles { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to blast alive messages.
+ ///
+ public bool BlastAliveMessages { get; set; } = true;
+
+ ///
+ /// gets or sets a value indicating whether to send only matched host.
+ ///
+ public bool SendOnlyMatchedHost { get; set; } = true;
}
}
diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
index f5a7eca720..916044a0cc 100644
--- a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
+++ b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
@@ -9,11 +9,21 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager
{
+ ///
+ /// Defines the .
+ ///
public class ConnectionManagerService : BaseService, IConnectionManager
{
private readonly IDlnaManager _dlna;
private readonly IServerConfigurationManager _config;
+ ///
+ /// 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 for use with the instance..
public ConnectionManagerService(
IDlnaManager dlna,
IServerConfigurationManager config,
@@ -28,7 +38,7 @@ namespace Emby.Dlna.ConnectionManager
///
public string GetServiceXml()
{
- return new ConnectionManagerXmlBuilder().GetXml();
+ return ConnectionManagerXmlBuilder.GetXml();
}
///
diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
index c8db5a3674..c484dac542 100644
--- a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
+++ b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
@@ -6,45 +6,57 @@ using Emby.Dlna.Service;
namespace Emby.Dlna.ConnectionManager
{
- public class ConnectionManagerXmlBuilder
+ ///
+ /// Defines the .
+ ///
+ public static class ConnectionManagerXmlBuilder
{
- public string GetXml()
+ ///
+ /// Gets the ConnectionManager:1 service template.
+ /// See http://upnp.org/specs/av/UPnP-av-ConnectionManager-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 = "SourceProtocolInfo",
- DataType = "string",
- SendsEvents = true
- });
+ new StateVariable
+ {
+ Name = "SourceProtocolInfo",
+ DataType = "string",
+ SendsEvents = true
+ },
- list.Add(new StateVariable
- {
- Name = "SinkProtocolInfo",
- DataType = "string",
- SendsEvents = true
- });
+ new StateVariable
+ {
+ Name = "SinkProtocolInfo",
+ DataType = "string",
+ SendsEvents = true
+ },
- list.Add(new StateVariable
- {
- Name = "CurrentConnectionIDs",
- DataType = "string",
- SendsEvents = true
- });
+ new StateVariable
+ {
+ Name = "CurrentConnectionIDs",
+ DataType = "string",
+ SendsEvents = true
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ConnectionStatus",
- DataType = "string",
- SendsEvents = false,
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ConnectionStatus",
+ DataType = "string",
+ SendsEvents = false,
- AllowedValues = new[]
+ AllowedValues = new[]
{
"OK",
"ContentFormatMismatch",
@@ -52,55 +64,56 @@ namespace Emby.Dlna.ConnectionManager
"UnreliableChannel",
"Unknown"
}
- });
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ConnectionManager",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ConnectionManager",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_Direction",
- DataType = "string",
- SendsEvents = false,
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_Direction",
+ DataType = "string",
+ SendsEvents = false,
- AllowedValues = new[]
+ AllowedValues = new[]
{
"Output",
"Input"
}
- });
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ProtocolInfo",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ProtocolInfo",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ConnectionID",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ConnectionID",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_AVTransportID",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_AVTransportID",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_RcsID",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_RcsID",
+ DataType = "ui4",
+ SendsEvents = false
+ }
+ };
return list;
}
diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs
index d4cc653942..2f8d197a7a 100644
--- a/Emby.Dlna/ConnectionManager/ControlHandler.cs
+++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs
@@ -11,10 +11,19 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager
{
+ ///
+ /// Defines the .
+ ///
public class ControlHandler : BaseControlHandler
{
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.
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
: base(config, logger)
{
@@ -33,6 +42,10 @@ namespace Emby.Dlna.ConnectionManager
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
+ ///
+ /// Builds the response to the GetProtocolInfo request.
+ ///
+ /// The .
private void HandleGetProtocolInfo(XmlWriter xmlWriter)
{
xmlWriter.WriteElementString("Source", _profile.ProtocolInfo);
diff --git a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
index b853e7eab6..542c7bfb4b 100644
--- a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
+++ b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
@@ -5,9 +5,16 @@ using Emby.Dlna.Common;
namespace Emby.Dlna.ConnectionManager
{
- public class ServiceActionListBuilder
+ ///
+ /// Defines the .
+ ///
+ public static class ServiceActionListBuilder
{
- public IEnumerable GetActions()
+ ///
+ /// Returns an enumerable of the ConnectionManagar:1 DLNA actions.
+ ///
+ /// An .
+ public static IEnumerable GetActions()
{
var list = new List
{
@@ -21,6 +28,10 @@ namespace Emby.Dlna.ConnectionManager
return list;
}
+ ///
+ /// Returns the action details for "PrepareForConnection".
+ ///
+ /// The .
private static ServiceAction PrepareForConnection()
{
var action = new ServiceAction
@@ -80,6 +91,10 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
+ ///
+ /// Returns the action details for "GetCurrentConnectionInfo".
+ ///
+ /// The .
private static ServiceAction GetCurrentConnectionInfo()
{
var action = new ServiceAction
@@ -146,7 +161,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
- private ServiceAction GetProtocolInfo()
+ ///
+ /// Returns the action details for "GetProtocolInfo".
+ ///
+ /// The .
+ private static ServiceAction GetProtocolInfo()
{
var action = new ServiceAction
{
@@ -170,7 +189,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
- private ServiceAction GetCurrentConnectionIDs()
+ ///
+ /// Returns the action details for "GetCurrentConnectionIDs".
+ ///
+ /// The .
+ private static ServiceAction GetCurrentConnectionIDs()
{
var action = new ServiceAction
{
@@ -187,7 +210,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
- private ServiceAction ConnectionComplete()
+ ///
+ /// Returns the action details for "ConnectionComplete".
+ ///
+ /// The .
+ private static ServiceAction ConnectionComplete()
{
var action = new ServiceAction
{
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 9f35c19594..b93651746b 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -1674,7 +1674,7 @@ namespace Emby.Dlna.ContentDirectory
}
///
- /// Retreives the ServerItem id.
+ /// Retrieves the ServerItem id.
///
/// The id.
/// The .
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index 069400833e..fedd20b68a 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -484,10 +484,10 @@ namespace Emby.Dlna
///
/// Recreates the object using serialization, to ensure it's not a subclass.
- /// If it's a subclass it may not serlialize properly to xml (different root element tag name).
+ /// If it's a subclass it may not serialize properly to xml (different root element tag name).
///
/// The device profile.
- /// The reserialized device profile.
+ /// The re-serialized device profile.
private DeviceProfile ReserializeProfile(DeviceProfile profile)
{
if (profile.GetType() == typeof(DeviceProfile))
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index c97acdb026..f8ff03076a 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -480,7 +480,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
+ // If we're not playing anything make sure we don't get data more often than necessary to keep the Session alive
if (transportState.Value == TransportState.Stopped)
{
RestartTimerInactive();
@@ -775,7 +775,7 @@ namespace Emby.Dlna.PlayTo
if (track == null)
{
- // If track is null, some vendors do this, use GetMediaInfo instead
+ // If track is null, some vendors do this, use GetMediaInfo instead.
return (true, null);
}
@@ -812,7 +812,7 @@ namespace Emby.Dlna.PlayTo
private XElement ParseResponse(string xml)
{
- // Handle different variations sent back by devices
+ // Handle different variations sent back by devices.
try
{
return XElement.Parse(xml);
@@ -821,7 +821,7 @@ namespace Emby.Dlna.PlayTo
{
}
- // first try to add a root node with a dlna namesapce
+ // first try to add a root node with a dlna namespace.
try
{
return XElement.Parse("" + xml + "")
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index c07c8aefa6..3907b2a396 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/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.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
{
+ ///
+ /// Helper class to determine if Album is multipart.
+ ///
public class AlbumParser
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Naming options containing AlbumStackingPrefixes.
public AlbumParser(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Function that determines if album is multipart.
+ ///
+ /// Path to file.
+ /// True if album is multipart.
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
{
+ ///
+ /// Static helper class to determine if file at path is audio file.
+ ///
public static class AudioFileParser
{
+ ///
+ /// Static helper method to determine if file at path is audio file.
+ ///
+ /// Path to file.
+ /// containing AudioFileExtensions.
+ /// True if file at path is audio file.
public static bool IsAudioFile(string path, NamingOptions options)
{
var extension = Path.GetExtension(path);
diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs
index c4863b50ab..862e396677 100644
--- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs
@@ -7,6 +7,21 @@ 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.
+ public AudioBookFileInfo(string path, string container, int? partNumber = default, int? chapterNumber = default)
+ {
+ Path = path;
+ Container = container;
+ PartNumber = partNumber;
+ ChapterNumber = chapterNumber;
+ }
+
///
/// Gets or sets the path.
///
@@ -31,14 +46,8 @@ 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)
+ public int CompareTo(AudioBookFileInfo? other)
{
if (ReferenceEquals(this, other))
{
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
index 14edd64926..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
{
+ ///
+ /// Parser class to extract part and/or chapter number from audiobook filename.
+ ///
public class AudioBookFilePathParser
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Naming options containing AudioBookPartsExpressions.
public AudioBookFilePathParser(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Based on regex determines if filename includes part/chapter number.
+ ///
+ /// Path to audiobook file.
+ /// Returns object.
public AudioBookFilePathParserResult Parse(string path)
{
AudioBookFilePathParserResult result = default;
@@ -52,8 +61,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..48ab8b57dc 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
@@ -1,14 +1,18 @@
-#nullable enable
-#pragma warning disable CS1591
-
namespace Emby.Naming.AudioBook
{
+ ///
+ /// Data object for passing result of audiobook part/chapter extraction.
+ ///
public struct AudioBookFilePathParserResult
{
+ ///
+ /// Gets or sets optional number of path extracted from audiobook filename.
+ ///
public int? PartNumber { get; set; }
+ ///
+ /// Gets or sets optional number of chapter extracted from audiobook filename.
+ ///
public int? ChapterNumber { get; set; }
-
- public bool Success { get; set; }
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs
index b0b5bd881f..adf403ab6d 100644
--- a/Emby.Naming/AudioBook/AudioBookInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookInfo.cs
@@ -10,11 +10,18 @@ namespace Emby.Naming.AudioBook
///
/// Initializes a new instance of the class.
///
- public AudioBookInfo()
+ /// Name of audiobook.
+ /// Year of audiobook release.
+ /// 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 f4ba11a0d1..e9ea9b7a5d 100644
--- a/Emby.Naming/AudioBook/AudioBookListResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs
@@ -1,6 +1,6 @@
-#pragma warning disable CS1591
-
+using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
@@ -8,40 +8,145 @@ using MediaBrowser.Model.IO;
namespace Emby.Naming.AudioBook
{
+ ///
+ /// Class used to resolve Name, Year, alternative files and extras from stack of files.
+ ///
public class AudioBookListResolver
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Naming options passed along to and .
public AudioBookListResolver(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Resolves Name, Year and differentiate alternative files and extras from regular audiobook files.
+ ///
+ /// List of files related to audiobook.
+ /// Returns IEnumerable of .
public IEnumerable Resolve(IEnumerable files)
{
var audioBookResolver = new AudioBookResolver(_options);
+ // File with empty fullname will be sorted out here.
var audiobookFileInfos = files
- .Select(i => audioBookResolver.Resolve(i.FullName, i.IsDirectory))
- .Where(i => i != null)
+ .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 });
-
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)).ToList();
+ var stackFiles = stack.Files
+ .Select(i => audioBookResolver.Resolve(i))
+ .OfType()
+ .ToList();
+
stackFiles.Sort();
- var info = new AudioBookInfo { Files = stackFiles, Name = stack.Name };
+
+ var nameParserResult = new AudioBookNameParser(_options).Parse(stack.Name);
+
+ 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 });
+ var nameWithReplacedDots = nameParserResult.Name.Replace(" ", ".");
+
+ 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.Equals("audiobook") ||
+ name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) ||
+ name.Contains(nameWithReplacedDots, 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).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();
+
+ return main;
+ }
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookNameParser.cs b/Emby.Naming/AudioBook/AudioBookNameParser.cs
new file mode 100644
index 0000000000..120482bc2c
--- /dev/null
+++ b/Emby.Naming/AudioBook/AudioBookNameParser.cs
@@ -0,0 +1,67 @@
+using System.Globalization;
+using System.Text.RegularExpressions;
+using Emby.Naming.Common;
+
+namespace Emby.Naming.AudioBook
+{
+ ///
+ /// Helper class to retrieve name and year from audiobook previously retrieved name.
+ ///
+ public class AudioBookNameParser
+ {
+ private readonly NamingOptions _options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Naming options containing AudioBookNamesExpressions.
+ public AudioBookNameParser(NamingOptions options)
+ {
+ _options = options;
+ }
+
+ ///
+ /// Parse name and year from previously determined name of audiobook.
+ ///
+ /// Name of the audiobook.
+ /// Returns object.
+ 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..3f2d7b2b0b
--- /dev/null
+++ b/Emby.Naming/AudioBook/AudioBookNameParserResult.cs
@@ -0,0 +1,18 @@
+namespace Emby.Naming.AudioBook
+{
+ ///
+ /// Data object used to pass result of name and year parsing.
+ ///
+ public struct AudioBookNameParserResult
+ {
+ ///
+ /// Gets or sets name of audiobook.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets optional year of release.
+ ///
+ public int? Year { get; set; }
+ }
+}
diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs
index 5807d4688c..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,25 +5,32 @@ using Emby.Naming.Common;
namespace Emby.Naming.AudioBook
{
+ ///
+ /// Resolve specifics (path, container, partNumber, chapterNumber) about audiobook file.
+ ///
public class AudioBookResolver
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// containing AudioFileExtensions and also used to pass to AudioBookFilePathParser.
public AudioBookResolver(NamingOptions options)
{
_options = options;
}
- public AudioBookFileInfo? Resolve(string path, bool isDirectory = false)
+ ///
+ /// Resolve specifics (path, container, partNumber, chapterNumber) about audiobook file.
+ ///
+ /// Path to audiobook file.
+ /// Returns object.
+ public AudioBookFileInfo? Resolve(string path)
{
- if (path.Length == 0)
- {
- throw new ArgumentException("String can't be empty.", nameof(path));
- }
-
- // TODO
- if (isDirectory)
+ 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;
}
@@ -42,14 +46,11 @@ 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);
}
}
}
diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs
index ed6ba8881c..19d3c7aab0 100644
--- a/Emby.Naming/Common/EpisodeExpression.cs
+++ b/Emby.Naming/Common/EpisodeExpression.cs
@@ -1,28 +1,32 @@
-#pragma warning disable CS1591
-
using System;
using System.Text.RegularExpressions;
namespace Emby.Naming.Common
{
+ ///
+ /// Regular expressions for parsing TV Episodes.
+ ///
public class EpisodeExpression
{
private string _expression;
- private Regex _regex;
+ private Regex? _regex;
- public EpisodeExpression(string expression, bool byDate)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Regular expressions.
+ /// True if date is expected.
+ public EpisodeExpression(string expression, bool byDate = false)
{
- Expression = expression;
+ _expression = expression;
IsByDate = byDate;
DateTimeFormats = Array.Empty();
SupportsAbsoluteEpisodeNumbers = true;
}
- public EpisodeExpression(string expression)
- : this(expression, false)
- {
- }
-
+ ///
+ /// Gets or sets raw expressions string.
+ ///
public string Expression
{
get => _expression;
@@ -33,16 +37,34 @@ namespace Emby.Naming.Common
}
}
+ ///
+ /// Gets or sets a value indicating whether gets or sets property indicating if date can be find in expression.
+ ///
public bool IsByDate { get; set; }
+ ///
+ /// Gets or sets a value indicating whether gets or sets property indicating if expression is optimistic.
+ ///
public bool IsOptimistic { get; set; }
+ ///
+ /// Gets or sets a value indicating whether gets or sets property indicating if expression is named.
+ ///
public bool IsNamed { get; set; }
+ ///
+ /// Gets or sets a value indicating whether gets or sets property indicating if expression supports episodes with absolute numbers.
+ ///
public bool SupportsAbsoluteEpisodeNumbers { get; set; }
+ ///
+ /// Gets or sets optional list of date formats used for date parsing.
+ ///
public string[] DateTimeFormats { get; set; }
+ ///
+ /// Gets a expressions objects (creates it if null).
+ ///
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..dc9784c6da 100644
--- a/Emby.Naming/Common/MediaType.cs
+++ b/Emby.Naming/Common/MediaType.cs
@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.Common
{
+ ///
+ /// Type of audiovisual media.
+ ///
public enum MediaType
{
///
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index fd4244f64d..035d1b2280 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -1,15 +1,21 @@
-#pragma warning disable CS1591
-
using System;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Video;
using MediaBrowser.Model.Entities;
+// ReSharper disable StringLiteralTypo
+
namespace Emby.Naming.Common
{
+ ///
+ /// Big ugly class containing lot of different naming options that should be split and injected instead of passes everywhere.
+ ///
public class NamingOptions
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public NamingOptions()
{
VideoFileExtensions = new[]
@@ -75,63 +81,52 @@ 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[]
{
- "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(.*?)(\\.[^.]+)$",
- "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(.*?)(\\.[^.]+)$",
- "(.*?)([ ._-]*[a-d])(.*?)(\\.[^.]+)$"
+ "(?.*?)(?[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?.*?)(?\\.[^.]+)$",
+ "(?.*?)(?[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?.*?)(?\\.[^.]+)$",
+ "(?.*?)(?[ ._-]*[a-d])(?.*?)(?\\.[^.]+)$"
};
CleanDateTimes = new[]
@@ -142,7 +137,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|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
@"(\[.*\])"
};
@@ -255,7 +250,7 @@ namespace Emby.Naming.Common
},
//
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
- new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true)
+ new EpisodeExpression("(?[0-9]{4})[\\.-](?[0-9]{2})[\\.-](?[0-9]{2})", true)
{
DateTimeFormats = new[]
{
@@ -264,7 +259,7 @@ namespace Emby.Naming.Common
"yyyy_MM_dd"
}
},
- new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true)
+ new EpisodeExpression(@"(?[0-9]{2})[.-](?[0-9]{2})[.-](?[0-9]{4})", true)
{
DateTimeFormats = new[]
{
@@ -286,7 +281,12 @@ namespace Emby.Naming.Common
{
SupportsAbsoluteEpisodeNumbers = true
},
- new EpisodeExpression(@"[\\\\/\\._ -](?(?![0-9]+[0-9][0-9])([^\\\/])*)[\\\\/\\._ -](?[0-9]+)(?[0-9][0-9](?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([\\._ -][^\\\\/]*)$")
+
+ // 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(@"[\\/._ -](?(?![0-9]+[0-9][0-9])([^\\\/_])*)[\\\/._ -](?[0-9]+)(?[0-9][0-9](?:(?:[a-i]|\.[1-9])(?![0-9]))?)([._ -][^\\\/]*)$")
{
IsOptimistic = true,
IsNamed = true,
@@ -381,247 +381,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(
+ precedingToken: "3d",
+ token: "hsbs"),
+
+ new Format3DRule(
+ precedingToken: "3d",
+ token: "sbs"),
+
+ new Format3DRule(
+ precedingToken: "3d",
+ token: "htab"),
+
+ new Format3DRule(
+ precedingToken: "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
@@ -631,13 +577,20 @@ 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.
@"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[]
@@ -673,7 +626,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}))+[^\\\/]*$",
@@ -697,56 +650,139 @@ namespace Emby.Naming.Common
Compile();
}
+ ///
+ /// Gets or sets list of audio file extensions.
+ ///
public string[] AudioFileExtensions { get; set; }
+ ///
+ /// Gets or sets list of album stacking prefixes.
+ ///
public string[] AlbumStackingPrefixes { get; set; }
+ ///
+ /// Gets or sets list of subtitle file extensions.
+ ///
public string[] SubtitleFileExtensions { get; set; }
+ ///
+ /// Gets or sets list of subtitles flag delimiters.
+ ///
public char[] SubtitleFlagDelimiters { get; set; }
+ ///
+ /// Gets or sets list of subtitle forced flags.
+ ///
public string[] SubtitleForcedFlags { get; set; }
+ ///
+ /// Gets or sets list of subtitle default flags.
+ ///
public string[] SubtitleDefaultFlags { get; set; }
+ ///
+ /// Gets or sets list of episode regular expressions.
+ ///
public EpisodeExpression[] EpisodeExpressions { get; set; }
+ ///
+ /// Gets or sets list of raw episode without season regular expressions strings.
+ ///
public string[] EpisodeWithoutSeasonExpressions { get; set; }
+ ///
+ /// Gets or sets list of raw multi-part episodes regular expressions strings.
+ ///
public string[] EpisodeMultiPartExpressions { get; set; }
+ ///
+ /// Gets or sets list of video file extensions.
+ ///
public string[] VideoFileExtensions { get; set; }
+ ///
+ /// Gets or sets list of video stub file extensions.
+ ///
public string[] StubFileExtensions { get; set; }
+ ///
+ /// Gets or sets list of raw audiobook parts regular expressions strings.
+ ///
public string[] AudioBookPartsExpressions { get; set; }
+ ///
+ /// Gets or sets list of raw audiobook names regular expressions strings.
+ ///
+ public string[] AudioBookNamesExpressions { get; set; }
+
+ ///
+ /// Gets or sets list of stub type rules.
+ ///
public StubTypeRule[] StubTypes { get; set; }
+ ///
+ /// Gets or sets list of video flag delimiters.
+ ///
public char[] VideoFlagDelimiters { get; set; }
+ ///
+ /// Gets or sets list of 3D Format rules.
+ ///
public Format3DRule[] Format3DRules { get; set; }
+ ///
+ /// Gets or sets list of raw video file-stacking expressions strings.
+ ///
public string[] VideoFileStackingExpressions { get; set; }
+ ///
+ /// Gets or sets list of raw clean DateTimes regular expressions strings.
+ ///
public string[] CleanDateTimes { get; set; }
+ ///
+ /// Gets or sets list of raw clean strings regular expressions strings.
+ ///
public string[] CleanStrings { get; set; }
+ ///
+ /// Gets or sets list of multi-episode regular expressions.
+ ///
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
+ ///
+ /// Gets or sets list of extra rules for videos.
+ ///
public ExtraRule[] VideoExtraRules { get; set; }
- public Regex[] VideoFileStackingRegexes { get; private set; }
+ ///
+ /// Gets list of video file-stack regular expressions.
+ ///
+ public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty();
- public Regex[] CleanDateTimeRegexes { get; private set; }
+ ///
+ /// Gets list of clean datetime regular expressions.
+ ///
+ public Regex[] CleanDateTimeRegexes { get; private set; } = Array.Empty();
- public Regex[] CleanStringRegexes { get; private set; }
+ ///
+ /// Gets list of clean string regular expressions.
+ ///
+ public Regex[] CleanStringRegexes { get; private set; } = Array.Empty();
- public Regex[] EpisodeWithoutSeasonRegexes { get; private set; }
+ ///
+ /// Gets list of episode without season regular expressions.
+ ///
+ public Regex[] EpisodeWithoutSeasonRegexes { get; private set; } = Array.Empty();
- public Regex[] EpisodeMultiPartRegexes { get; private set; }
+ ///
+ /// Gets list of multi-part episode regular expressions.
+ ///
+ public Regex[] EpisodeMultiPartRegexes { get; private set; } = Array.Empty();
+ ///
+ /// Compiles raw regex strings into regexes.
+ ///
public void Compile()
{
VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray();
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index 80800840ee..24c15759d4 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -14,6 +14,7 @@
true
true
snupkg
+ enable
@@ -38,7 +39,7 @@
-
+
diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs
index f39c496b7a..1fb2e0dc89 100644
--- a/Emby.Naming/Subtitles/SubtitleInfo.cs
+++ b/Emby.Naming/Subtitles/SubtitleInfo.cs
@@ -1,9 +1,23 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.Subtitles
{
+ ///
+ /// Class holding information about subtitle.
+ ///
public class SubtitleInfo
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Path to file.
+ /// Is subtitle default.
+ /// Is subtitle forced.
+ public SubtitleInfo(string path, bool isDefault, bool isForced)
+ {
+ Path = path;
+ IsDefault = isDefault;
+ IsForced = isForced;
+ }
+
///
/// Gets or sets the path.
///
@@ -14,7 +28,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..a19340ef69 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;
@@ -8,20 +5,32 @@ using Emby.Naming.Common;
namespace Emby.Naming.Subtitles
{
+ ///
+ /// Subtitle Parser class.
+ ///
public class SubtitleParser
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing SubtitleFileExtensions, SubtitleDefaultFlags, SubtitleForcedFlags and SubtitleFlagDelimiters.
public SubtitleParser(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Parse file to determine if is subtitle and .
+ ///
+ /// Path to file.
+ /// Returns null or object if parsing is successful.
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);
@@ -31,12 +40,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))
@@ -53,7 +60,7 @@ namespace Emby.Naming.Subtitles
private string[] GetFlags(string path)
{
- // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
+ // Note: the tags need be surrounded be either a space ( ), hyphen -, dot . or underscore _.
var file = Path.GetFileName(path);
diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs
index 250df4e2d3..a8920b36ae 100644
--- a/Emby.Naming/TV/EpisodeInfo.cs
+++ b/Emby.Naming/TV/EpisodeInfo.cs
@@ -1,9 +1,19 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.TV
{
+ ///
+ /// Holder object for Episode information.
+ ///
public class EpisodeInfo
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Path to the file.
+ public EpisodeInfo(string path)
+ {
+ Path = path;
+ }
+
///
/// Gets or sets the path.
///
@@ -14,19 +24,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,20 +54,41 @@ 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; }
+ ///
+ /// Gets or sets optional season number.
+ ///
public int? SeasonNumber { get; set; }
+ ///
+ /// Gets or sets optional episode number.
+ ///
public int? EpisodeNumber { get; set; }
- public int? EndingEpsiodeNumber { get; set; }
+ ///
+ /// Gets or sets optional ending episode number. For multi-episode files 1-13.
+ ///
+ public int? EndingEpisodeNumber { get; set; }
+ ///
+ /// Gets or sets optional year of release.
+ ///
public int? Year { get; set; }
+ ///
+ /// Gets or sets optional year of release.
+ ///
public int? Month { get; set; }
+ ///
+ /// Gets or sets optional day of release.
+ ///
public int? Day { get; set; }
+ ///
+ /// Gets or sets a value indicating whether by date expression was used.
+ ///
public bool IsByDate { get; set; }
}
}
diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs
index a6af689c72..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
{
+ ///
+ /// Used to parse information about episode from path.
+ ///
public class EpisodePathParser
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing EpisodeExpressions and MultipleEpisodeExpressions.
public EpisodePathParser(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Parses information about episode from path.
+ ///
+ /// Path.
+ /// Is path for a directory or file.
+ /// Do we want to use IsNamed expressions.
+ /// Do we want to use Optimistic expressions.
+ /// Do we want to use expressions supporting absolute episode numbers.
+ /// Should we attempt to retrieve extended information.
+ /// Returns object.
public EpisodePathParserResult Parse(
string path,
bool isDirectory,
@@ -146,7 +160,7 @@ namespace Emby.Naming.TV
{
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
- result.EndingEpsiodeNumber = num;
+ result.EndingEpisodeNumber = num;
}
}
}
@@ -186,7 +200,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 +214,6 @@ namespace Emby.Naming.TV
{
foreach (var i in expressions)
{
- if (!i.IsNamed)
- {
- continue;
- }
-
var result = Parse(path, i);
if (!result.Success)
@@ -217,13 +226,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 05f921edc9..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
{
+ ///
+ /// Holder object for result.
+ ///
public class EpisodePathParserResult
{
+ ///
+ /// Gets or sets optional season number.
+ ///
public int? SeasonNumber { get; set; }
+ ///
+ /// Gets or sets optional episode number.
+ ///
public int? EpisodeNumber { get; set; }
- public int? EndingEpsiodeNumber { get; set; }
+ ///
+ /// Gets or sets optional ending episode number. For multi-episode files 1-13.
+ ///
+ public int? EndingEpisodeNumber { get; set; }
- public string SeriesName { get; set; }
+ ///
+ /// Gets or sets the name of the series.
+ ///
+ /// The name of the series.
+ public string? SeriesName { get; set; }
+ ///
+ /// Gets or sets a value indicating whether parsing was successful.
+ ///
public bool Success { get; set; }
+ ///
+ /// Gets or sets a value indicating whether by date expression was used.
+ ///
public bool IsByDate { get; set; }
+ ///
+ /// Gets or sets optional year of release.
+ ///
public int? Year { get; set; }
+ ///
+ /// Gets or sets optional year of release.
+ ///
public int? Month { get; set; }
+ ///
+ /// Gets or sets optional day of release.
+ ///
public int? Day { get; set; }
}
}
diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs
index 6994f69fc4..f7df587864 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;
@@ -9,15 +6,32 @@ using Emby.Naming.Video;
namespace Emby.Naming.TV
{
+ ///
+ /// Used to resolve information about episode from path.
+ ///
public class EpisodeResolver
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing VideoFileExtensions and passed to , , and .
public EpisodeResolver(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Resolve information about episode from path.
+ ///
+ /// Path.
+ /// Is path for a directory or file.
+ /// Do we want to use IsNamed expressions.
+ /// Do we want to use Optimistic expressions.
+ /// Do we want to use expressions supporting absolute episode numbers.
+ /// Should we attempt to retrieve extended information.
+ /// Returns null or object if successful.
public EpisodeInfo? Resolve(
string path,
bool isDirectory,
@@ -54,12 +68,11 @@ 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,
+ 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..d11c7c99e8 100644
--- a/Emby.Naming/TV/SeasonPathParser.cs
+++ b/Emby.Naming/TV/SeasonPathParser.cs
@@ -1,11 +1,12 @@
-#pragma warning disable CS1591
-
using System;
using System.Globalization;
using System.IO;
namespace Emby.Naming.TV
{
+ ///
+ /// Class to parse season paths.
+ ///
public static class SeasonPathParser
{
///
@@ -23,6 +24,13 @@ namespace Emby.Naming.TV
"stagione"
};
+ ///
+ /// Attempts to parse season number from path.
+ ///
+ /// Path to season.
+ /// Support special aliases when parsing.
+ /// Support numeric season folders when parsing.
+ /// Returns object.
public static SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
{
var result = new SeasonPathParserResult();
@@ -101,9 +109,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 +147,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 +155,7 @@ namespace Emby.Naming.TV
{
if (char.IsNumber(path[i]))
{
- if (!hasOpenParenth)
+ if (!hasOpenParenthesis)
{
if (numericStart == -1)
{
@@ -167,11 +175,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/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs
index a142fafea0..b4b6f236a7 100644
--- a/Emby.Naming/TV/SeasonPathParserResult.cs
+++ b/Emby.Naming/TV/SeasonPathParserResult.cs
@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.TV
{
+ ///
+ /// Data object to pass result of .
+ ///
public class SeasonPathParserResult
{
///
@@ -16,6 +17,10 @@ namespace Emby.Naming.TV
/// true if success; otherwise, false.
public bool Success { get; set; }
+ ///
+ /// Gets or sets a value indicating whether "Is season folder".
+ /// Seems redundant and barely used.
+ ///
public bool IsSeasonFolder { get; set; }
}
}
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
///
public static class CleanDateTimeParser
{
+ ///
+ /// Attempts to clean the name.
+ ///
+ /// Name of video.
+ /// Optional list of regexes to clean the name.
+ /// Returns object.
public static CleanDateTimeResult Clean(string name, IReadOnlyList 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
{
+ ///
+ /// Holder structure for name and year.
+ ///
public readonly struct CleanDateTimeResult
{
- public CleanDateTimeResult(string name, int? year)
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Name of video.
+ /// Year of release.
+ public CleanDateTimeResult(string name, int? year = null)
{
Name = name;
Year = year;
}
- public CleanDateTimeResult(string name)
- {
- Name = name;
- Year = null;
- }
-
///
/// Gets the name.
///
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
///
public static class CleanStringParser
{
+ ///
+ /// Attempts to extract clean name with regular expressions.
+ ///
+ /// Name of file.
+ /// List of regex to parse name and year from.
+ /// Parsing result string.
+ /// True if parsing was successful.
public static bool TryClean(string name, IReadOnlyList expressions, out ReadOnlySpan newName)
{
var len = expressions.Count;
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index fc0424faab..1d3b36a1ad 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.IO;
using System.Linq;
@@ -9,15 +7,27 @@ using Emby.Naming.Common;
namespace Emby.Naming.Video
{
+ ///
+ /// Resolve if file is extra for video.
+ ///
public class ExtraResolver
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing VideoExtraRules and passed to and .
public ExtraResolver(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Attempts to resolve if file is extra.
+ ///
+ /// Path to file.
+ /// Returns object.
public ExtraResult GetExtraInfo(string path)
{
return _options.VideoExtraRules
@@ -43,10 +53,6 @@ namespace Emby.Naming.Video
return result;
}
}
- else
- {
- return result;
- }
if (rule.RuleType == ExtraRuleType.Filename)
{
diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs
index 15db32e876..243fc2b415 100644
--- a/Emby.Naming/Video/ExtraResult.cs
+++ b/Emby.Naming/Video/ExtraResult.cs
@@ -1,9 +1,10 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Model.Entities;
namespace Emby.Naming.Video
{
+ ///
+ /// Holder object for passing results from ExtraResolver.
+ ///
public class ExtraResult
{
///
@@ -16,6 +17,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..e267ac55fc 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;
@@ -10,6 +8,21 @@ namespace Emby.Naming.Video
///
public class ExtraRule
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type of extra.
+ /// Type of rule.
+ /// Token.
+ /// Media type.
+ 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/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs
index e89876f4ae..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
{
+ ///
+ /// Extra rules type to determine against what should be matched.
+ ///
public enum ExtraRuleType
{
///
@@ -22,6 +23,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/FileStack.cs b/Emby.Naming/Video/FileStack.cs
index 3ef190b865..6519db57c3 100644
--- a/Emby.Naming/Video/FileStack.cs
+++ b/Emby.Naming/Video/FileStack.cs
@@ -1,24 +1,43 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Linq;
namespace Emby.Naming.Video
{
+ ///
+ /// Object holding list of files paths with additional information.
+ ///
public class FileStack
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public FileStack()
{
Files = new List();
}
- public string Name { get; set; }
+ ///
+ /// Gets or sets name of file stack.
+ ///
+ public string Name { get; set; } = string.Empty;
+ ///
+ /// Gets or sets list of paths in stack.
+ ///
public List Files { get; set; }
+ ///
+ /// Gets or sets a value indicating whether stack is directory stack.
+ ///
public bool IsDirectoryStack { get; set; }
+ ///
+ /// Helper function to determine if path is in the stack.
+ ///
+ /// Path of desired file.
+ /// Requested type of stack.
+ /// True if file is in the stack.
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 a8bd9d5c5d..439de18138 100644
--- a/Emby.Naming/Video/FlagParser.cs
+++ b/Emby.Naming/Video/FlagParser.cs
@@ -1,37 +1,53 @@
-#pragma warning disable CS1591
-
using System;
using System.IO;
using Emby.Naming.Common;
namespace Emby.Naming.Video
{
+ ///
+ /// Parses list of flags from filename based on delimiters.
+ ///
public class FlagParser
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing VideoFlagDelimiters.
public FlagParser(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Calls GetFlags function with _options.VideoFlagDelimiters parameter.
+ ///
+ /// Path to file.
+ /// List of found flags.
public string[] GetFlags(string path)
{
return GetFlags(path, _options.VideoFlagDelimiters);
}
- public string[] GetFlags(string path, char[] delimeters)
+ ///
+ /// Parses flags from filename based on delimiters.
+ ///
+ /// Path to file.
+ /// Delimiters used to extract flags.
+ /// List of found flags.
+ public string[] GetFlags(string path, char[] delimiters)
{
if (string.IsNullOrEmpty(path))
{
- throw new ArgumentNullException(nameof(path));
+ return Array.Empty();
}
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
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 51c26af863..4fd5d78ba7 100644
--- a/Emby.Naming/Video/Format3DParser.cs
+++ b/Emby.Naming/Video/Format3DParser.cs
@@ -1,28 +1,38 @@
-#pragma warning disable CS1591
-
using System;
using System.Linq;
using Emby.Naming.Common;
namespace Emby.Naming.Video
{
+ ///
+ /// Parste 3D format related flags.
+ ///
public class Format3DParser
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing VideoFlagDelimiters and passes options to .
public Format3DParser(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Parse 3D format related flags.
+ ///
+ /// Path to file.
+ /// Returns object.
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 +54,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);
@@ -57,13 +67,13 @@ namespace Emby.Naming.Video
else
{
var foundPrefix = false;
- string format = null;
+ string? format = null;
foreach (var flag in videoFlags)
{
if (foundPrefix)
{
- result.Tokens.Add(rule.PreceedingToken);
+ result.Tokens.Add(rule.PrecedingToken);
if (string.Equals(rule.Token, flag, StringComparison.OrdinalIgnoreCase))
{
@@ -74,7 +84,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/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs
index fa0e9d3b80..ac935f2030 100644
--- a/Emby.Naming/Video/Format3DResult.cs
+++ b/Emby.Naming/Video/Format3DResult.cs
@@ -1,11 +1,15 @@
-#pragma warning disable CS1591
-
using System.Collections.Generic;
namespace Emby.Naming.Video
{
+ ///
+ /// Helper object to return data from .
+ ///
public class Format3DResult
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public Format3DResult()
{
Tokens = new List();
@@ -21,7 +25,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..e562691df9 100644
--- a/Emby.Naming/Video/Format3DRule.cs
+++ b/Emby.Naming/Video/Format3DRule.cs
@@ -1,9 +1,21 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.Video
{
+ ///
+ /// Data holder class for 3D format rule.
+ ///
public class Format3DRule
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Token.
+ /// Token present before current token.
+ public Format3DRule(string token, string? precedingToken = null)
+ {
+ Token = token;
+ PrecedingToken = precedingToken;
+ }
+
///
/// Gets or sets the token.
///
@@ -11,9 +23,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/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs
index f733cd2620..550c429614 100644
--- a/Emby.Naming/Video/StackResolver.cs
+++ b/Emby.Naming/Video/StackResolver.cs
@@ -1,58 +1,88 @@
-#pragma warning disable CS1591
-
using System;
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;
namespace Emby.Naming.Video
{
+ ///
+ /// Resolve from list of paths.
+ ///
public class StackResolver
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing VideoFileStackingRegexes and passes options to .
public StackResolver(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Resolves only directories from paths.
+ ///
+ /// List of paths.
+ /// Enumerable of directories.
public IEnumerable ResolveDirectories(IEnumerable files)
{
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
}
+ ///
+ /// Resolves only files from paths.
+ ///
+ /// List of paths.
+ /// Enumerable of files.
public IEnumerable ResolveFiles(IEnumerable files)
{
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
}
- public IEnumerable ResolveAudioBooks(IEnumerable files)
+ ///
+ /// Resolves audiobooks from paths.
+ ///
+ /// List of paths.
+ /// Enumerable of directories.
+ 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 (string.IsNullOrEmpty(directory.Key))
{
- if (file.IsDirectory)
+ foreach (var file in directory)
{
- continue;
+ 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);
}
- stack.Files.Add(file.FullName);
+ yield return stack;
}
-
- yield return stack;
}
}
+ ///
+ /// Resolves videos from paths.
+ ///
+ /// List of paths.
+ /// Enumerable of videos.
public IEnumerable Resolve(IEnumerable files)
{
var resolver = new VideoResolver(_options);
@@ -81,10 +111,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..079987fe8a 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;
@@ -8,13 +5,23 @@ using Emby.Naming.Common;
namespace Emby.Naming.Video
{
+ ///
+ /// Resolve if file is stub (.disc).
+ ///
public static class StubResolver
{
+ ///
+ /// Tries to resolve if file is stub (.disc).
+ ///
+ /// Path to file.
+ /// NamingOptions containing StubFileExtensions and StubTypes.
+ /// Stub type.
+ /// True if file is a stub.
public static bool TryResolveFile(string path, NamingOptions options, out string? stubType)
{
stubType = default;
- if (path == null)
+ if (string.IsNullOrEmpty(path))
{
return false;
}
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/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs
index 8285cb51a3..dfb3ac013d 100644
--- a/Emby.Naming/Video/StubTypeRule.cs
+++ b/Emby.Naming/Video/StubTypeRule.cs
@@ -1,9 +1,21 @@
-#pragma warning disable CS1591
-
namespace Emby.Naming.Video
{
+ ///
+ /// Data class holding information about Stub type rule.
+ ///
public class StubTypeRule
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Token.
+ /// Stub type.
+ 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..1457db7378 100644
--- a/Emby.Naming/Video/VideoFileInfo.cs
+++ b/Emby.Naming/Video/VideoFileInfo.cs
@@ -7,6 +7,35 @@ namespace Emby.Naming.Video
///
public class VideoFileInfo
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Name of file.
+ /// Path to the file.
+ /// Container type.
+ /// Year of release.
+ /// Extra type.
+ /// Extra rule.
+ /// Format 3D.
+ /// Is 3D.
+ /// Is Stub.
+ /// Stub type.
+ /// Is directory.
+ 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;
+ }
+
///
/// Gets or sets the path.
///
@@ -17,7 +46,7 @@ namespace Emby.Naming.Video
/// Gets or sets the container.
///
/// The container.
- public string Container { get; set; }
+ public string? Container { get; set; }
///
/// Gets or sets the name.
@@ -41,13 +70,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 +94,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.
@@ -84,8 +113,7 @@ namespace Emby.Naming.Video
///
public override string ToString()
{
- // Makes debugging easier
- return Name ?? base.ToString();
+ return "VideoFileInfo(Name: '" + Name + "')";
}
}
}
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..5f83355c8f 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;
@@ -11,22 +9,35 @@ using MediaBrowser.Model.IO;
namespace Emby.Naming.Video
{
+ ///
+ /// Resolves alternative versions and extras from list of video files.
+ ///
public class VideoListResolver
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing CleanStringRegexes and VideoFlagDelimiters and passes options to and .
public VideoListResolver(NamingOptions options)
{
_options = options;
}
+ ///
+ /// Resolves alternative versions and extras from list of video files.
+ ///
+ /// List of related video files.
+ /// Indication we should consider multi-versions of content.
+ /// Returns enumerable of which groups files together when related.
public IEnumerable Resolve(List files, bool supportMultiVersion = true)
{
var videoResolver = new VideoResolver(_options);
var videoInfos = files
.Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory))
- .Where(i => i != null)
+ .OfType()
.ToList();
// Filter out all extras, otherwise they could cause stacks to not be resolved
@@ -39,7 +50,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 +59,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;
@@ -133,7 +146,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
@@ -203,15 +216,21 @@ 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;
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].Equals('-')
+ || testFilename[0].Equals('_')
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
}
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index b4aee614b0..d7165d8d7f 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;
@@ -8,10 +5,18 @@ using Emby.Naming.Common;
namespace Emby.Naming.Video
{
+ ///
+ /// Resolves from file path.
+ ///
public class VideoResolver
{
private readonly NamingOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// object containing VideoFileExtensions, StubFileExtensions, CleanStringRegexes and CleanDateTimeRegexes
+ /// and passes options in , , and .
public VideoResolver(NamingOptions options)
{
_options = options;
@@ -22,7 +27,7 @@ namespace Emby.Naming.Video
///
/// The path.
/// VideoFileInfo.
- public VideoFileInfo? ResolveDirectory(string path)
+ public VideoFileInfo? ResolveDirectory(string? path)
{
return Resolve(path, true);
}
@@ -32,7 +37,7 @@ namespace Emby.Naming.Video
///
/// The path.
/// VideoFileInfo.
- public VideoFileInfo? ResolveFile(string path)
+ public VideoFileInfo? ResolveFile(string? path)
{
return Resolve(path, false);
}
@@ -45,11 +50,11 @@ 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))
{
- throw new ArgumentNullException(nameof(path));
+ return null;
}
bool isStub = false;
@@ -99,39 +104,58 @@ 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);
}
+ ///
+ /// Determines if path is video file based on extension.
+ ///
+ /// Path to file.
+ /// True if is video file.
public bool IsVideoFile(string path)
{
var extension = Path.GetExtension(path) ?? string.Empty;
return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
+ ///
+ /// Determines if path is video file stub based on extension.
+ ///
+ /// Path to file.
+ /// True if is video file stub.
public bool IsStubFile(string path)
{
var extension = Path.GetExtension(path) ?? string.Empty;
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
+ ///
+ /// Tries to clean name of clutter.
+ ///
+ /// Raw name.
+ /// Clean name.
+ /// True if cleaning of name was successful.
public bool TryCleanString(string name, out ReadOnlySpan newName)
{
return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
}
+ ///
+ /// Tries to get name and year from raw name.
+ ///
+ /// Raw name.
+ /// Returns with name and optional year.
public CleanDateTimeResult CleanDateTime(string name)
{
return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index ad3c196189..5d47d1e401 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -94,7 +94,6 @@ using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Providers.Chapters;
using MediaBrowser.Providers.Manager;
-using MediaBrowser.Providers.Plugins.TheTvdb;
using MediaBrowser.Providers.Plugins.Tmdb;
using MediaBrowser.Providers.Subtitles;
using MediaBrowser.XbmcMetadata.Providers;
@@ -520,7 +519,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton();
ServiceCollection.AddSingleton(_fileSystemManager);
- ServiceCollection.AddSingleton();
ServiceCollection.AddSingleton();
ServiceCollection.AddSingleton(_networkManager);
@@ -1070,7 +1068,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 +1077,8 @@ namespace Emby.Server.Implementations
{
Logger.LogWarning(e, "Unable to delete {Path}", versions[x].Path);
}
+
+ versions.RemoveAt(x);
}
}
@@ -1378,7 +1377,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(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 result, int index)
{
return result[index].SQLiteType == SQLiteType.Null;
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 @@
-
+
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 8ab5e4aef1..d838734414 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2486,9 +2486,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
{
@@ -2577,12 +2578,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/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
index 179e0ed986..28fa062396 100644
--- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs
+++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
@@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.Library
private static IEnumerable GetSortedStreams(IEnumerable streams, MediaStreamType type, string[] languagePreferences)
{
- // Give some preferance to external text subs for better performance
+ // Give some preference to external text subs for better performance
return streams.Where(i => i.Type == type)
.OrderBy(i =>
{
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..5d17ba1de9 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>(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>(innerResponseStream).ConfigureAwait(false);
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
@@ -261,7 +261,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Id = newID,
StartDate = startAt,
EndDate = endAt,
- Name = details.titles[0].title120 ?? "Unkown",
+ Name = details.titles[0].title120 ?? "Unknown",
OfficialRating = null,
CommunityRating = null,
EpisodeTitle = episodeTitle,
@@ -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>(
- response).ConfigureAwait(false);
+ await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ return await _jsonSerializer.DeserializeFromStreamAsync>(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>(response).ConfigureAwait(false);
@@ -542,6 +541,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ConcurrentDictionary _tokens = new ConcurrentDictionary();
private DateTime _lastErrorResponse;
+
private async Task 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(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(stream).ConfigureAwait(false);
@@ -780,7 +780,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var list = new List();
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(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>(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false) ?? new List();
@@ -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(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();
while (!sr.EndOfStream)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 7c13d45e95..1d6c26c130 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);
@@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (string.IsNullOrWhiteSpace(numberString))
{
// Using this as a fallback now as this leads to Problems with channels like "5 USA"
- // where 5 isnt ment to be the channel number
+ // where 5 isn't ment to be the channel number
// Check for channel number with the format from SatIp
// #EXTINF:0,84. VOX Schweiz
// #EXTINF:0,84.0 - VOX Schweiz
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/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"
}
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"
}
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"
}
diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json
index f906d6e117..981e8a06ed 100644
--- a/Emby.Server.Implementations/Localization/Core/he.json
+++ b/Emby.Server.Implementations/Localization/Core/he.json
@@ -113,5 +113,10 @@
"TaskRefreshChannels": "רענן ערוץ",
"TaskCleanTranscodeDescription": "מחק קבצי transcode שנוצרו מלפני יותר מיום.",
"TaskCleanTranscode": "נקה תקיית Transcode",
- "TaskUpdatePluginsDescription": "הורד והתקן עדכונים עבור תוספים שמוגדרים לעדכון אוטומטי."
+ "TaskUpdatePluginsDescription": "הורד והתקן עדכונים עבור תוספים שמוגדרים לעדכון אוטומטי.",
+ "TaskCleanActivityLogDescription": "מחק רשומת פעילות הישנה יותר מהגיל המוגדר.",
+ "TaskCleanActivityLog": "נקה רשומת פעילות",
+ "Undefined": "לא מוגדר",
+ "Forced": "כפוי",
+ "Default": "ברירת מחדל"
}
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": "இயல்புநிலை"
}
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"
}
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": "默认"
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
index 6f81bf49bb..cfbf03ddc0 100644
--- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
@@ -136,7 +136,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
{
var type = scheduledTask.ScheduledTask.GetType();
- _logger.LogInformation("Queueing task {0}", type.Name);
+ _logger.LogInformation("Queuing task {0}", type.Name);
lock (_taskQueue)
{
@@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
{
var type = task.ScheduledTask.GetType();
- _logger.LogInformation("Queueing task {0}", type.Name);
+ _logger.LogInformation("Queuing task {0}", type.Name);
lock (_taskQueue)
{
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
index 966ed5024e..7c4e003112 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
@@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.SyncPlay
new Dictionary();
///
- /// Lock used for accesing any group.
+ /// Lock used for accessing any group.
///
private readonly object _groupsLock = new object();
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/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index d4c6e4af94..ae8c05d858 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
@@ -42,7 +42,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The dlna device profile id to utilize.
/// The play session id.
/// The segment container.
- /// The segment lenght.
+ /// The segment length.
/// The minimum number of segments.
/// The media version id, if playing an alternate version.
/// The device id of the client requesting. Used to stop encoding processes when needed.
@@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 1c2e4474cb..5714a254a4 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The dlna device profile id to utilize.
/// The play session id.
/// The segment container.
- /// The segment lenght.
+ /// The segment length.
/// The minimum number of segments.
/// The media version id, if playing an alternate version.
/// The device id of the client requesting. Used to stop encoding processes when needed.
@@ -149,7 +149,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
@@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The dlna device profile id to utilize.
/// The play session id.
/// The segment container.
- /// The segment lenght.
+ /// The segment length.
/// The minimum number of segments.
/// The media version id, if playing an alternate version.
/// The device id of the client requesting. Used to stop encoding processes when needed.
@@ -315,7 +315,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
@@ -452,7 +452,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The dlna device profile id to utilize.
/// The play session id.
/// The segment container.
- /// The segment lenght.
+ /// The segment length.
/// The minimum number of segments.
/// The media version id, if playing an alternate version.
/// The device id of the client requesting. Used to stop encoding processes when needed.
@@ -481,7 +481,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
@@ -615,7 +615,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The dlna device profile id to utilize.
/// The play session id.
/// The segment container.
- /// The segment lenght.
+ /// The segment length.
/// The minimum number of segments.
/// The media version id, if playing an alternate version.
/// The device id of the client requesting. Used to stop encoding processes when needed.
@@ -645,7 +645,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
@@ -812,7 +812,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
@@ -953,7 +953,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The dlna device profile id to utilize.
/// The play session id.
/// The segment container.
- /// The segment lenght.
+ /// The segment length.
/// The minimum number of segments.
/// The media version id, if playing an alternate version.
/// The device id of the client requesting. Used to stop encoding processes when needed.
@@ -983,7 +983,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
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
///
/// Environment Controller.
///
- [Authorize(Policy = Policies.RequiresElevation)]
+ [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
public class EnvironmentController : BaseJellyfinApiController
{
private const char UncSeparator = '\\';
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index d8d371ebc3..15d7bd0b8c 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
@@ -73,8 +73,8 @@ namespace Jellyfin.Api.Controllers
/// Optional filter by items that have or do not have a parental rating.
/// Optional filter by items that are HD or not.
/// Optional filter by items that are 4K or not.
- /// Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.
- /// Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimeted.
+ /// Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.
+ /// Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.
/// Optional filter by items that are missing episodes or not.
/// Optional filter by items that are unaired episodes or not.
/// Optional filter by minimum community rating.
@@ -87,42 +87,42 @@ namespace Jellyfin.Api.Controllers
/// Optional filter by items that have an imdb id or not.
/// Optional filter by items that have a tmdb id or not.
/// Optional filter by items that have a tvdb id or not.
- /// Optional. If specified, results will be filtered by exxcluding item ids. This allows multiple, comma delimeted.
+ /// Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.
/// Optional. The record index to start at. All items with a lower index will be dropped from the results.
/// Optional. The maximum number of records to return.
/// When searching within folders, this determines whether or not the search will be recursive. true/false.
/// Optional. Filter based on a search term.
/// Sort Order - Ascending,Descending.
/// Specify this to localize the search to a specific item or folder. Omit to use the root.
- /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.
- /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.
- /// Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimeted.
- /// Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.
+ /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.
+ /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.
+ /// Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.
+ /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.
/// Optional filter by items that are marked as favorite, or not.
/// Optional filter by MediaType. Allows multiple, comma delimited.
/// Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.
- /// Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.
+ /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.
/// Optional filter by items that are played, or not.
- /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.
+ /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.
/// Optional, include user data.
/// Optional, the max number of images to return, per image type.
/// Optional. The image types to include in the output.
/// Optional. If specified, results will be filtered to include only those containing the specified person.
/// Optional. If specified, results will be filtered to include only those containing the specified person id.
/// Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.
- /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimeted.
+ /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.
/// Optional. If specified, results will be filtered to include only those containing the specified artist id.
/// Optional. If specified, results will be filtered to include only those containing the specified album artist id.
/// Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.
- /// Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimeted.
+ /// Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.
/// Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.
- /// Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.
+ /// Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.
/// Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).
/// Optional filter by items that are locked.
/// Optional filter by items that are placeholders.
@@ -133,12 +133,12 @@ namespace Jellyfin.Api.Controllers
/// Optional. Filter by the maximum width of the item.
/// Optional. Filter by the maximum height of the item.
/// Optional filter by items that are 3D, or not.
- /// Optional filter by Series Status. Allows multiple, comma delimeted.
+ /// Optional filter by Series Status. Allows multiple, comma delimited.
/// Optional filter by items whose name is sorted equally or greater than a given input string.
/// Optional filter by items whose name is sorted equally than a given input string.
/// Optional filter by items whose name is equally or lesser than a given input string.
- /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimeted.
+ /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.
/// Optional. Enable the total record count.
/// Optional, include image information in output.
/// A with the items.
@@ -513,13 +513,13 @@ namespace Jellyfin.Api.Controllers
/// The item limit.
/// The search term.
/// Specify this to localize the search to a specific item or folder. Omit to use the root.
- /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.
+ /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.
/// Optional. Filter by MediaType. Allows multiple, comma delimited.
/// Optional. Include user data.
/// Optional. The max number of images to return, per image type.
/// Optional. The image types to include in the output.
- /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.
- /// Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimeted.
+ /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.
+ /// Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.
/// Optional. Enable the total record count.
/// Optional. Include image information in output.
/// Items returned.
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 29c0f1df4d..41eb4f0303 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
@@ -1155,7 +1155,8 @@ namespace Jellyfin.Api.Controllers
/// Only discover new tuners.
/// Tuners returned.
/// An containing the tuners.
- [HttpGet("Tuners/Discvover")]
+ [HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
+ [HttpGet("Tuners/Discover")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index d78adcbcdc..12a14a72c9 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Jellyfin.Api.Constants;
using Jellyfin.Api.ModelBinders;
using MediaBrowser.Model.Dto;
@@ -42,8 +42,8 @@ namespace Jellyfin.Api.Controllers
/// Optional filter by items that have or do not have a parental rating.
/// Optional filter by items that are HD or not.
/// Optional filter by items that are 4K or not.
- /// Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.
- /// Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimeted.
+ /// Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.
+ /// Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.
/// Optional filter by items that are missing episodes or not.
/// Optional filter by items that are unaired episodes or not.
/// Optional filter by minimum community rating.
@@ -56,41 +56,41 @@ namespace Jellyfin.Api.Controllers
/// Optional filter by items that have an imdb id or not.
/// Optional filter by items that have a tmdb id or not.
/// Optional filter by items that have a tvdb id or not.
- /// Optional. If specified, results will be filtered by exxcluding item ids. This allows multiple, comma delimeted.
+ /// Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.
/// Optional. The record index to start at. All items with a lower index will be dropped from the results.
/// Optional. The maximum number of records to return.
/// When searching within folders, this determines whether or not the search will be recursive. true/false.
/// Optional. Filter based on a search term.
/// Sort Order - Ascending,Descending.
/// Specify this to localize the search to a specific item or folder. Omit to use the root.
- /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.
- /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.
- /// Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.
+ /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.
+ /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.
+ /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.
/// Optional filter by items that are marked as favorite, or not.
/// Optional filter by MediaType. Allows multiple, comma delimited.
/// Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.
- /// Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.
+ /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.
/// Optional filter by items that are played, or not.
- /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.
+ /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.
/// Optional, include user data.
/// Optional, the max number of images to return, per image type.
/// Optional. The image types to include in the output.
/// Optional. If specified, results will be filtered to include only those containing the specified person.
/// Optional. If specified, results will be filtered to include only those containing the specified person id.
/// Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.
- /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimeted.
+ /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.
/// Optional. If specified, results will be filtered to include only those containing the specified artist id.
/// Optional. If specified, results will be filtered to include only those containing the specified album artist id.
/// Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.
- /// Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimeted.
+ /// Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.
/// Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.
- /// Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.
+ /// Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.
/// Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).
/// Optional filter by items that are locked.
/// Optional filter by items that are placeholders.
@@ -101,12 +101,12 @@ namespace Jellyfin.Api.Controllers
/// Optional. Filter by the maximum width of the item.
/// Optional. Filter by the maximum height of the item.
/// Optional filter by items that are 3D, or not.
- /// Optional filter by Series Status. Allows multiple, comma delimeted.
+ /// Optional filter by Series Status. Allows multiple, comma delimited.
/// Optional filter by items whose name is sorted equally or greater than a given input string.
/// Optional filter by items whose name is sorted equally than a given input string.
/// Optional filter by items whose name is equally or lesser than a given input string.
- /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimeted.
- /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimeted.
+ /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.
+ /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.
/// Optional. Enable the total record count.
/// Optional, include image information in output.
/// A with the trailers.
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 6fd154836b..57b056f50b 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -176,7 +176,7 @@ namespace Jellyfin.Api.Controllers
///
/// The series id.
/// The user id.
- /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.
+ /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.
/// Optional filter by season number.
/// Optional. Filter by season id.
/// Optional. Filter by items that are missing episodes or not.
@@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers
/// Optional, the max number of images to return, per image type.
/// Optional. The image types to include in the output.
/// Optional. Include user data.
- /// Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.
+ /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.
/// A with the episodes on success or a if the series was not found.
[HttpGet("{seriesId}/Episodes")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers
///
/// The series id.
/// The user id.
- /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.
+ /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.
/// Optional. Filter by special season.
/// Optional. Filter by items that are missing episodes or not.
/// Optional. Return items that are siblings of a supplied item.
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index cfd8511297..2a6547bbb1 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
@@ -253,7 +253,7 @@ namespace Jellyfin.Api.Controllers
/// User id.
/// Specify this to localize the search to a specific item or folder. Omit to use the root.
/// Optional. Specify additional fields of information to return in the output.
- /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.
+ /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.
/// Filter by items that are played, or not.
/// Optional. include image information in output.
/// Optional. the max number of images to return, per image type.
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index df9d35b13f..86b8cdac20 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
@@ -145,7 +145,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 4de7aac71d..07b114bb77 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -283,7 +283,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The dlna device profile id to utilize.
/// The play session id.
/// The segment container.
- /// The segment lenght.
+ /// The segment length.
/// The minimum number of segments.
/// The media version id, if playing an alternate version.
/// The device id of the client requesting. Used to stop encoding processes when needed.
@@ -312,7 +312,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The maximum video bit depth.
/// Optional. Whether to require avc.
/// Optional. Whether to deinterlace the video.
- /// Optional. Whether to require a non anamporphic stream.
+ /// Optional. Whether to require a non anamorphic stream.
/// Optional. The maximum number of audio channels to transcode.
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
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
/// Whether the current request is a HTTP HEAD request so only the headers get returned.
/// The making the remote request.
/// The current http context.
+ /// A cancellation token that can be used to cancel the operation.
/// A containing the API response.
public static async Task 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(), contentType);
}
- return new FileStreamResult(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType);
+ return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
}
///
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index 168dc27a83..26a03105d7 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
@@ -582,7 +582,7 @@ namespace Jellyfin.Api.Helpers
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
_ = new JobLogger(_logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
- // Wait for the file to exist before proceeeding
+ // Wait for the file to exist before proceeding
var ffmpegTargetFile = state.WaitForPath ?? outputPath;
_logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile);
while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited)
diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
index 4467c9bbdc..f9539964d0 100644
--- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
@@ -73,7 +73,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Gets or sets the next item in the collection.
///
///
- /// TODO check if this properly updated dependant and has the proper principal relationship.
+ /// TODO check if this properly updated Dependant and has the proper principal relationship.
///
public virtual CollectionItem Next { get; set; }
@@ -81,7 +81,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Gets or sets the previous item in the collection.
///
///
- /// TODO check if this properly updated dependant and has the proper principal relationship.
+ /// TODO check if this properly updated Dependant and has the proper principal relationship.
///
public virtual CollectionItem Previous { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
index 1d2dc0f669..d74330c051 100644
--- a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
@@ -141,7 +141,7 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection PersonRoles { get; protected set; }
///
- /// Gets or sets a collection containing the generes for this item.
+ /// Gets or sets a collection containing the genres for this item.
///
public virtual ICollection Genres { get; protected set; }
diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
index f79e433a67..662b4bf651 100644
--- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
@@ -53,7 +53,7 @@ namespace Jellyfin.Server.Implementations.Users
bool success = false;
- // As long as jellyfin supports passwordless users, we need this little block here to accommodate
+ // As long as jellyfin supports password-less users, we need this little block here to accommodate
if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password))
{
return Task.FromResult(new ProviderAuthenticationResult
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.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index b67a549835..c2145aec5a 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -1,4 +1,4 @@
-
+
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
///
public DefaultHttpClientHandler()
{
- // TODO change to DecompressionMethods.All with .NET5
- AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
+ AutomaticDecompression = DecompressionMethods.All;
}
}
}
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index a0330afeff..12966a474f 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -58,7 +58,7 @@ namespace MediaBrowser.Common.Net
///
/// Investigates an caches a list of interface addresses, excluding local link and LAN excluded addresses.
///
- /// The list of ipaddresses.
+ /// The list of ip addresses.
IPAddress[] GetLocalIpAddresses();
///
@@ -73,7 +73,7 @@ namespace MediaBrowser.Common.Net
/// Returns true if address is in the LAN list in the config file.
///
/// The address to check.
- /// If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address.
+ /// If true, check against addresses in the LAN settings which have [] around and return true if it matches the address give in address.
/// If true, returns false if address is in the 127.x.x.x or 169.128.x.x range.
/// falseif the address isn't in the LAN list, true if the address has been defined as a LAN address.
bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC);
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);
}
///
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
new file mode 100644
index 0000000000..67aa7f3383
--- /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);
+ }
+ }
+}
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
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 1d44a55114..1b25fbdbb7 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -463,60 +463,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)
@@ -2611,7 +2557,7 @@ namespace MediaBrowser.Controller.Entities
{
if (!AllowsMultipleImages(type))
{
- throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
+ throw new ArgumentException("The change index operation is only applicable to backdrops and screen shots");
}
var info1 = GetImageInfo(type, index1);
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index a76c8a376b..1e3825c6e3 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.Entities
///
/// Loads our children. Validation will occur externally.
- /// We want this sychronous.
+ /// We want this synchronous.
///
protected virtual List LoadChildren()
{
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 0fb8a92381..a115179e83 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
- // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+ // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this.
if (state.VideoType == VideoType.VideoFile)
{
var hwType = encodingOptions.HardwareAccelerationType;
@@ -247,7 +247,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- // Seeing reported failures here, not sure yet if this is related to specfying input format
+ // Seeing reported failures here, not sure yet if this is related to specifying input format
if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
{
return null;
@@ -2757,7 +2757,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
- // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+ // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this.
if (videoType != VideoType.VideoFile)
{
return null;
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 6e9362cd14..db72fa56cc 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -409,7 +409,7 @@ 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 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.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.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
index a4305c8104..2f8614386a 100644
--- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
+++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
@@ -29,7 +29,7 @@ namespace MediaBrowser.Model.Dlna
int? maxWidth,
int? maxHeight)
{
- // If the bitrate isn't changing, then don't downlscale the resolution
+ // If the bitrate isn't changing, then don't downscale the resolution
if (inputBitrate.HasValue && outputBitrate >= inputBitrate.Value)
{
if (maxWidth.HasValue || maxHeight.HasValue)
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 13234c3813..43d1f3b443 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..93ea82c1c5 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; }
@@ -183,8 +185,10 @@ 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, "false", StringComparison.OrdinalIgnoreCase))
+ string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase))
{
continue;
}
@@ -250,6 +254,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 +526,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;
}
}
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..e7e44876db 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;
}
///
@@ -181,7 +186,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,
@@ -392,7 +397,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;
}
@@ -436,7 +441,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;
}
diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs
index 70a5a6ac13..5621d2b86c 100644
--- a/MediaBrowser.Providers/Manager/ProviderUtils.cs
+++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.Manager
{
if (replaceData || string.IsNullOrEmpty(target.Name))
{
- // Safeguard against incoming data having an emtpy name
+ // Safeguard against incoming data having an empty name
if (!string.IsNullOrWhiteSpace(source.Name))
{
target.Name = source.Name;
@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Manager
if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
{
- // Safeguard against incoming data having an emtpy name
+ // Safeguard against incoming data having an empty name
if (!string.IsNullOrWhiteSpace(source.OriginalTitle))
{
target.OriginalTitle = source.OriginalTitle;
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 @@
-
+
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();
if (isSearch)
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index 9eed6172dd..332479ff8a 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var result = await GetRootObject(imdbId, cancellationToken).ConfigureAwait(false);
- // Only take the name and rating if the user's language is set to english, since Omdb has no localization
+ // Only take the name and rating if the user's language is set to English, since Omdb has no localization
if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase) || _configurationManager.Configuration.EnableNewOmdbSupport)
{
item.Name = result.Title;
@@ -151,7 +151,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return false;
}
- // Only take the name and rating if the user's language is set to english, since Omdb has no localization
+ // Only take the name and rating if the user's language is set to English, since Omdb has no localization
if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase) || _configurationManager.Configuration.EnableNewOmdbSupport)
{
item.Name = result.Title;
@@ -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(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(stream).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path));
_jsonSerializer.SerializeToFile(rootObject, path);
@@ -385,7 +385,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var isConfiguredForEnglish = IsConfiguredForEnglish(item) || _configurationManager.Configuration.EnableNewOmdbSupport;
// Grab series genres because IMDb data is better than TVDB. Leave movies alone
- // But only do it if english is the preferred language because this data will not be localized
+ // But only do it if English is the preferred language because this data will not be localized
if (isConfiguredForEnglish && !string.IsNullOrWhiteSpace(result.Genre))
{
item.Genres = Array.Empty();
@@ -401,7 +401,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
if (isConfiguredForEnglish)
{
- // Omdb is currently english only, so for other languages skip this and let secondary providers fill it in
+ // Omdb is currently English only, so for other languages skip this and let secondary providers fill it in
item.Overview = result.Plot;
}
@@ -455,7 +455,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
{
var lang = item.GetPreferredMetadataLanguage();
- // The data isn't localized and so can only be used for english users
+ // The data isn't localized and so can only be used for English users
return string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase);
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs
deleted file mode 100644
index 690a52c4d3..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Plugins;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public class PluginConfiguration : BasePluginConfiguration
- {
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs
deleted file mode 100644
index e7079ed3cd..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Plugins;
-using MediaBrowser.Model.Serialization;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public class Plugin : BasePlugin
- {
- public static Plugin Instance { get; private set; }
-
- public override Guid Id => new Guid("a677c0da-fac5-4cde-941a-7134223f14c8");
-
- public override string Name => "TheTVDB";
-
- public override string Description => "Get metadata for movies and other video content from TheTVDB.";
-
- // TODO remove when plugin removed from server.
- public override string ConfigurationFileName => "Jellyfin.Plugin.TheTvdb.xml";
-
- public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
- : base(applicationPaths, xmlSerializer)
- {
- Instance = this;
- }
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
deleted file mode 100644
index ce0dab701d..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
+++ /dev/null
@@ -1,289 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using Microsoft.Extensions.Caching.Memory;
-using TvDbSharper;
-using TvDbSharper.Dto;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public class TvdbClientManager
- {
- private const string DefaultLanguage = "en";
-
- private readonly IMemoryCache _cache;
- private readonly TvDbClient _tvDbClient;
- private DateTime _tokenCreatedAt;
-
- public TvdbClientManager(IMemoryCache memoryCache)
- {
- _cache = memoryCache;
- _tvDbClient = new TvDbClient();
- }
-
- private TvDbClient TvDbClient
- {
- get
- {
- if (string.IsNullOrEmpty(_tvDbClient.Authentication.Token))
- {
- _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult();
- _tokenCreatedAt = DateTime.Now;
- }
-
- // Refresh if necessary
- if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20)))
- {
- try
- {
- _tvDbClient.Authentication.RefreshTokenAsync().GetAwaiter().GetResult();
- }
- catch
- {
- _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult();
- }
-
- _tokenCreatedAt = DateTime.Now;
- }
-
- return _tvDbClient;
- }
- }
-
- public Task> GetSeriesByNameAsync(string name, string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey("series", name, language);
- return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken));
- }
-
- public Task> GetSeriesByIdAsync(int tvdbId, string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey("series", tvdbId, language);
- return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetAsync(tvdbId, cancellationToken));
- }
-
- public Task> GetEpisodesAsync(int episodeTvdbId, string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey("episode", episodeTvdbId, language);
- return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
- }
-
- public Task> GetSeriesByImdbIdAsync(
- string imdbId,
- string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey("series", imdbId, language);
- return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken));
- }
-
- public Task> GetSeriesByZap2ItIdAsync(
- string zap2ItId,
- string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey("series", zap2ItId, language);
- return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken));
- }
-
- public Task> GetActorsAsync(
- int tvdbId,
- string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey("actors", tvdbId, language);
- return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetActorsAsync(tvdbId, cancellationToken));
- }
-
- public Task> GetImagesAsync(
- int tvdbId,
- ImagesQuery imageQuery,
- string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey("images", tvdbId, language, imageQuery);
- return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesAsync(tvdbId, imageQuery, cancellationToken));
- }
-
- public Task> GetLanguagesAsync(CancellationToken cancellationToken)
- {
- return TryGetValue("languages", null, () => TvDbClient.Languages.GetAllAsync(cancellationToken));
- }
-
- public Task> GetSeriesEpisodeSummaryAsync(
- int tvdbId,
- string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey("seriesepisodesummary", tvdbId, language);
- return TryGetValue(cacheKey, language,
- () => TvDbClient.Series.GetEpisodesSummaryAsync(tvdbId, cancellationToken));
- }
-
- public Task> GetEpisodesPageAsync(
- int tvdbId,
- int page,
- EpisodeQuery episodeQuery,
- string language,
- CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey(language, tvdbId, episodeQuery);
-
- return TryGetValue(cacheKey, language,
- () => TvDbClient.Series.GetEpisodesAsync(tvdbId, page, episodeQuery, cancellationToken));
- }
-
- public Task GetEpisodeTvdbId(
- EpisodeInfo searchInfo,
- string language,
- CancellationToken cancellationToken)
- {
- searchInfo.SeriesProviderIds.TryGetValue(
- nameof(MetadataProvider.Tvdb),
- out var seriesTvdbId);
-
- var episodeQuery = new EpisodeQuery();
-
- // Prefer SxE over premiere date as it is more robust
- if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue)
- {
- switch (searchInfo.SeriesDisplayOrder)
- {
- case "dvd":
- episodeQuery.DvdEpisode = searchInfo.IndexNumber.Value;
- episodeQuery.DvdSeason = searchInfo.ParentIndexNumber.Value;
- break;
- case "absolute":
- episodeQuery.AbsoluteNumber = searchInfo.IndexNumber.Value;
- break;
- default:
- // aired order
- episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value;
- episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value;
- break;
- }
- }
- else if (searchInfo.PremiereDate.HasValue)
- {
- // tvdb expects yyyy-mm-dd format
- episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
- }
-
- return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), episodeQuery, language, cancellationToken);
- }
-
- public async Task GetEpisodeTvdbId(
- int seriesTvdbId,
- EpisodeQuery episodeQuery,
- string language,
- CancellationToken cancellationToken)
- {
- var episodePage =
- await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken)
- .ConfigureAwait(false);
- return episodePage.Data.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
- }
-
- public Task> GetEpisodesPageAsync(
- int tvdbId,
- EpisodeQuery episodeQuery,
- string language,
- CancellationToken cancellationToken)
- {
- return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
- }
-
- public async IAsyncEnumerable GetImageKeyTypesForSeriesAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
- var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
-
- if (imagesSummary.Data.Fanart > 0)
- {
- yield return KeyType.Fanart;
- }
-
- if (imagesSummary.Data.Series > 0)
- {
- yield return KeyType.Series;
- }
-
- if (imagesSummary.Data.Poster > 0)
- {
- yield return KeyType.Poster;
- }
- }
-
- public async IAsyncEnumerable GetImageKeyTypesForSeasonAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
- {
- var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
- var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
-
- if (imagesSummary.Data.Season > 0)
- {
- yield return KeyType.Season;
- }
-
- if (imagesSummary.Data.Fanart > 0)
- {
- yield return KeyType.Fanart;
- }
-
- // TODO seasonwide is not supported in TvDbSharper
- }
-
- private async Task TryGetValue(string key, string language, Func> resultFactory)
- {
- if (_cache.TryGetValue(key, out T cachedValue))
- {
- return cachedValue;
- }
-
- _tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
- var result = await resultFactory.Invoke().ConfigureAwait(false);
- _cache.Set(key, result, TimeSpan.FromHours(1));
- return result;
- }
-
- private static string GenerateKey(params object[] objects)
- {
- var key = string.Empty;
-
- foreach (var obj in objects)
- {
- var objType = obj.GetType();
- if (objType.IsPrimitive || objType == typeof(string))
- {
- key += obj + ";";
- }
- else
- {
- foreach (PropertyInfo propertyInfo in objType.GetProperties())
- {
- var currentValue = propertyInfo.GetValue(obj, null);
- if (currentValue == null)
- {
- continue;
- }
-
- key += propertyInfo.Name + "=" + currentValue + ";";
- }
- }
- }
-
- return key;
- }
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
deleted file mode 100644
index 50a876d6c5..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Globalization;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using Microsoft.Extensions.Logging;
-using TvDbSharper;
-using TvDbSharper.Dto;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public class TvdbEpisodeImageProvider : IRemoteImageProvider
- {
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly ILogger _logger;
- private readonly TvdbClientManager _tvdbClientManager;
-
- public TvdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
- {
- _httpClientFactory = httpClientFactory;
- _logger = logger;
- _tvdbClientManager = tvdbClientManager;
- }
-
- public string Name => "TheTVDB";
-
- public bool Supports(BaseItem item)
- {
- return item is Episode;
- }
-
- public IEnumerable GetSupportedImages(BaseItem item)
- {
- return new List
- {
- ImageType.Primary
- };
- }
-
- public async Task> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- var episode = (Episode)item;
- var series = episode.Series;
- var imageResult = new List();
- var language = item.GetPreferredMetadataLanguage();
- if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
- {
- // Process images
- try
- {
- string episodeTvdbId = null;
-
- if (episode.IndexNumber.HasValue && episode.ParentIndexNumber.HasValue)
- {
- var episodeInfo = new EpisodeInfo
- {
- IndexNumber = episode.IndexNumber.Value,
- ParentIndexNumber = episode.ParentIndexNumber.Value,
- SeriesProviderIds = series.ProviderIds,
- SeriesDisplayOrder = series.DisplayOrder
- };
-
- episodeTvdbId = await _tvdbClientManager
- .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
- }
-
- if (string.IsNullOrEmpty(episodeTvdbId))
- {
- _logger.LogError(
- "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
- episode.ParentIndexNumber,
- episode.IndexNumber,
- series.GetProviderId(MetadataProvider.Tvdb));
- return imageResult;
- }
-
- var episodeResult =
- await _tvdbClientManager
- .GetEpisodesAsync(Convert.ToInt32(episodeTvdbId, CultureInfo.InvariantCulture), language, cancellationToken)
- .ConfigureAwait(false);
-
- var image = GetImageInfo(episodeResult.Data);
- if (image != null)
- {
- imageResult.Add(image);
- }
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}", series.GetProviderId(MetadataProvider.Tvdb));
- }
- }
-
- return imageResult;
- }
-
- private RemoteImageInfo GetImageInfo(EpisodeRecord episode)
- {
- if (string.IsNullOrEmpty(episode.Filename))
- {
- return null;
- }
-
- return new RemoteImageInfo
- {
- Width = Convert.ToInt32(episode.ThumbWidth, CultureInfo.InvariantCulture),
- Height = Convert.ToInt32(episode.ThumbHeight, CultureInfo.InvariantCulture),
- ProviderName = Name,
- Url = TvdbUtils.BannerUrl + episode.Filename,
- Type = ImageType.Primary
- };
- }
-
- public int Order => 0;
-
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
- }
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
deleted file mode 100644
index fd72ea4a81..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
+++ /dev/null
@@ -1,262 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using Microsoft.Extensions.Logging;
-using TvDbSharper;
-using TvDbSharper.Dto;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- ///
- /// Class RemoteEpisodeProvider.
- ///
- public class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder
- {
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly ILogger _logger;
- private readonly TvdbClientManager _tvdbClientManager;
-
- public TvdbEpisodeProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
- {
- _httpClientFactory = httpClientFactory;
- _logger = logger;
- _tvdbClientManager = tvdbClientManager;
- }
-
- public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
- {
- var list = new List();
-
- // Either an episode number or date must be provided; and the dictionary of provider ids must be valid
- if ((searchInfo.IndexNumber == null && searchInfo.PremiereDate == null)
- || !TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds))
- {
- return list;
- }
-
- var metadataResult = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false);
-
- if (!metadataResult.HasMetadata)
- {
- return list;
- }
-
- var item = metadataResult.Item;
-
- list.Add(new RemoteSearchResult
- {
- IndexNumber = item.IndexNumber,
- Name = item.Name,
- ParentIndexNumber = item.ParentIndexNumber,
- PremiereDate = item.PremiereDate,
- ProductionYear = item.ProductionYear,
- ProviderIds = item.ProviderIds,
- SearchProviderName = Name,
- IndexNumberEnd = item.IndexNumberEnd
- });
-
- return list;
- }
-
- public string Name => "TheTVDB";
-
- public async Task> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
- {
- var result = new MetadataResult
- {
- QueriedById = true
- };
-
- if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
- (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
- {
- result = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name);
- }
-
- return result;
- }
-
- private async Task> GetEpisode(EpisodeInfo searchInfo, CancellationToken cancellationToken)
- {
- var result = new MetadataResult
- {
- QueriedById = true
- };
-
- string seriesTvdbId = searchInfo.GetProviderId(MetadataProvider.Tvdb);
- string episodeTvdbId = null;
- try
- {
- episodeTvdbId = await _tvdbClientManager
- .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
- .ConfigureAwait(false);
- if (string.IsNullOrEmpty(episodeTvdbId))
- {
- _logger.LogError(
- "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
- searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId);
- return result;
- }
-
- var episodeResult = await _tvdbClientManager.GetEpisodesAsync(
- Convert.ToInt32(episodeTvdbId), searchInfo.MetadataLanguage,
- cancellationToken).ConfigureAwait(false);
-
- result = MapEpisodeToResult(searchInfo, episodeResult.Data);
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to retrieve episode with id {EpisodeTvDbId}, series id {SeriesTvdbId}", episodeTvdbId, seriesTvdbId);
- }
-
- return result;
- }
-
- private static MetadataResult MapEpisodeToResult(EpisodeInfo id, EpisodeRecord episode)
- {
- var result = new MetadataResult
- {
- HasMetadata = true,
- Item = new Episode
- {
- IndexNumber = id.IndexNumber,
- ParentIndexNumber = id.ParentIndexNumber,
- IndexNumberEnd = id.IndexNumberEnd,
- AirsBeforeEpisodeNumber = episode.AirsBeforeEpisode,
- AirsAfterSeasonNumber = episode.AirsAfterSeason,
- AirsBeforeSeasonNumber = episode.AirsBeforeSeason,
- Name = episode.EpisodeName,
- Overview = episode.Overview,
- CommunityRating = (float?)episode.SiteRating,
- OfficialRating = episode.ContentRating,
- }
- };
- result.ResetPeople();
-
- var item = result.Item;
- item.SetProviderId(MetadataProvider.Tvdb, episode.Id.ToString());
- item.SetProviderId(MetadataProvider.Imdb, episode.ImdbId);
-
- if (string.Equals(id.SeriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase))
- {
- item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber);
- item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason;
- }
- else if (string.Equals(id.SeriesDisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase))
- {
- if (episode.AbsoluteNumber.GetValueOrDefault() != 0)
- {
- item.IndexNumber = episode.AbsoluteNumber;
- }
- }
- else if (episode.AiredEpisodeNumber.HasValue)
- {
- item.IndexNumber = episode.AiredEpisodeNumber;
- }
- else if (episode.AiredSeason.HasValue)
- {
- item.ParentIndexNumber = episode.AiredSeason;
- }
-
- if (DateTime.TryParse(episode.FirstAired, out var date))
- {
- // dates from tvdb are UTC but without offset or Z
- item.PremiereDate = date;
- item.ProductionYear = date.Year;
- }
-
- foreach (var director in episode.Directors)
- {
- result.AddPerson(new PersonInfo
- {
- Name = director,
- Type = PersonType.Director
- });
- }
-
- // GuestStars is a weird list of names and roles
- // Example:
- // 1: Some Actor (Role1
- // 2: Role2
- // 3: Role3)
- // 4: Another Actor (Role1
- // ...
- for (var i = 0; i < episode.GuestStars.Length; ++i)
- {
- var currentActor = episode.GuestStars[i];
- var roleStartIndex = currentActor.IndexOf('(', StringComparison.Ordinal);
-
- if (roleStartIndex == -1)
- {
- result.AddPerson(new PersonInfo
- {
- Type = PersonType.GuestStar,
- Name = currentActor,
- Role = string.Empty
- });
- continue;
- }
-
- var roles = new List { currentActor.Substring(roleStartIndex + 1) };
-
- // Fetch all roles
- for (var j = i + 1; j < episode.GuestStars.Length; ++j)
- {
- var currentRole = episode.GuestStars[j];
- var roleEndIndex = currentRole.IndexOf(')', StringComparison.Ordinal);
-
- if (roleEndIndex == -1)
- {
- roles.Add(currentRole);
- continue;
- }
-
- roles.Add(currentRole.TrimEnd(')'));
- // Update the outer index (keep in mind it adds 1 after the iteration)
- i = j;
- break;
- }
-
- result.AddPerson(new PersonInfo
- {
- Type = PersonType.GuestStar,
- Name = currentActor.Substring(0, roleStartIndex).Trim(),
- Role = string.Join(", ", roles)
- });
- }
-
- foreach (var writer in episode.Writers)
- {
- result.AddPerson(new PersonInfo
- {
- Name = writer,
- Type = PersonType.Writer
- });
- }
-
- result.ResultLanguage = episode.Language.EpisodeName;
- return result;
- }
-
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
- }
-
- public int Order => 0;
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
deleted file mode 100644
index a5cd425f6d..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using Microsoft.Extensions.Logging;
-using TvDbSharper;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder
- {
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly ILogger _logger;
- private readonly ILibraryManager _libraryManager;
- private readonly TvdbClientManager _tvdbClientManager;
-
- public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
- {
- _libraryManager = libraryManager;
- _httpClientFactory = httpClientFactory;
- _logger = logger;
- _tvdbClientManager = tvdbClientManager;
- }
-
- ///
- public string Name => "TheTVDB";
-
- ///
- public int Order => 1;
-
- ///
- public bool Supports(BaseItem item) => item is Person;
-
- ///
- public IEnumerable GetSupportedImages(BaseItem item)
- {
- yield return ImageType.Primary;
- }
-
- ///
- public async Task> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- var seriesWithPerson = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { nameof(Series) },
- PersonIds = new[] { item.Id },
- DtoOptions = new DtoOptions(false)
- {
- EnableImages = false
- }
- }).Cast()
- .Where(i => TvdbSeriesProvider.IsValidSeries(i.ProviderIds))
- .ToList();
-
- var infos = (await Task.WhenAll(seriesWithPerson.Select(async i =>
- await GetImageFromSeriesData(i, item.Name, cancellationToken).ConfigureAwait(false)))
- .ConfigureAwait(false))
- .Where(i => i != null)
- .Take(1);
-
- return infos;
- }
-
- private async Task GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken)
- {
- var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProvider.Tvdb));
-
- try
- {
- var actorsResult = await _tvdbClientManager
- .GetActorsAsync(tvdbId, series.GetPreferredMetadataLanguage(), cancellationToken)
- .ConfigureAwait(false);
- var actor = actorsResult.Data.FirstOrDefault(a =>
- string.Equals(a.Name, personName, StringComparison.OrdinalIgnoreCase) &&
- !string.IsNullOrEmpty(a.Image));
- if (actor == null)
- {
- return null;
- }
-
- return new RemoteImageInfo
- {
- Url = TvdbUtils.BannerUrl + actor.Image,
- Type = ImageType.Primary,
- ProviderName = Name
- };
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to retrieve actor {ActorName} from series {SeriesTvdbId}", personName, tvdbId);
- return null;
- }
- }
-
- ///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
- }
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
deleted file mode 100644
index 49576d488d..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using Microsoft.Extensions.Logging;
-using TvDbSharper;
-using TvDbSharper.Dto;
-using RatingType = MediaBrowser.Model.Dto.RatingType;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
- {
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly ILogger _logger;
- private readonly TvdbClientManager _tvdbClientManager;
-
- public TvdbSeasonImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
- {
- _httpClientFactory = httpClientFactory;
- _logger = logger;
- _tvdbClientManager = tvdbClientManager;
- }
-
- public string Name => ProviderName;
-
- public static string ProviderName => "TheTVDB";
-
- public bool Supports(BaseItem item)
- {
- return item is Season;
- }
-
- public IEnumerable GetSupportedImages(BaseItem item)
- {
- return new List
- {
- ImageType.Primary,
- ImageType.Banner,
- ImageType.Backdrop
- };
- }
-
- public async Task> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- var season = (Season)item;
- var series = season.Series;
-
- if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
- {
- return Array.Empty();
- }
-
- var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProvider.Tvdb));
- var seasonNumber = season.IndexNumber.Value;
- var language = item.GetPreferredMetadataLanguage();
- var remoteImages = new List();
-
- var keyTypes = _tvdbClientManager.GetImageKeyTypesForSeasonAsync(tvdbId, language, cancellationToken).ConfigureAwait(false);
- await foreach (var keyType in keyTypes)
- {
- var imageQuery = new ImagesQuery
- {
- KeyType = keyType,
- SubKey = seasonNumber.ToString()
- };
- try
- {
- var imageResults = await _tvdbClientManager
- .GetImagesAsync(tvdbId, imageQuery, language, cancellationToken).ConfigureAwait(false);
- remoteImages.AddRange(GetImages(imageResults.Data, language));
- }
- catch (TvDbServerException)
- {
- _logger.LogDebug("No images of type {KeyType} found for series {TvdbId}", keyType, tvdbId);
- }
- }
-
- return remoteImages;
- }
-
- private IEnumerable GetImages(Image[] images, string preferredLanguage)
- {
- var list = new List();
- // any languages with null ids are ignored
- var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data.Where(x => x.Id.HasValue);
- foreach (Image image in images)
- {
- var imageInfo = new RemoteImageInfo
- {
- RatingType = RatingType.Score,
- CommunityRating = (double?)image.RatingsInfo.Average,
- VoteCount = image.RatingsInfo.Count,
- Url = TvdbUtils.BannerUrl + image.FileName,
- ProviderName = ProviderName,
- Language = languages.FirstOrDefault(lang => lang.Id == image.LanguageId)?.Abbreviation,
- ThumbnailUrl = TvdbUtils.BannerUrl + image.Thumbnail
- };
-
- var resolution = image.Resolution.Split('x');
- if (resolution.Length == 2)
- {
- imageInfo.Width = Convert.ToInt32(resolution[0]);
- imageInfo.Height = Convert.ToInt32(resolution[1]);
- }
-
- imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType);
- list.Add(imageInfo);
- }
-
- var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
- return list.OrderByDescending(i =>
- {
- if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
-
- if (!isLanguageEn)
- {
- if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
- }
-
- if (string.IsNullOrEmpty(i.Language))
- {
- return isLanguageEn ? 3 : 2;
- }
-
- return 0;
- })
- .ThenByDescending(i => i.CommunityRating ?? 0)
- .ThenByDescending(i => i.VoteCount ?? 0);
- }
-
- public int Order => 0;
-
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
- }
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
deleted file mode 100644
index d96840e51c..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
+++ /dev/null
@@ -1,153 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using Microsoft.Extensions.Logging;
-using TvDbSharper;
-using TvDbSharper.Dto;
-using RatingType = MediaBrowser.Model.Dto.RatingType;
-using Series = MediaBrowser.Controller.Entities.TV.Series;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
- {
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly ILogger _logger;
- private readonly TvdbClientManager _tvdbClientManager;
-
- public TvdbSeriesImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
- {
- _httpClientFactory = httpClientFactory;
- _logger = logger;
- _tvdbClientManager = tvdbClientManager;
- }
-
- public string Name => ProviderName;
-
- public static string ProviderName => "TheTVDB";
-
- public bool Supports(BaseItem item)
- {
- return item is Series;
- }
-
- public IEnumerable GetSupportedImages(BaseItem item)
- {
- return new List
- {
- ImageType.Primary,
- ImageType.Banner,
- ImageType.Backdrop
- };
- }
-
- public async Task> GetImages(BaseItem item, CancellationToken cancellationToken)
- {
- if (!TvdbSeriesProvider.IsValidSeries(item.ProviderIds))
- {
- return Array.Empty();
- }
-
- var language = item.GetPreferredMetadataLanguage();
- var remoteImages = new List();
- var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tvdb));
- var allowedKeyTypes = _tvdbClientManager.GetImageKeyTypesForSeriesAsync(tvdbId, language, cancellationToken)
- .ConfigureAwait(false);
- await foreach (KeyType keyType in allowedKeyTypes)
- {
- var imageQuery = new ImagesQuery
- {
- KeyType = keyType
- };
- try
- {
- var imageResults =
- await _tvdbClientManager.GetImagesAsync(tvdbId, imageQuery, language, cancellationToken)
- .ConfigureAwait(false);
-
- remoteImages.AddRange(GetImages(imageResults.Data, language));
- }
- catch (TvDbServerException)
- {
- _logger.LogDebug("No images of type {KeyType} exist for series {TvDbId}", keyType,
- tvdbId);
- }
- }
-
- return remoteImages;
- }
-
- private IEnumerable GetImages(Image[] images, string preferredLanguage)
- {
- var list = new List();
- var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data;
-
- foreach (Image image in images)
- {
- var imageInfo = new RemoteImageInfo
- {
- RatingType = RatingType.Score,
- CommunityRating = (double?)image.RatingsInfo.Average,
- VoteCount = image.RatingsInfo.Count,
- Url = TvdbUtils.BannerUrl + image.FileName,
- ProviderName = Name,
- Language = languages.FirstOrDefault(lang => lang.Id == image.LanguageId)?.Abbreviation,
- ThumbnailUrl = TvdbUtils.BannerUrl + image.Thumbnail
- };
-
- var resolution = image.Resolution.Split('x');
- if (resolution.Length == 2)
- {
- imageInfo.Width = Convert.ToInt32(resolution[0]);
- imageInfo.Height = Convert.ToInt32(resolution[1]);
- }
-
- imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType);
- list.Add(imageInfo);
- }
-
- var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
- return list.OrderByDescending(i =>
- {
- if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
-
- if (!isLanguageEn)
- {
- if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
- }
-
- if (string.IsNullOrEmpty(i.Language))
- {
- return isLanguageEn ? 3 : 2;
- }
-
- return 0;
- })
- .ThenByDescending(i => i.CommunityRating ?? 0)
- .ThenByDescending(i => i.VoteCount ?? 0);
- }
-
- public int Order => 0;
-
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
- }
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
deleted file mode 100644
index e5a3e9a6a3..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
+++ /dev/null
@@ -1,419 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Net.Http;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Providers;
-using Microsoft.Extensions.Logging;
-using TvDbSharper;
-using TvDbSharper.Dto;
-using Series = MediaBrowser.Controller.Entities.TV.Series;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder
- {
- internal static TvdbSeriesProvider Current { get; private set; }
-
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly ILogger _logger;
- private readonly ILibraryManager _libraryManager;
- private readonly ILocalizationManager _localizationManager;
- private readonly TvdbClientManager _tvdbClientManager;
-
- public TvdbSeriesProvider(IHttpClientFactory httpClientFactory, ILogger logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvdbClientManager tvdbClientManager)
- {
- _httpClientFactory = httpClientFactory;
- _logger = logger;
- _libraryManager = libraryManager;
- _localizationManager = localizationManager;
- Current = this;
- _tvdbClientManager = tvdbClientManager;
- }
-
- public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
- {
- if (IsValidSeries(searchInfo.ProviderIds))
- {
- var metadata = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false);
-
- if (metadata.HasMetadata)
- {
- return new List
- {
- new RemoteSearchResult
- {
- Name = metadata.Item.Name,
- PremiereDate = metadata.Item.PremiereDate,
- ProductionYear = metadata.Item.ProductionYear,
- ProviderIds = metadata.Item.ProviderIds,
- SearchProviderName = Name
- }
- };
- }
- }
-
- return await FindSeries(searchInfo.Name, searchInfo.Year, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
- }
-
- public async Task> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken)
- {
- var result = new MetadataResult
- {
- QueriedById = true
- };
-
- if (!IsValidSeries(itemId.ProviderIds))
- {
- result.QueriedById = false;
- await Identify(itemId).ConfigureAwait(false);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (IsValidSeries(itemId.ProviderIds))
- {
- result.Item = new Series();
- result.HasMetadata = true;
-
- await FetchSeriesData(result, itemId.MetadataLanguage, itemId.ProviderIds, cancellationToken)
- .ConfigureAwait(false);
- }
-
- return result;
- }
-
- private async Task FetchSeriesData(MetadataResult result, string metadataLanguage, Dictionary seriesProviderIds, CancellationToken cancellationToken)
- {
- var series = result.Item;
-
- if (seriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(tvdbId))
- {
- series.SetProviderId(MetadataProvider.Tvdb, tvdbId);
- }
-
- if (seriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId))
- {
- series.SetProviderId(MetadataProvider.Imdb, imdbId);
- tvdbId = await GetSeriesByRemoteId(imdbId, MetadataProvider.Imdb.ToString(), metadataLanguage,
- cancellationToken).ConfigureAwait(false);
- }
-
- if (seriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(zap2It))
- {
- series.SetProviderId(MetadataProvider.Zap2It, zap2It);
- tvdbId = await GetSeriesByRemoteId(zap2It, MetadataProvider.Zap2It.ToString(), metadataLanguage,
- cancellationToken).ConfigureAwait(false);
- }
-
- try
- {
- var seriesResult =
- await _tvdbClientManager
- .GetSeriesByIdAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken)
- .ConfigureAwait(false);
- await MapSeriesToResult(result, seriesResult.Data, metadataLanguage).ConfigureAwait(false);
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to retrieve series with id {TvdbId}", tvdbId);
- return;
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- result.ResetPeople();
-
- try
- {
- var actorsResult = await _tvdbClientManager
- .GetActorsAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken).ConfigureAwait(false);
- MapActorsToResult(result, actorsResult.Data);
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to retrieve actors for series {TvdbId}", tvdbId);
- }
- }
-
- private async Task GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken)
- {
- TvDbResponse result = null;
-
- try
- {
- if (string.Equals(idType, MetadataProvider.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase))
- {
- result = await _tvdbClientManager.GetSeriesByZap2ItIdAsync(id, language, cancellationToken)
- .ConfigureAwait(false);
- }
- else
- {
- result = await _tvdbClientManager.GetSeriesByImdbIdAsync(id, language, cancellationToken)
- .ConfigureAwait(false);
- }
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to retrieve series with remote id {RemoteId}", id);
- }
-
- return result?.Data[0].Id.ToString(CultureInfo.InvariantCulture);
- }
-
- ///
- /// Check whether a dictionary of provider IDs includes an entry for a valid TV metadata provider.
- ///
- /// The dictionary to check.
- /// True, if the dictionary contains a valid TV provider ID, otherwise false.
- internal static bool IsValidSeries(Dictionary seriesProviderIds)
- {
- return seriesProviderIds.ContainsKey(MetadataProvider.Tvdb.ToString()) ||
- seriesProviderIds.ContainsKey(MetadataProvider.Imdb.ToString()) ||
- seriesProviderIds.ContainsKey(MetadataProvider.Zap2It.ToString());
- }
-
- ///
- /// Finds the series.
- ///
- /// The name.
- /// The year.
- /// The language.
- /// The cancellation token.
- /// Task{System.String}.
- private async Task> FindSeries(string name, int? year, string language, CancellationToken cancellationToken)
- {
- var results = await FindSeriesInternal(name, language, cancellationToken).ConfigureAwait(false);
-
- if (results.Count == 0)
- {
- var parsedName = _libraryManager.ParseName(name);
- var nameWithoutYear = parsedName.Name;
-
- if (!string.IsNullOrWhiteSpace(nameWithoutYear) && !string.Equals(nameWithoutYear, name, StringComparison.OrdinalIgnoreCase))
- {
- results = await FindSeriesInternal(nameWithoutYear, language, cancellationToken).ConfigureAwait(false);
- }
- }
-
- return results.Where(i =>
- {
- if (year.HasValue && i.ProductionYear.HasValue)
- {
- // Allow one year tolerance
- return Math.Abs(year.Value - i.ProductionYear.Value) <= 1;
- }
-
- return true;
- });
- }
-
- private async Task> FindSeriesInternal(string name, string language, CancellationToken cancellationToken)
- {
- var comparableName = GetComparableName(name);
- var list = new List, RemoteSearchResult>>();
- TvDbResponse result;
- try
- {
- result = await _tvdbClientManager.GetSeriesByNameAsync(comparableName, language, cancellationToken)
- .ConfigureAwait(false);
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "No series results found for {Name}", comparableName);
- return new List();
- }
-
- foreach (var seriesSearchResult in result.Data)
- {
- var tvdbTitles = new List
- {
- GetComparableName(seriesSearchResult.SeriesName)
- };
- tvdbTitles.AddRange(seriesSearchResult.Aliases.Select(GetComparableName));
-
- DateTime.TryParse(seriesSearchResult.FirstAired, out var firstAired);
- var remoteSearchResult = new RemoteSearchResult
- {
- Name = tvdbTitles.FirstOrDefault(),
- ProductionYear = firstAired.Year,
- SearchProviderName = Name
- };
-
- if (!string.IsNullOrEmpty(seriesSearchResult.Banner))
- {
- // Results from their Search endpoints already include the /banners/ part in the url, because reasons...
- remoteSearchResult.ImageUrl = TvdbUtils.TvdbImageBaseUrl + seriesSearchResult.Banner;
- }
-
- try
- {
- var seriesSesult =
- await _tvdbClientManager.GetSeriesByIdAsync(seriesSearchResult.Id, language, cancellationToken)
- .ConfigureAwait(false);
- remoteSearchResult.SetProviderId(MetadataProvider.Imdb, seriesSesult.Data.ImdbId);
- remoteSearchResult.SetProviderId(MetadataProvider.Zap2It, seriesSesult.Data.Zap2itId);
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Unable to retrieve series with id {TvdbId}", seriesSearchResult.Id);
- }
-
- remoteSearchResult.SetProviderId(MetadataProvider.Tvdb, seriesSearchResult.Id.ToString());
- list.Add(new Tuple, RemoteSearchResult>(tvdbTitles, remoteSearchResult));
- }
-
- return list
- .OrderBy(i => i.Item1.Contains(comparableName, StringComparer.OrdinalIgnoreCase) ? 0 : 1)
- .ThenBy(i => list.IndexOf(i))
- .Select(i => i.Item2)
- .ToList();
- }
-
- ///
- /// Gets the name of the comparable.
- ///
- /// The name.
- /// System.String.
- private string GetComparableName(string name)
- {
- name = name.ToLowerInvariant();
- name = name.Normalize(NormalizationForm.FormKD);
- name = name.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " ");
- name = name.Replace("&", " and " );
- name = Regex.Replace(name, @"[\p{Lm}\p{Mn}]", string.Empty); // Remove diacritics, etc
- name = Regex.Replace(name, @"[\W\p{Pc}]+", " "); // Replace sequences of non-word characters and _ with " "
- return name.Trim();
- }
-
- private async Task MapSeriesToResult(MetadataResult result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage)
- {
- Series series = result.Item;
- series.SetProviderId(MetadataProvider.Tvdb, tvdbSeries.Id.ToString());
- series.Name = tvdbSeries.SeriesName;
- series.Overview = (tvdbSeries.Overview ?? string.Empty).Trim();
- result.ResultLanguage = metadataLanguage;
- series.AirDays = TVUtils.GetAirDays(tvdbSeries.AirsDayOfWeek);
- series.AirTime = tvdbSeries.AirsTime;
- series.CommunityRating = (float?)tvdbSeries.SiteRating;
- series.SetProviderId(MetadataProvider.Imdb, tvdbSeries.ImdbId);
- series.SetProviderId(MetadataProvider.Zap2It, tvdbSeries.Zap2itId);
- if (Enum.TryParse(tvdbSeries.Status, true, out SeriesStatus seriesStatus))
- {
- series.Status = seriesStatus;
- }
-
- if (DateTime.TryParse(tvdbSeries.FirstAired, out var date))
- {
- // dates from tvdb are UTC but without offset or Z
- series.PremiereDate = date;
- series.ProductionYear = date.Year;
- }
-
- if (!string.IsNullOrEmpty(tvdbSeries.Runtime) && double.TryParse(tvdbSeries.Runtime, out double runtime))
- {
- series.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
- }
-
- foreach (var genre in tvdbSeries.Genre)
- {
- series.AddGenre(genre);
- }
-
- if (!string.IsNullOrEmpty(tvdbSeries.Network))
- {
- series.AddStudio(tvdbSeries.Network);
- }
-
- if (result.Item.Status.HasValue && result.Item.Status.Value == SeriesStatus.Ended)
- {
- try
- {
- var episodeSummary = await _tvdbClientManager.GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).ConfigureAwait(false);
-
- if (episodeSummary.Data.AiredSeasons.Length != 0)
- {
- var maxSeasonNumber = episodeSummary.Data.AiredSeasons.Max(s => Convert.ToInt32(s, CultureInfo.InvariantCulture));
- var episodeQuery = new EpisodeQuery
- {
- AiredSeason = maxSeasonNumber
- };
- var episodesPage = await _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).ConfigureAwait(false);
-
- result.Item.EndDate = episodesPage.Data
- .Select(e => DateTime.TryParse(e.FirstAired, out var firstAired) ? firstAired : (DateTime?)null)
- .Max();
- }
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to find series end date for series {TvdbId}", tvdbSeries.Id);
- }
- }
- }
-
- private static void MapActorsToResult(MetadataResult result, IEnumerable actors)
- {
- foreach (Actor actor in actors)
- {
- var personInfo = new PersonInfo
- {
- Type = PersonType.Actor,
- Name = (actor.Name ?? string.Empty).Trim(),
- Role = actor.Role,
- SortOrder = actor.SortOrder
- };
-
- if (!string.IsNullOrEmpty(actor.Image))
- {
- personInfo.ImageUrl = TvdbUtils.BannerUrl + actor.Image;
- }
-
- if (!string.IsNullOrWhiteSpace(personInfo.Name))
- {
- result.AddPerson(personInfo);
- }
- }
- }
-
- public string Name => "TheTVDB";
-
- public async Task Identify(SeriesInfo info)
- {
- if (!string.IsNullOrWhiteSpace(info.GetProviderId(MetadataProvider.Tvdb)))
- {
- return;
- }
-
- var srch = await FindSeries(info.Name, info.Year, info.MetadataLanguage, CancellationToken.None)
- .ConfigureAwait(false);
-
- var entry = srch.FirstOrDefault();
-
- if (entry != null)
- {
- var id = entry.GetProviderId(MetadataProvider.Tvdb);
- info.SetProviderId(MetadataProvider.Tvdb, id);
- }
- }
-
- public int Order => 0;
-
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
- }
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
deleted file mode 100644
index 37a8d04a6f..0000000000
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Providers.Plugins.TheTvdb
-{
- public static class TvdbUtils
- {
- public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
- public const string TvdbBaseUrl = "https://www.thetvdb.com/";
- public const string TvdbImageBaseUrl = "https://www.thetvdb.com";
- public const string BannerUrl = TvdbImageBaseUrl + "/banners/";
-
- public static ImageType GetImageTypeFromKeyType(string keyType)
- {
- switch (keyType.ToLowerInvariant())
- {
- case "poster":
- case "season": return ImageType.Primary;
- case "series":
- case "seasonwide": return ImageType.Banner;
- case "fanart": return ImageType.Backdrop;
- default: throw new ArgumentException($"Invalid or unknown keytype: {keyType}", nameof(keyType));
- }
- }
-
- public static string NormalizeLanguage(string language)
- {
- if (string.IsNullOrWhiteSpace(language))
- {
- return null;
- }
-
- // pt-br is just pt to tvdb
- return language.Split('-')[0].ToLowerInvariant();
- }
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index b754a07953..0e8a5baab6 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
@@ -98,7 +98,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
if (preferredLanguage.Length == 5) // like en-US
{
- // Currenty, TMDB supports 2-letter language codes only
+ // Currently, TMDB supports 2-letter language codes only
// They are planning to change this in the future, thus we're
// supplying both codes if we're having a 5-letter code.
languages.Add(preferredLanguage.Substring(0, 2));
diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs b/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs
deleted file mode 100644
index 40c5f2d785..0000000000
--- a/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.TheTvdb;
-
-namespace MediaBrowser.Providers.TV
-{
- public class TvdbEpisodeExternalId : IExternalId
- {
- ///
- public string ProviderName => "TheTVDB";
-
- ///
- public string Key => MetadataProvider.Tvdb.ToString();
-
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
-
- ///
- public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}";
-
- ///
- public bool Supports(IHasProviderIds item) => item is Episode;
- }
-}
diff --git a/MediaBrowser.Providers/TV/TvdbExternalId.cs b/MediaBrowser.Providers/TV/TvdbExternalId.cs
deleted file mode 100644
index 4c54de9f82..0000000000
--- a/MediaBrowser.Providers/TV/TvdbExternalId.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.TheTvdb;
-
-namespace MediaBrowser.Providers.TV
-{
- public class TvdbExternalId : IExternalId
- {
- ///
- public string ProviderName => "TheTVDB";
-
- ///
- public string Key => MetadataProvider.Tvdb.ToString();
-
- ///
- public ExternalIdMediaType? Type => null;
-
- ///
- public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}";
-
- ///
- public bool Supports(IHasProviderIds item) => item is Series;
- }
-}
diff --git a/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs b/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs
deleted file mode 100644
index 807ebb3eee..0000000000
--- a/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.TheTvdb;
-
-namespace MediaBrowser.Providers.TV
-{
- public class TvdbSeasonExternalId : IExternalId
- {
- ///
- public string ProviderName => "TheTVDB";
-
- ///
- public string Key => MetadataProvider.Tvdb.ToString();
-
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.Season;
-
- ///
- public string UrlFormatString => null;
-
- ///
- public bool Supports(IHasProviderIds item) => item is Season;
- }
-}
diff --git a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs
index c9f314af94..3cb18e4248 100644
--- a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs
+++ b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs
@@ -4,7 +4,6 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.TheTvdb;
namespace MediaBrowser.Providers.TV
{
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index 9cc0344c1c..bce4cf0093 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -134,7 +134,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val))
{
- // int.TryParse is local aware, so it can be probamatic, force us culture
+ // int.TryParse is local aware, so it can be problematic, force us culture
if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval))
{
item.AirsBeforeEpisodeNumber = rval;
@@ -150,7 +150,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val))
{
- // int.TryParse is local aware, so it can be probamatic, force us culture
+ // int.TryParse is local aware, so it can be problematic, force us culture
if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval))
{
item.AirsAfterSeasonNumber = rval;
@@ -166,7 +166,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val))
{
- // int.TryParse is local aware, so it can be probamatic, force us culture
+ // int.TryParse is local aware, so it can be problematic, force us culture
if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval))
{
item.AirsBeforeSeasonNumber = rval;
@@ -182,7 +182,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val))
{
- // int.TryParse is local aware, so it can be probamatic, force us culture
+ // int.TryParse is local aware, so it can be problematic, force us culture
if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval))
{
item.AirsBeforeSeasonNumber = rval;
@@ -198,7 +198,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val))
{
- // int.TryParse is local aware, so it can be probamatic, force us culture
+ // int.TryParse is local aware, so it can be problematic, force us culture
if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval))
{
item.AirsBeforeEpisodeNumber = rval;
diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs
index 745ec359c9..7d6a471f95 100644
--- a/RSSDP/DisposableManagedObjectBase.cs
+++ b/RSSDP/DisposableManagedObjectBase.cs
@@ -5,7 +5,7 @@ using System.Text;
namespace Rssdp.Infrastructure
{
///
- /// Correclty implements the interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an property.
+ /// Correctly implements the interface and pattern for an object containing only managed resources, and adds a few common niceties not on the interface such as an property.
///
public abstract class DisposableManagedObjectBase : IDisposable
{
@@ -61,10 +61,10 @@ namespace Rssdp.Infrastructure
/// Disposes this object instance and all internally managed resources.
///
///
- /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes.
+ /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behavior of derived classes.
///
///
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfere with the dispose process.")]
public void Dispose()
{
IsDisposed = true;
diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs
index 11202940e1..c56249523d 100644
--- a/RSSDP/HttpParserBase.cs
+++ b/RSSDP/HttpParserBase.cs
@@ -105,7 +105,7 @@ namespace Rssdp.Infrastructure
var headerName = line.Substring(0, headerKeySeparatorIndex).Trim();
var headerValue = line.Substring(headerKeySeparatorIndex + 1).Trim();
- // Not sure how to determine where request headers and and content headers begin,
+ // Not sure how to determine where request headers and content headers begin,
// at least not without a known set of headers (general headers first the content headers)
// which seems like a bad way of doing it. So we'll assume if it's a known content header put it there
// else use request headers.
diff --git a/RSSDP/ISsdpDeviceLocator.cs b/RSSDP/ISsdpDeviceLocator.cs
index 4130556434..4df166cd26 100644
--- a/RSSDP/ISsdpDeviceLocator.cs
+++ b/RSSDP/ISsdpDeviceLocator.cs
@@ -52,7 +52,7 @@ namespace Rssdp.Infrastructure
}
///
- /// Aynchronously performs a search for all devices using the default search timeout, and returns an awaitable task that can be used to retrieve the results.
+ /// Asynchronously performs a search for all devices using the default search timeout, and returns an awaitable task that can be used to retrieve the results.
///
/// A task whose result is an of instances, representing all found devices.
System.Threading.Tasks.Task> SearchAsync();
@@ -83,7 +83,7 @@ namespace Rssdp.Infrastructure
///
/// The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 is recommended by the UPnP 1.1 specification. Specify TimeSpan.Zero to return only devices already in the cache.
///
- /// By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implemetning these services if you know the service type.
+ /// By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implementing these services if you know the service type.
///
/// A task whose result is an of instances, representing all found devices.
System.Threading.Tasks.Task> SearchAsync(string searchTarget, TimeSpan searchWaitTime);
diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs
index 1a8577d8da..90925b9e0a 100644
--- a/RSSDP/SsdpDevicePublisher.cs
+++ b/RSSDP/SsdpDevicePublisher.cs
@@ -102,7 +102,7 @@ namespace Rssdp.Infrastructure
/// The instance to add.
/// Thrown if the argument is null.
/// Thrown if the contains property values that are not acceptable to the UPnP 1.0 specification.
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capture task to local variable supresses compiler warning, but task is not really needed.")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capture task to local variable suppresses compiler warning, but task is not really needed.")]
public void AddDevice(SsdpRootDevice device)
{
if (device == null)
@@ -180,7 +180,7 @@ namespace Rssdp.Infrastructure
///
///
/// Enabling this option will cause devices to show up in Microsoft Windows Explorer's network screens (if discovery is enabled etc.). Windows Explorer appears to search only for pnp:rootdeivce and not upnp:rootdevice.
- /// If false, the system will only use upnp:rootdevice for notifiation broadcasts and and search responses, which is correct according to the UPnP/SSDP spec.
+ /// If false, the system will only use upnp:rootdevice for notification broadcasts and and search responses, which is correct according to the UPnP/SSDP spec.
///
public bool SupportPnpRootDevice
{
diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs
index 8937ec331f..4084b31ca6 100644
--- a/RSSDP/SsdpRootDevice.cs
+++ b/RSSDP/SsdpRootDevice.cs
@@ -25,7 +25,7 @@ namespace Rssdp
/// Specifies how long clients can cache this device's details for. Optional but defaults to which means no-caching. Recommended value is half an hour.
///
///
- /// Specifiy to indicate no caching allowed.
+ /// Specify to indicate no caching allowed.
/// Also used to specify how often to rebroadcast alive notifications.
/// The UPnP/SSDP specifications indicate this should not be less than 1800 seconds (half an hour), but this is not enforced by this library.
///
@@ -50,7 +50,7 @@ namespace Rssdp
public IPAddress SubnetMask { get; set; }
///
- /// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional.
+ /// The base URL to use for all relative url's provided in other properties (and those of child devices). Optional.
///
///
/// Defines the base URL. Used to construct fully-qualified URLs. All relative URLs that appear elsewhere in the description are combined with this base URL. If URLBase is empty or not given, the base URL is the URL from which the device description was retrieved (which is the preferred implementation; use of URLBase is no longer recommended). Specified by UPnP vendor. Single URL.
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 @@
-
+
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/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
index 1084e20bda..e5768b6209 100644
--- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
@@ -1,4 +1,6 @@
-using System.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
using MediaBrowser.Model.IO;
@@ -18,11 +20,22 @@ 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",
+
+ "Badman/audiobook.mp3",
+ "Badman/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();
@@ -33,13 +46,141 @@ namespace Jellyfin.Naming.Tests.AudioBook
FullName = i
})).ToList();
+ Assert.Equal(5, result.Count);
+
Assert.Equal(2, result[0].Files.Count);
- // Assert.Empty(result[0].Extras); FIXME: AudioBookListResolver should resolve extra files properly
+ 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.Single(result[2].Files);
+ Assert.Single(result[2].Extras);
+ Assert.Equal("Badman", result[2].Name);
+
+ 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]
+ 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.mp3",
+ "Deadpool [HQ].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();
+
+ var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+ })).ToList();
+
+ Assert.Equal(5, result.Count);
+ // HP - Same name so we don't care which file is alternative
+ Assert.Single(result[0].AlternateVersions);
+ // DP
+ Assert.Empty(result[1].AlternateVersions);
+ // DP HQ (directory missing so we do not group deadpools together)
+ Assert.Empty(result[2].AlternateVersions);
+ // Superman
+ // Priority:
+ // 1. Name
+ // 2. audiobook
+ // 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[4].AlternateVersions);
+ }
+
+ [Fact]
+ public void TestNameYearExtraction()
+ {
+ var data = new[]
+ {
+ 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(data.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i.Path
+ })).ToList();
+
+ 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]
@@ -82,9 +223,51 @@ 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);
}
+
+ 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 673289436d..b3257ace3b 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)
};
}
@@ -52,13 +46,22 @@ 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);
}
[Fact]
- public void Resolve_EmptyFileName_ArgumentException()
+ public void Resolve_InvalidExtension()
{
- Assert.Throws(() => new AudioBookResolver(_namingOptions).Resolve(string.Empty));
+ var result = new AudioBookResolver(_namingOptions).Resolve(@"/server/AudioBooks/Larry Potter/Larry Potter.mp9");
+
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void Resolve_EmptyFileName()
+ {
+ var result = new AudioBookResolver(_namingOptions).Resolve(string.Empty);
+
+ Assert.Null(result);
}
}
}
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);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
index d11809de11..f3abacb4f9 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,21 +26,17 @@ 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]
[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(() => new SubtitleParser(_namingOptions).ParseFile(string.Empty));
- }
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
index 03aeb7f76b..12fc19bc48 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,98 @@ 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);
+ }
+
+ /*
+ * 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()
+ {
+ 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);
+ }
}
}
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);
}
}
}
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);
}
}
}
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 8dfb8f8591..d34f65409f 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,23 @@ namespace Jellyfin.Naming.Tests.Video
}
}
+ [Fact]
+ public void TestExtraInfo_InvalidRuleType()
+ {
+ 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]
+ 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/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);
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]);
}
}
}
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 @@
-
+