From 58061a729528e3969b07b35fdaac534f5948eb89 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 8 Mar 2019 17:15:52 +0100 Subject: Make MusicBrainz base url configurable --- Emby.Server.Implementations/ConfigurationOptions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 30bfd8749..9bc60972a 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -6,7 +6,8 @@ namespace Emby.Server.Implementations { public static readonly Dictionary Configuration = new Dictionary { - {"HttpListenerHost:DefaultRedirectPath", "web/index.html"} + {"HttpListenerHost:DefaultRedirectPath", "web/index.html"}, + {"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"} }; } } -- cgit v1.2.3 From 25917db07a835bf0e5c6afdc0fec165851fffab2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 9 Aug 2019 23:50:40 +0200 Subject: Fix doc releated warnings --- .../AppBase/BaseApplicationPaths.cs | 37 +++++---- .../AppBase/BaseConfigurationManager.cs | 92 ++++++++++++---------- Emby.Server.Implementations/ApplicationHost.cs | 6 +- .../ConfigurationOptions.cs | 4 +- .../Emby.Server.Implementations.csproj | 1 + .../ServerApplicationPaths.cs | 27 ++++--- 6 files changed, 91 insertions(+), 76 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 00cfa0c9a..f67a09daa 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -10,6 +10,8 @@ namespace Emby.Server.Implementations.AppBase /// public abstract class BaseApplicationPaths : IApplicationPaths { + private string _dataPath; + /// /// Initializes a new instance of the class. /// @@ -30,27 +32,27 @@ namespace Emby.Server.Implementations.AppBase } /// - /// Gets the path to the program data folder + /// Gets the path to the program data folder. /// /// The program data path. - public string ProgramDataPath { get; private set; } + public string ProgramDataPath { get; } /// - /// Gets the path to the web UI resources folder + /// Gets the path to the web UI resources folder. /// /// The web UI resources path. - public string WebPath { get; set; } + public string WebPath { get; } /// - /// Gets the path to the system folder + /// Gets the path to the system folder. /// + /// The path to the system folder. public string ProgramSystemPath { get; } = AppContext.BaseDirectory; /// - /// Gets the folder path to the data directory + /// Gets the folder path to the data directory. /// /// The data directory. - private string _dataPath; public string DataPath { get => _dataPath; @@ -58,8 +60,9 @@ namespace Emby.Server.Implementations.AppBase } /// - /// Gets the magic strings used for virtual path manipulation. + /// Gets the magic string used for virtual path manipulation. /// + /// The magic string used for virtual path manipulation. public string VirtualDataPath { get; } = "%AppDataPath%"; /// @@ -69,43 +72,43 @@ namespace Emby.Server.Implementations.AppBase public string ImageCachePath => Path.Combine(CachePath, "images"); /// - /// Gets the path to the plugin directory + /// Gets the path to the plugin directory. /// /// The plugins path. public string PluginsPath => Path.Combine(ProgramDataPath, "plugins"); /// - /// Gets the path to the plugin configurations directory + /// Gets the path to the plugin configurations directory. /// /// The plugin configurations path. public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations"); /// - /// Gets the path to the log directory + /// Gets the path to the log directory. /// /// The log directory path. - public string LogDirectoryPath { get; private set; } + public string LogDirectoryPath { get; } /// - /// Gets the path to the application configuration root directory + /// Gets the path to the application configuration root directory. /// /// The configuration directory path. - public string ConfigurationDirectoryPath { get; private set; } + public string ConfigurationDirectoryPath { get; } /// - /// Gets the path to the system configuration file + /// Gets the path to the system configuration file. /// /// The system configuration file path. public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml"); /// - /// Gets the folder path to the cache directory + /// Gets or sets the folder path to the cache directory. /// /// The cache directory. public string CachePath { get; set; } /// - /// Gets the folder path to the temp directory within the cache folder + /// Gets the folder path to the temp directory within the cache folder. /// /// The temp directory. public string TempDirectory => Path.Combine(CachePath, "temp"); diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index af60a8dce..4832c19c4 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -19,11 +20,44 @@ namespace Emby.Server.Implementations.AppBase /// public abstract class BaseConfigurationManager : IConfigurationManager { + private readonly IFileSystem _fileSystem; + + private readonly ConcurrentDictionary _configurations = new ConcurrentDictionary(); + + private ConfigurationStore[] _configurationStores = Array.Empty(); + private IConfigurationFactory[] _configurationFactories = Array.Empty(); + /// - /// Gets the type of the configuration. + /// The _configuration loaded. /// - /// The type of the configuration. - protected abstract Type ConfigurationType { get; } + private bool _configurationLoaded; + + /// + /// The _configuration sync lock. + /// + private object _configurationSyncLock = new object(); + + /// + /// The _configuration. + /// + private BaseApplicationConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The logger factory. + /// The XML serializer. + /// The file system + protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem) + { + CommonApplicationPaths = applicationPaths; + XmlSerializer = xmlSerializer; + _fileSystem = fileSystem; + Logger = loggerFactory.CreateLogger(GetType().Name); + + UpdateCachePath(); + } /// /// Occurs when [configuration updated]. @@ -40,6 +74,12 @@ namespace Emby.Server.Implementations.AppBase /// public event EventHandler NamedConfigurationUpdated; + /// + /// Gets the type of the configuration. + /// + /// The type of the configuration. + protected abstract Type ConfigurationType { get; } + /// /// Gets the logger. /// @@ -56,20 +96,7 @@ namespace Emby.Server.Implementations.AppBase /// /// The application paths. public IApplicationPaths CommonApplicationPaths { get; private set; } - public readonly IFileSystem FileSystem; - /// - /// The _configuration loaded - /// - private bool _configurationLoaded; - /// - /// The _configuration sync lock - /// - private object _configurationSyncLock = new object(); - /// - /// The _configuration - /// - private BaseApplicationConfiguration _configuration; /// /// Gets the system configuration /// @@ -90,26 +117,6 @@ namespace Emby.Server.Implementations.AppBase } } - private ConfigurationStore[] _configurationStores = { }; - private IConfigurationFactory[] _configurationFactories = { }; - - /// - /// Initializes a new instance of the class. - /// - /// The application paths. - /// The logger factory. - /// The XML serializer. - /// The file system - protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem) - { - CommonApplicationPaths = applicationPaths; - XmlSerializer = xmlSerializer; - FileSystem = fileSystem; - Logger = loggerFactory.CreateLogger(GetType().Name); - - UpdateCachePath(); - } - public virtual void AddParts(IEnumerable factories) { _configurationFactories = factories.ToArray(); @@ -171,6 +178,7 @@ namespace Emby.Server.Implementations.AppBase private void UpdateCachePath() { string cachePath; + // If the configuration file has no entry (i.e. not set in UI) if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath)) { @@ -207,12 +215,16 @@ namespace Emby.Server.Implementations.AppBase var newPath = newConfig.CachePath; if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath)) + && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath, StringComparison.Ordinal)) { // Validate if (!Directory.Exists(newPath)) { - throw new FileNotFoundException(string.Format("{0} does not exist.", newPath)); + throw new FileNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + "{0} does not exist.", + newPath)); } EnsureWriteAccess(newPath); @@ -223,11 +235,9 @@ namespace Emby.Server.Implementations.AppBase { var file = Path.Combine(path, Guid.NewGuid().ToString()); File.WriteAllText(file, string.Empty); - FileSystem.DeleteFile(file); + _fileSystem.DeleteFile(file); } - private readonly ConcurrentDictionary _configurations = new ConcurrentDictionary(); - private string GetConfigurationFile(string key) { return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml"); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f0aa60428..0493fd9f5 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -418,7 +418,7 @@ namespace Emby.Server.Implementations public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); /// - /// Gets the current application user agent + /// Gets the current application user agent. /// /// The application user agent. public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion; @@ -467,8 +467,8 @@ namespace Emby.Server.Implementations /// /// Creates an instance of type and resolves all constructor dependencies /// - /// /// The type - /// T + /// /// The type. + /// T. public T CreateInstance() => ActivatorUtilities.CreateInstance(_serviceProvider); diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 9bc60972a..62408ee70 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -6,8 +6,8 @@ namespace Emby.Server.Implementations { public static readonly Dictionary Configuration = new Dictionary { - {"HttpListenerHost:DefaultRedirectPath", "web/index.html"}, - {"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"} + { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, + { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" } }; } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index abbaef26b..f07f8e3bf 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -44,6 +44,7 @@ netstandard2.0 false + true diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index adaf23234..2f5a8af80 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -10,8 +10,12 @@ namespace Emby.Server.Implementations /// public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths { + private string _defaultTranscodingTempPath; + private string _transcodingTempPath; + private string _internalMetadataPath; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ServerApplicationPaths( string programDataPath, @@ -30,7 +34,7 @@ namespace Emby.Server.Implementations public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory; /// - /// Gets the path to the base root media directory + /// Gets the path to the base root media directory. /// /// The root folder path. public string RootFolderPath => Path.Combine(ProgramDataPath, "root"); @@ -48,7 +52,7 @@ namespace Emby.Server.Implementations public string LocalizationPath => Path.Combine(ProgramDataPath, "localization"); /// - /// Gets the path to the People directory + /// Gets the path to the People directory. /// /// The people path. public string PeoplePath => Path.Combine(InternalMetadataPath, "People"); @@ -56,37 +60,37 @@ namespace Emby.Server.Implementations public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists"); /// - /// Gets the path to the Genre directory + /// Gets the path to the Genre directory. /// /// The genre path. public string GenrePath => Path.Combine(InternalMetadataPath, "Genre"); /// - /// Gets the path to the Genre directory + /// Gets the path to the Genre directory. /// /// The genre path. public string MusicGenrePath => Path.Combine(InternalMetadataPath, "MusicGenre"); /// - /// Gets the path to the Studio directory + /// Gets the path to the Studio directory. /// /// The studio path. public string StudioPath => Path.Combine(InternalMetadataPath, "Studio"); /// - /// Gets the path to the Year directory + /// Gets the path to the Year directory. /// /// The year path. public string YearPath => Path.Combine(InternalMetadataPath, "Year"); /// - /// Gets the path to the General IBN directory + /// Gets the path to the General IBN directory. /// /// The general path. public string GeneralPath => Path.Combine(InternalMetadataPath, "general"); /// - /// Gets the path to the Ratings IBN directory + /// Gets the path to the Ratings IBN directory. /// /// The ratings path. public string RatingsPath => Path.Combine(InternalMetadataPath, "ratings"); @@ -98,15 +102,13 @@ namespace Emby.Server.Implementations public string MediaInfoImagesPath => Path.Combine(InternalMetadataPath, "mediainfo"); /// - /// Gets the path to the user configuration directory + /// Gets the path to the user configuration directory. /// /// The user configuration directory path. public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users"); - private string _defaultTranscodingTempPath; public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp")); - private string _transcodingTempPath; public string TranscodingTempPath { get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath); @@ -139,7 +141,6 @@ namespace Emby.Server.Implementations return path; } - private string _internalMetadataPath; public string InternalMetadataPath { get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata")); -- cgit v1.2.3 From cc5acf37f75d2c652d9cd855ebc34a1e7d414a9f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 26 Oct 2019 22:53:53 +0200 Subject: Make probesize and analyzeduration configurable and simplify circular dependencies Makes the probesize and analyzeduration configurable with env args. (`JELLYFIN_FFmpeg_probesize` and `FFmpeg_analyzeduration`) --- Emby.Server.Implementations/ApplicationHost.cs | 23 ++- .../ConfigurationOptions.cs | 9 +- .../Emby.Server.Implementations.csproj | 1 - .../HttpServer/HttpListenerHost.cs | 2 +- Jellyfin.Server/Program.cs | 7 +- MediaBrowser.Api/Playback/BaseStreamingService.cs | 11 +- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 56 +++--- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 9 +- MediaBrowser.Api/Playback/Hls/VideoHlsService.cs | 56 +++--- .../Playback/Progressive/AudioService.cs | 8 +- .../Progressive/BaseProgressiveStreamingService.cs | 11 +- .../Playback/Progressive/VideoService.cs | 8 +- MediaBrowser.Api/Playback/UniversalAudioService.cs | 63 +++---- MediaBrowser.Controller/Entities/Video.cs | 5 +- .../Extensions/ConfigurationExtensions.cs | 36 ++++ .../MediaBrowser.Controller.csproj | 4 + .../MediaEncoding/EncodingHelper.cs | 31 +++- .../MediaEncoding/IMediaEncoder.cs | 4 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 191 ++++++++++----------- .../Subtitles/ISubtitleWriter.cs | 2 +- MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs | 36 ++-- MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs | 17 +- .../Subtitles/SubtitleEncoder.cs | 103 ++++++----- MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs | 7 - .../Configuration/ServerConfiguration.cs | 1 - MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs | 7 +- .../MediaInfo/VideoImageProvider.cs | 6 +- .../Music/MusicBrainzAlbumProvider.cs | 2 +- 28 files changed, 396 insertions(+), 320 deletions(-) create mode 100644 MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index bd5e973c0..1c034ca79 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -886,16 +886,14 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ChapterManager); MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - LoggerFactory, - JsonSerializer, - StartupOptions.FFmpegPath, + LoggerFactory.CreateLogger(), ServerConfigurationManager, FileSystemManager, - () => SubtitleEncoder, - () => MediaSourceManager, ProcessFactory, - 5000, - LocalizationManager); + LocalizationManager, + () => SubtitleEncoder, + _configuration, + StartupOptions.FFmpegPath); serviceCollection.AddSingleton(MediaEncoder); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); @@ -912,10 +910,19 @@ namespace Emby.Server.Implementations AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager); serviceCollection.AddSingleton(AuthService); - SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); + SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder( + LibraryManager, + LoggerFactory.CreateLogger(), + ApplicationPaths, + FileSystemManager, + MediaEncoder, + HttpClient, + MediaSourceManager, + ProcessFactory); serviceCollection.AddSingleton(SubtitleEncoder); serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); + serviceCollection.AddSingleton(); _displayPreferencesRepository.Initialize(); diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 62408ee70..445a554b2 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,13 +1,16 @@ using System.Collections.Generic; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations { public static class ConfigurationOptions { - public static readonly Dictionary Configuration = new Dictionary + public static Dictionary Configuration => new Dictionary { - { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, - { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" } + { "HttpListenerHost_DefaultRedirectPath", "web/index.html" }, + { "MusicBrainz_BaseUrl", "https://www.musicbrainz.org" }, + { FfmpegProbeSizeKey, "1G" }, + { FfmpegAnalyzeDuration, "200M" } }; } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 214ea5aff..618f54ce7 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -28,7 +28,6 @@ - diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dc1a56e27..2736339b1 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer _appHost = applicationHost; _logger = logger; _config = config; - _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; + _defaultRedirectPath = configuration["HttpListenerHost_DefaultRedirectPath"]; _baseUrlPrefix = _config.Configuration.BaseUrl; _networkManager = networkManager; _jsonSerializer = jsonSerializer; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index bdf3689f1..c9ca79a2b 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Security; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -133,6 +134,10 @@ namespace Jellyfin.Server ApplicationHost.LogEnvironmentInfo(_logger, appPaths); + // Make sure we have all the code pages we can get + // Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + // Increase the max http request limit // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); @@ -369,9 +374,9 @@ namespace Jellyfin.Server return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) + .AddInMemoryCollection(ConfigurationOptions.Configuration) .AddJsonFile("logging.json", false, true) .AddEnvironmentVariables("JELLYFIN_") - .AddInMemoryCollection(ConfigurationOptions.Configuration) .Build(); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 4bd729aac..d554930ac 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -69,8 +69,6 @@ namespace MediaBrowser.Api.Playback protected IDeviceManager DeviceManager { get; private set; } - protected ISubtitleEncoder SubtitleEncoder { get; private set; } - protected IMediaSourceManager MediaSourceManager { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; } @@ -96,11 +94,11 @@ namespace MediaBrowser.Api.Playback IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) { ServerConfigurationManager = serverConfig; UserManager = userManager; @@ -109,13 +107,12 @@ namespace MediaBrowser.Api.Playback MediaEncoder = mediaEncoder; FileSystem = fileSystem; DlnaManager = dlnaManager; - SubtitleEncoder = subtitleEncoder; DeviceManager = deviceManager; MediaSourceManager = mediaSourceManager; JsonSerializer = jsonSerializer; AuthorizationContext = authorizationContext; - EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder); + EncodingHelper = encodingHelper; } /// @@ -152,8 +149,6 @@ namespace MediaBrowser.Api.Playback return Path.Combine(folder, filename + ext); } - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected virtual string GetDefaultEncoderPreset() { return "superfast"; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 27eb67ee6..390e85d08 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -25,6 +25,34 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { + public BaseHlsService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) + : base(serverConfig, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext, + encodingHelper) + { + } + /// /// Gets the audio arguments. /// @@ -313,33 +341,5 @@ namespace MediaBrowser.Api.Playback.Hls { return 0; } - - public BaseHlsService( - IServerConfigurationManager serverConfig, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) - : base(serverConfig, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - subtitleEncoder, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext) - { - } } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 9ecb5fe8c..60a1f6899 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -94,7 +94,6 @@ namespace MediaBrowser.Api.Playback.Hls [Authenticated] public class DynamicHlsService : BaseHlsService { - public DynamicHlsService( IServerConfigurationManager serverConfig, IUserManager userManager, @@ -103,12 +102,12 @@ namespace MediaBrowser.Api.Playback.Hls IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, - INetworkManager networkManager) + INetworkManager networkManager, + EncodingHelper encodingHelper) : base(serverConfig, userManager, libraryManager, @@ -116,11 +115,11 @@ namespace MediaBrowser.Api.Playback.Hls mediaEncoder, fileSystem, dlnaManager, - subtitleEncoder, deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext) + authorizationContext, + encodingHelper) { NetworkManager = networkManager; } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 4a5f4025b..cada7138c 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -26,6 +26,34 @@ namespace MediaBrowser.Api.Playback.Hls [Authenticated] public class VideoHlsService : BaseHlsService { + public VideoHlsService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) + : base(serverConfig, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext, + encodingHelper) + { + } + public Task Get(GetLiveHlsStream request) { return ProcessRequestAsync(request, true); @@ -135,33 +163,5 @@ namespace MediaBrowser.Api.Playback.Hls return args; } - - public VideoHlsService( - IServerConfigurationManager serverConfig, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) - : base(serverConfig, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - subtitleEncoder, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext) - { - } } } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index dfe4b2b8e..5679a4e17 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -40,11 +40,11 @@ namespace MediaBrowser.Api.Playback.Progressive IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) : base(httpClient, serverConfig, userManager, @@ -53,11 +53,11 @@ namespace MediaBrowser.Api.Playback.Progressive mediaEncoder, fileSystem, dlnaManager, - subtitleEncoder, deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext) + authorizationContext, + encodingHelper) { } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 97c1a7a49..ee7b99c2a 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -35,23 +35,24 @@ namespace MediaBrowser.Api.Playback.Progressive IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) - : base(serverConfig, + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) + : base( + serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, - subtitleEncoder, deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext) + authorizationContext, + encodingHelper) { HttpClient = httpClient; } diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index cfc8a283d..976e11b47 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -77,11 +77,11 @@ namespace MediaBrowser.Api.Playback.Progressive IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, - ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext) + IAuthorizationContext authorizationContext, + EncodingHelper encodingHelper) : base(httpClient, serverConfig, userManager, @@ -90,11 +90,11 @@ namespace MediaBrowser.Api.Playback.Progressive mediaEncoder, fileSystem, dlnaManager, - subtitleEncoder, deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext) + authorizationContext, + encodingHelper) { } diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index b3d8bfe59..70c0f4b01 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -9,7 +9,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; @@ -75,6 +74,9 @@ namespace MediaBrowser.Api.Playback [Authenticated] public class UniversalAudioService : BaseApiService { + private readonly ILoggerFactory _loggerFactory; + private readonly EncodingHelper _encodingHelper; + public UniversalAudioService( IHttpClient httpClient, IServerConfigurationManager serverConfigurationManager, @@ -85,14 +87,12 @@ namespace MediaBrowser.Api.Playback IFileSystem fileSystem, IDlnaManager dlnaManager, IDeviceManager deviceManager, - ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, - IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, - IImageProcessor imageProcessor, INetworkManager networkManager, - ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + EncodingHelper encodingHelper) { HttpClient = httpClient; ServerConfigurationManager = serverConfigurationManager; @@ -103,15 +103,12 @@ namespace MediaBrowser.Api.Playback FileSystem = fileSystem; DlnaManager = dlnaManager; DeviceManager = deviceManager; - SubtitleEncoder = subtitleEncoder; MediaSourceManager = mediaSourceManager; - ZipClient = zipClient; JsonSerializer = jsonSerializer; AuthorizationContext = authorizationContext; - ImageProcessor = imageProcessor; NetworkManager = networkManager; _loggerFactory = loggerFactory; - _logger = loggerFactory.CreateLogger(nameof(UniversalAudioService)); + _encodingHelper = encodingHelper; } protected IHttpClient HttpClient { get; private set; } @@ -123,15 +120,10 @@ namespace MediaBrowser.Api.Playback protected IFileSystem FileSystem { get; private set; } protected IDlnaManager DlnaManager { get; private set; } protected IDeviceManager DeviceManager { get; private set; } - protected ISubtitleEncoder SubtitleEncoder { get; private set; } protected IMediaSourceManager MediaSourceManager { get; private set; } - protected IZipClient ZipClient { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; } protected IAuthorizationContext AuthorizationContext { get; private set; } - protected IImageProcessor ImageProcessor { get; private set; } protected INetworkManager NetworkManager { get; private set; } - private ILoggerFactory _loggerFactory; - private ILogger _logger; public Task Get(GetUniversalAudioStream request) { @@ -242,7 +234,17 @@ namespace MediaBrowser.Api.Playback AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId; - var mediaInfoService = new MediaInfoService(MediaSourceManager, DeviceManager, LibraryManager, ServerConfigurationManager, NetworkManager, MediaEncoder, UserManager, JsonSerializer, AuthorizationContext, _loggerFactory) + var mediaInfoService = new MediaInfoService( + MediaSourceManager, + DeviceManager, + LibraryManager, + ServerConfigurationManager, + NetworkManager, + MediaEncoder, + UserManager, + JsonSerializer, + AuthorizationContext, + _loggerFactory) { Request = Request }; @@ -276,19 +278,20 @@ namespace MediaBrowser.Api.Playback if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) { - var service = new DynamicHlsService(ServerConfigurationManager, - UserManager, - LibraryManager, - IsoManager, - MediaEncoder, - FileSystem, - DlnaManager, - SubtitleEncoder, - DeviceManager, - MediaSourceManager, - JsonSerializer, - AuthorizationContext, - NetworkManager) + var service = new DynamicHlsService( + ServerConfigurationManager, + UserManager, + LibraryManager, + IsoManager, + MediaEncoder, + FileSystem, + DlnaManager, + DeviceManager, + MediaSourceManager, + JsonSerializer, + AuthorizationContext, + NetworkManager, + _encodingHelper) { Request = Request }; @@ -330,11 +333,11 @@ namespace MediaBrowser.Api.Playback MediaEncoder, FileSystem, DlnaManager, - SubtitleEncoder, DeviceManager, MediaSourceManager, JsonSerializer, - AuthorizationContext) + AuthorizationContext, + _encodingHelper) { Request = Request }; diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 60906bdb0..af4d227bc 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.Controller.Entities /// The video3 D format. public Video3DFormat? Video3DFormat { get; set; } - public string[] GetPlayableStreamFileNames(IMediaEncoder mediaEncoder) + public string[] GetPlayableStreamFileNames() { var videoType = VideoType; @@ -153,7 +153,8 @@ namespace MediaBrowser.Controller.Entities { return Array.Empty(); } - return mediaEncoder.GetPlayableStreamFileNames(Path, videoType); + + throw new NotImplementedException(); } /// diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs new file mode 100644 index 000000000..80a98ad5f --- /dev/null +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Configuration; + +namespace MediaBrowser.Controller.Extensions +{ + /// + /// Configuration extensions for MediaBrowser.Controller. + /// + public static class ConfigurationExtensions + { + /// + /// The key for the FFmpeg probe size option. + /// + public const string FfmpegProbeSizeKey = "FFmpeg_probesize"; + + /// + /// The key for the FFmpeg analyse duration option. + /// + public const string FfmpegAnalyzeDuration = "FFmpeg_analyzeduration"; + + /// + /// Retrieves the FFmpeg probe size from the . + /// + /// This configuration. + /// The FFmpeg probe size option. + public static string GetProbeSize(this IConfiguration configuration) + => configuration[FfmpegProbeSizeKey]; + + /// + /// Retrieves the FFmpeg analyse duration from the . + /// + /// This configuration. + /// The FFmpeg analyse duration option. + public static string GetAnalyzeDuration(this IConfiguration configuration) + => configuration[FfmpegAnalyzeDuration]; + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 276eb71bc..60c76ef7d 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -7,6 +7,10 @@ https://github.com/jellyfin/jellyfin + + + + diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 349e371a7..d829db44b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -12,6 +12,7 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; +using Microsoft.Extensions.Configuration; namespace MediaBrowser.Controller.MediaEncoding { @@ -22,6 +23,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; private readonly ISubtitleEncoder _subtitleEncoder; + private readonly IConfiguration _configuration; private static readonly string[] _videoProfiles = new[] { @@ -34,11 +36,16 @@ namespace MediaBrowser.Controller.MediaEncoding "ConstrainedHigh" }; - public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder) + public EncodingHelper( + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + ISubtitleEncoder subtitleEncoder, + IConfiguration configuration) { _mediaEncoder = mediaEncoder; _fileSystem = fileSystem; _subtitleEncoder = subtitleEncoder; + _configuration = configuration; } public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) @@ -172,7 +179,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - public string GetInputFormat(string container) + public static string GetInputFormat(string container) { if (string.IsNullOrEmpty(container)) { @@ -641,7 +648,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) { - var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result; + var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet( + subtitlePath, + state.SubtitleStream.Language, + state.MediaSource.Protocol, + CancellationToken.None).GetAwaiter().GetResult(); if (!string.IsNullOrEmpty(charenc)) { @@ -1897,7 +1908,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If transcoding from 10 bit, transform colour spaces too if (!string.IsNullOrEmpty(videoStream.PixelFormat) && videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1 - && string.Equals(outputVideoCodec,"libx264", StringComparison.OrdinalIgnoreCase)) + && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { filters.Add("format=p010le"); filters.Add("format=nv12"); @@ -1946,7 +1957,9 @@ namespace MediaBrowser.Controller.MediaEncoding var output = string.Empty; - if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + if (state.SubtitleStream != null + && state.SubtitleStream.IsTextSubtitleStream + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { var subParam = GetTextSubtitleParam(state); @@ -2035,11 +2048,11 @@ namespace MediaBrowser.Controller.MediaEncoding } } - public static string GetProbeSizeArgument(int numInputFiles) - => numInputFiles > 1 ? "-probesize 1G" : ""; + public string GetProbeSizeArgument(int numInputFiles) + => numInputFiles > 1 ? "-probesize " + _configuration["FFmpeg:probesize"] : string.Empty; - public static string GetAnalyzeDurationArgument(int numInputFiles) - => numInputFiles > 1 ? "-analyzeduration 200M" : ""; + public string GetAnalyzeDurationArgument(int numInputFiles) + => numInputFiles > 1 ? "-analyzeduration " + _configuration["FFmpeg:analyzeduration"] : string.Empty; public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) { diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index d032a849e..37f0b11a7 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -15,6 +15,9 @@ namespace MediaBrowser.Controller.MediaEncoding /// public interface IMediaEncoder : ITranscoderSupport { + /// + /// The location of the discovered FFmpeg tool. + /// FFmpegLocation EncoderLocation { get; } /// @@ -97,7 +100,6 @@ namespace MediaBrowser.Controller.MediaEncoding void UpdateEncoderPath(string path, string pathType); bool SupportsEncoder(string encoder); - string[] GetPlayableStreamFileNames(string path, VideoType videoType); IEnumerable GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 04ff66991..6bcd6cd46 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Configuration; @@ -19,9 +19,9 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; namespace MediaBrowser.MediaEncoding.Encoder { @@ -31,55 +31,60 @@ namespace MediaBrowser.MediaEncoding.Encoder public class MediaEncoder : IMediaEncoder, IDisposable { /// - /// Gets the encoder path. + /// The default image extraction timeout in milliseconds. /// - /// The encoder path. - public string EncoderPath => FFmpegPath; - - /// - /// The location of the discovered FFmpeg tool. - /// - public FFmpegLocation EncoderLocation { get; private set; } + internal const int DefaultImageExtractionTimeout = 5000; private readonly ILogger _logger; - private readonly IJsonSerializer _jsonSerializer; - private string FFmpegPath; - private string FFprobePath; - protected readonly IServerConfigurationManager ConfigurationManager; - protected readonly IFileSystem FileSystem; - protected readonly Func SubtitleEncoder; - protected readonly Func MediaSourceManager; + private readonly IServerConfigurationManager _configurationManager; + private readonly IFileSystem _fileSystem; private readonly IProcessFactory _processFactory; - private readonly int DefaultImageExtractionTimeoutMs; - private readonly string StartupOptionFFmpegPath; + private readonly ILocalizationManager _localization; + private readonly Func _subtitleEncoder; + private readonly IConfiguration _configuration; + private readonly string _startupOptionFFmpegPath; private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2); + + private readonly object _runningProcessesLock = new object(); private readonly List _runningProcesses = new List(); - private readonly ILocalizationManager _localization; + + private EncodingHelper _encodingHelper; + + private string _ffmpegPath; + private string _ffprobePath; public MediaEncoder( - ILoggerFactory loggerFactory, - IJsonSerializer jsonSerializer, - string startupOptionsFFmpegPath, + ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, - Func subtitleEncoder, - Func mediaSourceManager, IProcessFactory processFactory, - int defaultImageExtractionTimeoutMs, - ILocalizationManager localization) - { - _logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); - _jsonSerializer = jsonSerializer; - StartupOptionFFmpegPath = startupOptionsFFmpegPath; - ConfigurationManager = configurationManager; - FileSystem = fileSystem; - SubtitleEncoder = subtitleEncoder; + ILocalizationManager localization, + Func subtitleEncoder, + IConfiguration configuration, + string startupOptionsFFmpegPath) + { + _logger = logger; + _configurationManager = configurationManager; + _fileSystem = fileSystem; _processFactory = processFactory; - DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; _localization = localization; + _startupOptionFFmpegPath = startupOptionsFFmpegPath; + _subtitleEncoder = subtitleEncoder; + _configuration = configuration; } + private EncodingHelper EncodingHelper + => LazyInitializer.EnsureInitialized( + ref _encodingHelper, + () => new EncodingHelper(this, _fileSystem, _subtitleEncoder(), _configuration)); + + /// + public string EncoderPath => _ffmpegPath; + + /// + public FFmpegLocation EncoderLocation { get; private set; } + /// /// Run at startup or if the user removes a Custom path from transcode page. /// Sets global variables FFmpegPath. @@ -88,39 +93,39 @@ namespace MediaBrowser.MediaEncoding.Encoder public void SetFFmpegPath() { // 1) Custom path stored in config/encoding xml file under tag takes precedence - if (!ValidatePath(ConfigurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom)) + if (!ValidatePath(_configurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom)) { // 2) Check if the --ffmpeg CLI switch has been given - if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument)) + if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument)) { // 3) Search system $PATH environment variable for valid FFmpeg if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) { EncoderLocation = FFmpegLocation.NotFound; - FFmpegPath = null; + _ffmpegPath = null; } } } // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty; - ConfigurationManager.SaveConfiguration("encoding", config); + var config = _configurationManager.GetConfiguration("encoding"); + config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; + _configurationManager.SaveConfiguration("encoding", config); // Only if mpeg path is set, try and set path to probe - if (FFmpegPath != null) + if (_ffmpegPath != null) { // Determine a probe path from the mpeg path - FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1"); + _ffprobePath = Regex.Replace(_ffmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1"); // Interrogate to understand what coders are supported - var validator = new EncoderValidator(_logger, FFmpegPath); + var validator = new EncoderValidator(_logger, _ffmpegPath); SetAvailableDecoders(validator.GetDecoders()); SetAvailableEncoders(validator.GetEncoders()); } - _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, FFmpegPath ?? string.Empty); + _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, _ffmpegPath ?? string.Empty); } /// @@ -160,9 +165,9 @@ namespace MediaBrowser.MediaEncoding.Encoder // Write the new ffmpeg path to the xml as // This ensures its not lost on next startup - var config = ConfigurationManager.GetConfiguration("encoding"); + var config = _configurationManager.GetConfiguration("encoding"); config.EncoderAppPath = newPath; - ConfigurationManager.SaveConfiguration("encoding", config); + _configurationManager.SaveConfiguration("encoding", config); // Trigger SetFFmpegPath so we validate the new path and setup probe path SetFFmpegPath(); @@ -193,7 +198,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // ToDo - Enable the ffmpeg validator. At the moment any version can be used. rc = true; - FFmpegPath = path; + _ffmpegPath = path; EncoderLocation = location; } else @@ -209,7 +214,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { try { - var files = FileSystem.GetFilePaths(path); + var files = _fileSystem.GetFilePaths(path); var excludeExtensions = new[] { ".c" }; @@ -304,7 +309,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; - var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames); + var inputFiles = MediaEncoderHelpers.GetInputArgument(_fileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames); var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length); string analyzeDuration; @@ -365,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Must consume both or ffmpeg may hang due to deadlocks. See comments below. RedirectStandardOutput = true, - FileName = FFprobePath, + FileName = _ffprobePath, Arguments = args, @@ -383,7 +388,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); } - using (var processWrapper = new ProcessWrapper(process, this, _logger)) + using (var processWrapper = new ProcessWrapper(process, this)) { _logger.LogDebug("Starting ffprobe with args {Args}", args); StartProcess(processWrapper); @@ -391,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder InternalMediaInfoResult result; try { - result = await _jsonSerializer.DeserializeFromStreamAsync( + result = await JsonSerializer.DeserializeAsync( process.StandardOutput.BaseStream).ConfigureAwait(false); } catch @@ -423,7 +428,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return new ProbeResultNormalizer(_logger, FileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); + return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); } } @@ -486,7 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException(nameof(inputPath)); } - var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. @@ -545,7 +550,6 @@ namespace MediaBrowser.MediaEncoding.Encoder args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args; } - var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder()); if (videoStream != null) { /* fix @@ -559,7 +563,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrWhiteSpace(container)) { - var inputFormat = encodinghelper.GetInputFormat(container); + var inputFormat = EncodingHelper.GetInputFormat(container); if (!string.IsNullOrWhiteSpace(inputFormat)) { args = "-f " + inputFormat + " " + args; @@ -570,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFmpegPath, + FileName = _ffmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, @@ -579,7 +583,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - using (var processWrapper = new ProcessWrapper(process, this, _logger)) + using (var processWrapper = new ProcessWrapper(process, this)) { bool ranToCompletion; @@ -588,10 +592,10 @@ namespace MediaBrowser.MediaEncoding.Encoder { StartProcess(processWrapper); - var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; + var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs; if (timeoutMs <= 0) { - timeoutMs = DefaultImageExtractionTimeoutMs; + timeoutMs = DefaultImageExtractionTimeout; } ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false); @@ -607,7 +611,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - var file = FileSystem.GetFileInfo(tempExtractPath); + var file = _fileSystem.GetFileInfo(tempExtractPath); if (exitCode == -1 || !file.Exists || file.Length == 0) { @@ -675,7 +679,6 @@ namespace MediaBrowser.MediaEncoding.Encoder args = analyzeDurationArgument + " " + args; } - var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder()); if (videoStream != null) { /* fix @@ -689,7 +692,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrWhiteSpace(container)) { - var inputFormat = encodinghelper.GetInputFormat(container); + var inputFormat = EncodingHelper.GetInputFormat(container); if (!string.IsNullOrWhiteSpace(inputFormat)) { args = "-f " + inputFormat + " " + args; @@ -700,7 +703,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFmpegPath, + FileName = _ffmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, @@ -713,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool ranToCompletion = false; - using (var processWrapper = new ProcessWrapper(process, this, _logger)) + using (var processWrapper = new ProcessWrapper(process, this)) { try { @@ -736,10 +739,10 @@ namespace MediaBrowser.MediaEncoding.Encoder cancellationToken.ThrowIfCancellationRequested(); - var jpegCount = FileSystem.GetFilePaths(targetDirectory) + var jpegCount = _fileSystem.GetFilePaths(targetDirectory) .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); - isResponsive = (jpegCount > lastCount); + isResponsive = jpegCount > lastCount; lastCount = jpegCount; } @@ -770,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { process.Process.Start(); - lock (_runningProcesses) + lock (_runningProcessesLock) { _runningProcesses.Add(process); } @@ -804,7 +807,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private void StopProcesses() { List proceses; - lock (_runningProcesses) + lock (_runningProcessesLock) { proceses = _runningProcesses.ToList(); _runningProcesses.Clear(); @@ -827,12 +830,11 @@ namespace MediaBrowser.MediaEncoding.Encoder return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''"); } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// + /// public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// @@ -852,11 +854,6 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new NotImplementedException(); } - public string[] GetPlayableStreamFileNames(string path, VideoType videoType) - { - throw new NotImplementedException(); - } - public IEnumerable GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber) { throw new NotImplementedException(); @@ -870,21 +867,24 @@ namespace MediaBrowser.MediaEncoding.Encoder private class ProcessWrapper : IDisposable { - public readonly IProcess Process; - public bool HasExited; - public int? ExitCode; private readonly MediaEncoder _mediaEncoder; - private readonly ILogger _logger; - public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger) + private bool _disposed = false; + + public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder) { Process = process; _mediaEncoder = mediaEncoder; - _logger = logger; - Process.Exited += Process_Exited; + Process.Exited += OnProcessExited; } - void Process_Exited(object sender, EventArgs e) + public IProcess Process { get; } + + public bool HasExited { get; private set; } + + public int? ExitCode { get; private set; } + + void OnProcessExited(object sender, EventArgs e) { var process = (IProcess)sender; @@ -903,7 +903,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private void DisposeProcess(IProcess process) { - lock (_mediaEncoder._runningProcesses) + lock (_mediaEncoder._runningProcessesLock) { _mediaEncoder._runningProcesses.Remove(this); } @@ -917,23 +917,18 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - private bool _disposed; - private readonly object _syncLock = new object(); public void Dispose() { - lock (_syncLock) + if (!_disposed) { - if (!_disposed) + if (Process != null) { - if (Process != null) - { - Process.Exited -= Process_Exited; - DisposeProcess(Process); - } + Process.Exited -= OnProcessExited; + DisposeProcess(Process); } - - _disposed = true; } + + _disposed = true; } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs index 3401c2d67..dec714121 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.MediaEncoding.Subtitles { /// - /// Interface ISubtitleWriter + /// Interface ISubtitleWriter. /// public interface ISubtitleWriter { diff --git a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs index 8995fcfe1..241ebc6df 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs @@ -1,27 +1,39 @@ using System.IO; -using System.Text; +using System.Text.Json; using System.Threading; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.MediaEncoding.Subtitles { + /// + /// JSON subtitle writer. + /// public class JsonWriter : ISubtitleWriter { - private readonly IJsonSerializer _json; - - public JsonWriter(IJsonSerializer json) - { - _json = json; - } - + /// public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + using (var writer = new Utf8JsonWriter(stream)) { - var json = _json.SerializeToString(info); + var trackevents = info.TrackEvents; + writer.WriteStartArray("TrackEvents"); + + for (int i = 0; i < trackevents.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + var current = trackevents[i]; + writer.WriteStartObject(); + + writer.WriteString("Id", current.Id); + writer.WriteString("Text", current.Text); + writer.WriteNumber("StartPositionTicks", current.StartPositionTicks); + writer.WriteNumber("EndPositionTicks", current.EndPositionTicks); + + writer.WriteEndObject(); + } - writer.Write(json); + writer.WriteEndObject(); } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs index 6f96a641e..45b317b2e 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs @@ -14,14 +14,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles { using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { - var index = 1; + var trackEvents = info.TrackEvents; - foreach (var trackEvent in info.TrackEvents) + for (int i = 0; i < trackEvents.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); - writer.WriteLine(index.ToString(CultureInfo.InvariantCulture)); - writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks)); + var trackEvent = trackEvents[i]; + + writer.WriteLine((i + 1).ToString(CultureInfo.InvariantCulture)); + writer.WriteLine( + @"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", + TimeSpan.FromTicks(trackEvent.StartPositionTicks), + TimeSpan.FromTicks(trackEvent.EndPositionTicks)); var text = trackEvent.Text; @@ -29,9 +34,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); writer.WriteLine(text); - writer.WriteLine(string.Empty); - - index++; + writer.WriteLine(); } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index d5fa76c3a..183d7566d 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -17,7 +17,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; using UtfUnknown; @@ -30,28 +29,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; - private readonly IJsonSerializer _json; private readonly IHttpClient _httpClient; private readonly IMediaSourceManager _mediaSourceManager; private readonly IProcessFactory _processFactory; public SubtitleEncoder( ILibraryManager libraryManager, - ILoggerFactory loggerFactory, + ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, - IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) { _libraryManager = libraryManager; - _logger = loggerFactory.CreateLogger(nameof(SubtitleEncoder)); + _logger = logger; _appPaths = appPaths; _fileSystem = fileSystem; _mediaEncoder = mediaEncoder; - _json = json; _httpClient = httpClient; _mediaSourceManager = mediaSourceManager; _processFactory = processFactory; @@ -59,7 +55,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles"); - private Stream ConvertSubtitles(Stream stream, + private Stream ConvertSubtitles( + Stream stream, string inputFormat, string outputFormat, long startTimeTicks, @@ -170,7 +167,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles && (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd)) { var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id)); - inputFiles = mediaSourceItem.GetPlayableStreamFileNames(_mediaEncoder); + inputFiles = mediaSourceItem.GetPlayableStreamFileNames(); } else { @@ -179,32 +176,27 @@ namespace MediaBrowser.MediaEncoding.Subtitles var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false); - var stream = await GetSubtitleStream(fileInfo.Path, subtitleStream.Language, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false); + var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false); return (stream, fileInfo.Format); } - private async Task GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) + private async Task GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) { if (requiresCharset) { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; - _logger.LogDebug("charset {CharSet} detected for {Path}", charset ?? "null", path); - - if (!string.IsNullOrEmpty(charset)) + using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) { - // Make sure we have all the code pages we can get - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - using (var inputStream = new MemoryStream(bytes)) - using (var reader = new StreamReader(inputStream, Encoding.GetEncoding(charset))) + var result = CharsetDetector.DetectFromStream(stream).Detected; + + if (result != null) { - var text = await reader.ReadToEndAsync().ConfigureAwait(false); + _logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, path); - bytes = Encoding.UTF8.GetBytes(text); + using var reader = new StreamReader(stream, result.Encoding); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); - return new MemoryStream(bytes); + return new MemoryStream(Encoding.UTF8.GetBytes(text)); } } } @@ -323,7 +315,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase)) { - return new JsonWriter(_json); + return new JsonWriter(); } if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) { @@ -544,7 +536,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles { if (!File.Exists(outputPath)) { - await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false); + await ExtractTextSubtitleInternal( + _mediaEncoder.GetInputArgument(inputFiles, protocol), + subtitleStreamIndex, + outputCodec, + outputPath, + cancellationToken).ConfigureAwait(false); } } finally @@ -572,8 +569,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath, - subtitleStreamIndex, outputCodec, outputPath); + var processArgs = string.Format( + CultureInfo.InvariantCulture, + "-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", + inputPath, + subtitleStreamIndex, + outputCodec, + outputPath); var process = _processFactory.Create(new ProcessOptions { @@ -721,41 +723,38 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } + /// public async Task GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; + using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) + { + var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName; - _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path); + _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path); - return charset; + return charset; + } } - private async Task GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken) + private Task GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken) { - if (protocol == MediaProtocol.Http) + switch (protocol) { - var opts = new HttpRequestOptions() - { - Url = path, - CancellationToken = cancellationToken - }; - using (var file = await _httpClient.Get(opts).ConfigureAwait(false)) - using (var memoryStream = new MemoryStream()) - { - await file.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; + case MediaProtocol.Http: + var opts = new HttpRequestOptions() + { + Url = path, + CancellationToken = cancellationToken, + BufferContent = true + }; - return memoryStream.ToArray(); - } - } - if (protocol == MediaProtocol.File) - { - return File.ReadAllBytes(path); - } + return _httpClient.Get(opts); - throw new ArgumentOutOfRangeException(nameof(protocol)); + case MediaProtocol.File: + return Task.FromResult(File.OpenRead(path)); + default: + throw new ArgumentOutOfRangeException(nameof(protocol)); + } } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs index cdaf94964..4f15bac49 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs @@ -49,12 +49,5 @@ namespace MediaBrowser.MediaEncoding.Subtitles writer.WriteLine(""); } } - - private string FormatTime(long ticks) - { - var time = TimeSpan.FromTicks(ticks); - - return string.Format(@"{0:hh\:mm\:ss\,fff}", time); - } } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index b8abe49e3..ede2d71ad 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -231,7 +231,6 @@ namespace MediaBrowser.Model.Configuration LocalNetworkSubnets = Array.Empty(); LocalNetworkAddresses = Array.Empty(); CodecsUsed = Array.Empty(); - ImageExtractionTimeoutMs = 0; PathSubstitutions = Array.Empty(); IgnoreVirtualInterfaces = false; EnableSimpleArtistDetection = true; diff --git a/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs b/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs index 962f4d2fe..c382b20c9 100644 --- a/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs +++ b/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs @@ -1,12 +1,15 @@ +using System; +using System.Collections.Generic; + namespace MediaBrowser.Model.MediaInfo { public class SubtitleTrackInfo { - public SubtitleTrackEvent[] TrackEvents { get; set; } + public IReadOnlyList TrackEvents { get; set; } public SubtitleTrackInfo() { - TrackEvents = new SubtitleTrackEvent[] { }; + TrackEvents = Array.Empty(); } } } diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index e0b23108f..95b915b3d 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -62,7 +62,11 @@ namespace MediaBrowser.Providers.MediaInfo { var protocol = item.PathProtocol ?? MediaProtocol.File; - var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, null, item.GetPlayableStreamFileNames(_mediaEncoder)); + var inputPath = MediaEncoderHelpers.GetInputArgument( + _fileSystem, + item.Path, + null, + item.GetPlayableStreamFileNames()); var mediaStreams = item.GetMediaStreams(); diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 8e71b625e..e9ca7938e 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Providers.Music _appHost = appHost; _logger = logger; - _musicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; + _musicBrainzBaseUrl = configuration["MusicBrainz_BaseUrl"]; // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit _stopWatchMusicBrainz.Start(); -- cgit v1.2.3 From c6d48f51f608601775d98fc7866eefc367bfd63b Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 25 Nov 2019 12:12:48 +0100 Subject: Fix naming const --- Emby.Server.Implementations/ConfigurationOptions.cs | 2 +- MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 445a554b2..a55a9eb2d 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -10,7 +10,7 @@ namespace Emby.Server.Implementations { "HttpListenerHost_DefaultRedirectPath", "web/index.html" }, { "MusicBrainz_BaseUrl", "https://www.musicbrainz.org" }, { FfmpegProbeSizeKey, "1G" }, - { FfmpegAnalyzeDuration, "200M" } + { FfmpegAnalyzeDurationKey, "200M" } }; } } diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 80a98ad5f..4048207bd 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Extensions /// /// The key for the FFmpeg analyse duration option. /// - public const string FfmpegAnalyzeDuration = "FFmpeg_analyzeduration"; + public const string FfmpegAnalyzeDurationKey = "FFmpeg_analyzeduration"; /// /// Retrieves the FFmpeg probe size from the . @@ -31,6 +31,6 @@ namespace MediaBrowser.Controller.Extensions /// This configuration. /// The FFmpeg analyse duration option. public static string GetAnalyzeDuration(this IConfiguration configuration) - => configuration[FfmpegAnalyzeDuration]; + => configuration[FfmpegAnalyzeDurationKey]; } } -- cgit v1.2.3 From 2c0259f920406b67569457f5bed844339d0d1c9b Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 12 Dec 2019 16:57:16 +0100 Subject: Replace '_' with ':' in config keys --- Emby.Server.Implementations/ConfigurationOptions.cs | 4 ++-- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs | 4 ++-- MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index a55a9eb2d..2ea7ff6e9 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -7,8 +7,8 @@ namespace Emby.Server.Implementations { public static Dictionary Configuration => new Dictionary { - { "HttpListenerHost_DefaultRedirectPath", "web/index.html" }, - { "MusicBrainz_BaseUrl", "https://www.musicbrainz.org" }, + { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, + { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" } }; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index b77c53d87..2aefc9fe5 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.HttpServer _appHost = applicationHost; _logger = logger; _config = config; - _defaultRedirectPath = configuration["HttpListenerHost_DefaultRedirectPath"]; + _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; _baseUrlPrefix = _config.Configuration.BaseUrl; _networkManager = networkManager; _jsonSerializer = jsonSerializer; diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 4048207bd..7ab62b262 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -10,12 +10,12 @@ namespace MediaBrowser.Controller.Extensions /// /// The key for the FFmpeg probe size option. /// - public const string FfmpegProbeSizeKey = "FFmpeg_probesize"; + public const string FfmpegProbeSizeKey = "FFmpeg:probesize"; /// /// The key for the FFmpeg analyse duration option. /// - public const string FfmpegAnalyzeDurationKey = "FFmpeg_analyzeduration"; + public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration"; /// /// Retrieves the FFmpeg probe size from the . diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index e9ca7938e..8e71b625e 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Providers.Music _appHost = appHost; _logger = logger; - _musicBrainzBaseUrl = configuration["MusicBrainz_BaseUrl"]; + _musicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit _stopWatchMusicBrainz.Start(); -- cgit v1.2.3 From 65a9d618ccfb5e015eb2dccf437e8795ac5350d5 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 22 Feb 2020 15:04:52 +0900 Subject: add config options for musicbrainz --- .../ConfigurationOptions.cs | 1 - .../Library/LibraryManager.cs | 3 +- .../Entities/Audio/MusicArtist.cs | 1 + MediaBrowser.Controller/Entities/Folder.cs | 8 +- .../MediaBrowser.Providers.csproj | 5 + .../Music/MusicBrainzAlbumProvider.cs | 800 --------------------- .../Music/MusicBrainzArtistProvider.cs | 281 -------- MediaBrowser.Providers/Music/MusicExternalIds.cs | 95 --- .../Plugins/MusicBrainz/AlbumProvider.cs | 799 ++++++++++++++++++++ .../Plugins/MusicBrainz/ArtistProvider.cs | 298 ++++++++ .../Configuration/PluginConfiguration.cs | 16 + .../Plugins/MusicBrainz/Configuration/config.html | 69 ++ .../Plugins/MusicBrainz/MusicBrainzExternalIds.cs | 102 +++ .../Plugins/MusicBrainz/Plugin.cs | 35 + 14 files changed, 1330 insertions(+), 1183 deletions(-) delete mode 100644 MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs delete mode 100644 MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 2ea7ff6e9..d0f3d6723 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -8,7 +8,6 @@ namespace Emby.Server.Implementations public static Dictionary Configuration => new Dictionary { { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, - { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" } }; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 55566ed51..8a66ffdaa 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -944,7 +944,6 @@ namespace Emby.Server.Implementations.Library IncludeItemTypes = new[] { typeof(T).Name }, Name = name, DtoOptions = options - }).Cast() .OrderBy(i => i.IsAccessedByName ? 1 : 0) .Cast() @@ -1080,7 +1079,7 @@ namespace Emby.Server.Implementations.Library var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => progress.Report(pct * pct * 0.96)); + innerProgress.RegisterAction(pct => progress.Report(pct * 0.96)); // Validate the entire media library await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index efe0d3cf7..5e3056ccb 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -198,6 +198,7 @@ namespace MediaBrowser.Controller.Entities.Audio return true; } } + return base.RequiresRefresh(); } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 07fbe6035..a892be7a9 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -322,10 +322,10 @@ namespace MediaBrowser.Controller.Entities ProviderManager.OnRefreshProgress(this, 5); } - //build a dictionary of the current children we have now by Id so we can compare quickly and easily + // Build a dictionary of the current children we have now by Id so we can compare quickly and easily var currentChildren = GetActualChildrenDictionary(); - //create a list for our validated children + // Create a list for our validated children var newItems = new List(); cancellationToken.ThrowIfCancellationRequested(); @@ -391,7 +391,7 @@ namespace MediaBrowser.Controller.Entities var folder = this; innerProgress.RegisterAction(p => { - double newPct = .80 * p + 10; + double newPct = 0.80 * p + 10; progress.Report(newPct); ProviderManager.OnRefreshProgress(folder, newPct); }); @@ -421,7 +421,7 @@ namespace MediaBrowser.Controller.Entities var folder = this; innerProgress.RegisterAction(p => { - double newPct = .10 * p + 90; + double newPct = 0.10 * p + 90; progress.Report(newPct); if (recursive) { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 5593c5036..48c16b643 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -24,4 +24,9 @@ true + + + + + diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs deleted file mode 100644 index 8e71b625e..000000000 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ /dev/null @@ -1,800 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MediaBrowser.Common; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.Music -{ - public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder - { - /// - /// The Jellyfin user-agent is unrestricted but source IP must not exceed - /// one request per second, therefore we rate limit to avoid throttling. - /// Be prudent, use a value slightly above the minimun required. - /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting - /// - private const long MusicBrainzQueryIntervalMs = 1050u; - - /// - /// For each single MB lookup/search, this is the maximum number of - /// attempts that shall be made whilst receiving a 503 Server - /// Unavailable (indicating throttled) response. - /// - private const uint MusicBrainzQueryAttempts = 5u; - - internal static MusicBrainzAlbumProvider Current; - - private readonly IHttpClient _httpClient; - private readonly IApplicationHost _appHost; - private readonly ILogger _logger; - - private readonly string _musicBrainzBaseUrl; - - private Stopwatch _stopWatchMusicBrainz = new Stopwatch(); - - public MusicBrainzAlbumProvider( - IHttpClient httpClient, - IApplicationHost appHost, - ILogger logger, - IConfiguration configuration) - { - _httpClient = httpClient; - _appHost = appHost; - _logger = logger; - - _musicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; - - // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit - _stopWatchMusicBrainz.Start(); - - Current = this; - } - - /// - public string Name => "MusicBrainz"; - - /// - public int Order => 0; - - /// - public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) - { - var releaseId = searchInfo.GetReleaseId(); - var releaseGroupId = searchInfo.GetReleaseGroupId(); - - string url; - - if (!string.IsNullOrEmpty(releaseId)) - { - url = "/ws/2/release/?query=reid:" + releaseId.ToString(CultureInfo.InvariantCulture); - } - else if (!string.IsNullOrEmpty(releaseGroupId)) - { - url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); - } - else - { - var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId(); - - if (!string.IsNullOrWhiteSpace(artistMusicBrainzId)) - { - url = string.Format( - CultureInfo.InvariantCulture, - "/ws/2/release/?query=\"{0}\" AND arid:{1}", - WebUtility.UrlEncode(searchInfo.Name), - artistMusicBrainzId); - } - else - { - // I'm sure there is a better way but for now it resolves search for 12" Mixes - var queryName = searchInfo.Name.Replace("\"", string.Empty); - - url = string.Format( - CultureInfo.InvariantCulture, - "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", - WebUtility.UrlEncode(queryName), - WebUtility.UrlEncode(searchInfo.GetAlbumArtist())); - } - } - - if (!string.IsNullOrWhiteSpace(url)) - { - using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - using (var stream = response.Content) - { - return GetResultsFromResponse(stream); - } - } - - return Enumerable.Empty(); - } - - private IEnumerable GetResultsFromResponse(Stream stream) - { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; - - using (var reader = XmlReader.Create(oReader, settings)) - { - var results = ReleaseResult.Parse(reader); - - return results.Select(i => - { - var result = new RemoteSearchResult - { - Name = i.Title, - ProductionYear = i.Year - }; - - if (i.Artists.Count > 0) - { - result.AlbumArtist = new RemoteSearchResult - { - SearchProviderName = Name, - Name = i.Artists[0].Item1 - }; - - result.AlbumArtist.SetProviderId(MetadataProviders.MusicBrainzArtist, i.Artists[0].Item2); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseId)) - { - result.SetProviderId(MetadataProviders.MusicBrainzAlbum, i.ReleaseId); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) - { - result.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, i.ReleaseGroupId); - } - - return result; - - }); - } - } - } - - /// - public async Task> GetMetadata(AlbumInfo id, CancellationToken cancellationToken) - { - var releaseId = id.GetReleaseId(); - var releaseGroupId = id.GetReleaseGroupId(); - - var result = new MetadataResult - { - Item = new MusicAlbum() - }; - - // If we have a release group Id but not a release Id... - if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId)) - { - releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false); - result.HasMetadata = true; - } - - if (string.IsNullOrWhiteSpace(releaseId)) - { - var artistMusicBrainzId = id.GetMusicBrainzArtistId(); - - var releaseResult = await GetReleaseResult(artistMusicBrainzId, id.GetAlbumArtist(), id.Name, cancellationToken).ConfigureAwait(false); - - if (releaseResult != null) - { - if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId)) - { - releaseId = releaseResult.ReleaseId; - result.HasMetadata = true; - } - - if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId)) - { - releaseGroupId = releaseResult.ReleaseGroupId; - result.HasMetadata = true; - } - - result.Item.ProductionYear = releaseResult.Year; - result.Item.Overview = releaseResult.Overview; - } - } - - // If we have a release Id but not a release group Id... - if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId)) - { - releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false); - result.HasMetadata = true; - } - - if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId)) - { - result.HasMetadata = true; - } - - if (result.HasMetadata) - { - if (!string.IsNullOrEmpty(releaseId)) - { - result.Item.SetProviderId(MetadataProviders.MusicBrainzAlbum, releaseId); - } - - if (!string.IsNullOrEmpty(releaseGroupId)) - { - result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId); - } - } - - return result; - } - - private Task GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken) - { - if (!string.IsNullOrEmpty(artistMusicBrainId)) - { - return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken); - } - - if (string.IsNullOrWhiteSpace(artistName)) - { - return Task.FromResult(new ReleaseResult()); - } - - return GetReleaseResultByArtistName(albumName, artistName, cancellationToken); - } - - private async Task GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken) - { - var url = string.Format("/ws/2/release/?query=\"{0}\" AND arid:{1}", - WebUtility.UrlEncode(albumName), - artistId); - - using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - using (var stream = response.Content) - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; - - using (var reader = XmlReader.Create(oReader, settings)) - { - return ReleaseResult.Parse(reader).FirstOrDefault(); - } - } - } - - private async Task GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken) - { - var url = string.Format( - CultureInfo.InvariantCulture, - "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", - WebUtility.UrlEncode(albumName), - WebUtility.UrlEncode(artistName)); - - using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - using (var stream = response.Content) - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; - - using (var reader = XmlReader.Create(oReader, settings)) - { - return ReleaseResult.Parse(reader).FirstOrDefault(); - } - } - } - - private class ReleaseResult - { - public string ReleaseId; - public string ReleaseGroupId; - public string Title; - public string Overview; - public int? Year; - - public List> Artists = new List>(); - - public static IEnumerable Parse(XmlReader reader) - { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "release-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - return ParseReleaseList(subReader).ToList(); - } - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return Enumerable.Empty(); - } - - private static IEnumerable ParseReleaseList(XmlReader reader) - { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "release": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - var releaseId = reader.GetAttribute("id"); - - using (var subReader = reader.ReadSubtree()) - { - var release = ParseRelease(subReader, releaseId); - if (release != null) - { - yield return release; - } - } - break; - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - } - - private static ReleaseResult ParseRelease(XmlReader reader, string releaseId) - { - var result = new ReleaseResult - { - ReleaseId = releaseId - }; - - reader.MoveToContent(); - reader.Read(); - - // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "title": - { - result.Title = reader.ReadElementContentAsString(); - break; - } - case "date": - { - var val = reader.ReadElementContentAsString(); - if (DateTime.TryParse(val, out var date)) - { - result.Year = date.Year; - } - break; - } - case "annotation": - { - result.Overview = reader.ReadElementContentAsString(); - break; - } - case "release-group": - { - result.ReleaseGroupId = reader.GetAttribute("id"); - reader.Skip(); - break; - } - case "artist-credit": - { - // TODO - - /* - * - - -SARCASTIC+ZOOKEEPER -SARCASTIC+ZOOKEEPER - - - - */ - using (var subReader = reader.ReadSubtree()) - { - var artist = ParseArtistCredit(subReader); - - if (!string.IsNullOrEmpty(artist.Item1)) - { - result.Artists.Add(artist); - } - } - - break; - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return result; - } - } - - private static ValueTuple ParseArtistCredit(XmlReader reader) - { - reader.MoveToContent(); - reader.Read(); - - // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "name-credit": - { - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistNameCredit(subReader); - } - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return new ValueTuple(); - } - - private static (string, string) ParseArtistNameCredit(XmlReader reader) - { - reader.MoveToContent(); - reader.Read(); - - // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "artist": - { - var id = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistArtistCredit(subReader, id); - } - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return (null, null); - } - - private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId) - { - reader.MoveToContent(); - reader.Read(); - - string name = null; - - // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "name": - { - name = reader.ReadElementContentAsString(); - break; - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return (name, artistId); - } - - private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) - { - var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); - - using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - using (var stream = response.Content) - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; - - using (var reader = XmlReader.Create(oReader, settings)) - { - var result = ReleaseResult.Parse(reader).FirstOrDefault(); - - if (result != null) - { - return result.ReleaseId; - } - } - } - - return null; - } - - /// - /// Gets the release group id internal. - /// - /// The release entry id. - /// The cancellation token. - /// Task{System.String}. - private async Task GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken) - { - var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture); - - using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - using (var stream = response.Content) - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; - - using (var reader = XmlReader.Create(oReader, settings)) - { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "release-group-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subReader = reader.ReadSubtree()) - { - return GetFirstReleaseGroupId(subReader); - } - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return null; - } - } - } - - private string GetFirstReleaseGroupId(XmlReader reader) - { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "release-group": - { - return reader.GetAttribute("id"); - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return null; - } - - /// - /// Makes request to MusicBrainz server and awaits a response. - /// A 503 Service Unavailable response indicates throttling to maintain a rate limit. - /// A number of retries shall be made in order to try and satisfy the request before - /// giving up and returning null. - /// - internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) - { - var options = new HttpRequestOptions - { - Url = _musicBrainzBaseUrl.TrimEnd('/') + url, - CancellationToken = cancellationToken, - // MusicBrainz request a contact email address is supplied, as comment, in user agent field: - // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent - UserAgent = string.Format( - CultureInfo.InvariantCulture, - "{0} ( {1} )", - _appHost.ApplicationUserAgent, - _appHost.ApplicationUserAgentAddress), - BufferContent = false - }; - - HttpResponseInfo response; - var attempts = 0u; - - do - { - attempts++; - - if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs) - { - // MusicBrainz is extremely adamant about limiting to one request per second - var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; - await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); - } - - // Write time since last request to debug log as evidence we're meeting rate limit - // requirement, before resetting stopwatch back to zero. - _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); - _stopWatchMusicBrainz.Restart(); - - response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); - - // We retry a finite number of times, and only whilst MB is indcating 503 (throttling) - } - while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); - - // Log error if unable to query MB database due to throttling - if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) - { - _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.Url); - } - - return response; - } - - /// - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs deleted file mode 100644 index 5d675392c..000000000 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ /dev/null @@ -1,281 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Extensions; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Providers.Music -{ - public class MusicBrainzArtistProvider : IRemoteMetadataProvider - { - /// - public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) - { - var musicBrainzId = searchInfo.GetMusicBrainzArtistId(); - - if (!string.IsNullOrWhiteSpace(musicBrainzId)) - { - var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture); - - using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - using (var stream = response.Content) - { - return GetResultsFromResponse(stream); - } - } - else - { - // They seem to throw bad request failures on any term with a slash - var nameToSearch = searchInfo.Name.Replace('/', ' '); - - var url = string.Format("/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch)); - - using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - using (var stream = response.Content) - { - var results = GetResultsFromResponse(stream).ToList(); - - if (results.Count > 0) - { - return results; - } - } - - if (HasDiacritics(searchInfo.Name)) - { - // Try again using the search with accent characters url - url = string.Format("/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch)); - - using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - { - using (var stream = response.Content) - { - return GetResultsFromResponse(stream); - } - } - } - } - - return Enumerable.Empty(); - } - - private IEnumerable GetResultsFromResponse(Stream stream) - { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; - - using (var reader = XmlReader.Create(oReader, settings)) - { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "artist-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistList(subReader).ToList(); - } - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return Enumerable.Empty(); - } - } - } - - private IEnumerable ParseArtistList(XmlReader reader) - { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "artist": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - var mbzId = reader.GetAttribute("id"); - - using (var subReader = reader.ReadSubtree()) - { - var artist = ParseArtist(subReader, mbzId); - if (artist != null) - { - yield return artist; - } - } - break; - } - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - } - - private RemoteSearchResult ParseArtist(XmlReader reader, string artistId) - { - var result = new RemoteSearchResult(); - - reader.MoveToContent(); - reader.Read(); - - // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "name": - { - result.Name = reader.ReadElementContentAsString(); - break; - } - case "annotation": - { - result.Overview = reader.ReadElementContentAsString(); - break; - } - default: - { - // there is sort-name if ever needed - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - result.SetProviderId(MetadataProviders.MusicBrainzArtist, artistId); - - if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name)) - { - return null; - } - - return result; - } - - public async Task> GetMetadata(ArtistInfo id, CancellationToken cancellationToken) - { - var result = new MetadataResult - { - Item = new MusicArtist() - }; - - var musicBrainzId = id.GetMusicBrainzArtistId(); - - if (string.IsNullOrWhiteSpace(musicBrainzId)) - { - var searchResults = await GetSearchResults(id, cancellationToken).ConfigureAwait(false); - - var singleResult = searchResults.FirstOrDefault(); - - if (singleResult != null) - { - musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist); - //result.Item.Name = singleResult.Name; - result.Item.Overview = singleResult.Overview; - } - } - - if (!string.IsNullOrWhiteSpace(musicBrainzId)) - { - result.HasMetadata = true; - result.Item.SetProviderId(MetadataProviders.MusicBrainzArtist, musicBrainzId); - } - - return result; - } - - /// - /// Determines whether the specified text has diacritics. - /// - /// The text. - /// true if the specified text has diacritics; otherwise, false. - private bool HasDiacritics(string text) - { - return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal); - } - - /// - /// Encodes an URL. - /// - /// The name. - /// System.String. - private static string UrlEncode(string name) - { - return WebUtility.UrlEncode(name); - } - - public string Name => "MusicBrainz"; - - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 585c98af9..b431c1e41 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -5,101 +5,6 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Providers.Music { - public class MusicBrainzReleaseGroupExternalId : IExternalId - { - /// - public string Name => "MusicBrainz Release Group"; - - /// - public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/release-group/{0}"; - - /// - public bool Supports(IHasProviderIds item) - => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzAlbumArtistExternalId : IExternalId - { - /// - public string Name => "MusicBrainz Album Artist"; - - /// - public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) - => item is Audio; - } - - public class MusicBrainzAlbumExternalId : IExternalId - { - /// - public string Name => "MusicBrainz Album"; - - /// - public string Key => MetadataProviders.MusicBrainzAlbum.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/release/{0}"; - - /// - public bool Supports(IHasProviderIds item) - => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzArtistExternalId : IExternalId - { - /// - public string Name => "MusicBrainz"; - - /// - public string Key => MetadataProviders.MusicBrainzArtist.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is MusicArtist; - } - - public class MusicBrainzOtherArtistExternalId : IExternalId - { - /// - public string Name => "MusicBrainz Artist"; - - /// - - public string Key => MetadataProviders.MusicBrainzArtist.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) - => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzTrackId : IExternalId - { - /// - public string Name => "MusicBrainz Track"; - - /// - public string Key => MetadataProviders.MusicBrainzTrack.ToString(); - - /// - public string UrlFormatString => "https://musicbrainz.org/track/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio; - } - public class ImvdbId : IExternalId { /// diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs new file mode 100644 index 000000000..0fddb0557 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs @@ -0,0 +1,799 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using MediaBrowser.Common; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.MusicBrainz; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.Music +{ + public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder + { + /// + /// The Jellyfin user-agent is unrestricted but source IP must not exceed + /// one request per second, therefore we rate limit to avoid throttling. + /// Be prudent, use a value slightly above the minimun required. + /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting + /// + private readonly long _musicBrainzQueryIntervalMs = 1050u; + + /// + /// For each single MB lookup/search, this is the maximum number of + /// attempts that shall be made whilst receiving a 503 Server + /// Unavailable (indicating throttled) response. + /// + private const uint MusicBrainzQueryAttempts = 5u; + + internal static MusicBrainzAlbumProvider Current; + + private readonly IHttpClient _httpClient; + private readonly IApplicationHost _appHost; + private readonly ILogger _logger; + + private readonly string _musicBrainzBaseUrl; + + private Stopwatch _stopWatchMusicBrainz = new Stopwatch(); + + public MusicBrainzAlbumProvider( + IHttpClient httpClient, + IApplicationHost appHost, + ILogger logger) + { + _httpClient = httpClient; + _appHost = appHost; + _logger = logger; + + _musicBrainzBaseUrl = Plugin.Instance.Configuration.Server; + _musicBrainzQueryIntervalMs = Plugin.Instance.Configuration.RateLimit; + + // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit + _stopWatchMusicBrainz.Start(); + + Current = this; + } + + /// + public string Name => "MusicBrainz"; + + /// + public int Order => 0; + + /// + public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) + { + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return Enumerable.Empty(); + } + + var releaseId = searchInfo.GetReleaseId(); + var releaseGroupId = searchInfo.GetReleaseGroupId(); + + string url; + + if (!string.IsNullOrEmpty(releaseId)) + { + url = "/ws/2/release/?query=reid:" + releaseId.ToString(CultureInfo.InvariantCulture); + } + else if (!string.IsNullOrEmpty(releaseGroupId)) + { + url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); + } + else + { + var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId(); + + if (!string.IsNullOrWhiteSpace(artistMusicBrainzId)) + { + url = string.Format( + CultureInfo.InvariantCulture, + "/ws/2/release/?query=\"{0}\" AND arid:{1}", + WebUtility.UrlEncode(searchInfo.Name), + artistMusicBrainzId); + } + else + { + // I'm sure there is a better way but for now it resolves search for 12" Mixes + var queryName = searchInfo.Name.Replace("\"", string.Empty); + + url = string.Format( + CultureInfo.InvariantCulture, + "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", + WebUtility.UrlEncode(queryName), + WebUtility.UrlEncode(searchInfo.GetAlbumArtist())); + } + } + + if (!string.IsNullOrWhiteSpace(url)) + { + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + { + return GetResultsFromResponse(stream); + } + } + + return Enumerable.Empty(); + } + + private IEnumerable GetResultsFromResponse(Stream stream) + { + using (var oReader = new StreamReader(stream, Encoding.UTF8)) + { + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var reader = XmlReader.Create(oReader, settings)) + { + var results = ReleaseResult.Parse(reader); + + return results.Select(i => + { + var result = new RemoteSearchResult + { + Name = i.Title, + ProductionYear = i.Year + }; + + if (i.Artists.Count > 0) + { + result.AlbumArtist = new RemoteSearchResult + { + SearchProviderName = Name, + Name = i.Artists[0].Item1 + }; + + result.AlbumArtist.SetProviderId(MetadataProviders.MusicBrainzArtist, i.Artists[0].Item2); + } + + if (!string.IsNullOrWhiteSpace(i.ReleaseId)) + { + result.SetProviderId(MetadataProviders.MusicBrainzAlbum, i.ReleaseId); + } + + if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) + { + result.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, i.ReleaseGroupId); + } + + return result; + }); + } + } + } + + /// + public async Task> GetMetadata(AlbumInfo id, CancellationToken cancellationToken) + { + var releaseId = id.GetReleaseId(); + var releaseGroupId = id.GetReleaseGroupId(); + + var result = new MetadataResult + { + Item = new MusicAlbum() + }; + + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return result; + } + + // If we have a release group Id but not a release Id... + if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId)) + { + releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false); + result.HasMetadata = true; + } + + if (string.IsNullOrWhiteSpace(releaseId)) + { + var artistMusicBrainzId = id.GetMusicBrainzArtistId(); + + var releaseResult = await GetReleaseResult(artistMusicBrainzId, id.GetAlbumArtist(), id.Name, cancellationToken).ConfigureAwait(false); + + if (releaseResult != null) + { + if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId)) + { + releaseId = releaseResult.ReleaseId; + result.HasMetadata = true; + } + + if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId)) + { + releaseGroupId = releaseResult.ReleaseGroupId; + result.HasMetadata = true; + } + + result.Item.ProductionYear = releaseResult.Year; + result.Item.Overview = releaseResult.Overview; + } + } + + // If we have a release Id but not a release group Id... + if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId)) + { + releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false); + result.HasMetadata = true; + } + + if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId)) + { + result.HasMetadata = true; + } + + if (result.HasMetadata) + { + if (!string.IsNullOrEmpty(releaseId)) + { + result.Item.SetProviderId(MetadataProviders.MusicBrainzAlbum, releaseId); + } + + if (!string.IsNullOrEmpty(releaseGroupId)) + { + result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId); + } + } + + return result; + } + + private Task GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken) + { + if (!string.IsNullOrEmpty(artistMusicBrainId)) + { + return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken); + } + + if (string.IsNullOrWhiteSpace(artistName)) + { + return Task.FromResult(new ReleaseResult()); + } + + return GetReleaseResultByArtistName(albumName, artistName, cancellationToken); + } + + private async Task GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken) + { + var url = string.Format("/ws/2/release/?query=\"{0}\" AND arid:{1}", + WebUtility.UrlEncode(albumName), + artistId); + + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) + { + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var reader = XmlReader.Create(oReader, settings)) + { + return ReleaseResult.Parse(reader).FirstOrDefault(); + } + } + } + + private async Task GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken) + { + var url = string.Format( + CultureInfo.InvariantCulture, + "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", + WebUtility.UrlEncode(albumName), + WebUtility.UrlEncode(artistName)); + + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) + { + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var reader = XmlReader.Create(oReader, settings)) + { + return ReleaseResult.Parse(reader).FirstOrDefault(); + } + } + } + + private class ReleaseResult + { + public string ReleaseId; + public string ReleaseGroupId; + public string Title; + public string Overview; + public int? Year; + + public List> Artists = new List>(); + + public static IEnumerable Parse(XmlReader reader) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "release-list": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + + using (var subReader = reader.ReadSubtree()) + { + return ParseReleaseList(subReader).ToList(); + } + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return Enumerable.Empty(); + } + + private static IEnumerable ParseReleaseList(XmlReader reader) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "release": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + var releaseId = reader.GetAttribute("id"); + + using (var subReader = reader.ReadSubtree()) + { + var release = ParseRelease(subReader, releaseId); + if (release != null) + { + yield return release; + } + } + break; + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + } + + private static ReleaseResult ParseRelease(XmlReader reader, string releaseId) + { + var result = new ReleaseResult + { + ReleaseId = releaseId + }; + + reader.MoveToContent(); + reader.Read(); + + // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "title": + { + result.Title = reader.ReadElementContentAsString(); + break; + } + case "date": + { + var val = reader.ReadElementContentAsString(); + if (DateTime.TryParse(val, out var date)) + { + result.Year = date.Year; + } + break; + } + case "annotation": + { + result.Overview = reader.ReadElementContentAsString(); + break; + } + case "release-group": + { + result.ReleaseGroupId = reader.GetAttribute("id"); + reader.Skip(); + break; + } + case "artist-credit": + { + using (var subReader = reader.ReadSubtree()) + { + var artist = ParseArtistCredit(subReader); + + if (!string.IsNullOrEmpty(artist.Item1)) + { + result.Artists.Add(artist); + } + } + + break; + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return result; + } + } + + private static ValueTuple ParseArtistCredit(XmlReader reader) + { + reader.MoveToContent(); + reader.Read(); + + // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "name-credit": + { + using (var subReader = reader.ReadSubtree()) + { + return ParseArtistNameCredit(subReader); + } + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return new ValueTuple(); + } + + private static (string, string) ParseArtistNameCredit(XmlReader reader) + { + reader.MoveToContent(); + reader.Read(); + + // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "artist": + { + var id = reader.GetAttribute("id"); + using (var subReader = reader.ReadSubtree()) + { + return ParseArtistArtistCredit(subReader, id); + } + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return (null, null); + } + + private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId) + { + reader.MoveToContent(); + reader.Read(); + + string name = null; + + // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "name": + { + name = reader.ReadElementContentAsString(); + break; + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return (name, artistId); + } + + private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) + { + var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); + + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) + { + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var reader = XmlReader.Create(oReader, settings)) + { + var result = ReleaseResult.Parse(reader).FirstOrDefault(); + + if (result != null) + { + return result.ReleaseId; + } + } + } + + return null; + } + + /// + /// Gets the release group id internal. + /// + /// The release entry id. + /// The cancellation token. + /// Task{System.String}. + private async Task GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken) + { + var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture); + + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) + { + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var reader = XmlReader.Create(oReader, settings)) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "release-group-list": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + using (var subReader = reader.ReadSubtree()) + { + return GetFirstReleaseGroupId(subReader); + } + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return null; + } + } + } + + private string GetFirstReleaseGroupId(XmlReader reader) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "release-group": + { + return reader.GetAttribute("id"); + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return null; + } + + /// + /// Makes request to MusicBrainz server and awaits a response. + /// A 503 Service Unavailable response indicates throttling to maintain a rate limit. + /// A number of retries shall be made in order to try and satisfy the request before + /// giving up and returning null. + /// + internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) + { + var options = new HttpRequestOptions + { + Url = _musicBrainzBaseUrl.TrimEnd('/') + url, + CancellationToken = cancellationToken, + // MusicBrainz request a contact email address is supplied, as comment, in user agent field: + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent + UserAgent = string.Format( + CultureInfo.InvariantCulture, + "{0} ( {1} )", + _appHost.ApplicationUserAgent, + _appHost.ApplicationUserAgentAddress), + BufferContent = false + }; + + HttpResponseInfo response; + var attempts = 0u; + + do + { + attempts++; + + if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) + { + // MusicBrainz is extremely adamant about limiting to one request per second + var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; + await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); + } + + // Write time since last request to debug log as evidence we're meeting rate limit + // requirement, before resetting stopwatch back to zero. + _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); + _stopWatchMusicBrainz.Restart(); + + response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); + + // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) + } + while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); + + // Log error if unable to query MB database due to throttling + if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) + { + _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.Url); + } + + return response; + } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs new file mode 100644 index 000000000..260a3b6e7 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Extensions; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.MusicBrainz; + +namespace MediaBrowser.Providers.Music +{ + public class MusicBrainzArtistProvider : IRemoteMetadataProvider + { + /// + public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) + { + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return Enumerable.Empty(); + } + + var musicBrainzId = searchInfo.GetMusicBrainzArtistId(); + + if (!string.IsNullOrWhiteSpace(musicBrainzId)) + { + var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture); + + using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + { + return GetResultsFromResponse(stream); + } + } + else + { + // They seem to throw bad request failures on any term with a slash + var nameToSearch = searchInfo.Name.Replace('/', ' '); + + var url = string.Format("/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch)); + + using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + { + var results = GetResultsFromResponse(stream).ToList(); + + if (results.Count > 0) + { + return results; + } + } + + if (HasDiacritics(searchInfo.Name)) + { + // Try again using the search with accent characters url + url = string.Format("/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch)); + + using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + { + using (var stream = response.Content) + { + return GetResultsFromResponse(stream); + } + } + } + } + + return Enumerable.Empty(); + } + + private IEnumerable GetResultsFromResponse(Stream stream) + { + using (var oReader = new StreamReader(stream, Encoding.UTF8)) + { + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var reader = XmlReader.Create(oReader, settings)) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "artist-list": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + using (var subReader = reader.ReadSubtree()) + { + return ParseArtistList(subReader).ToList(); + } + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return Enumerable.Empty(); + } + } + } + + private IEnumerable ParseArtistList(XmlReader reader) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "artist": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + var mbzId = reader.GetAttribute("id"); + + using (var subReader = reader.ReadSubtree()) + { + var artist = ParseArtist(subReader, mbzId); + if (artist != null) + { + yield return artist; + } + } + break; + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + } + + private RemoteSearchResult ParseArtist(XmlReader reader, string artistId) + { + var result = new RemoteSearchResult(); + + reader.MoveToContent(); + reader.Read(); + + // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "name": + { + result.Name = reader.ReadElementContentAsString(); + break; + } + case "annotation": + { + result.Overview = reader.ReadElementContentAsString(); + break; + } + default: + { + // there is sort-name if ever needed + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + result.SetProviderId(MetadataProviders.MusicBrainzArtist, artistId); + + if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name)) + { + return null; + } + + return result; + } + + public async Task> GetMetadata(ArtistInfo id, CancellationToken cancellationToken) + { + var result = new MetadataResult + { + Item = new MusicArtist() + }; + + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return result; + } + + var musicBrainzId = id.GetMusicBrainzArtistId(); + + if (string.IsNullOrWhiteSpace(musicBrainzId)) + { + var searchResults = await GetSearchResults(id, cancellationToken).ConfigureAwait(false); + + var singleResult = searchResults.FirstOrDefault(); + + if (singleResult != null) + { + musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist); + result.Item.Overview = singleResult.Overview; + + if (Plugin.Instance.Configuration.ReplaceArtistName) + { + result.Item.Name = singleResult.Name; + } + } + } + + if (!string.IsNullOrWhiteSpace(musicBrainzId)) + { + result.HasMetadata = true; + result.Item.SetProviderId(MetadataProviders.MusicBrainzArtist, musicBrainzId); + } + + return result; + } + + /// + /// Determines whether the specified text has diacritics. + /// + /// The text. + /// true if the specified text has diacritics; otherwise, false. + private bool HasDiacritics(string text) + { + return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal); + } + + /// + /// Encodes an URL. + /// + /// The name. + /// System.String. + private static string UrlEncode(string name) + { + return WebUtility.UrlEncode(name); + } + + public string Name => "MusicBrainz"; + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs new file mode 100644 index 000000000..6bce0b447 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz +{ + public class PluginConfiguration : BasePluginConfiguration + { + public bool Enable { get; set; } = false; + + public bool ReplaceArtistName { get; set; } = false; + + public string Server { get; set; } = "https://www.musicbrainz.org"; + + public long RateLimit { get; set; } = 1000u; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html new file mode 100644 index 000000000..dfffa9065 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html @@ -0,0 +1,69 @@ + + + + MusicBrainz + + +
+
+
+
+
+ +
This can either be a mirror of the official server or a custom server.
+
+
+ +
Span of time between each request in milliseconds.
+
+ + +
+
+ +
+
+
+
+ +
+ + diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs new file mode 100644 index 000000000..0184d4241 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzExternalIds.cs @@ -0,0 +1,102 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Music +{ + public class MusicBrainzReleaseGroupExternalId : IExternalId + { + /// + public string Name => "MusicBrainz Release Group"; + + /// + public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/release-group/{0}"; + + /// + public bool Supports(IHasProviderIds item) + => item is Audio || item is MusicAlbum; + } + + public class MusicBrainzAlbumArtistExternalId : IExternalId + { + /// + public string Name => "MusicBrainz Album Artist"; + + /// + public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) + => item is Audio; + } + + public class MusicBrainzAlbumExternalId : IExternalId + { + /// + public string Name => "MusicBrainz Album"; + + /// + public string Key => MetadataProviders.MusicBrainzAlbum.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/release/{0}"; + + /// + public bool Supports(IHasProviderIds item) + => item is Audio || item is MusicAlbum; + } + + public class MusicBrainzArtistExternalId : IExternalId + { + /// + public string Name => "MusicBrainz"; + + /// + public string Key => MetadataProviders.MusicBrainzArtist.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is MusicArtist; + } + + public class MusicBrainzOtherArtistExternalId : IExternalId + { + /// + public string Name => "MusicBrainz Artist"; + + /// + + public string Key => MetadataProviders.MusicBrainzArtist.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) + => item is Audio || item is MusicAlbum; + } + + public class MusicBrainzTrackId : IExternalId + { + /// + public string Name => "MusicBrainz Track"; + + /// + public string Key => MetadataProviders.MusicBrainzTrack.ToString(); + + /// + public string UrlFormatString => "https://musicbrainz.org/track/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs new file mode 100644 index 000000000..2211d513f --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz +{ + public class Plugin : BasePlugin, IHasWebPages + { + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public IEnumerable GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html" + }; + } + + public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"); + + public override string Name => "MusicBrainz"; + + public override string Description => "Get artist and album metadata from any MusicBrainz server."; + + public static Plugin Instance { get; private set; } + } +} -- cgit v1.2.3 From f3be93a4de86177d87a3be919b7aaa6e394b819d Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 25 Feb 2020 17:02:51 +0100 Subject: Use the swagger API page as the default redirect path if not hosting the jellyfin-web content --- Emby.Server.Implementations/ConfigurationOptions.cs | 8 +++++++- Jellyfin.Server/Program.cs | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 2ea7ff6e9..b733bd2d5 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -3,9 +3,15 @@ using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations { + /// + /// Static class containing the default configuration options for the web server. + /// public static class ConfigurationOptions { - public static Dictionary Configuration => new Dictionary + /// + /// Gets the default configuration options. + /// + public static Dictionary DefaultConfiguration => new Dictionary { { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 2ce7379fb..8c133576c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -468,9 +468,16 @@ namespace Jellyfin.Server await resource.CopyToAsync(dst).ConfigureAwait(false); } + // Use the swagger API page as the default redirect path if not hosting the jellyfin-web content + var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; + if (string.IsNullOrEmpty(appPaths.WebPath)) + { + inMemoryDefaultConfig["HttpListenerHost:DefaultRedirectPath"] = "swagger/index.html"; + } + return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) - .AddInMemoryCollection(ConfigurationOptions.Configuration) + .AddInMemoryCollection(inMemoryDefaultConfig) .AddJsonFile("logging.json", false, true) .AddEnvironmentVariables("JELLYFIN_") .Build(); -- cgit v1.2.3 From 192ec57b608a2cd1f8b337fb416b84abccd7a2a0 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 25 Feb 2020 17:22:21 +0100 Subject: Remove log message executed before logger is initialized --- Emby.Server.Implementations/ConfigurationOptions.cs | 2 +- Jellyfin.Server/Program.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index b733bd2d5..456484813 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations public static class ConfigurationOptions { /// - /// Gets the default configuration options. + /// Gets a new copy of the default configuration options. /// public static Dictionary DefaultConfiguration => new Dictionary { diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 8c133576c..c4e3cd726 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -392,7 +392,6 @@ namespace Jellyfin.Server // ELSE IF $JELLYFIN_WEB_DIR // ELSE /jellyfin-web var webDir = options.WebDir; - if (string.IsNullOrEmpty(webDir)) { webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR"); @@ -407,7 +406,6 @@ namespace Jellyfin.Server // Reset webDir if the directory does not exist, or is empty if (!Directory.Exists(webDir) || !Directory.GetFiles(webDir).Any()) { - _logger.LogInformation("Server will not host static content because the web content directory does not exist or is empty: {ContentRoot}", webDir); webDir = null; } -- cgit v1.2.3 From 3043b7323b1a12e888ba8c4eb71f904724f43e3a Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 28 Feb 2020 17:57:38 +0100 Subject: Use constants for settings keys --- Emby.Server.Implementations/ConfigurationOptions.cs | 6 ++++-- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 8 +++++++- Jellyfin.Server/Program.cs | 3 ++- MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs | 7 ++++++- 4 files changed, 19 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 456484813..cc0d314ed 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Providers.Music; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations @@ -13,8 +15,8 @@ namespace Emby.Server.Implementations ///
public static Dictionary DefaultConfiguration => new Dictionary { - { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, - { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, + { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, + { MusicBrainzAlbumProvider.BaseUrlKey, "https://www.musicbrainz.org" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" } }; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 85602a67f..7f16c6834 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -30,6 +30,12 @@ namespace Emby.Server.Implementations.HttpServer { public class HttpListenerHost : IHttpServer, IDisposable { + /// + /// The settings key for a setting that specifies the default redirect path + /// to use for requests where the URL base prefix is invalid or missing. + /// + public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; @@ -58,7 +64,7 @@ namespace Emby.Server.Implementations.HttpServer _appHost = applicationHost; _logger = logger; _config = config; - _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; + _defaultRedirectPath = configuration[DefaultRedirectKey]; _baseUrlPrefix = _config.Configuration.BaseUrl; _networkManager = networkManager; _jsonSerializer = jsonSerializer; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 4048a0d03..7e03b0c9c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using CommandLine; using Emby.Drawing; using Emby.Server.Implementations; +using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Networking; using Jellyfin.Drawing.Skia; @@ -470,7 +471,7 @@ namespace Jellyfin.Server var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; if (string.IsNullOrEmpty(appPaths.WebPath)) { - inMemoryDefaultConfig["HttpListenerHost:DefaultRedirectPath"] = "swagger/index.html"; + inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html"; } return new ConfigurationBuilder() diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 8e71b625e..ed7688e5a 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -22,6 +22,11 @@ namespace MediaBrowser.Providers.Music { public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder { + /// + /// The settings key for a setting that specifies the base URL to use for sending requests to MusicBrainz. + /// + public const string BaseUrlKey = "MusicBrainz:BaseUrl"; + /// /// The Jellyfin user-agent is unrestricted but source IP must not exceed /// one request per second, therefore we rate limit to avoid throttling. @@ -57,7 +62,7 @@ namespace MediaBrowser.Providers.Music _appHost = appHost; _logger = logger; - _musicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; + _musicBrainzBaseUrl = configuration[BaseUrlKey]; // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit _stopWatchMusicBrainz.Start(); -- cgit v1.2.3 From 3f4b9e9a81278c6f5c817036a7ae52b6f70557e4 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 28 Feb 2020 20:40:45 +0100 Subject: Add new 'nowebcontent' configuration flag --- .../ConfigurationOptions.cs | 1 + .../HttpServer/HttpListenerHost.cs | 2 +- .../Extensions/ConfigurationExtensions.cs | 31 ++++++++++++++++++++++ .../Music/MusicBrainzAlbumProvider.cs | 2 +- 4 files changed, 34 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index cc0d314ed..08a493cb7 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -15,6 +15,7 @@ namespace Emby.Server.Implementations /// public static Dictionary DefaultConfiguration => new Dictionary { + { NoWebContentKey, bool.FalseString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, { MusicBrainzAlbumProvider.BaseUrlKey, "https://www.musicbrainz.org" }, { FfmpegProbeSizeKey, "1G" }, diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 7f16c6834..546a59517 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.HttpServer public class HttpListenerHost : IHttpServer, IDisposable { /// - /// The settings key for a setting that specifies the default redirect path + /// The key for a setting that specifies the default redirect path /// to use for requests where the URL base prefix is invalid or missing. /// public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 76c9b4b26..9dbc1a243 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Extensions.Configuration; namespace MediaBrowser.Controller.Extensions @@ -7,6 +8,11 @@ namespace MediaBrowser.Controller.Extensions ///
public static class ConfigurationExtensions { + /// + /// The key for a setting that indicates whether the application should host static web content. + /// + public const string NoWebContentKey = "nowebcontent"; + /// /// The key for the FFmpeg probe size option. /// @@ -17,6 +23,16 @@ namespace MediaBrowser.Controller.Extensions ///
public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration"; + /// + /// Retrieves a config value indicating whether the application should not host + /// static web content from the . + /// + /// The configuration to retrieve the value from. + /// The parsed config value. + /// The config value is not a valid bool string. See . + public static bool IsNoWebContent(this IConfiguration configuration) + => configuration.ParseBoolean(NoWebContentKey); + /// /// Retrieves the FFmpeg probe size from the . /// @@ -32,5 +48,20 @@ namespace MediaBrowser.Controller.Extensions /// The FFmpeg analyse duration option. public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration) => configuration[FfmpegAnalyzeDurationKey]; + + /// + /// Convert the specified configuration string value its equivalent. + /// + /// The configuration to retrieve and parse the setting from. + /// The key to use to retrieve the string value from the configuration. + /// The parsed boolean value. + /// The config value is not a valid bool string. See . + public static bool ParseBoolean(this IConfiguration configuration, string key) + { + string configValue = configuration[key]; + return bool.TryParse(configValue, out bool result) ? + result : + throw new FormatException($"Invalid value for configuration option '{key}' (expected a boolean): {configValue}"); + } } } diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index ed7688e5a..d217c6ad2 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder { /// - /// The settings key for a setting that specifies the base URL to use for sending requests to MusicBrainz. + /// The key for a setting that specifies the base URL to use for sending requests to MusicBrainz. /// public const string BaseUrlKey = "MusicBrainz:BaseUrl"; -- cgit v1.2.3 From 79413d9f33aad2aae933e79ad22a8a01ae3b6807 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 13 Mar 2020 23:11:59 +0100 Subject: Add a configuration flag to allow/disallow duplicates in playlists --- .../ConfigurationOptions.cs | 3 ++- .../Extensions/ConfigurationExtensions.cs | 25 ++++++++++++++++------ .../MediaBrowser.Controller.csproj | 1 + 3 files changed, 22 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index d0f3d6723..31fb5ca58 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -9,7 +9,8 @@ namespace Emby.Server.Implementations { { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, - { FfmpegAnalyzeDurationKey, "200M" } + { FfmpegAnalyzeDurationKey, "200M" }, + { PlaylistsAllowDuplicatesKey, bool.TrueString } }; } } diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 76c9b4b26..48316499a 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -13,24 +13,37 @@ namespace MediaBrowser.Controller.Extensions public const string FfmpegProbeSizeKey = "FFmpeg:probesize"; /// - /// The key for the FFmpeg analyse duration option. + /// The key for the FFmpeg analyze duration option. /// public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration"; /// - /// Retrieves the FFmpeg probe size from the . + /// The key for a setting that indicates whether playlists should allow duplicate entries. /// - /// This configuration. + public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates"; + + /// + /// Gets the FFmpeg probe size from the . + /// + /// The configuration to read the setting from. /// The FFmpeg probe size option. public static string GetFFmpegProbeSize(this IConfiguration configuration) => configuration[FfmpegProbeSizeKey]; /// - /// Retrieves the FFmpeg analyse duration from the . + /// Gets the FFmpeg analyze duration from the . /// - /// This configuration. - /// The FFmpeg analyse duration option. + /// The configuration to read the setting from. + /// The FFmpeg analyze duration option. public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration) => configuration[FfmpegAnalyzeDurationKey]; + + /// + /// Gets a value indicating whether playlists should allow duplicate entries from the . + /// + /// The configuration to read the setting from. + /// True if playlists should allow duplicates, otherwise false. + public static bool DoPlaylistsAllowDuplicates(this IConfiguration configuration) + => configuration.GetValue(PlaylistsAllowDuplicatesKey); } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 88e9055e8..bcca9e4a1 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -9,6 +9,7 @@ + -- cgit v1.2.3 From aa546dd36abb688cb3a5d10e589521ebf79ef610 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sat, 21 Mar 2020 18:25:09 +0100 Subject: Rename command line option to --nowebclient and config setting to HostWebClient --- .../ConfigurationOptions.cs | 2 +- .../EntryPoints/StartupWizard.cs | 2 +- Jellyfin.Server/Program.cs | 4 ++-- Jellyfin.Server/Properties/launchSettings.json | 6 ++--- Jellyfin.Server/StartupOptions.cs | 10 ++++---- .../Extensions/ConfigurationExtensions.cs | 8 +++---- MediaBrowser.WebDashboard/Api/DashboardService.cs | 27 ++++++++++++---------- 7 files changed, 31 insertions(+), 28 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 814d4b8b5..4574a64fd 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations ///
public static Dictionary DefaultConfiguration => new Dictionary { - { NoWebContentKey, bool.FalseString }, + { HostWebClientKey, bool.TrueString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 9a41396ce..8e9771931 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (_appConfig.NoWebContent()) + if (!_appConfig.HostWebClient()) { BrowserLauncher.OpenSwaggerPage(_appHost); } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 67251eb24..d9ca14136 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -490,9 +490,9 @@ namespace Jellyfin.Server IApplicationPaths appPaths, IConfiguration? startupConfig = null) { - // Use the swagger API page as the default redirect path if not hosting the jellyfin-web content + // Use the swagger API page as the default redirect path if not hosting the web client var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; - if (startupConfig != null && startupConfig.NoWebContent()) + if (startupConfig != null && !startupConfig.HostWebClient()) { inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html"; } diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json index d68a611c1..53d9fe165 100644 --- a/Jellyfin.Server/Properties/launchSettings.json +++ b/Jellyfin.Server/Properties/launchSettings.json @@ -3,9 +3,9 @@ "Jellyfin.Server": { "commandName": "Project" }, - "Jellyfin.Server (nowebcontent)": { + "Jellyfin.Server (nowebclient)": { "commandName": "Project", - "commandLineArgs": "--nowebcontent" + "commandLineArgs": "--nowebclient" } } -} \ No newline at end of file +} diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 0abc0fd91..c93577d3e 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -19,10 +19,10 @@ namespace Jellyfin.Server public string? DataDir { get; set; } /// - /// Gets or sets a value indicating whether the server should not host static web content. + /// Gets or sets a value indicating whether the server should not host the web client. /// - [Option(ConfigurationExtensions.NoWebContentKey, Required = false, HelpText = "Indicates that the web server should not host any static web content.")] - public bool NoWebContent { get; set; } + [Option("nowebclient", Required = false, HelpText = "Indicates that the web server should not host the web client.")] + public bool NoWebClient { get; set; } /// /// Gets or sets the path to the web directory. @@ -84,9 +84,9 @@ namespace Jellyfin.Server { var config = new Dictionary(); - if (NoWebContent) + if (NoWebClient) { - config.Add(ConfigurationExtensions.NoWebContentKey, bool.TrueString); + config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); } return config; diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 900cc6cb5..c95149984 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -9,9 +9,9 @@ namespace MediaBrowser.Controller.Extensions public static class ConfigurationExtensions { /// - /// The key for a setting that indicates whether the application should host static web content. + /// The key for a setting that indicates whether the application should host web client content. /// - public const string NoWebContentKey = "nowebcontent"; + public const string HostWebClientKey = "hostwebclient"; /// /// The key for the FFmpeg probe size option. @@ -34,8 +34,8 @@ namespace MediaBrowser.Controller.Extensions /// The configuration to retrieve the value from. /// The parsed config value. /// The config value is not a valid bool string. See . - public static bool NoWebContent(this IConfiguration configuration) - => configuration.GetValue(NoWebContentKey); + public static bool HostWebClient(this IConfiguration configuration) + => configuration.GetValue(HostWebClientKey); /// /// Gets the FFmpeg probe size from the . diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 3e47ce682..a71d685fb 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -136,15 +136,18 @@ namespace MediaBrowser.WebDashboard.Api _fileSystem = fileSystem; _resultFactory = resultFactory; - // Validate web content path - string webContentPath = DashboardUIPath; - bool webContentPathValid = appConfig.NoWebContent() || (Directory.Exists(webContentPath) && Directory.GetFiles(webContentPath).Any()); - if (!webContentPathValid) + // If hosting the web client, validate the client content path + if (appConfig.HostWebClient()) { - throw new InvalidOperationException( - "The server is expected to host web content, but the provided content directory is either " + - $"invalid or empty: {webContentPath}. If you do not want to host web content with the server, " + - $"you may set the '{Controller.Extensions.ConfigurationExtensions.NoWebContentKey}' flag."); + string webContentPath = DashboardUIPath; + if (!Directory.Exists(webContentPath) || !Directory.GetFiles(webContentPath).Any()) + { + throw new InvalidOperationException( + "The server is expected to host the web client, but the provided content directory is either " + + $"invalid or empty: {webContentPath}. If you do not want to host the web client with the " + + "server, you may set the '--nowebclient' command line flag, or set" + + $"'{Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings."); + } } } @@ -156,13 +159,13 @@ namespace MediaBrowser.WebDashboard.Api /// /// Gets the path of the directory containing the static web interface content, or null if the server is not - /// hosting the static web content. + /// hosting the web client. /// public string DashboardUIPath { get { - if (_appConfig.NoWebContent()) + if (!_appConfig.HostWebClient()) { return null; } @@ -329,7 +332,7 @@ namespace MediaBrowser.WebDashboard.Api /// System.Object. public async Task Get(GetDashboardResource request) { - if (_appConfig.NoWebContent() || DashboardUIPath == null) + if (!_appConfig.HostWebClient() || DashboardUIPath == null) { throw new ResourceNotFoundException(); } @@ -405,7 +408,7 @@ namespace MediaBrowser.WebDashboard.Api public async Task Get(GetDashboardPackage request) { - if (_appConfig.NoWebContent() || DashboardUIPath == null) + if (!_appConfig.HostWebClient() || DashboardUIPath == null) { throw new ResourceNotFoundException(); } -- cgit v1.2.3 From 15dd46c25a54862b35f885a91479c5507c8bf2d9 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 5 Apr 2020 13:46:36 -0400 Subject: Add '--plugin-manifest-url' command line option and 'InstallationManager:PluginManifestUrl' config option --- Emby.Server.Implementations/ConfigurationOptions.cs | 2 ++ Emby.Server.Implementations/IStartupOptions.cs | 17 +++++++++++------ .../Updates/InstallationManager.cs | 13 +++++++++++-- Jellyfin.Server/StartupOptions.cs | 11 ++++++++++- 4 files changed, 34 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 4574a64fd..20bdd18e7 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Emby.Server.Implementations.HttpServer; +using Emby.Server.Implementations.Updates; using MediaBrowser.Providers.Music; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; @@ -17,6 +18,7 @@ namespace Emby.Server.Implementations { { HostWebClientKey, bool.TrueString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, + { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString } diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 6e915de3d..16b68170b 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -3,33 +3,38 @@ namespace Emby.Server.Implementations public interface IStartupOptions { /// - /// --ffmpeg + /// Gets the value of the --ffmpeg command line option. /// string FFmpegPath { get; } /// - /// --service + /// Gets the value of the --service command line option. /// bool IsService { get; } /// - /// --noautorunwebapp + /// Gets the value of the --noautorunwebapp command line option. /// bool NoAutoRunWebApp { get; } /// - /// --package-name + /// Gets the value of the --package-name command line option. /// string PackageName { get; } /// - /// --restartpath + /// Gets the value of the --restartpath command line option. /// string RestartPath { get; } /// - /// --restartargs + /// Gets the value of the --restartargs command line option. /// string RestartArgs { get; } + + /// + /// Gets the value of the --plugin-manifest-url command line option. + /// + string PluginManifestUrl { get; } } } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index c897036eb..22bdbc979 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Updates @@ -27,6 +28,11 @@ namespace Emby.Server.Implementations.Updates /// public class InstallationManager : IInstallationManager { + /// + /// The key for a setting that specifies a URL for the plugin repository JSON manifest. + /// + public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl"; + /// /// The _logger. /// @@ -44,6 +50,7 @@ namespace Emby.Server.Implementations.Updates private readonly IApplicationHost _applicationHost; private readonly IZipClient _zipClient; + private readonly IConfiguration _appConfig; private readonly object _currentInstallationsLock = new object(); @@ -65,7 +72,8 @@ namespace Emby.Server.Implementations.Updates IJsonSerializer jsonSerializer, IServerConfigurationManager config, IFileSystem fileSystem, - IZipClient zipClient) + IZipClient zipClient, + IConfiguration appConfig) { if (logger == null) { @@ -83,6 +91,7 @@ namespace Emby.Server.Implementations.Updates _config = config; _fileSystem = fileSystem; _zipClient = zipClient; + _appConfig = appConfig; } /// @@ -115,7 +124,7 @@ namespace Emby.Server.Implementations.Updates using (var response = await _httpClient.SendAsync( new HttpRequestOptions { - Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", + Url = _appConfig.GetValue(PluginManifestUrlKey), CancellationToken = cancellationToken, CacheMode = CacheMode.Unconditional, CacheLength = TimeSpan.FromMinutes(3) diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index c93577d3e..6e15d058f 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using System.Globalization; using CommandLine; using Emby.Server.Implementations; +using Emby.Server.Implementations.Updates; using MediaBrowser.Controller.Extensions; namespace Jellyfin.Server @@ -76,6 +76,10 @@ namespace Jellyfin.Server [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] public string? RestartArgs { get; set; } + /// + [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")] + public string? PluginManifestUrl { get; set; } + /// /// Gets the command line options as a dictionary that can be used in the .NET configuration system. /// @@ -84,6 +88,11 @@ namespace Jellyfin.Server { var config = new Dictionary(); + if (PluginManifestUrl != null) + { + config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl); + } + if (NoWebClient) { config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); -- cgit v1.2.3 From ff065df9863f30a4eec52a95d260cdadcced7b1e Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 11 Apr 2020 20:11:41 +0900 Subject: update plugin manifest url --- Emby.Server.Implementations/ConfigurationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 20bdd18e7..db7c35a7c 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations { { HostWebClientKey, bool.TrueString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, - { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" }, + { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString } -- cgit v1.2.3 From 735e7c3f7d505b8e00359a8f9498cde7906a0b83 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 21 Apr 2020 12:11:55 +0200 Subject: Fix VideoResolver and tests --- Emby.Naming/Video/VideoResolver.cs | 4 +- .../ConfigurationOptions.cs | 1 - .../Video/CleanDateTimeTests.cs | 1 + tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs | 2 +- tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs | 59 ++- .../Video/MultiVersionTests.cs | 5 +- tests/Jellyfin.Naming.Tests/Video/StackTests.cs | 6 +- tests/Jellyfin.Naming.Tests/Video/StubTests.cs | 10 +- .../Video/VideoListResolverTests.cs | 4 +- .../Video/VideoResolverTests.cs | 461 +++++++++------------ 10 files changed, 242 insertions(+), 311 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 0b75a8cce..a26c4bbc6 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -89,14 +89,14 @@ namespace Emby.Naming.Video if (parseName) { var cleanDateTimeResult = CleanDateTime(name); + name = cleanDateTimeResult.Name; + year = cleanDateTimeResult.Year; if (extraResult.ExtraType == null && TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan newName)) { name = newName.ToString(); } - - year = cleanDateTimeResult.Year; } return new VideoFileInfo diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index db7c35a7c..dea9b6682 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.Updates; -using MediaBrowser.Providers.Music; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs index 49cb2387b..917d8fb3a 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs @@ -46,6 +46,7 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)] // FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)] [InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again + [InlineData("3 days to kill (2005).mkv", "3 days to kill", 2005)] 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 a64d17349..a2722a175 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -5,7 +5,7 @@ using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class ExtraTests : BaseVideoTest + public class ExtraTests { private readonly NamingOptions _videoOptions = new NamingOptions(); diff --git a/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs index ed3112936..82004081e 100644 --- a/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs @@ -4,26 +4,26 @@ using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class Format3DTests : BaseVideoTest + public class Format3DTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + [Fact] public void TestKodiFormat3D() { - var options = new NamingOptions(); - - Test("Super movie.3d.mp4", false, null, options); - Test("Super movie.3d.hsbs.mp4", true, "hsbs", options); - Test("Super movie.3d.sbs.mp4", true, "sbs", options); - Test("Super movie.3d.htab.mp4", true, "htab", options); - Test("Super movie.3d.tab.mp4", true, "tab", options); - Test("Super movie 3d hsbs.mp4", true, "hsbs", options); + Test("Super movie.3d.mp4", false, null); + Test("Super movie.3d.hsbs.mp4", true, "hsbs"); + Test("Super movie.3d.sbs.mp4", true, "sbs"); + Test("Super movie.3d.htab.mp4", true, "htab"); + Test("Super movie.3d.tab.mp4", true, "tab"); + Test("Super movie 3d hsbs.mp4", true, "hsbs"); } [Fact] public void Test3DName() { var result = - GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv"); + new VideoResolver(_namingOptions).ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv"); Assert.Equal("hsbs", result.Format3D); Assert.Equal("Oblivion", result.Name); @@ -34,32 +34,31 @@ namespace Jellyfin.Naming.Tests.Video { // These were introduced for Media Browser 3 // Kodi conventions are preferred but these still need to be supported - var options = new NamingOptions(); - Test("Super movie.3d.mp4", false, null, options); - Test("Super movie.3d.hsbs.mp4", true, "hsbs", options); - Test("Super movie.3d.sbs.mp4", true, "sbs", options); - Test("Super movie.3d.htab.mp4", true, "htab", options); - Test("Super movie.3d.tab.mp4", true, "tab", options); + Test("Super movie.3d.mp4", false, null); + Test("Super movie.3d.hsbs.mp4", true, "hsbs"); + Test("Super movie.3d.sbs.mp4", true, "sbs"); + Test("Super movie.3d.htab.mp4", true, "htab"); + Test("Super movie.3d.tab.mp4", true, "tab"); - Test("Super movie.hsbs.mp4", true, "hsbs", options); - Test("Super movie.sbs.mp4", true, "sbs", options); - Test("Super movie.htab.mp4", true, "htab", options); - Test("Super movie.tab.mp4", true, "tab", options); - Test("Super movie.sbs3d.mp4", true, "sbs3d", options); - Test("Super movie.3d.mvc.mp4", true, "mvc", options); + Test("Super movie.hsbs.mp4", true, "hsbs"); + Test("Super movie.sbs.mp4", true, "sbs"); + Test("Super movie.htab.mp4", true, "htab"); + Test("Super movie.tab.mp4", true, "tab"); + Test("Super movie.sbs3d.mp4", true, "sbs3d"); + Test("Super movie.3d.mvc.mp4", true, "mvc"); - Test("Super movie [3d].mp4", false, null, options); - Test("Super movie [hsbs].mp4", true, "hsbs", options); - Test("Super movie [fsbs].mp4", true, "fsbs", options); - Test("Super movie [ftab].mp4", true, "ftab", options); - Test("Super movie [htab].mp4", true, "htab", options); - Test("Super movie [sbs3d].mp4", true, "sbs3d", options); + Test("Super movie [3d].mp4", false, null); + Test("Super movie [hsbs].mp4", true, "hsbs"); + Test("Super movie [fsbs].mp4", true, "fsbs"); + Test("Super movie [ftab].mp4", true, "ftab"); + Test("Super movie [htab].mp4", true, "htab"); + Test("Super movie [sbs3d].mp4", true, "sbs3d"); } - private void Test(string input, bool is3D, string format3D, NamingOptions options) + private void Test(string input, bool is3D, string format3D) { - var parser = new Format3DParser(options); + var parser = new Format3DParser(_namingOptions); var result = parser.Parse(input); diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index b8fbb2cb2..03fe32b6e 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -8,6 +8,8 @@ namespace Jellyfin.Naming.Tests.Video { public class MultiVersionTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + // FIXME // [Fact] public void TestMultiEdition1() @@ -430,8 +432,7 @@ namespace Jellyfin.Naming.Tests.Video private VideoListResolver GetResolver() { - var options = new NamingOptions(); - return new VideoListResolver(options); + return new VideoListResolver(_namingOptions); } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs index 3e0cbaf0c..3630a07e4 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs @@ -6,8 +6,10 @@ using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class StackTests : BaseVideoTest + public class StackTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + [Fact] public void TestSimpleStack() { @@ -446,7 +448,7 @@ namespace Jellyfin.Naming.Tests.Video private StackResolver GetResolver() { - return new StackResolver(new NamingOptions()); + return new StackResolver(_namingOptions); } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs index 8d5ced9a4..e31d97e2e 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs @@ -4,8 +4,10 @@ using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class StubTests : BaseVideoTest + public class StubTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + [Fact] public void TestStubs() { @@ -27,16 +29,14 @@ namespace Jellyfin.Naming.Tests.Video public void TestStubName() { var result = - GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc"); + new VideoResolver(_namingOptions).ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc"); Assert.Equal("Oblivion", result.Name); } private void Test(string path, bool isStub, string stubType) { - var options = new NamingOptions(); - - var isStubResult = StubResolver.TryResolveFile(path, options, out var stubTypeResult); + var isStubResult = StubResolver.TryResolveFile(path, _namingOptions, out var stubTypeResult); Assert.Equal(isStub, isStubResult); diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index ef8a17898..566dc9f7c 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -8,6 +8,7 @@ namespace Jellyfin.Naming.Tests.Video { public class VideoListResolverTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); // FIXME // [Fact] public void TestStackAndExtras() @@ -450,8 +451,7 @@ namespace Jellyfin.Naming.Tests.Video private VideoListResolver GetResolver() { - var options = new NamingOptions(); - return new VideoListResolver(options); + return new VideoListResolver(_namingOptions); } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index 5a3ce8886..7bfe90f67 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -1,275 +1,204 @@ -using MediaBrowser.Model.Entities; +using System.Collections; +using System.Collections.Generic; +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Model.Entities; using Xunit; namespace Jellyfin.Naming.Tests.Video { public class VideoResolverTests : BaseVideoTest { - // FIXME - // [Fact] - public void TestSimpleFile() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("Brave", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestSimpleFile2() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv"); - - Assert.Equal(1995, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("Bad Boys", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestSimpleFileWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestExtra() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal(ExtraType.Trailer, result.ExtraType); - Assert.Equal("Brave (2006)-trailer", result.Name); - } - - // FIXME - // [Fact] - public void TestExtraWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("300 (2006)-trailer", result.Name); - Assert.Equal(ExtraType.Trailer, result.ExtraType); - } - - // FIXME - // [Fact] - public void TestStubFileWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).bluray.disc"); - - Assert.Equal(2006, result.Year); - Assert.True(result.IsStub); - Assert.Equal("bluray", result.StubType); - Assert.False(result.Is3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestStubFile() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).bluray.disc"); - - Assert.Equal(2006, result.Year); - Assert.True(result.IsStub); - Assert.Equal("bluray", result.StubType); - Assert.False(result.Is3D); - Assert.Equal("Brave", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestExtraStubWithNumericNameNotSupported() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc"); - - Assert.Equal(2006, result.Year); - Assert.True(result.IsStub); - Assert.Equal("bluray", result.StubType); - Assert.False(result.Is3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestExtraStubNotSupported() - { - // Using a stub for an extra is currently not supported - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc"); - - Assert.Equal(2006, result.Year); - Assert.True(result.IsStub); - Assert.Equal("bluray", result.StubType); - Assert.False(result.Is3D); - Assert.Equal("brave", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void Test3DFileWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.True(result.Is3D); - Assert.Equal("sbs", result.Format3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestBad3DFileWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - Assert.Null(result.Format3D); - } - - // FIXME - // [Fact] - public void Test3DFile() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.True(result.Is3D); - Assert.Equal("sbs", result.Format3D); - Assert.Equal("brave", result.Name); - Assert.Null(result.ExtraType); - } - - [Fact] - public void TestNameWithoutDate() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/American Psycho/American.Psycho.mkv"); - - Assert.Null(result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Null(result.Format3D); - Assert.Equal("American.Psycho", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestCleanDateAndStringsSequence() - { - var parser = GetParser(); - - // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again - var result = - parser.ResolveFile(@"/server/Movies/3.Days.to.Kill/3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv"); - - Assert.Equal(2014, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Null(result.Format3D); - Assert.Equal("3.Days.to.Kill", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestCleanDateAndStringsSequence1() - { - var parser = GetParser(); - - // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again - var result = - parser.ResolveFile(@"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv"); - - Assert.Equal(2005, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Null(result.Format3D); - Assert.Equal("3 days to kill", result.Name); - Assert.Null(result.ExtraType); - } - - [Fact] - public void TestFolderNameWithExtension() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/7 Psychos.mkv/7 Psychos.mkv"); - - Assert.Null(result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("7 Psychos", result.Name); - Assert.Null(result.ExtraType); + private readonly NamingOptions _namingOptions = new NamingOptions(); + + private class ResolveFileTestData : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return new object?[] + { + 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 + } + }; + yield return new object?[] + { + 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", + } + }; + yield return new object?[] + { + 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", + } + }; + 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", + } + }; + 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", + } + }; + 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", + } + }; + 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", + } + }; + yield return new object?[] + { + 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, + } + }; + yield return new object?[] + { + 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, + } + }; + yield return new object?[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/Brave (2007)/Brave (2006).mkv", + Container = "mkv", + Name = "Brave", + Year = 2006, + } + }; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + [Theory] + [ClassData(typeof(ResolveFileTestData))] + public void ResolveFile_ValidFileName_Success(VideoFileInfo? expectedResult) + { + var result = new VideoResolver(_namingOptions).ResolveFile(expectedResult.Path); + + Assert.Equal(result.Path, expectedResult.Path); + Assert.Equal(result.Container, expectedResult.Container); + Assert.Equal(result.Name, expectedResult.Name); + Assert.Equal(result.Year, expectedResult.Year); + Assert.Equal(result.ExtraType, expectedResult.ExtraType); + Assert.Equal(result.Format3D, expectedResult.Format3D); + Assert.Equal(result.Is3D, expectedResult.Is3D); + Assert.Equal(result.IsStub, expectedResult.IsStub); + Assert.Equal(result.StubType, expectedResult.StubType); + Assert.Equal(result.IsDirectory, expectedResult.IsDirectory); + Assert.Equal(result.FileNameWithoutExtension, expectedResult.FileNameWithoutExtension); } } } -- cgit v1.2.3 From b7f4b8e2b5a61e3784b3e5dc68c1123bddbff264 Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 4 Jun 2020 23:57:57 +0900 Subject: initial implementation for custom plugin repositories --- .../ConfigurationOptions.cs | 1 - Emby.Server.Implementations/IStartupOptions.cs | 5 --- .../Updates/InstallationManager.cs | 39 ++++++++++++---------- Jellyfin.Server/StartupOptions.cs | 9 ----- MediaBrowser.Api/Devices/DeviceService.cs | 1 - MediaBrowser.Api/PackageService.cs | 25 ++++++++++++++ .../Updates/IInstallationManager.cs | 8 +++++ .../Configuration/ServerConfiguration.cs | 18 ++++++++++ MediaBrowser.Model/Updates/RepositoryInfo.cs | 34 +++++++++++++++++++ 9 files changed, 106 insertions(+), 34 deletions(-) create mode 100644 MediaBrowser.Model/Updates/RepositoryInfo.cs (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index dea9b6682..ff7ee085f 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -17,7 +17,6 @@ namespace Emby.Server.Implementations { { HostWebClientKey, bool.TrueString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, - { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString } diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 0b9f80538..e7e72c686 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -36,11 +36,6 @@ namespace Emby.Server.Implementations /// string RestartArgs { get; } - /// - /// Gets the value of the --plugin-manifest-url command line option. - /// - string PluginManifestUrl { get; } - /// /// Gets the value of the --published-server-url command line option. /// diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 178f32c31..bdd7c31d6 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -31,11 +31,6 @@ namespace Emby.Server.Implementations.Updates /// public class InstallationManager : IInstallationManager { - /// - /// The key for a setting that specifies a URL for the plugin repository JSON manifest. - /// - public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl"; - /// /// The logger. /// @@ -122,16 +117,14 @@ namespace Emby.Server.Implementations.Updates public IEnumerable CompletedInstallations => _completedInstallationsInternal; /// - public async Task> GetAvailablePackages(CancellationToken cancellationToken = default) + public async Task> GetPackages(string manifest, CancellationToken cancellationToken = default) { - var manifestUrl = _appConfig.GetValue(PluginManifestUrlKey); - try { using (var response = await _httpClient.SendAsync( new HttpRequestOptions { - Url = manifestUrl, + Url = manifest, CancellationToken = cancellationToken, CacheMode = CacheMode.Unconditional, CacheLength = TimeSpan.FromMinutes(3) @@ -145,25 +138,35 @@ namespace Emby.Server.Implementations.Updates } catch (SerializationException ex) { - const string LogTemplate = - "Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " + - "have specified a custom plugin repository manifest URL with --plugin-manifest-url or " + - PluginManifestUrlKey + ", please ensure that it is correct."; - _logger.LogError(ex, LogTemplate, manifestUrl); + _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest); throw; } } } catch (UriFormatException ex) { - const string LogTemplate = - "The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " + - "Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey; - _logger.LogError(ex, LogTemplate, manifestUrl); + _logger.LogError(ex, "The URL configured for the plugin repository manifest URL is not valid: {Manifest}", manifest); throw; } } + /// + public async Task> GetAvailablePackages(CancellationToken cancellationToken = default) + { + var result = new List(); + foreach (RepositoryInfo repository in _config.Configuration.PluginRepositories) + { + if (!repository.Enabled) + { + continue; + } + + result.AddRange(await GetPackages(repository.Url, cancellationToken).ConfigureAwait(true)); + } + + return result.ToList().AsReadOnly(); + } + /// public IEnumerable FilterPackages( IEnumerable availablePackages, diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index cc250b06e..a26114e77 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -79,10 +79,6 @@ namespace Jellyfin.Server [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] public string? RestartArgs { get; set; } - /// - [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")] - public string? PluginManifestUrl { get; set; } - /// [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] public Uri? PublishedServerUrl { get; set; } @@ -95,11 +91,6 @@ namespace Jellyfin.Server { var config = new Dictionary(); - if (PluginManifestUrl != null) - { - config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl); - } - if (NoWebClient) { config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs index dd3f3e738..18860983e 100644 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ b/MediaBrowser.Api/Devices/DeviceService.cs @@ -92,7 +92,6 @@ namespace MediaBrowser.Api.Devices var sessions = _authRepo.Get(new AuthenticationInfoQuery { DeviceId = request.Id - }).Items; foreach (var session in sessions) diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 444354a99..31ca05759 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -13,6 +13,18 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { + [Route("/Repositories", "GET", Summary = "Gets all package repositories")] + [Authenticated] + public class GetRepositories : IReturnVoid + { + } + + [Route("/Repositories", "POST", Summary = "Sets the enabled and existing package repositories")] + [Authenticated] + public class SetRepositories : List, IReturnVoid + { + } + /// /// Class GetPackage /// @@ -94,6 +106,7 @@ namespace MediaBrowser.Api public class PackageService : BaseApiService { private readonly IInstallationManager _installationManager; + private readonly IServerConfigurationManager _serverConfigurationManager; public PackageService( ILogger logger, @@ -103,6 +116,18 @@ namespace MediaBrowser.Api : base(logger, serverConfigurationManager, httpResultFactory) { _installationManager = installationManager; + _serverConfigurationManager = serverConfigurationManager; + } + + public object Get(GetRepositories request) + { + var result = _serverConfigurationManager.Configuration.PluginRepositories; + return ToOptimizedResult(result); + } + + public void Post(SetRepositories request) + { + _serverConfigurationManager.Configuration.PluginRepositories = request; } /// diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 965ffe0ec..a5025aee9 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -40,6 +40,14 @@ namespace MediaBrowser.Common.Updates /// IEnumerable CompletedInstallations { get; } + /// + /// Parses a plugin manifest at the supplied URL. + /// + /// The URL to query. + /// The cancellation token. + /// Task{IReadOnlyList{PackageInfo}}. + Task> GetPackages(string manifest, CancellationToken cancellationToken = default); + /// /// Gets all available packages. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 60b1e6eae..b8ec1c710 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -2,7 +2,9 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Updates; namespace MediaBrowser.Model.Configuration { @@ -229,6 +231,8 @@ namespace MediaBrowser.Model.Configuration public string[] CodecsUsed { get; set; } + public List PluginRepositories { get; set; } + public bool IgnoreVirtualInterfaces { get; set; } public bool EnableExternalContentInSuggestions { get; set; } @@ -241,11 +245,13 @@ namespace MediaBrowser.Model.Configuration public bool EnableNewOmdbSupport { get; set; } public string[] RemoteIPFilter { get; set; } + public bool IsRemoteIPFilterBlacklist { get; set; } public int ImageExtractionTimeoutMs { get; set; } public PathSubstitution[] PathSubstitutions { get; set; } + public bool EnableSimpleArtistDetection { get; set; } public string[] UninstalledPlugins { get; set; } @@ -298,6 +304,17 @@ namespace MediaBrowser.Model.Configuration SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" }; SortRemoveWords = new[] { "the", "a", "an" }; + PluginRepositories = new List + { + new RepositoryInfo + { + Name = "Jellyfin Stable", + Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json", + Id = Guid.Parse("3721cd80-b10f-4b26-aecd-74c0f0defe97"), + Enabled = true + } + }; + BaseUrl = string.Empty; UICulture = "en-US"; @@ -355,6 +372,7 @@ namespace MediaBrowser.Model.Configuration public class PathSubstitution { public string From { get; set; } + public string To { get; set; } } } diff --git a/MediaBrowser.Model/Updates/RepositoryInfo.cs b/MediaBrowser.Model/Updates/RepositoryInfo.cs new file mode 100644 index 000000000..c07abc809 --- /dev/null +++ b/MediaBrowser.Model/Updates/RepositoryInfo.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.Updates +{ + /// + /// Class RepositoryInfo. + /// + public class RepositoryInfo + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + + /// + /// Gets or sets the ID. + /// + /// The ID. + public Guid Id { get; set; } + + /// + /// Gets or sets the enabled status of the repository. + /// + /// The enabled status. + public bool Enabled { get; set; } + } +} -- cgit v1.2.3 From cbe47325b328b70e5a49dc231a70d3709f71dbbd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 23 Jul 2020 13:18:47 +0200 Subject: Make UNIX socket configurable --- .../ConfigurationOptions.cs | 4 ++-- Jellyfin.Server/Program.cs | 21 +++++++++++++---- .../Extensions/ConfigurationExtensions.cs | 26 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index ff7ee085f..9a4693db0 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using Emby.Server.Implementations.HttpServer; -using Emby.Server.Implementations.Updates; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations @@ -19,7 +18,8 @@ namespace Emby.Server.Implementations { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, - { PlaylistsAllowDuplicatesKey, bool.TrueString } + { PlaylistsAllowDuplicatesKey, bool.TrueString }, + { BindToUnixSocketKey, bool.TrueString } }; } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 444a91c02..f3ee339d1 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -344,11 +344,24 @@ namespace Jellyfin.Server } } - // Bind to unix socket (only on OSX and Linux) - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + // Bind to unix socket (only on macOS and Linux) + if (startupConfig.UseUnixSocket() && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // TODO: allow configuration of socket path - var socketPath = $"{appPaths.DataPath}/socket.sock"; + var socketPath = startupConfig.GetUnixSocketPath(); + if (string.IsNullOrEmpty(socketPath)) + { + var xdgRuntimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); + if (xdgRuntimeDir == null) + { + // Fall back to config dir + socketPath = Path.Join(appPaths.ConfigurationDirectoryPath, "socket.sock"); + } + else + { + socketPath = Path.Join(xdgRuntimeDir, "jellyfin-socket"); + } + } + // Workaround for https://github.com/aspnet/AspNetCore/issues/14134 if (File.Exists(socketPath)) { diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index c2932cc4c..ae02c1cee 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -33,6 +33,16 @@ namespace MediaBrowser.Controller.Extensions /// public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates"; + /// + /// The key for a setting that indicates whether kestrel should bind to a unix socket. + /// + public const string BindToUnixSocketKey = "kerstrel:socket"; + + /// + /// The key for the unix socket path. + /// + public const string UnixSocketPathKey = "kerstrel:socketPath"; + /// /// Gets a value indicating whether the application should host static web content from the . /// @@ -65,5 +75,21 @@ namespace MediaBrowser.Controller.Extensions /// True if playlists should allow duplicates, otherwise false. public static bool DoPlaylistsAllowDuplicates(this IConfiguration configuration) => configuration.GetValue(PlaylistsAllowDuplicatesKey); + + /// + /// Gets a value indicating whether kestrel should bind to a unix socket from the . + /// + /// The configuration to read the setting from. + /// true if kestrel should bind to a unix socket, otherwise false. + public static bool UseUnixSocket(this IConfiguration configuration) + => configuration.GetValue(BindToUnixSocketKey); + + /// + /// Gets the path for the unix socket from the . + /// + /// The configuration to read the setting from. + /// The unix socket path. + public static string GetUnixSocketPath(this IConfiguration configuration) + => configuration[FfmpegAnalyzeDurationKey]; } } -- cgit v1.2.3 From d006f4301a472038d1ee77c8c3b3c615b4a0f59a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 23 Jul 2020 13:20:10 +0200 Subject: Disable unix socket by default --- Emby.Server.Implementations/ConfigurationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 9a4693db0..64ccff53b 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString }, - { BindToUnixSocketKey, bool.TrueString } + { BindToUnixSocketKey, bool.FalseString } }; } } -- cgit v1.2.3 From 571d0570f5560bde79d21c33173742f6a31e24cf Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 3 Sep 2020 11:32:22 +0200 Subject: Kill HttpListenerHost --- Emby.Server.Implementations/ApplicationHost.cs | 19 +- .../ConfigurationOptions.cs | 2 +- .../HttpServer/HttpListenerHost.cs | 315 --------------------- .../HttpServer/WebSocketManager.cs | 102 +++++++ .../Session/SessionWebSocketListener.cs | 12 +- .../Extensions/ApiApplicationBuilderExtensions.cs | 61 ++++ .../Middleware/BaseUrlRedirectionMiddleware.cs | 62 ++++ .../Middleware/CorsOptionsResponseMiddleware.cs | 69 +++++ .../IpBasedAccessValidationMiddleware.cs | 76 +++++ .../Middleware/LanFilteringMiddleware.cs | 76 +++++ .../Middleware/ServerStartupMessageMiddleware.cs | 38 +++ .../Middleware/WebSocketHandlerMiddleware.cs | 40 +++ Jellyfin.Server/Program.cs | 4 +- Jellyfin.Server/Startup.cs | 12 +- .../Extensions/ConfigurationExtensions.cs | 6 + MediaBrowser.Controller/IServerApplicationHost.cs | 3 +- MediaBrowser.Controller/Net/IHttpServer.cs | 50 ---- MediaBrowser.Controller/Net/IWebSocketManager.cs | 32 +++ 18 files changed, 589 insertions(+), 390 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpServer/HttpListenerHost.cs create mode 100644 Emby.Server.Implementations/HttpServer/WebSocketManager.cs create mode 100644 Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/LanFilteringMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs delete mode 100644 MediaBrowser.Controller/Net/IHttpServer.cs create mode 100644 MediaBrowser.Controller/Net/IWebSocketManager.cs (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5ed0ad415..c8af6b73a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -96,12 +96,12 @@ using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.TheTvdb; using MediaBrowser.Providers.Subtitles; using MediaBrowser.XbmcMetadata.Providers; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; +using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager; namespace Emby.Server.Implementations { @@ -122,9 +122,11 @@ namespace Emby.Server.Implementations private IMediaEncoder _mediaEncoder; private ISessionManager _sessionManager; - private IHttpServer _httpServer; + private IWebSocketManager _webSocketManager; private IHttpClient _httpClient; + private string[] _urlPrefixes; + /// /// Gets a value indicating whether this instance can self restart. /// @@ -444,7 +446,6 @@ namespace Emby.Server.Implementations Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); - _httpServer.GlobalResponse = null; stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); @@ -500,9 +501,6 @@ namespace Emby.Server.Implementations RegisterServices(); } - public Task ExecuteHttpHandlerAsync(HttpContext context, Func next) - => _httpServer.RequestHandler(context, next); - /// /// Registers services/resources with the service collection that will be available via DI. /// @@ -577,7 +575,7 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); @@ -650,7 +648,7 @@ namespace Emby.Server.Implementations _mediaEncoder = Resolve(); _sessionManager = Resolve(); - _httpServer = Resolve(); + _webSocketManager = Resolve(); _httpClient = Resolve(); ((AuthenticationRepository)Resolve()).Initialize(); @@ -771,7 +769,8 @@ namespace Emby.Server.Implementations .Where(i => i != null) .ToArray(); - _httpServer.Init(GetExports(), GetUrlPrefixes()); + _urlPrefixes = GetUrlPrefixes().ToArray(); + _webSocketManager.Init(GetExports()); Resolve().AddParts( GetExports(), @@ -937,7 +936,7 @@ namespace Emby.Server.Implementations } } - if (!_httpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) + if (!_urlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) { requiresRestart = true; } diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 64ccff53b..fde6fa115 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations public static Dictionary DefaultConfiguration => new Dictionary { { HostWebClientKey, bool.TrueString }, - { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, + { DefaultRedirectKey, "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString }, diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs deleted file mode 100644 index 27369960b..000000000 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ /dev/null @@ -1,315 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.WebSockets; -using System.Threading.Tasks; -using Jellyfin.Data.Events; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Globalization; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; - -namespace Emby.Server.Implementations.HttpServer -{ - public class HttpListenerHost : IHttpServer - { - /// - /// The key for a setting that specifies the default redirect path - /// to use for requests where the URL base prefix is invalid or missing. - /// - public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; - - private readonly ILogger _logger; - private readonly ILoggerFactory _loggerFactory; - private readonly IServerConfigurationManager _config; - private readonly INetworkManager _networkManager; - private readonly string _defaultRedirectPath; - private readonly string _baseUrlPrefix; - - private IWebSocketListener[] _webSocketListeners = Array.Empty(); - private bool _disposed = false; - - public HttpListenerHost( - ILogger logger, - IServerConfigurationManager config, - IConfiguration configuration, - INetworkManager networkManager, - ILocalizationManager localizationManager, - ILoggerFactory loggerFactory) - { - _logger = logger; - _config = config; - _defaultRedirectPath = configuration[DefaultRedirectKey]; - _baseUrlPrefix = _config.Configuration.BaseUrl; - _networkManager = networkManager; - _loggerFactory = loggerFactory; - - Instance = this; - GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); - } - - public event EventHandler> WebSocketConnected; - - public static HttpListenerHost Instance { get; protected set; } - - public string[] UrlPrefixes { get; private set; } - - public string GlobalResponse { get; set; } - - private static string NormalizeConfiguredLocalAddress(string address) - { - var add = address.AsSpan().Trim('/'); - int index = add.IndexOf('/'); - if (index != -1) - { - add = add.Slice(index + 1); - } - - return add.TrimStart('/').ToString(); - } - - private bool ValidateHost(string host) - { - var hosts = _config - .Configuration - .LocalNetworkAddresses - .Select(NormalizeConfiguredLocalAddress) - .ToList(); - - if (hosts.Count == 0) - { - return true; - } - - host ??= string.Empty; - - if (_networkManager.IsInPrivateAddressSpace(host)) - { - hosts.Add("localhost"); - hosts.Add("127.0.0.1"); - - return hosts.Any(i => host.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1); - } - - return true; - } - - private bool ValidateRequest(string remoteIp, bool isLocal) - { - if (isLocal) - { - return true; - } - - if (_config.Configuration.EnableRemoteAccess) - { - var addressFilter = _config.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - - if (addressFilter.Length > 0 && !_networkManager.IsInLocalNetwork(remoteIp)) - { - if (_config.Configuration.IsRemoteIPFilterBlacklist) - { - return !_networkManager.IsAddressInSubnets(remoteIp, addressFilter); - } - else - { - return _networkManager.IsAddressInSubnets(remoteIp, addressFilter); - } - } - } - else - { - if (!_networkManager.IsInLocalNetwork(remoteIp)) - { - return false; - } - } - - return true; - } - - /// - public Task RequestHandler(HttpContext context, Func next) - { - if (context.WebSockets.IsWebSocketRequest) - { - return WebSocketRequestHandler(context); - } - - return HttpRequestHandler(context, next); - } - - /// - /// Overridable method that can be used to implement a custom handler. - /// - private async Task HttpRequestHandler(HttpContext httpContext, Func next) - { - var cancellationToken = httpContext.RequestAborted; - var httpRes = httpContext.Response; - var host = httpContext.Request.Host.ToString(); - var localPath = httpContext.Request.Path.ToString(); - string remoteIp = httpContext.Request.RemoteIp(); - - if (_disposed) - { - httpRes.StatusCode = 503; - httpRes.ContentType = "text/plain"; - await httpRes.WriteAsync("Server shutting down", cancellationToken).ConfigureAwait(false); - return; - } - - if (!ValidateHost(host)) - { - httpRes.StatusCode = 400; - httpRes.ContentType = "text/plain"; - await httpRes.WriteAsync("Invalid host", cancellationToken).ConfigureAwait(false); - return; - } - - if (!ValidateRequest(remoteIp, httpContext.Request.IsLocal())) - { - httpRes.StatusCode = 403; - httpRes.ContentType = "text/plain"; - await httpRes.WriteAsync("Forbidden", cancellationToken).ConfigureAwait(false); - return; - } - - if (string.Equals(httpContext.Request.Method, "OPTIONS", StringComparison.OrdinalIgnoreCase)) - { - httpRes.StatusCode = 200; - foreach (var (key, value) in GetDefaultCorsHeaders(httpContext)) - { - httpRes.Headers.Add(key, value); - } - - httpRes.ContentType = "text/plain"; - await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); - return; - } - - if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) - || string.IsNullOrEmpty(localPath) - || !localPath.StartsWith(_baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) - { - // Always redirect back to the default path if the base prefix is invalid or missing - _logger.LogDebug("Normalizing a URL at {0}", localPath); - httpRes.Redirect(_baseUrlPrefix + "/" + _defaultRedirectPath); - return; - } - - if (!string.IsNullOrEmpty(GlobalResponse)) - { - // We don't want the address pings in ApplicationHost to fail - if (localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) - { - httpRes.StatusCode = 503; - httpRes.ContentType = "text/html"; - await httpRes.WriteAsync(GlobalResponse, cancellationToken).ConfigureAwait(false); - return; - } - } - - await next().ConfigureAwait(false); - } - - private async Task WebSocketRequestHandler(HttpContext context) - { - if (_disposed) - { - return; - } - - try - { - _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); - - WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); - - using var connection = new WebSocketConnection( - _loggerFactory.CreateLogger(), - webSocket, - context.Connection.RemoteIpAddress, - context.Request.Query) - { - OnReceive = ProcessWebSocketMessageReceived - }; - - WebSocketConnected?.Invoke(this, new GenericEventArgs(connection)); - - await connection.ProcessAsync().ConfigureAwait(false); - _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); - } - catch (Exception ex) // Otherwise ASP.Net will ignore the exception - { - _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress); - if (!context.Response.HasStarted) - { - context.Response.StatusCode = 500; - } - } - } - - /// - public IDictionary GetDefaultCorsHeaders(HttpContext httpContext) - { - var origin = httpContext.Request.Headers["Origin"]; - if (origin == StringValues.Empty) - { - origin = httpContext.Request.Headers["Host"]; - if (origin == StringValues.Empty) - { - origin = "*"; - } - } - - var headers = new Dictionary(); - headers.Add("Access-Control-Allow-Origin", origin); - headers.Add("Access-Control-Allow-Credentials", "true"); - headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); - headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie"); - return headers; - } - - /// - /// Adds the rest handlers. - /// - /// The web socket listeners. - /// The URL prefixes. See . - public void Init(IEnumerable listeners, IEnumerable urlPrefixes) - { - _webSocketListeners = listeners.ToArray(); - UrlPrefixes = urlPrefixes.ToArray(); - } - - /// - /// Processes the web socket message received. - /// - /// The result. - private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) - { - if (_disposed) - { - return Task.CompletedTask; - } - - IEnumerable GetTasks() - { - foreach (var x in _webSocketListeners) - { - yield return x.ProcessMessageAsync(result); - } - } - - return Task.WhenAll(GetTasks()); - } - } -} diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs new file mode 100644 index 000000000..89c1b7ea0 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -0,0 +1,102 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.WebSockets; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.HttpServer +{ + public class WebSocketManager : IWebSocketManager + { + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; + + private IWebSocketListener[] _webSocketListeners = Array.Empty(); + private bool _disposed = false; + + public WebSocketManager( + ILogger logger, + ILoggerFactory loggerFactory) + { + _logger = logger; + _loggerFactory = loggerFactory; + } + + public event EventHandler> WebSocketConnected; + + /// + public async Task WebSocketRequestHandler(HttpContext context) + { + if (_disposed) + { + return; + } + + try + { + _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); + + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); + + using var connection = new WebSocketConnection( + _loggerFactory.CreateLogger(), + webSocket, + context.Connection.RemoteIpAddress, + context.Request.Query) + { + OnReceive = ProcessWebSocketMessageReceived + }; + + WebSocketConnected?.Invoke(this, new GenericEventArgs(connection)); + + await connection.ProcessAsync().ConfigureAwait(false); + _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); + } + catch (Exception ex) // Otherwise ASP.Net will ignore the exception + { + _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress); + if (!context.Response.HasStarted) + { + context.Response.StatusCode = 500; + } + } + } + + /// + /// Adds the rest handlers. + /// + /// The web socket listeners. + public void Init(IEnumerable listeners) + { + _webSocketListeners = listeners.ToArray(); + } + + /// + /// Processes the web socket message received. + /// + /// The result. + private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) + { + if (_disposed) + { + return Task.CompletedTask; + } + + IEnumerable GetTasks() + { + foreach (var x in _webSocketListeners) + { + yield return x.ProcessMessageAsync(result); + } + } + + return Task.WhenAll(GetTasks()); + } + } +} diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 1da7a6473..15c2af220 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Session private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly IHttpServer _httpServer; + private readonly IWebSocketManager _webSocketManager; /// /// The KeepAlive cancellation token. @@ -72,19 +72,19 @@ namespace Emby.Server.Implementations.Session /// The logger. /// The session manager. /// The logger factory. - /// The HTTP server. + /// The HTTP server. public SessionWebSocketListener( ILogger logger, ISessionManager sessionManager, ILoggerFactory loggerFactory, - IHttpServer httpServer) + IWebSocketManager webSocketManager) { _logger = logger; _sessionManager = sessionManager; _loggerFactory = loggerFactory; - _httpServer = httpServer; + _webSocketManager = webSocketManager; - httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; + webSocketManager.WebSocketConnected += OnServerManagerWebSocketConnected; } private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Session /// public void Dispose() { - _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected; + _webSocketManager.WebSocketConnected -= OnServerManagerWebSocketConnected; StopKeepAlive(); } diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 745567703..33a8d7532 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -1,3 +1,4 @@ +using Jellyfin.Server.Middleware; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Builder; @@ -46,5 +47,65 @@ namespace Jellyfin.Server.Extensions c.RoutePrefix = $"{baseUrl}api-docs/redoc"; }); } + + /// + /// Adds IP based access validation to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseIpBasedAccessValidation(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware(); + } + + /// + /// Adds LAN based access filtering to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseLanFiltering(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware(); + } + + /// + /// Adds CORS OPTIONS request handling to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseCorsOptionsResponse(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware(); + } + + /// + /// Adds base url redirection to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseBaseUrlRedirection(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware(); + } + + /// + /// Adds a custom message during server startup to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseServerStartupMessage(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware(); + } + + /// + /// Adds a WebSocket request handler to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseWebSocketHandler(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware(); + } } } diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs new file mode 100644 index 000000000..9316737bd --- /dev/null +++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Redirect requests without baseurl prefix to the baseurl prefixed URL. + /// + public class BaseUrlRedirectionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + /// The logger. + /// The application configuration. + public BaseUrlRedirectionMiddleware( + RequestDelegate next, + ILogger logger, + IConfiguration configuration) + { + _next = next; + _logger = logger; + _configuration = configuration; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The server configuration manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, IServerConfigurationManager serverConfigurationManager) + { + var localPath = httpContext.Request.Path.ToString(); + var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl; + + if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) + || string.IsNullOrEmpty(localPath) + || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) + { + // Always redirect back to the default path if the base prefix is invalid or missing + _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); + httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]); + return; + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs b/Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs new file mode 100644 index 000000000..8214f8907 --- /dev/null +++ b/Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Net.Mime; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Middleware for handling OPTIONS requests. + /// + public class CorsOptionsResponseMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public CorsOptionsResponseMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The async task. + public async Task Invoke(HttpContext httpContext) + { + if (string.Equals(httpContext.Request.Method, HttpMethods.Options, StringComparison.OrdinalIgnoreCase)) + { + httpContext.Response.StatusCode = 200; + foreach (var (key, value) in GetDefaultCorsHeaders(httpContext)) + { + httpContext.Response.Headers.Add(key, value); + } + + httpContext.Response.ContentType = MediaTypeNames.Text.Plain; + await httpContext.Response.WriteAsync(string.Empty, httpContext.RequestAborted).ConfigureAwait(false); + return; + } + + await _next(httpContext).ConfigureAwait(false); + } + + private static IDictionary GetDefaultCorsHeaders(HttpContext httpContext) + { + var origin = httpContext.Request.Headers["Origin"]; + if (origin == StringValues.Empty) + { + origin = httpContext.Request.Headers["Host"]; + if (origin == StringValues.Empty) + { + origin = "*"; + } + } + + var headers = new Dictionary(); + headers.Add("Access-Control-Allow-Origin", origin); + headers.Add("Access-Control-Allow-Credentials", "true"); + headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); + headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie"); + return headers; + } + } +} diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs new file mode 100644 index 000000000..59b5fb1ed --- /dev/null +++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Validates the IP of requests coming from local networks wrt. remote access. + /// + public class IpBasedAccessValidationMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public IpBasedAccessValidationMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The network manager. + /// The server configuration manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) + { + if (httpContext.Request.IsLocal()) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + var remoteIp = httpContext.Request.RemoteIp(); + + if (serverConfigurationManager.Configuration.EnableRemoteAccess) + { + var addressFilter = serverConfigurationManager.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + + if (addressFilter.Length > 0 && !networkManager.IsInLocalNetwork(remoteIp)) + { + if (serverConfigurationManager.Configuration.IsRemoteIPFilterBlacklist) + { + if (networkManager.IsAddressInSubnets(remoteIp, addressFilter)) + { + return; + } + } + else + { + if (!networkManager.IsAddressInSubnets(remoteIp, addressFilter)) + { + return; + } + } + } + } + else + { + if (!networkManager.IsInLocalNetwork(remoteIp)) + { + return; + } + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs new file mode 100644 index 000000000..9d795145a --- /dev/null +++ b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Validates the LAN host IP based on application configuration. + /// + public class LanFilteringMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public LanFilteringMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The network manager. + /// The server configuration manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) + { + var currentHost = httpContext.Request.Host.ToString(); + var hosts = serverConfigurationManager + .Configuration + .LocalNetworkAddresses + .Select(NormalizeConfiguredLocalAddress) + .ToList(); + + if (hosts.Count == 0) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + currentHost ??= string.Empty; + + if (networkManager.IsInPrivateAddressSpace(currentHost)) + { + hosts.Add("localhost"); + hosts.Add("127.0.0.1"); + + if (hosts.All(i => currentHost.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1)) + { + return; + } + } + + await _next(httpContext).ConfigureAwait(false); + } + + private static string NormalizeConfiguredLocalAddress(string address) + { + var add = address.AsSpan().Trim('/'); + int index = add.IndexOf('/'); + if (index != -1) + { + add = add.Slice(index + 1); + } + + return add.TrimStart('/').ToString(); + } + } +} diff --git a/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs b/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs new file mode 100644 index 000000000..4f347d6d3 --- /dev/null +++ b/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs @@ -0,0 +1,38 @@ +using System.Net.Mime; +using System.Threading.Tasks; +using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Shows a custom message during server startup. + /// + public class ServerStartupMessageMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public ServerStartupMessageMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The localization manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, ILocalizationManager localizationManager) + { + var message = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); + httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; + httpContext.Response.ContentType = MediaTypeNames.Text.Html; + await httpContext.Response.WriteAsync(message, httpContext.RequestAborted).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs new file mode 100644 index 000000000..b7a5d2b34 --- /dev/null +++ b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Handles WebSocket requests. + /// + public class WebSocketHandlerMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public WebSocketHandlerMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The WebSocket connection manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, IWebSocketManager webSocketManager) + { + if (!httpContext.WebSockets.IsWebSocketRequest) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + await webSocketManager.WebSocketRequestHandler(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 14cc5f4c2..b9a90f9db 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using CommandLine; using Emby.Server.Implementations; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Networking; using Jellyfin.Api.Controllers; @@ -28,6 +27,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Serilog; using Serilog.Extensions.Logging; using SQLitePCL; +using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Jellyfin.Server @@ -594,7 +594,7 @@ namespace Jellyfin.Server var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; if (startupConfig != null && !startupConfig.HostWebClient()) { - inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "api-docs/swagger"; + inMemoryDefaultConfig[ConfigurationExtensions.DefaultRedirectKey] = "api-docs/swagger"; } return config diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 9316ab79e..80f679420 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -84,11 +84,9 @@ namespace Jellyfin.Server /// /// The application builder. /// The webhost environment. - /// The server application host. public void Configure( IApplicationBuilder app, - IWebHostEnvironment env, - IServerApplicationHost serverApplicationHost) + IWebHostEnvironment env) { if (env.IsDevelopment()) { @@ -120,7 +118,11 @@ namespace Jellyfin.Server app.UseHttpMetrics(); } - app.Use(serverApplicationHost.ExecuteHttpHandlerAsync); + app.UseLanFiltering(); + app.UseIpBasedAccessValidation(); + app.UseCorsOptionsResponse(); + app.UseBaseUrlRedirection(); + app.UseWebSocketHandler(); app.UseEndpoints(endpoints => { @@ -131,6 +133,8 @@ namespace Jellyfin.Server } }); + app.UseServerStartupMessage(); + // Add type descriptor for legacy datetime parsing. TypeDescriptor.AddAttributes(typeof(DateTime?), new TypeConverterAttribute(typeof(DateTimeTypeConverter))); } diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 4c2209b67..f9285c768 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -8,6 +8,12 @@ namespace MediaBrowser.Controller.Extensions /// public static class ConfigurationExtensions { + /// + /// The key for a setting that specifies the default redirect path + /// to use for requests where the URL base prefix is invalid or missing.. + /// + public const string DefaultRedirectKey = "DefaultRedirectPath"; + /// /// The key for a setting that indicates whether the application should host web client content. /// diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 39b896c0f..d482c19d9 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -117,8 +117,7 @@ namespace MediaBrowser.Controller IEnumerable GetWakeOnLanInfo(); string ExpandVirtualPath(string path); - string ReverseVirtualPath(string path); - Task ExecuteHttpHandlerAsync(HttpContext context, Func next); + string ReverseVirtualPath(string path); } } diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs deleted file mode 100644 index 6739f2fa6..000000000 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Jellyfin.Data.Events; -using Microsoft.AspNetCore.Http; - -namespace MediaBrowser.Controller.Net -{ - /// - /// Interface IHttpServer. - /// - public interface IHttpServer - { - /// - /// Gets the URL prefix. - /// - /// The URL prefix. - string[] UrlPrefixes { get; } - - /// - /// Occurs when [web socket connected]. - /// - event EventHandler> WebSocketConnected; - - /// - /// Inits this instance. - /// - void Init(IEnumerable listener, IEnumerable urlPrefixes); - - /// - /// If set, all requests will respond with this message. - /// - string GlobalResponse { get; set; } - - /// - /// The HTTP request handler. - /// - /// The current HTTP context. - /// The next middleware in the ASP.NET pipeline. - /// The task. - Task RequestHandler(HttpContext context, Func next); - - /// - /// Get the default CORS headers. - /// - /// The HTTP context of the current request. - /// The default CORS headers for the context. - IDictionary GetDefaultCorsHeaders(HttpContext httpContext); - } -} diff --git a/MediaBrowser.Controller/Net/IWebSocketManager.cs b/MediaBrowser.Controller/Net/IWebSocketManager.cs new file mode 100644 index 000000000..e9f00ae88 --- /dev/null +++ b/MediaBrowser.Controller/Net/IWebSocketManager.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using Microsoft.AspNetCore.Http; + +namespace MediaBrowser.Controller.Net +{ + /// + /// Interface IHttpServer. + /// + public interface IWebSocketManager + { + /// + /// Occurs when [web socket connected]. + /// + event EventHandler> WebSocketConnected; + + /// + /// Inits this instance. + /// + /// The websocket listeners. + void Init(IEnumerable listeners); + + /// + /// The HTTP request handler. + /// + /// The current HTTP context. + /// The task. + Task WebSocketRequestHandler(HttpContext context); + } +} -- cgit v1.2.3 From 73bff9da9d03172e0a96665fcab063c672629a92 Mon Sep 17 00:00:00 2001 From: Segi Hovav Date: Thu, 10 Sep 2020 10:03:08 -0700 Subject: Change default value for allow duplicates in playlist option to False --- Emby.Server.Implementations/ConfigurationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index fde6fa115..cd9dbb1bd 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations { DefaultRedirectKey, "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, - { PlaylistsAllowDuplicatesKey, bool.TrueString }, + { PlaylistsAllowDuplicatesKey, bool.FalseString }, { BindToUnixSocketKey, bool.FalseString } }; } -- cgit v1.2.3 From bc1cc2d04ae0e823becf59964e5bdc5a74ae7741 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 17 Apr 2021 11:37:55 +0100 Subject: Remove unused using directives --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 2 -- Emby.Dlna/Main/DlnaEntryPoint.cs | 1 - .../MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs | 1 - Emby.Dlna/PlayTo/SsdpHttpClient.cs | 1 - Emby.Server.Implementations/AppBase/ConfigurationHelper.cs | 1 - Emby.Server.Implementations/Channels/ChannelManager.cs | 1 - Emby.Server.Implementations/Collections/CollectionManager.cs | 3 --- Emby.Server.Implementations/ConfigurationOptions.cs | 1 - Emby.Server.Implementations/HttpServer/Security/AuthService.cs | 1 - Emby.Server.Implementations/IStartupOptions.cs | 1 - Emby.Server.Implementations/Images/ArtistImageProvider.cs | 8 -------- Emby.Server.Implementations/Images/DynamicImageProvider.cs | 1 - Emby.Server.Implementations/Library/PathExtensions.cs | 2 -- Emby.Server.Implementations/Library/SearchEngine.cs | 1 - Emby.Server.Implementations/Library/UserDataManager.cs | 2 +- Emby.Server.Implementations/Library/UserViewManager.cs | 1 - Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 1 - Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs | 2 -- Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs | 1 - .../LiveTv/Listings/XmlTvListingsProvider.cs | 1 - .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 3 --- Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs | 1 - Emby.Server.Implementations/Localization/LocalizationManager.cs | 1 - Emby.Server.Implementations/MediaEncoder/EncodingManager.cs | 1 - Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs | 1 - Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs | 1 - .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 2 +- .../ScheduledTasks/Tasks/PeopleValidationTask.cs | 2 +- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 1 - .../ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs | 2 +- Jellyfin.Api/Controllers/PluginsController.cs | 1 - Jellyfin.Api/Extensions/DtoExtensions.cs | 1 - Jellyfin.Api/Helpers/AudioHelper.cs | 3 +-- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 1 - Jellyfin.Data/Entities/HomeSection.cs | 3 +-- Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs | 1 - Jellyfin.Networking/Configuration/NetworkConfiguration.cs | 1 - .../Configuration/NetworkConfigurationExtensions.cs | 1 - Jellyfin.Server/Filters/ParameterObsoleteFilter.cs | 1 - Jellyfin.Server/Formatters/CssOutputFormatter.cs | 3 +-- Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs | 2 -- Jellyfin.Server/Middleware/LanFilteringMiddleware.cs | 3 --- .../Migrations/Routines/DisableTranscodingThrottling.cs | 1 - Jellyfin.Server/Program.cs | 2 -- Jellyfin.Server/StartupOptions.cs | 3 --- MediaBrowser.Common/Cryptography/PasswordHash.cs | 1 - MediaBrowser.Common/Net/INetworkManager.cs | 1 - MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs | 1 - MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs | 1 - MediaBrowser.Controller/Drawing/ImageHelper.cs | 3 --- MediaBrowser.Controller/Entities/Folder.cs | 1 - .../Events/Updates/PluginUninstalledEventArgs.cs | 1 - MediaBrowser.Controller/IServerApplicationHost.cs | 3 --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 -- MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs | 1 - MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs | 1 - MediaBrowser.Controller/MediaEncoding/JobLogger.cs | 1 - MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs | 5 ----- MediaBrowser.Controller/Playlists/Playlist.cs | 1 - MediaBrowser.Controller/Providers/IRemoteImageProvider.cs | 1 - MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs | 1 - MediaBrowser.Controller/Subtitles/ISubtitleManager.cs | 1 - MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 - MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs | 1 - MediaBrowser.Model/Dto/NameIdPair.cs | 2 -- MediaBrowser.Model/LiveTv/TunerHostInfo.cs | 3 --- MediaBrowser.Model/Notifications/NotificationOptions.cs | 2 -- MediaBrowser.Providers/Manager/ProviderManager.cs | 1 - MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs | 1 - MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs | 1 - MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs | 1 - MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs | 1 - MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs | 2 -- MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 4 ++-- tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs | 1 - .../Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs | 1 - tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs | 1 - .../Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs | 1 - tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs | 1 - tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs | 1 - tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs | 1 - .../IO/ManagedFileSystemTests.cs | 1 - tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs | 2 -- tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs | 1 - tests/Jellyfin.Server.Tests/ParseNetworkTests.cs | 1 - .../Parsers/MusicArtistNfoParserTests.cs | 1 - 86 files changed, 9 insertions(+), 125 deletions(-) (limited to 'Emby.Server.Implementations/ConfigurationOptions.cs') diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 713f95099..90ba601b4 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -7,7 +6,6 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; -using Emby.Dlna.Configuration; using Emby.Dlna.Didl; using Emby.Dlna.Service; using Jellyfin.Data.Entities; diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index d3e9a41ec..bdfe430cf 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.Linq; using System.Net.Http; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; using Emby.Dlna.Ssdp; diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs index 37840cd09..f3789a791 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Emby.Dlna.Common; using Emby.Dlna.Service; -using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index e750f5bbc..d9f1ce490 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -2,7 +2,6 @@ using System; using System.Globalization; -using System.IO; using System.Net.Http; using System.Net.Mime; using System.Text; diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 3f7076383..29bac6634 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -3,7 +3,6 @@ using System; using System.IO; using System.Linq; -using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.AppBase diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 87ebe960a..7324b0ee9 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index db532ce5b..e984afdba 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -8,11 +7,9 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Collections; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index cd9dbb1bd..01dc728c1 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Emby.Server.Implementations.HttpServer; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 4a0fc8239..9afabf527 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 0b823ff06..f719dc5f8 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 #nullable enable -using System; namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/Images/ArtistImageProvider.cs b/Emby.Server.Implementations/Images/ArtistImageProvider.cs index afa4ec7b1..e96b64595 100644 --- a/Emby.Server.Implementations/Images/ArtistImageProvider.cs +++ b/Emby.Server.Implementations/Images/ArtistImageProvider.cs @@ -2,20 +2,12 @@ using System; using System.Collections.Generic; -using System.Linq; -using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Images { diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 462eb03a8..50c531482 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 6eaecff0f..770cf6bb0 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -2,8 +2,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text.RegularExpressions; using MediaBrowser.Common.Providers; namespace Emby.Server.Implementations.Library diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 94602582b..bcdf854ca 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -12,7 +12,6 @@ using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; -using Microsoft.Extensions.Logging; using Genre = MediaBrowser.Controller.Entities.Genre; using Person = MediaBrowser.Controller.Entities.Person; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index d16275b19..e8caea196 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -13,8 +13,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using Book = MediaBrowser.Controller.Entities.Book; using AudioBook = MediaBrowser.Controller.Entities.AudioBook; +using Book = MediaBrowser.Controller.Entities.Book; namespace Emby.Server.Implementations.Library { diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index b6b7ea949..ac041bcf6 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 91a21db60..c9d9cc49a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -17,7 +17,6 @@ using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index c20b08088..1cac9cb96 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; using MediaBrowser.Common.Json; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs index da707fec6..b1259de23 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs @@ -2,7 +2,6 @@ using System; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 76c875737..6824aa442 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Threading; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 68173a0ef..1dcc78687 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -8,10 +8,8 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; @@ -19,7 +17,6 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 2af635492..cc30a516d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 98de848bc..2fdc2b4d9 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -11,7 +11,6 @@ using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Localization diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index a9dab9138..031b5d2e7 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -15,7 +15,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.MediaEncoder diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 7bed06de3..22739a008 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -3,7 +3,6 @@ using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Security.Cryptography; -using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 3cc2cefb9..9c0e92705 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 649305fd5..2312c85d9 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -12,9 +12,9 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index c384cf4bb..57d294a40 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index a69380cbb..11a5fb79f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index e470adcf4..51b620404 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -6,8 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 24285bfb9..adec86a10 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -12,7 +12,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Json; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs index e0c744325..06173315a 100644 --- a/Jellyfin.Api/Extensions/DtoExtensions.cs +++ b/Jellyfin.Api/Extensions/DtoExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index 21ec2d32f..9c35d1ec1 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -1,5 +1,4 @@ -using System; -using System.Net.Http; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 751b48682..1bb504ad1 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; -using System.Net.Mime; using System.Security.Claims; using System.Text; using System.Threading; diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs index 5adc52491..d03d0f7a8 100644 --- a/Jellyfin.Data/Entities/HomeSection.cs +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs index 730deccae..cc04d033a 100644 --- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; namespace Jellyfin.Data.Entities.Libraries diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs index 91bf0015f..faf814c06 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs @@ -1,7 +1,6 @@ #pragma warning disable CA1819 // Properties should not return arrays using System; -using MediaBrowser.Model.Configuration; namespace Jellyfin.Networking.Configuration { diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs index e77b17ba9..8cbe398b0 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs @@ -1,4 +1,3 @@ -using Jellyfin.Networking.Configuration; using MediaBrowser.Common.Configuration; namespace Jellyfin.Networking.Configuration diff --git a/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs b/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs index e54044d0e..b9ce221f5 100644 --- a/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs +++ b/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using Jellyfin.Api.Attributes; -using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; diff --git a/Jellyfin.Server/Formatters/CssOutputFormatter.cs b/Jellyfin.Server/Formatters/CssOutputFormatter.cs index e8dd48e4e..cfc9d1ad3 100644 --- a/Jellyfin.Server/Formatters/CssOutputFormatter.cs +++ b/Jellyfin.Server/Formatters/CssOutputFormatter.cs @@ -1,5 +1,4 @@ -using System; -using System.Text; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs index 7d92bd7d3..0afcd61a0 100644 --- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs +++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs @@ -1,9 +1,7 @@ using System.Net; using System.Threading.Tasks; -using Jellyfin.Networking.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Http; namespace Jellyfin.Server.Middleware diff --git a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs index 8065054a1..67bf24d2a 100644 --- a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs +++ b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs @@ -1,9 +1,6 @@ -using System; -using System.Linq; using System.Net; using System.Threading.Tasks; using Jellyfin.Networking.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Http; diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index bf0225e98..378e88e25 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.Routines diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 464e02419..c10b2ddb3 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -12,12 +12,10 @@ using System.Threading.Tasks; using CommandLine; using Emby.Server.Implementations; using Emby.Server.Implementations.IO; -using Jellyfin.Api.Controllers; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Extensions; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 6d8210527..a1cecc8c6 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,10 +1,7 @@ -using System; using System.Collections.Generic; using CommandLine; using Emby.Server.Implementations; -using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.Udp; -using Emby.Server.Implementations.Updates; using MediaBrowser.Controller.Extensions; namespace Jellyfin.Server diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index f2ecc4741..ec21d0580 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Text; namespace MediaBrowser.Common.Cryptography diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 012824f65..185df5b77 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net; using System.Net.NetworkInformation; -using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Common.Net diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 31dd95402..a233c358e 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Threading; -using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index e1f5d05a6..8a8736427 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -1,4 +1,3 @@ -using System; using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index 181f8e905..596fcbc8c 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -1,10 +1,7 @@ #pragma warning disable CS1591 #nullable enable -using System; -using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Drawing { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index cac5026f7..bdca5c0ee 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Text.Json.Serialization; diff --git a/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs index a111e6d82..0f27be9bb 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs @@ -1,5 +1,4 @@ using Jellyfin.Data.Events; -using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 20bfa697e..6a65a8e47 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -3,10 +3,7 @@ using System; using System.Collections.Generic; using System.Net; -using System.Threading; -using System.Threading.Tasks; using MediaBrowser.Common; -using MediaBrowser.Common.Plugins; using MediaBrowser.Model.System; using Microsoft.AspNetCore.Http; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 92b9a8c7e..1379efacb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -10,8 +10,6 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using Jellyfin.Data.Enums; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index dacd6dea6..d47a689f4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -9,7 +9,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using MediaBrowser.Model.Session; diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 5cbb57990..05dd1a69b 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index cc8820f39..227c5f258 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -3,7 +3,6 @@ using System; using System.Globalization; using System.IO; -using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 281d50372..89e01c08b 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -1,10 +1,5 @@ #pragma warning disable CS1591 -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.MediaEncoding { diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 977b14cb0..a5b7363fb 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Playlists diff --git a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs index ee8f5b860..de1631dcf 100644 --- a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs @@ -2,7 +2,6 @@ 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.Model.Entities; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs b/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs index 9592baa7c..e401ed211 100644 --- a/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs @@ -3,7 +3,6 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Net; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index feb26bc10..6d63286ef 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 205933ae2..36bf77c84 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -16,7 +16,6 @@ using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index 8a7c032c5..7b7744163 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; namespace MediaBrowser.MediaEncoding.Probing { diff --git a/MediaBrowser.Model/Dto/NameIdPair.cs b/MediaBrowser.Model/Dto/NameIdPair.cs index 7f18b4502..31516947f 100644 --- a/MediaBrowser.Model/Dto/NameIdPair.cs +++ b/MediaBrowser.Model/Dto/NameIdPair.cs @@ -1,8 +1,6 @@ #nullable disable #pragma warning disable CS1591 -using System; - namespace MediaBrowser.Model.Dto { public class NameIdPair diff --git a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs index 7d4bbb2d0..05576a0f8 100644 --- a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs +++ b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs @@ -1,9 +1,6 @@ #nullable disable #pragma warning disable CS1591 -using System; -using MediaBrowser.Model.Dto; - namespace MediaBrowser.Model.LiveTv { public class TunerHostInfo diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 94bb5d6e3..12e093b21 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -5,8 +5,6 @@ using System; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Users; namespace MediaBrowser.Model.Notifications { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index b4b0b826f..3bb2c6f0b 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -25,7 +25,6 @@ using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; using Priority_Queue; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs index 2adb11908..85a28747f 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Plugins.AudioDb { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index 00feeec1f..25bb3f9ce 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -19,7 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Music; namespace MediaBrowser.Providers.Plugins.AudioDb diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs index b8095ff04..db8536cc9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Plugins.AudioDb { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 59ecbc017..cbb61fa35 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -18,7 +18,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Music; namespace MediaBrowser.Providers.Plugins.AudioDb diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index d35805a84..46d303890 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -6,9 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; -using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 833d1ae38..d22c1b50a 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -7,8 +7,6 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using TMDbLib.Objects.Find; -using TMDbLib.Objects.Search; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -16,6 +14,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using TMDbLib.Objects.Find; +using TMDbLib.Objects.Search; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { diff --git a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs index 1e1cde957..dbfad3c2f 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Text.Json; using MediaBrowser.Common.Json.Converters; using Xunit; diff --git a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs index 22bc7afb9..cb3b66c4c 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Text.Json; using MediaBrowser.Common.Json.Converters; using Xunit; diff --git a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs index 51633e157..5864a0509 100644 --- a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs +++ b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs @@ -1,4 +1,3 @@ -using System; using MediaBrowser.Model.Extensions; using Xunit; diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs index e5768b620..d9e77dd2e 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Emby.Naming.AudioBook; using Emby.Naming.Common; diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index ad63adadc..53b35c2d6 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Emby.Naming.AudioBook; using Emby.Naming.Common; diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs index f3abacb4f..2446660f3 100644 --- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs +++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs @@ -1,4 +1,3 @@ -using System; using Emby.Naming.Common; using Emby.Naming.Subtitles; using Xunit; diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index d34f65409..2f173b0ce 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -1,4 +1,3 @@ -using System; using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Model.Entities; diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs index 5a535ac51..614a68975 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs index 3cbd638f9..0ade345a1 100644 --- a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs @@ -1,8 +1,6 @@ using System.IO; using System.Reflection; -using System.Text.Json; using System.Threading.Tasks; -using MediaBrowser.Model.Branding; using Xunit; using Xunit.Abstractions; diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs index 4e5d0fcb6..0a463cfa3 100644 --- a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; -using Jellyfin.Server; using MediaBrowser.Controller; using MediaBrowser.Model.IO; using Microsoft.Extensions.Configuration; diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs index 0b714e80a..146b16cf9 100644 --- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using System.Text; using Jellyfin.Networking.Configuration; diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs index 3d8e13e96..8ca3dd96e 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.Audio; -- cgit v1.2.3