diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs index 157b2e197f..c0f9cf4107 100644 --- a/DvdLib/Ifo/Dvd.cs +++ b/DvdLib/Ifo/Dvd.cs @@ -33,7 +33,7 @@ namespace DvdLib.Ifo continue; } - var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries); + var nums = ifo.Name.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) { ReadVTS(ifoNumber, ifo.FullName); diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 45335f90d7..c1dc96bb33 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -392,24 +392,24 @@ namespace Emby.Dlna.Didl { switch (itemStubType.Value) { - case StubType.Latest: return _localization.GetLocalizedString("Latest"); - case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); - case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); - case StubType.Albums: return _localization.GetLocalizedString("Albums"); - case StubType.Artists: return _localization.GetLocalizedString("Artists"); - case StubType.Songs: return _localization.GetLocalizedString("Songs"); - case StubType.Genres: return _localization.GetLocalizedString("Genres"); - case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); - case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); - case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); + case StubType.Latest: return _localization.GetLocalizedString("Latest"); + case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); + case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); + case StubType.Albums: return _localization.GetLocalizedString("Albums"); + case StubType.Artists: return _localization.GetLocalizedString("Artists"); + case StubType.Songs: return _localization.GetLocalizedString("Songs"); + case StubType.Genres: return _localization.GetLocalizedString("Genres"); + case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); + case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); + case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching"); - case StubType.Movies: return _localization.GetLocalizedString("Movies"); - case StubType.Collections: return _localization.GetLocalizedString("Collections"); - case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); - case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); - case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); + case StubType.Movies: return _localization.GetLocalizedString("Movies"); + case StubType.Collections: return _localization.GetLocalizedString("Collections"); + case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); + case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); + case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); - case StubType.Series: return _localization.GetLocalizedString("Shows"); + case StubType.Series: return _localization.GetLocalizedString("Shows"); default: break; } } @@ -435,6 +435,29 @@ namespace Emby.Dlna.Didl return number + " - " + item.Name; } } + else if (item is Episode ep) + { + var parent = ep.GetParent(); + var name = parent.Name + " - "; + + if (ep.ParentIndexNumber.HasValue) + { + name += "S" + ep.ParentIndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); + } + else if (!item.IndexNumber.HasValue) + { + return name + " - " + item.Name; + } + + name += "E" + ep.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); + if (ep.IndexNumberEnd.HasValue) + { + name += "-" + ep.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture); + } + + name += " - " + item.Name; + return name; + } return item.Name; } diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs index f6217d91ef..412259e904 100644 --- a/Emby.Dlna/Didl/Filter.cs +++ b/Emby.Dlna/Didl/Filter.cs @@ -1,7 +1,6 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Model.Extensions; namespace Emby.Dlna.Didl { diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 770d481680..c5d60b2a05 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -262,8 +262,8 @@ namespace Emby.Dlna.Main { if (address.AddressFamily == AddressFamily.InterNetworkV6) { - // Not support IPv6 right now - continue; + // Not supporting IPv6 right now + continue; } var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs index 0bc6ec7e40..c4863b50ab 100644 --- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs @@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook /// The type. public bool IsDirectory { get; set; } - /// + /// public int CompareTo(AudioBookFileInfo other) { if (ReferenceEquals(this, other)) diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 081510f952..f4ba11a0d1 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -29,11 +29,7 @@ namespace Emby.Naming.AudioBook // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer var metadata = audiobookFileInfos - .Select(i => new FileSystemMetadata - { - FullName = i.Path, - IsDirectory = i.IsDirectory - }); + .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); var stackResult = new StackResolver(_options) .ResolveAudioBooks(metadata); @@ -42,11 +38,7 @@ namespace Emby.Naming.AudioBook { var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).ToList(); stackFiles.Sort(); - var info = new AudioBookInfo - { - Files = stackFiles, - Name = stack.Name - }; + var info = new AudioBookInfo { Files = stackFiles, Name = stack.Name }; yield return info; } diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs index 082696da4f..88ec3e2d60 100644 --- a/Emby.Naming/Subtitles/SubtitleParser.cs +++ b/Emby.Naming/Subtitles/SubtitleParser.cs @@ -37,7 +37,8 @@ namespace Emby.Naming.Subtitles IsForced = _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)) }; - var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase)) + var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) + && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase)) .ToList(); // Should have a name, language and file extension diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index d3a822b173..a6af689c72 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -18,7 +18,13 @@ namespace Emby.Naming.TV _options = options; } - public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true) + public EpisodePathParserResult Parse( + string path, + bool isDirectory, + bool? isNamed = null, + bool? isOptimistic = null, + bool? supportsAbsoluteNumbers = null, + bool fillExtendedInfo = true) { // Added to be able to use regex patterns which require a file extension. // There were no failed tests without this block, but to be safe, we can keep it until @@ -64,7 +70,7 @@ namespace Emby.Naming.TV { result.SeriesName = result.SeriesName .Trim() - .Trim(new[] { '_', '.', '-' }) + .Trim('_', '.', '-') .Trim(); } } diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs index 44090c059f..a142fafea0 100644 --- a/Emby.Naming/TV/SeasonPathParserResult.cs +++ b/Emby.Naming/TV/SeasonPathParserResult.cs @@ -11,7 +11,7 @@ namespace Emby.Naming.TV public int? SeasonNumber { get; set; } /// - /// Gets or sets a value indicating whether this is success. + /// Gets or sets a value indicating whether this is success. /// /// true if success; otherwise, false. public bool Success { get; set; } diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index ee05904c75..f733cd2620 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -21,31 +21,24 @@ namespace Emby.Naming.Video public IEnumerable ResolveDirectories(IEnumerable files) { - return Resolve(files.Select(i => new FileSystemMetadata - { - FullName = i, - IsDirectory = true - })); + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true })); } public IEnumerable ResolveFiles(IEnumerable files) { - return Resolve(files.Select(i => new FileSystemMetadata - { - FullName = i, - IsDirectory = false - })); + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false })); } public IEnumerable ResolveAudioBooks(IEnumerable files) { - foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName))) + var groupedDirectoryFiles = files.GroupBy(file => + file.IsDirectory + ? file.FullName + : Path.GetDirectoryName(file.FullName)); + + foreach (var directory in groupedDirectoryFiles) { - var stack = new FileStack() - { - Name = Path.GetFileName(directory.Key), - IsDirectoryStack = false - }; + var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false }; foreach (var file in directory) { if (file.IsDirectory) diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index aa4f3a35c3..11e789b663 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -77,7 +77,9 @@ namespace Emby.Naming.Video /// Gets the file name without extension. /// /// The file name without extension. - public string FileNameWithoutExtension => !IsDirectory ? System.IO.Path.GetFileNameWithoutExtension(Path) : System.IO.Path.GetFileName(Path); + public string FileNameWithoutExtension => !IsDirectory + ? System.IO.Path.GetFileNameWithoutExtension(Path) + : System.IO.Path.GetFileName(Path); /// public override string ToString() diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index d4b02cf2a6..7f755fd25e 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -33,11 +33,7 @@ namespace Emby.Naming.Video // See the unit test TestStackedWithTrailer var nonExtras = videoInfos .Where(i => i.ExtraType == null) - .Select(i => new FileSystemMetadata - { - FullName = i.Path, - IsDirectory = i.IsDirectory - }); + .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); var stackResult = new StackResolver(_options) .Resolve(nonExtras).ToList(); @@ -57,11 +53,7 @@ namespace Emby.Naming.Video info.Year = info.Files[0].Year; - var extraBaseNames = new List - { - stack.Name, - Path.GetFileNameWithoutExtension(stack.Files[0]) - }; + var extraBaseNames = new List { stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0]) }; var extras = GetExtras(remainingFiles, extraBaseNames); @@ -83,10 +75,7 @@ namespace Emby.Naming.Video foreach (var media in standaloneMedia) { - var info = new VideoInfo(media.Name) - { - Files = new List { media } - }; + var info = new VideoInfo(media.Name) { Files = new List { media } }; info.Year = info.Files[0].Year; @@ -222,8 +211,8 @@ namespace Emby.Naming.Video { testFilename = testFilename.Substring(folderName.Length).Trim(); return string.IsNullOrEmpty(testFilename) - || testFilename[0] == '-' - || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); + || testFilename[0] == '-' + || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); } return false; @@ -239,7 +228,8 @@ namespace Emby.Naming.Video return remainingFiles .Where(i => i.ExtraType == null) - .Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) + .Where(i => baseNames.Any(b => + i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) .ToList(); } } diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs index f2f3818381..67401c1f5b 100644 --- a/Emby.Notifications/Api/NotificationsService.cs +++ b/Emby.Notifications/Api/NotificationsService.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 #pragma warning disable SA1402 -#pragma warning disable SA1600 #pragma warning disable SA1649 using System; diff --git a/Emby.Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs index 73e0b0256a..a602b72213 100644 --- a/Emby.Notifications/CoreNotificationTypes.cs +++ b/Emby.Notifications/CoreNotificationTypes.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Notifications/NotificationConfigurationFactory.cs b/Emby.Notifications/NotificationConfigurationFactory.cs index b168ed221b..3fb3553d0e 100644 --- a/Emby.Notifications/NotificationConfigurationFactory.cs +++ b/Emby.Notifications/NotificationConfigurationFactory.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using MediaBrowser.Common.Configuration; diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 332dfa95c8..d900520b2a 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -8,7 +8,6 @@ using System.Text; using System.Threading.Tasks; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; -using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index c3cdcc2222..bc47817438 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration; namespace Emby.Server.Implementations.AppBase { /// - /// Provides a base class to hold common application paths used by both the Ui and Server. + /// Provides a base class to hold common application paths used by both the UI and Server. /// This can be subclassed to add application-specific paths. /// public abstract class BaseApplicationPaths : IApplicationPaths @@ -37,10 +37,7 @@ namespace Emby.Server.Implementations.AppBase /// The program data path. public string ProgramDataPath { get; } - /// - /// Gets the path to the web UI resources folder. - /// - /// The web UI resources path. + /// public string WebPath { get; } /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f6c08bba8d..2f7c0a3ebd 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -42,6 +42,7 @@ using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; +using Emby.Server.Implementations.Services; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; @@ -233,11 +234,6 @@ namespace Emby.Server.Implementations /// public int HttpsPort { get; private set; } - /// - /// Gets the content root for the webhost. - /// - public string ContentRoot { get; private set; } - /// /// Gets the server configuration manager. /// @@ -608,13 +604,7 @@ namespace Emby.Server.Implementations DiscoverTypes(); - await RegisterResources(serviceCollection, startupConfig).ConfigureAwait(false); - - ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; - if (string.IsNullOrEmpty(ContentRoot)) - { - ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; - } + await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false); } public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) @@ -645,9 +635,9 @@ namespace Emby.Server.Implementations } /// - /// Registers resources that classes will depend on + /// Registers services/resources with the service collection that will be available via DI. /// - protected async Task RegisterResources(IServiceCollection serviceCollection, IConfiguration startupConfig) + protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig) { serviceCollection.AddMemoryCache(); @@ -761,20 +751,9 @@ namespace Emby.Server.Implementations CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); - HttpServer = new HttpListenerHost( - this, - LoggerFactory.CreateLogger(), - ServerConfigurationManager, - startupConfig, - NetworkManager, - JsonSerializer, - XmlSerializer, - CreateHttpListener()) - { - GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading") - }; - - serviceCollection.AddSingleton(HttpServer); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); serviceCollection.AddSingleton(ImageProcessor); @@ -836,10 +815,15 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(new DeviceDiscovery(ServerConfigurationManager)); - ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); + ChapterManager = new ChapterManager(ItemRepository); serviceCollection.AddSingleton(ChapterManager); - EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); + EncodingManager = new MediaEncoder.EncodingManager( + LoggerFactory.CreateLogger(), + FileSystemManager, + MediaEncoder, + ChapterManager, + LibraryManager); serviceCollection.AddSingleton(EncodingManager); var activityLogRepo = GetActivityLogRepository(); @@ -881,6 +865,14 @@ namespace Emby.Server.Implementations ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; } + /// + /// Create services registered with the service container that need to be initialized at application startup. + /// + public void InitializeServices() + { + HttpServer = Resolve(); + } + public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) { // Distinct these to prevent users from reporting problems that aren't actually problems @@ -1064,7 +1056,7 @@ namespace Emby.Server.Implementations .Where(i => i != null) .ToArray(); - HttpServer.Init(GetExports(false), GetExports(), GetUrlPrefixes()); + HttpServer.Init(GetExportTypes(), GetExports(), GetUrlPrefixes()); LibraryManager.AddParts( GetExports(), @@ -1158,7 +1150,7 @@ namespace Emby.Server.Implementations { exportedTypes = ass.GetExportedTypes(); } - catch (TypeLoadException ex) + catch (FileNotFoundException ex) { Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); continue; @@ -1198,8 +1190,6 @@ namespace Emby.Server.Implementations }); } - protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(LoggerFactory.CreateLogger()); - private CertificateInfo GetCertificateInfo(bool generateCertificate) { // Custom cert @@ -1510,18 +1500,10 @@ namespace Emby.Server.Implementations public string GetLocalApiUrl(ReadOnlySpan host) { var url = new StringBuilder(64); - if (EnableHttps) - { - url.Append("https://"); - } - else - { - url.Append("http://"); - } - - url.Append(host) + url.Append(EnableHttps ? "https://" : "http://") + .Append(host) .Append(':') - .Append(HttpPort); + .Append(EnableHttps ? HttpsPort : HttpPort); string baseUrl = ServerConfigurationManager.Configuration.BaseUrl; if (baseUrl.Length != 0) diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index f5da0d0183..96096e142a 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -1,51 +1,48 @@ using System; using MediaBrowser.Controller; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Browser { /// - /// Class BrowserLauncher. + /// Assists in opening application URLs in an external browser. /// public static class BrowserLauncher { /// - /// Opens the dashboard page. - /// - /// The page. - /// The app host. - private static void OpenDashboardPage(string page, IServerApplicationHost appHost) - { - var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page; - - OpenUrl(appHost, url); - } - - /// - /// Opens the web client. + /// Opens the home page of the web client. /// /// The app host. public static void OpenWebApp(IServerApplicationHost appHost) { - OpenDashboardPage("index.html", appHost); + TryOpenUrl(appHost, "/web/index.html"); } /// - /// Opens the URL. + /// Opens the swagger API page. /// - /// The application host instance. + /// The app host. + public static void OpenSwaggerPage(IServerApplicationHost appHost) + { + TryOpenUrl(appHost, "/swagger/index.html"); + } + + /// + /// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored. + /// + /// The application host. /// The URL. - private static void OpenUrl(IServerApplicationHost appHost, string url) + private static void TryOpenUrl(IServerApplicationHost appHost, string url) { try { - appHost.LaunchUrl(url); + string baseUrl = appHost.GetLocalApiUrl("localhost"); + appHost.LaunchUrl(baseUrl + url); } - catch (NotSupportedException) - { - - } - catch (Exception) + catch (Exception ex) { + var logger = appHost.Resolve(); + logger?.LogError(ex, "Failed to open browser window with URL {URL}", url); } } } diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 891b81a36c..367efcb134 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.Channels { @@ -18,27 +19,30 @@ namespace Emby.Server.Implementations.Channels private readonly IUserManager _userManager; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; public RefreshChannelsScheduledTask( IChannelManager channelManager, IUserManager userManager, ILogger logger, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + ILocalizationManager localization) { _channelManager = channelManager; _userManager = userManager; _logger = logger; _libraryManager = libraryManager; + _localization = localization; } /// - public string Name => "Refresh Channels"; + public string Name => _localization.GetLocalizedString("TasksRefreshChannels"); /// - public string Description => "Refreshes internet channel information."; + public string Description => _localization.GetLocalizedString("TasksRefreshChannelsDescription"); /// - public string Category => "Internet Channels"; + public string Category => _localization.GetLocalizedString("TasksChannelsCategory"); /// public bool IsHidden => ((ChannelManager)_channelManager).Channels.Length == 0; diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 30b654886b..f407317ec7 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Configuration var newPath = newConfig.MetadataPath; if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal)) + && !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal)) { // Validate if (!Directory.Exists(newPath)) diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 31fb5ca58c..4574a64fdb 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,13 +1,22 @@ using System.Collections.Generic; +using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Providers.Music; 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 a new copy of the default configuration options. + /// + public static Dictionary DefaultConfiguration => new Dictionary { - { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, + { HostWebClientKey, bool.TrueString }, + { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 3f2d33de25..e3242f7b4f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -454,7 +454,7 @@ namespace Emby.Server.Implementations.Data private static string GetSaveItemCommandText() { - var saveColumns = new [] + var saveColumns = new[] { "guid", "type", @@ -560,7 +560,7 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(item)); } - SaveItems(new [] { item }, cancellationToken); + SaveItems(new[] { item }, cancellationToken); } public void SaveImages(BaseItem item) @@ -1622,7 +1622,7 @@ namespace Emby.Server.Implementations.Data { IEnumerable GetLockedFields(string s) { - foreach (var i in s.Split(new [] { '|' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var i in s.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)) { if (Enum.TryParse(i, true, out MetadataFields parsedValue)) { @@ -1818,7 +1818,7 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.ProductionLocations = reader.GetString(index).Split(new [] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); + item.ProductionLocations = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); } index++; } @@ -2006,7 +2006,7 @@ namespace Emby.Server.Implementations.Data /// /// Saves the chapters. /// - public void SaveChapters(Guid id, List chapters) + public void SaveChapters(Guid id, IReadOnlyList chapters) { CheckDisposed(); @@ -2035,22 +2035,24 @@ namespace Emby.Server.Implementations.Data } } - private void InsertChapters(byte[] idBlob, List chapters, IDatabaseConnection db) + private void InsertChapters(byte[] idBlob, IReadOnlyList chapters, IDatabaseConnection db) { var startIndex = 0; var limit = 100; var chapterIndex = 0; + const string StartInsertText = "insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values "; + var insertText = new StringBuilder(StartInsertText, 256); + while (startIndex < chapters.Count) { - var insertText = new StringBuilder("insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values "); - var endIndex = Math.Min(chapters.Count, startIndex + limit); for (var i = startIndex; i < endIndex; i++) { insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture)); } + insertText.Length -= 1; // Remove last , using (var statement = PrepareStatement(db, insertText.ToString())) @@ -2077,6 +2079,7 @@ namespace Emby.Server.Implementations.Data } startIndex += limit; + insertText.Length = StartInsertText.Length; } } @@ -2897,8 +2900,8 @@ namespace Emby.Server.Implementations.Data BindSimilarParams(query, statement); BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } @@ -2925,7 +2928,7 @@ namespace Emby.Server.Implementations.Data prepend.Add(("SearchScore", SortOrder.Descending)); prepend.Add((ItemSortBy.SortName, SortOrder.Ascending)); } - + if (hasSimilar) { prepend.Add(("SimilarityScore", SortOrder.Descending)); @@ -3266,8 +3269,8 @@ namespace Emby.Server.Implementations.Data BindSimilarParams(query, statement); BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); foreach (var row in statement.ExecuteQuery()) { @@ -3288,8 +3291,8 @@ namespace Emby.Server.Implementations.Data BindSimilarParams(query, statement); BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } @@ -6159,7 +6162,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type item.ColorTransfer = reader[34].ToString(); } - if (item.Type == MediaStreamType.Subtitle){ + if (item.Type == MediaStreamType.Subtitle) + { item.localizedUndefined = _localization.GetLocalizedString("Undefined"); item.localizedDefault = _localization.GetLocalizedString("Default"); item.localizedForced = _localization.GetLocalizedString("Forced"); diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index a042320c91..0c3f26974f 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Data IServerApplicationPaths appPaths) : base(logger) { - _jsonOptions = JsonDefaults.GetOptions();; + _jsonOptions = JsonDefaults.GetOptions(); DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); } diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 5f2d629fea..8e97719311 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -2,7 +2,9 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Plugins; +using Microsoft.Extensions.Configuration; namespace Emby.Server.Implementations.EntryPoints { @@ -11,10 +13,8 @@ namespace Emby.Server.Implementations.EntryPoints /// public sealed class StartupWizard : IServerEntryPoint { - /// - /// The app host. - /// private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _appConfig; private readonly IServerConfigurationManager _config; /// @@ -22,9 +22,10 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The application host. /// The configuration manager. - public StartupWizard(IServerApplicationHost appHost, IServerConfigurationManager config) + public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config) { _appHost = appHost; + _appConfig = appConfig; _config = config; } @@ -36,7 +37,11 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (!_config.Configuration.IsStartupWizardCompleted) + if (!_appConfig.HostWebClient()) + { + BrowserLauncher.OpenSwaggerPage(_appHost); + } + else if (!_config.Configuration.IsStartupWizardCompleted) { BrowserLauncher.OpenWebApp(_appHost); } diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 45fa03cdd7..882bfe2f6e 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -96,13 +96,13 @@ namespace Emby.Server.Implementations.HttpClientManager switch (options.DecompressionMethod) { - case CompressionMethod.Deflate | CompressionMethod.Gzip: + case CompressionMethods.Deflate | CompressionMethods.Gzip: request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); break; - case CompressionMethod.Deflate: + case CompressionMethods.Deflate: request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); break; - case CompressionMethod.Gzip: + case CompressionMethods.Gzip: request.Headers.Add(HeaderNames.AcceptEncoding, "gzip"); break; default: @@ -239,15 +239,10 @@ namespace Emby.Server.Implementations.HttpClientManager var httpWebRequest = GetRequestMessage(options, httpMethod); - if (options.RequestContentBytes != null - || !string.IsNullOrEmpty(options.RequestContent) + if (!string.IsNullOrEmpty(options.RequestContent) || httpMethod == HttpMethod.Post) { - if (options.RequestContentBytes != null) - { - httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes); - } - else if (options.RequestContent != null) + if (options.RequestContent != null) { httpWebRequest.Content = new StringContent( options.RequestContent, diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 82f1e5b529..0b61e40b05 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -11,8 +11,8 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 93572b8bfe..7a812f3201 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -17,6 +17,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; @@ -29,6 +30,12 @@ namespace Emby.Server.Implementations.HttpServer { public class HttpListenerHost : IHttpServer, IDisposable { + /// + /// 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 IServerConfigurationManager _config; private readonly INetworkManager _networkManager; @@ -52,23 +59,28 @@ namespace Emby.Server.Implementations.HttpServer INetworkManager networkManager, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, - IHttpListener socketListener) + IHttpListener socketListener, + ILocalizationManager localizationManager, + ServiceController serviceController) { _appHost = applicationHost; _logger = logger; _config = config; - _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; + _defaultRedirectPath = configuration[DefaultRedirectKey]; _baseUrlPrefix = _config.Configuration.BaseUrl; _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; _socketListener = socketListener; + ServiceController = serviceController; + _socketListener.WebSocketConnected = OnWebSocketConnected; _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; ResponseFilters = Array.Empty>(); + GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); } public event EventHandler> WebSocketConnected; @@ -81,7 +93,7 @@ namespace Emby.Server.Implementations.HttpServer public string GlobalResponse { get; set; } - public ServiceController ServiceController { get; private set; } + public ServiceController ServiceController { get; } public object CreateInstance(Type type) { @@ -585,17 +597,15 @@ namespace Emby.Server.Implementations.HttpServer /// /// Adds the rest handlers. /// - /// The services. - /// - /// - public void Init(IEnumerable services, IEnumerable listeners, IEnumerable urlPrefixes) + /// The service types to register with the . + /// The web socket listeners. + /// The URL prefixes. See . + public void Init(IEnumerable serviceTypes, IEnumerable listeners, IEnumerable urlPrefixes) { _webSocketListeners = listeners.ToArray(); UrlPrefixes = urlPrefixes.ToArray(); - ServiceController = new ServiceController(); - var types = services.Select(r => r.GetType()); - ServiceController.Init(this, types); + ServiceController.Init(this, serviceTypes); ResponseFilters = new Action[] { diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 48599beb7b..7461ec4f1d 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -587,11 +587,11 @@ namespace Emby.Server.Implementations.IO // some drives on linux have no actual size or are used for other purposes return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram) .Select(d => new FileSystemMetadata - { - Name = d.Name, - FullName = d.RootDirectory.FullName, - IsDirectory = true - }).ToList(); + { + Name = d.Name, + FullName = d.RootDirectory.FullName, + IsDirectory = true + }).ToList(); } public virtual IEnumerable GetDirectories(string path, bool recursive = false) diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index c759e71151..dd6bd8ee87 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 25d733a651..7b17cc913f 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -805,17 +805,17 @@ namespace Emby.Server.Implementations.Library // Delete user config dir lock (_configSyncLock) - lock (_policySyncLock) - { - try + lock (_policySyncLock) { - Directory.Delete(user.ConfigurationDirectoryPath, true); + try + { + Directory.Delete(user.ConfigurationDirectoryPath, true); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath); + } } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath); - } - } _users.TryRemove(user.Id, out _); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 9c4f5fe3d8..2e13a3bb37 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.IO; @@ -73,7 +72,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV UserAgent = "Emby/3.0", // Shouldn't matter but may cause issues - DecompressionMethod = CompressionMethod.None + DecompressionMethod = CompressionMethods.None }; using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false)) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 46fb47b7b9..0f2203ca0f 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs index a716b6240f..69a9cb78aa 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Threading.Tasks; using MediaBrowser.Controller.Plugins; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs index d6a1aee38b..4712724d67 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Threading; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 6d42a58f42..fc543dc551 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 4cb9f6fe88..0b0ff6cb31 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs index 9cc53fddcd..194e4606de 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using MediaBrowser.Controller.LiveTv; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 330e881ef5..7ebb043d8e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Concurrent; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index e9d3105bf1..89b81fd968 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Concurrent; @@ -636,7 +635,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings ListingsProviderInfo providerInfo) { // Schedules direct requires that the client support compression and will return a 400 response without it - options.DecompressionMethod = CompressionMethod.Deflate; + options.DecompressionMethod = CompressionMethods.Deflate; try { @@ -666,7 +665,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings ListingsProviderInfo providerInfo) { // Schedules direct requires that the client support compression and will return a 400 response without it - options.DecompressionMethod = CompressionMethod.Deflate; + options.DecompressionMethod = CompressionMethods.Deflate; try { diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index c159b60a91..07f8539c5e 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -84,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { CancellationToken = cancellationToken, Url = path, - DecompressionMethod = CompressionMethod.Gzip, + DecompressionMethod = CompressionMethods.Gzip, }, HttpMethod.Get).ConfigureAwait(false)) using (var stream = res.Content) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs b/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs index 222fed9d92..ba916af389 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using MediaBrowser.Common.Configuration; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 14b627f82e..6e903a18ef 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Globalization; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index f20f6140e5..b64fe8634c 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 33887bbfd2..7f63991d0c 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 419ec3635a..80ee1ee33a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Concurrent; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index a2d972d19c..25b2c674c5 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 56864ab116..57c5b75002 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Buffers; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 77669da39f..03ee5bfb65 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -8,8 +7,8 @@ using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 5354489f9c..4e4f1d7f60 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -8,8 +7,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.Library; using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 46c77e7b0e..f5dda79db3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 511af150bb..59451fccd2 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 8615183871..d63588bbd1 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -60,7 +59,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Url = url, CancellationToken = CancellationToken.None, BufferContent = false, - DecompressionMethod = CompressionMethod.None + DecompressionMethod = CompressionMethods.None }; foreach (var header in mediaSource.RequiredHttpHeaders) diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index dcec268017..1363eaf854 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -41,7 +41,6 @@ "User": "Gebruiker", "TvShows": "TV Programme", "System": "Stelsel", - "SubtitlesDownloadedForItem": "Ondertitels afgelaai vir {0}", "SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}", "StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.", "ServerNameNeedsToBeRestarted": "{0} moet herbegin word", diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index fa0e48bafc..7fffe7b83f 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -4,10 +4,10 @@ "Application": "تطبيق", "Artists": "الفنانين", "AuthenticationSucceededWithUserName": "{0} سجل الدخول بنجاح", - "Books": "كتب", + "Books": "الكتب", "CameraImageUploadedFrom": "صورة كاميرا جديدة تم رفعها من {0}", "Channels": "القنوات", - "ChapterNameValue": "فصل {0}", + "ChapterNameValue": "الفصل {0}", "Collections": "مجموعات", "DeviceOfflineWithName": "قُطِع الاتصال بـ{0}", "DeviceOnlineWithName": "{0} متصل", @@ -51,8 +51,8 @@ "NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي", "NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا", "NotificationOptionInstallationFailed": "فشل في التثبيت", - "NotificationOptionNewLibraryContent": "أُضِيفَ محتوى جديد", - "NotificationOptionPluginError": "فشل في الـPlugin", + "NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد", + "NotificationOptionPluginError": "فشل في البرنامج المضاف", "NotificationOptionPluginInstalled": "تم تثبيت الملحق", "NotificationOptionPluginUninstalled": "تمت إزالة الملحق", "NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق", @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "سيرفر Jellyfin قيد التشغيل . الرجاء المحاولة بعد قليل.", "SubtitleDownloadFailureForItem": "عملية إنزال الترجمة فشلت لـ{0}", "SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} الى {1}", - "SubtitlesDownloadedForItem": "تم تحميل الترجمات الى {0}", "Sync": "مزامنة", "System": "النظام", "TvShows": "البرامج التلفزيونية", diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index 8a1bbaa161..3fc7c7dc0f 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -1,8 +1,8 @@ { "Albums": "Албуми", - "AppDeviceValues": "Програма: {0}, устройство: {1}", + "AppDeviceValues": "Програма: {0}, Устройство: {1}", "Application": "Програма", - "Artists": "Изпълнители", + "Artists": "Артисти", "AuthenticationSucceededWithUserName": "{0} се удостовери успешно", "Books": "Книги", "CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}", @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.", "SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}", "SubtitleDownloadFailureFromForItem": "Поднадписите за {1} от {0} не можаха да се изтеглят", - "SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}", "Sync": "Синхронизиране", "System": "Система", "TvShows": "Телевизионни сериали", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} спря {1}", "ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека", "ValueSpecialEpisodeName": "Специални - {0}", - "VersionNumber": "Версия {0}" + "VersionNumber": "Версия {0}", + "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи поднадписи, на база конфигурацията за мета-данни.", + "TaskDownloadMissingSubtitles": "Изтегляне на липсващи поднадписи", + "TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.", + "TaskRefreshChannels": "Обновяване на Канали", + "TaskCleanTranscodeDescription": "Изтрива прекодирани файлове по-стари от един ден.", + "TaskCleanTranscode": "Изчиства директорията за прекодиране", + "TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.", + "TaskUpdatePlugins": "Актуализира добавките", + "TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.", + "TaskRefreshPeople": "Обновяване на участниците", + "TaskCleanLogsDescription": "Изтрива лог файлове по-стари от {0} дни.", + "TaskCleanLogs": "Изчисти директорията с логове", + "TaskRefreshLibraryDescription": "Сканира Вашата библиотека с медия за нови файлове и обновява мета-данните.", + "TaskRefreshLibrary": "Сканиране на библиотеката с медия", + "TaskRefreshChapterImagesDescription": "Създава иконки за видеа, които имат епизоди.", + "TaskRefreshChapterImages": "Извличане на изображения за епизода", + "TaskCleanCacheDescription": "Изтриване на ненужните от системата файлове.", + "TaskCleanCache": "Изчистване на Кеш-директорията", + "TasksChannelsCategory": "Интернет Канали", + "TasksApplicationCategory": "Приложение", + "TasksLibraryCategory": "Библиотека", + "TasksMaintenanceCategory": "Поддръжка" } diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index a7219a7254..ef7792356a 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -38,7 +38,6 @@ "TvShows": "টিভি শোগুলো", "System": "সিস্টেম", "Sync": "সিংক", - "SubtitlesDownloadedForItem": "{0} এর জন্য সাবটাইটেল ডাউনলোড করা হয়েছে", "SubtitleDownloadFailureFromForItem": "{2} থেকে {1} এর জন্য সাবটাইটেল ডাউনলোড ব্যর্থ", "StartupEmbyServerIsLoading": "জেলিফিন সার্ভার লোড হচ্ছে। দয়া করে একটু পরে আবার চেষ্টা করুন।", "Songs": "গানগুলো", diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index 44e7cf0ce6..2d82993675 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "El Servidor d'Jellyfin està carregant. Si et plau, prova de nou en breus.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}", - "SubtitlesDownloadedForItem": "Subtítols descarregats per a {0}", "Sync": "Sincronitzar", "System": "System", "TvShows": "Espectacles de TV", diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 86fbac3805..992bb9df37 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -5,7 +5,7 @@ "Artists": "Umělci", "AuthenticationSucceededWithUserName": "{0} úspěšně ověřen", "Books": "Knihy", - "CameraImageUploadedFrom": "Z {0} byla nahrána nová fotografie", + "CameraImageUploadedFrom": "Z {0} byla nahrána nová fotografie z fotoaparátu", "Channels": "Kanály", "ChapterNameValue": "Kapitola {0}", "Collections": "Kolekce", @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server je spouštěn. Zkuste to prosím v brzké době znovu.", "SubtitleDownloadFailureForItem": "Stahování titulků selhalo pro {0}", "SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo", - "SubtitlesDownloadedForItem": "Staženy titulky pro {0}", "Sync": "Synchronizace", "System": "Systém", "TvShows": "TV seriály", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} zastavil přehrávání {1}", "ValueHasBeenAddedToLibrary": "{0} byl přidán do vaší knihovny médií", "ValueSpecialEpisodeName": "Speciál - {0}", - "VersionNumber": "Verze {0}" + "VersionNumber": "Verze {0}", + "TaskDownloadMissingSubtitlesDescription": "Vyhledá na internetu chybějící titulky na základě nastavení metadat.", + "TaskDownloadMissingSubtitles": "Stáhnout chybějící titulky", + "TaskRefreshChannelsDescription": "Obnoví informace o internetových kanálech.", + "TaskRefreshChannels": "Obnovit kanály", + "TaskCleanTranscodeDescription": "Odstraní více než 1 den staré transkódované soubory.", + "TaskCleanTranscode": "Vyčistit adresář s transkódovaným obsahem", + "TaskUpdatePluginsDescription": "Stáhne a nainstaluje aktualizace zásuvných modulů, které mají nastavenou automatickou aktualizaci.", + "TaskUpdatePlugins": "Aktualizovat zásuvné moduly", + "TaskRefreshPeopleDescription": "Aktualizuje metadata umělců a režisérů ve Vaší knihovně médií.", + "TaskRefreshPeople": "Obnovit umělce", + "TaskCleanLogsDescription": "Odstraní soubory protokolu, které jsou starší více než {0} dní.", + "TaskCleanLogs": "Vyčistit adresář se souborem protokolu", + "TaskRefreshLibraryDescription": "Prohledá Vaši knihovnu médií zda neobsahuje nové soubory a obnoví metadatada.", + "TaskRefreshLibrary": "Prohledat knihovnu médií", + "TaskRefreshChapterImagesDescription": "Vytvoří náhledy videí, které obsahují kapitoly.", + "TaskRefreshChapterImages": "Extrahovat obrázky kapitol", + "TaskCleanCacheDescription": "Odstraní soubory mezipaměti, které systém již nebude potřebovat.", + "TaskCleanCache": "Vyčistit složku s mezipamětí", + "TasksChannelsCategory": "Internetové kanály", + "TasksApplicationCategory": "Aplikace", + "TasksLibraryCategory": "Knihovna", + "TasksMaintenanceCategory": "Údržba" } diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index c421db87d4..94437d237b 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server er i gang med at starte op. Prøv venligst igen om lidt.", "SubtitleDownloadFailureForItem": "Fejlet i download af undertekster for {0}", "SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke downloades fra {0} til {1}", - "SubtitlesDownloadedForItem": "Undertekster downloadet for {0}", "Sync": "Synk", "System": "System", "TvShows": "TV serier", diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index b0a3af0569..414430ff7b 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin-Server startet, bitte versuche es gleich noch einmal.", "SubtitleDownloadFailureForItem": "Download der Untertitel fehlgeschlagen für {0}", "SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden", - "SubtitlesDownloadedForItem": "Untertitel heruntergeladen für {0}", "Sync": "Synchronisation", "System": "System", "TvShows": "TV-Sendungen", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet", "ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt", "ValueSpecialEpisodeName": "Extra - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Meta Einstellungen.", + "TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter", + "TaskRefreshChannelsDescription": "Erneuere Internet Kanal Informationen.", + "TaskRefreshChannels": "Erneuere Kanäle", + "TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.", + "TaskCleanTranscode": "Lösche Transkodier Pfad", + "TaskUpdatePluginsDescription": "Läd Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.", + "TaskUpdatePlugins": "Update Plugins", + "TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.", + "TaskRefreshPeople": "Erneuere Schausteller", + "TaskCleanLogsDescription": "Lösche Log Datein die älter als {0} Tage sind.", + "TaskCleanLogs": "Lösche Log Pfad", + "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", + "TaskRefreshLibrary": "Scanne alle Bibliotheken", + "TaskRefreshChapterImagesDescription": "Kreiert Vorschaubilder für Videos welche Kapitel haben.", + "TaskRefreshChapterImages": "Extrahiert Kapitel-Bilder", + "TaskCleanCacheDescription": "Löscht Zwischenspeicherdatein die nicht länger von System gebraucht werden.", + "TaskCleanCache": "Leere Cache Pfad", + "TasksChannelsCategory": "Internet Kanäle", + "TasksApplicationCategory": "Anwendung", + "TasksLibraryCategory": "Bibliothek", + "TasksMaintenanceCategory": "Wartung" } diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 580b423302..53e2f58de8 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Ο Jellyfin Server φορτώνει. Παρακαλώ δοκιμάστε σε λίγο.", "SubtitleDownloadFailureForItem": "Οι υπότιτλοι απέτυχαν να κατέβουν για {0}", "SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}", - "SubtitlesDownloadedForItem": "Οι υπότιτλοι κατέβηκαν για {0}", "Sync": "Συγχρονισμός", "System": "Σύστημα", "TvShows": "Τηλεοπτικές Σειρές", diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 67d4068cf3..dc4f0b212a 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "Sync": "Sync", "System": "System", "TvShows": "TV Shows", diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index aa855ed21f..97a843160e 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -75,7 +75,6 @@ "Songs": "Songs", "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "Sync": "Sync", "System": "System", "TvShows": "TV Shows", @@ -92,5 +91,27 @@ "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueSpecialEpisodeName": "Special - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TasksMaintenanceCategory": "Maintenance", + "TasksLibraryCategory": "Library", + "TasksApplicationCategory": "Application", + "TasksChannelsCategory": "Internet Channels", + "TaskCleanCache": "Clean Cache Directory", + "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", + "TaskRefreshChapterImages": "Extract Chapter Images", + "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.", + "TaskRefreshLibrary": "Scan Media Library", + "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.", + "TaskCleanLogs": "Clean Log Directory", + "TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.", + "TaskRefreshPeople": "Refresh People", + "TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.", + "TaskUpdatePlugins": "Update Plugins", + "TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.", + "TaskCleanTranscode": "Clean Transcode Directory", + "TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.", + "TaskRefreshChannels": "Refresh Channels", + "TaskRefreshChannelsDescription": "Refreshes internet channel information.", + "TaskDownloadMissingSubtitles": "Download missing subtitles", + "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration." } diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index dc73ba6b34..154c72bc68 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}", - "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}", "Sync": "Sincronizar", "System": "Sistema", "TvShows": "Series de TV", diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 99fda7aa63..24fde8e62f 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "El servidor Jellyfin esta cargando. Por favor intente de nuevo dentro de poco.", "SubtitleDownloadFailureForItem": "Falló la descarga de subtítulos para {0}", "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtitulos desde {0} para {1}", - "SubtitlesDownloadedForItem": "Subtítulos descargados para {0}", "Sync": "Sincronizar", "System": "Sistema", "TvShows": "Programas de TV", diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 2dcc2c1c8a..de1baada84 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", "SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}", "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}", - "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}", "Sync": "Sincronizar", "System": "Sistema", "TvShows": "Programas de televisión", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", "ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia", "ValueSpecialEpisodeName": "Especial - {0}", - "VersionNumber": "Versión {0}" + "VersionNumber": "Versión {0}", + "TasksMaintenanceCategory": "Mantenimiento", + "TasksLibraryCategory": "Librería", + "TasksApplicationCategory": "Aplicación", + "TasksChannelsCategory": "Canales de internet", + "TaskCleanCache": "Eliminar archivos temporales", + "TaskCleanCacheDescription": "Elimina los archivos temporales que ya no son necesarios para el servidor.", + "TaskRefreshChapterImages": "Extraer imágenes de los capítulos", + "TaskRefreshChapterImagesDescription": "Crea las miniaturas de los vídeos que tengan capítulos.", + "TaskRefreshLibrary": "Escanear la biblioteca", + "TaskRefreshLibraryDescription": "Añade los archivos que se hayan añadido a la biblioteca y actualiza las etiquetas de los ya presentes.", + "TaskCleanLogs": "Limpiar registros", + "TaskCleanLogsDescription": "Elimina los archivos de registro que tengan más de {0} días.", + "TaskRefreshPeople": "Actualizar personas", + "TaskRefreshPeopleDescription": "Actualiza las etiquetas de los intérpretes y directores presentes en tus bibliotecas.", + "TaskUpdatePlugins": "Actualizar extensiones", + "TaskUpdatePluginsDescription": "Actualiza las extensiones que están configuradas para actualizarse automáticamente.", + "TaskCleanTranscode": "Limpiar las transcodificaciones", + "TaskCleanTranscodeDescription": "Elimina los archivos temporales de transcodificación anteriores a un día de antigüedad.", + "TaskRefreshChannels": "Actualizar canales", + "TaskRefreshChannelsDescription": "Actualiza la información de los canales de internet.", + "TaskDownloadMissingSubtitles": "Descargar los subtítulos que faltan", + "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten en el contenido de tus bibliotecas, basándose en la configuración de los metadatos." } diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index faa658ed58..45e74b8ebe 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -1,56 +1,56 @@ { - "Albums": "آلبوم ها", + "Albums": "آلبوم‌ها", "AppDeviceValues": "برنامه: {0} ، دستگاه: {1}", "Application": "برنامه", "Artists": "هنرمندان", "AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد", - "Books": "کتاب ها", - "CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده {0}", - "Channels": "کانال ها", - "ChapterNameValue": "فصل {0}", - "Collections": "کلکسیون ها", + "Books": "کتاب‌ها", + "CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}", + "Channels": "کانال‌ها", + "ChapterNameValue": "قسمت {0}", + "Collections": "مجموعه‌ها", "DeviceOfflineWithName": "ارتباط {0} قطع شد", - "DeviceOnlineWithName": "{0} متصل شده", + "DeviceOnlineWithName": "{0} متصل شد", "FailedLoginAttemptWithUserName": "تلاش برای ورود از {0} ناموفق بود", - "Favorites": "مورد علاقه ها", - "Folders": "پوشه ها", + "Favorites": "مورد علاقه‌ها", + "Folders": "پوشه‌ها", "Genres": "ژانرها", "HeaderAlbumArtists": "هنرمندان آلبوم", "HeaderCameraUploads": "آپلودهای دوربین", "HeaderContinueWatching": "ادامه تماشا", - "HeaderFavoriteAlbums": "آلبوم های مورد علاقه", + "HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه", "HeaderFavoriteArtists": "هنرمندان مورد علاقه", - "HeaderFavoriteEpisodes": "قسمت های مورد علاقه", - "HeaderFavoriteShows": "سریال های مورد علاقه", - "HeaderFavoriteSongs": "آهنگ های مورد علاقه", + "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه", + "HeaderFavoriteShows": "سریال‌های مورد علاقه", + "HeaderFavoriteSongs": "آهنگ‌های مورد علاقه", "HeaderLiveTV": "پخش زنده تلویزیون", - "HeaderNextUp": "بعدی چیه", - "HeaderRecordingGroups": "گروه های ضبط", + "HeaderNextUp": "قسمت بعدی", + "HeaderRecordingGroups": "گروه‌های ضبط", "HomeVideos": "ویدیوهای خانگی", "Inherit": "به ارث برده", "ItemAddedWithName": "{0} به کتابخانه افزوده شد", "ItemRemovedWithName": "{0} از کتابخانه حذف شد", "LabelIpAddressValue": "آدرس آی پی: {0}", "LabelRunningTimeValue": "زمان اجرا: {0}", - "Latest": "آخرین", + "Latest": "جدیدترین‌ها", "MessageApplicationUpdated": "سرور Jellyfin بروزرسانی شد", - "MessageApplicationUpdatedTo": "سرور جلیفین آپدیت شده به نسخه {0}", + "MessageApplicationUpdatedTo": "سرور Jellyfin به نسخه {0} بروزرسانی شد", "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", "MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد", - "MixedContent": "محتوای درهم", - "Movies": "فیلم های سینمایی", + "MixedContent": "محتوای مخلوط", + "Movies": "فیلم‌ها", "Music": "موسیقی", "MusicVideos": "موزیک ویدیوها", - "NameInstallFailed": "{0} نصب با مشکل مواجه شده", + "NameInstallFailed": "{0} نصب با مشکل مواجه شد", "NameSeasonNumber": "فصل {0}", - "NameSeasonUnknown": "فصل های ناشناخته", - "NewVersionIsAvailable": "یک نسخه جدید جلیفین برای بروزرسانی آماده میباشد.", + "NameSeasonUnknown": "فصل ناشناخته", + "NewVersionIsAvailable": "یک نسخه جدید Jellyfin برای بروزرسانی آماده می‌باشد.", "NotificationOptionApplicationUpdateAvailable": "بروزرسانی برنامه موجود است", "NotificationOptionApplicationUpdateInstalled": "بروزرسانی برنامه نصب شد", "NotificationOptionAudioPlayback": "پخش صدا آغاز شد", "NotificationOptionAudioPlaybackStopped": "پخش صدا متوقف شد", "NotificationOptionCameraImageUploaded": "تصاویر دوربین آپلود شد", - "NotificationOptionInstallationFailed": "شکست نصب", + "NotificationOptionInstallationFailed": "نصب شکست خورد", "NotificationOptionNewLibraryContent": "محتوای جدید افزوده شد", "NotificationOptionPluginError": "خرابی افزونه", "NotificationOptionPluginInstalled": "افزونه نصب شد", @@ -58,40 +58,39 @@ "NotificationOptionPluginUpdateInstalled": "بروزرسانی افزونه نصب شد", "NotificationOptionServerRestartRequired": "شروع مجدد سرور نیاز است", "NotificationOptionTaskFailed": "شکست وظیفه برنامه ریزی شده", - "NotificationOptionUserLockedOut": "کاربر از سیستم خارج شد", + "NotificationOptionUserLockedOut": "کاربر قفل شد", "NotificationOptionVideoPlayback": "پخش ویدیو آغاز شد", "NotificationOptionVideoPlaybackStopped": "پخش ویدیو متوقف شد", - "Photos": "عکس ها", - "Playlists": "لیست های پخش", + "Photos": "عکس‌ها", + "Playlists": "لیست‌های پخش", "Plugin": "افزونه", "PluginInstalledWithName": "{0} نصب شد", "PluginUninstalledWithName": "{0} حذف شد", "PluginUpdatedWithName": "{0} آپدیت شد", "ProviderValue": "ارائه دهنده: {0}", - "ScheduledTaskFailedWithName": "{0} ناموفق بود", + "ScheduledTaskFailedWithName": "{0} شکست خورد", "ScheduledTaskStartedWithName": "{0} شروع شد", - "ServerNameNeedsToBeRestarted": "{0} احتیاج به راه اندازی مجدد", - "Shows": "سریال ها", - "Songs": "آهنگ ها", + "ServerNameNeedsToBeRestarted": "{0} نیاز به راه اندازی مجدد دارد", + "Shows": "سریال‌ها", + "Songs": "موسیقی‌ها", "StartupEmbyServerIsLoading": "سرور Jellyfin در حال بارگیری است. لطفا کمی بعد دوباره تلاش کنید.", "SubtitleDownloadFailureForItem": "دانلود زیرنویس برای {0} ناموفق بود", - "SubtitleDownloadFailureFromForItem": "زیرنویس برای دانلود با مشکل مواجه شده از {0} برای {1}", - "SubtitlesDownloadedForItem": "زیرنویس {0} دانلود شد", - "Sync": "همگامسازی", + "SubtitleDownloadFailureFromForItem": "بارگیری زیرنویس برای {1} از {0} شکست خورد", + "Sync": "همگام‌سازی", "System": "سیستم", - "TvShows": "سریال های تلویزیونی", + "TvShows": "سریال‌های تلویزیونی", "User": "کاربر", "UserCreatedWithName": "کاربر {0} ایجاد شد", "UserDeletedWithName": "کاربر {0} حذف شد", - "UserDownloadingItemWithValues": "{0} در حال دانلود است {1}", - "UserLockedOutWithName": "کاربر {0} از سیستم خارج شد", + "UserDownloadingItemWithValues": "{0} در حال بارگیری {1} می‌باشد", + "UserLockedOutWithName": "کاربر {0} قفل شده است", "UserOfflineFromDevice": "ارتباط {0} از {1} قطع شد", - "UserOnlineFromDevice": "{0}از {1} آنلاین میباشد", - "UserPasswordChangedWithName": "رمز برای کاربر {0} تغییر یافت", + "UserOnlineFromDevice": "{0} از {1} آنلاین می‌باشد", + "UserPasswordChangedWithName": "گذرواژه برای کاربر {0} تغییر کرد", "UserPolicyUpdatedWithName": "سیاست کاربری برای {0} بروزرسانی شد", - "UserStartedPlayingItemWithValues": "{0} شروع به پخش {1} کرد", - "UserStoppedPlayingItemWithValues": "{0} پخش {1} را متوقف کرد", - "ValueHasBeenAddedToLibrary": "{0} اضافه شده به کتابخانه رسانه شما", - "ValueSpecialEpisodeName": "ویژه- {0}", + "UserStartedPlayingItemWithValues": "{0} در حال پخش {1} بر روی {2} است", + "UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند", + "ValueHasBeenAddedToLibrary": "{0} به کتابخانه‌ی رسانه‌ی شما افزوده شد", + "ValueSpecialEpisodeName": "ویژه - {0}", "VersionNumber": "نسخه {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index a38103d25a..bf5fc05c4d 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -69,7 +69,6 @@ "UserCreatedWithName": "Luotiin käyttäjä {0}", "TvShows": "TV-Ohjelmat", "Sync": "Synkronoi", - "SubtitlesDownloadedForItem": "Tekstitys ladattu {0}", "SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}", "StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Kokeile hetken kuluttua uudelleen.", "Songs": "Kappaleet", diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json index 66db059d97..86a6d18367 100644 --- a/Emby.Server.Implementations/Localization/Core/fil.json +++ b/Emby.Server.Implementations/Localization/Core/fil.json @@ -16,7 +16,6 @@ "TvShows": "Pelikula", "System": "Sistema", "Sync": "Pag-sync", - "SubtitlesDownloadedForItem": "Naidownload na ang subtitles {0}", "SubtitleDownloadFailureFromForItem": "Hindi naidownload ang subtitles {0} para sa {1}", "StartupEmbyServerIsLoading": "Nagloload ang Jellyfin Server. Sandaling maghintay.", "Songs": "Kanta", diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index 4b4db39a8b..2c9dae6a17 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}", - "SubtitlesDownloadedForItem": "Les sous-titres de {0} ont été téléchargés", "Sync": "Synchroniser", "System": "Système", "TvShows": "Séries Télé", @@ -93,5 +92,7 @@ "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque", "ValueSpecialEpisodeName": "Spécial - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TasksLibraryCategory": "Bibliothèque", + "TasksMaintenanceCategory": "Entretien" } diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 7dfee10854..88a7ac1906 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.", "SubtitleDownloadFailureForItem": "Le téléchargement des sous-titres pour {0} a échoué.", "SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}", - "SubtitlesDownloadedForItem": "Les sous-titres de {0} ont été téléchargés", "Sync": "Synchroniser", "System": "Système", "TvShows": "Séries Télé", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque", "ValueSpecialEpisodeName": "Spécial - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TasksChannelsCategory": "Chaines en ligne", + "TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.", + "TaskDownloadMissingSubtitles": "Télécharge les sous-titres manquant", + "TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.", + "TaskRefreshChannels": "Rafraîchit les chaines", + "TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.", + "TaskCleanTranscode": "Nettoie les dossier des transcodages", + "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des plugins configurés pour être mis à jour automatiquement.", + "TaskUpdatePlugins": "Mettre à jour les plugins", + "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et directeurs dans votre bibliothèque.", + "TaskRefreshPeople": "Rafraîchit les acteurs", + "TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.", + "TaskCleanLogs": "Nettoie le répertoire des journaux", + "TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.", + "TaskRefreshLibrary": "Scanne toute les Bibliothèques", + "TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.", + "TaskRefreshChapterImages": "Extrait les images de chapitre", + "TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.", + "TaskCleanCache": "Vider le répertoire cache", + "TasksApplicationCategory": "Application", + "TasksLibraryCategory": "Bibliothèque", + "TasksMaintenanceCategory": "Maintenance" } diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json index 69c1574014..9611e33f57 100644 --- a/Emby.Server.Implementations/Localization/Core/gsw.json +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server ladt. Bitte grad noeinisch probiere.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Ondertetle vo {0} för {1} hend ned chönne abeglade wärde", - "SubtitlesDownloadedForItem": "Ondertetle abeglade för {0}", "Sync": "Synchronisation", "System": "System", "TvShows": "Färnsehserie", diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 5618719dd3..1ce8b08a0a 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "שרת Jellyfin בהליכי טעינה. אנא נסה שנית בעוד זמן קצר.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "Sync": "סנכרן", "System": "System", "TvShows": "סדרות טלוויזיה", diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index f284b3cd98..6947178d7a 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server se učitava. Pokušajte ponovo kasnije.", "SubtitleDownloadFailureForItem": "Titlovi prijevoda nisu preuzeti za {0}", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Titlovi prijevoda preuzeti za {0}", "Sync": "Sink.", "System": "Sistem", "TvShows": "TV Shows", diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index 6017aa7f90..8f1288a559 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}", - "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}", "Sync": "Szinkronizál", "System": "Rendszer", "TvShows": "TV műsorok", diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json index 68fffbf0ad..eabdb9138a 100644 --- a/Emby.Server.Implementations/Localization/Core/id.json +++ b/Emby.Server.Implementations/Localization/Core/id.json @@ -54,7 +54,6 @@ "User": "Pengguna", "System": "Sistem", "Sync": "Sinkron", - "SubtitlesDownloadedForItem": "Talop telah diunduh untuk {0}", "Shows": "Tayangan", "ServerNameNeedsToBeRestarted": "{0} perlu dimuat ulang", "ScheduledTaskStartedWithName": "{0} dimulai", diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json index 3490a73022..ef2a57e8e8 100644 --- a/Emby.Server.Implementations/Localization/Core/is.json +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -86,7 +86,6 @@ "UserOfflineFromDevice": "{0} hefur aftengst frá {1}", "UserLockedOutWithName": "Notanda {0} hefur verið hindraður aðgangur", "UserDownloadingItemWithValues": "{0} Hleður niður {1}", - "SubtitlesDownloadedForItem": "Skjátextum halað niður fyrir {0}", "SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}", "ProviderValue": "Veitandi: {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón", diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 395924af4b..0758bbe9ce 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -5,7 +5,7 @@ "Artists": "Artisti", "AuthenticationSucceededWithUserName": "{0} autenticato con successo", "Books": "Libri", - "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera da {0}", + "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera dal device {0}", "Channels": "Canali", "ChapterNameValue": "Capitolo {0}", "Collections": "Collezioni", @@ -15,7 +15,7 @@ "Favorites": "Preferiti", "Folders": "Cartelle", "Genres": "Generi", - "HeaderAlbumArtists": "Artisti dell' Album", + "HeaderAlbumArtists": "Artisti degli Album", "HeaderCameraUploads": "Caricamenti Fotocamera", "HeaderContinueWatching": "Continua a guardare", "HeaderFavoriteAlbums": "Album Preferiti", @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.", "SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}", "SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}", - "SubtitlesDownloadedForItem": "Sottotitoli scaricati per {0}", "Sync": "Sincronizza", "System": "Sistema", "TvShows": "Serie TV", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}", "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", "ValueSpecialEpisodeName": "Speciale - {0}", - "VersionNumber": "Versione {0}" + "VersionNumber": "Versione {0}", + "TaskRefreshChannelsDescription": "Aggiorna le informazioni dei canali Internet.", + "TaskDownloadMissingSubtitlesDescription": "Cerca su internet i sottotitoli mancanti basandosi sulle configurazioni dei metadati.", + "TaskDownloadMissingSubtitles": "Scarica i sottotitoli mancanti", + "TaskRefreshChannels": "Aggiorna i canali", + "TaskCleanTranscodeDescription": "Cancella i file di transcode più vecchi di un giorno.", + "TaskCleanTranscode": "Svuota la cartella del transcoding", + "TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin che sono stati configurati per essere aggiornati contemporaneamente.", + "TaskUpdatePlugins": "Aggiorna i Plugin", + "TaskRefreshPeopleDescription": "Aggiorna i metadati per gli attori e registi nella tua libreria multimediale.", + "TaskRefreshPeople": "Aggiorna persone", + "TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.", + "TaskCleanLogs": "Pulisci la cartella dei log", + "TaskRefreshLibraryDescription": "Analizza la tua libreria multimediale per nuovi file e rinnova i metadati.", + "TaskRefreshLibrary": "Analizza la libreria dei contenuti multimediali", + "TaskRefreshChapterImagesDescription": "Crea le thumbnail per i video che hanno capitoli.", + "TaskRefreshChapterImages": "Estrai immagini capitolo", + "TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.", + "TaskCleanCache": "Pulisci la directory della cache", + "TasksChannelsCategory": "Canali su Internet", + "TasksApplicationCategory": "Applicazione", + "TasksLibraryCategory": "Libreria", + "TasksMaintenanceCategory": "Manutenzione" } diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index 4aa0637c59..1ec4a06689 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -75,7 +75,6 @@ "Songs": "曲", "StartupEmbyServerIsLoading": "Jellyfin Server は現在読み込み中です。しばらくしてからもう一度お試しください。", "SubtitleDownloadFailureFromForItem": "{0} から {1}の字幕のダウンロードに失敗しました", - "SubtitlesDownloadedForItem": "{0} の字幕がダウンロードされました", "Sync": "同期", "System": "システム", "TvShows": "テレビ番組", diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index cbee711551..5618ff4a8f 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server júktelýde. Áreketti kóp uzamaı qaıtalańyz.", "SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз", "SubtitleDownloadFailureFromForItem": "{1} úshin sýbtıtrlerdi {0} kózinen júktep alý sátsiz", - "SubtitlesDownloadedForItem": "{0} úshin sýbtıtrler júktelip alyndy", "Sync": "Úndestirý", "System": "Júıe", "TvShows": "TD-kórsetimder", diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index 0320a0a1b7..c4b22901ef 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin 서버를 불러오고 있습니다. 잠시 후에 다시 시도하십시오.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "{0}에서 {1} 자막 다운로드에 실패했습니다", - "SubtitlesDownloadedForItem": "{0} 자막 다운로드 완료", "Sync": "동기화", "System": "시스템", "TvShows": "TV 쇼", diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index e8e1b7740e..01a740187d 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server kraunasi. Netrukus pabandykite dar kartą.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "{1} subtitrai buvo nesėkmingai parsiųsti iš {0}", - "SubtitlesDownloadedForItem": "{0} subtitrai parsiųsti", "Sync": "Sinchronizuoti", "System": "System", "TvShows": "TV Serialai", diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json index 8b8d46b2e0..dbcf172871 100644 --- a/Emby.Server.Implementations/Localization/Core/lv.json +++ b/Emby.Server.Implementations/Localization/Core/lv.json @@ -31,7 +31,6 @@ "TvShows": "TV Raidījumi", "Sync": "Sinhronizācija", "System": "Sistēma", - "SubtitlesDownloadedForItem": "Subtitri lejupielādēti priekš {0}", "StartupEmbyServerIsLoading": "Jellyfin Serveris lādējas. Lūdzu mēģiniet vēlreiz pēc brīža.", "Songs": "Dziesmas", "Shows": "Raidījumi", @@ -92,5 +91,27 @@ "HeaderFavoriteShows": "Raidījumu Favorīti", "HeaderFavoriteEpisodes": "Episožu Favorīti", "HeaderFavoriteArtists": "Izpildītāju Favorīti", - "HeaderFavoriteAlbums": "Albumu Favorīti" + "HeaderFavoriteAlbums": "Albumu Favorīti", + "TaskCleanCacheDescription": "Nodzēš keša datnes, kas vairs nav sistēmai vajadzīgas.", + "TaskRefreshChapterImages": "Izvilkt Nodaļu Attēlus", + "TasksApplicationCategory": "Lietotne", + "TasksLibraryCategory": "Bibliotēka", + "TaskDownloadMissingSubtitlesDescription": "Internetā meklē trūkstošus subtitrus pēc metadatu uzstādījumiem.", + "TaskDownloadMissingSubtitles": "Lejupielādēt trūkstošus subtitrus", + "TaskRefreshChannelsDescription": "Atjauno interneta kanālu informāciju.", + "TaskRefreshChannels": "Atjaunot Kanālus", + "TaskCleanTranscodeDescription": "Izdzēš trans-kodēšanas datnes, kas ir vecākas par vienu dienu.", + "TaskCleanTranscode": "Iztīrīt Trans-kodēšanas Mapi", + "TaskUpdatePluginsDescription": "Lejupielādē un uzstāda atjauninājumus paplašinājumiem, kam ir uzstādīta automātiskā atjaunināšana.", + "TaskUpdatePlugins": "Atjaunot Paplašinājumus", + "TaskRefreshPeopleDescription": "Atjauno metadatus priekš aktieriem un direktoriem tavā mediju bibliotēkā.", + "TaskRefreshPeople": "Atjaunot Cilvēkus", + "TaskCleanLogsDescription": "Nodzēš log datnes, kas ir vairāk par {0} dienām vecas.", + "TaskCleanLogs": "Iztīrīt Logdatņu Mapi", + "TaskRefreshLibraryDescription": "Skenē tavas mediju bibliotēkas priekš jaunām datnēm un atjauno metadatus.", + "TaskRefreshLibrary": "Skanēt Mediju Bibliotēku", + "TaskRefreshChapterImagesDescription": "Izveido sīktēlus priekš video ar sadaļām.", + "TaskCleanCache": "Iztīrīt Kešošanas Mapi", + "TasksChannelsCategory": "Interneta Kanāli", + "TasksMaintenanceCategory": "Apkope" } diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json index 684a97aade..8df137302f 100644 --- a/Emby.Server.Implementations/Localization/Core/mk.json +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -86,7 +86,6 @@ "TvShows": "ТВ Серии", "System": "Систем", "Sync": "Синхронизација", - "SubtitlesDownloadedForItem": "Спуштање превод за {0}", "SubtitleDownloadFailureFromForItem": "Преводот неуспешно се спушти од {0} за {1}", "StartupEmbyServerIsLoading": "Jellyfin Server се пушта. Ве молиме причекајте.", "Songs": "Песни", diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 1d86257f84..79d078d4a8 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "Sync": "Sync", "System": "Sistem", "TvShows": "TV Shows", diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index f9fa1b68cd..e523ae90ba 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.", "SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}", "SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}", - "SubtitlesDownloadedForItem": "Undertekster lastet ned for {0}", "Sync": "Synkroniser", "System": "System", "TvShows": "TV-serier", @@ -93,5 +92,9 @@ "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt", "ValueSpecialEpisodeName": "Spesialepisode - {0}", - "VersionNumber": "Versjon {0}" + "VersionNumber": "Versjon {0}", + "TasksChannelsCategory": "Internett kanaler", + "TasksApplicationCategory": "Applikasjon", + "TasksLibraryCategory": "Bibliotek", + "TasksMaintenanceCategory": "Vedlikehold" } diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index e22f95ab46..bc36cbdd37 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden, probeer het later opnieuw.", "SubtitleDownloadFailureForItem": "Downloaden van ondertiteling voor {0} is mislukt", "SubtitleDownloadFailureFromForItem": "Ondertitels konden niet gedownload worden van {0} voor {1}", - "SubtitlesDownloadedForItem": "Ondertiteling voor {0} is gedownload", "Sync": "Synchronisatie", "System": "Systeem", "TvShows": "TV-series", diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index e72f1a2621..e9d9bbf2e1 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Trwa wczytywanie serwera Jellyfin. Spróbuj ponownie za chwilę.", "SubtitleDownloadFailureForItem": "Pobieranie napisów dla {0} zakończone niepowodzeniem", "SubtitleDownloadFailureFromForItem": "Nieudane pobieranie napisów z {0} dla {1}", - "SubtitlesDownloadedForItem": "Pobrano napisy dla {0}", "Sync": "Synchronizacja", "System": "System", "TvShows": "Seriale", diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 41a389e3b1..3a69b6d7a5 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "O Servidor Jellyfin está carregando. Por favor, tente novamente mais tarde.", "SubtitleDownloadFailureForItem": "Download de legendas falhou para {0}", "SubtitleDownloadFailureFromForItem": "Houve um problema ao baixar as legendas de {0} para {1}", - "SubtitlesDownloadedForItem": "Legendas baixadas para {0}", "Sync": "Sincronizar", "System": "Sistema", "TvShows": "Séries", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} parou de reproduzir {1} em {2}", "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca de mídia", "ValueSpecialEpisodeName": "Especial - {0}", - "VersionNumber": "Versão {0}" + "VersionNumber": "Versão {0}", + "TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas faltando baseado na configuração de metadados.", + "TaskDownloadMissingSubtitles": "Baixar legendas que estão faltando", + "TaskRefreshChannelsDescription": "Atualizar informação de canais da internet .", + "TaskRefreshChannels": "Atualizar Canais", + "TaskCleanTranscodeDescription": "Deletar arquivos de transcodificação com mais de um dia de criação.", + "TaskCleanTranscode": "Limpar pasta de transcodificação", + "TaskUpdatePluginsDescription": "Baixa e instala atualizações para plugins que estão configurados para atualizar automaticamente.", + "TaskUpdatePlugins": "Atualizar Plugins", + "TaskRefreshPeopleDescription": "Atualiza metadados para atores e diretores na sua biblioteca de mídia.", + "TaskRefreshPeople": "Atualizar pessoas", + "TaskCleanLogsDescription": "Deletar arquivos temporários com mais de {0} dias.", + "TaskCleanLogs": "Limpar pasta de logs", + "TaskRefreshLibraryDescription": "Escaneie a sua biblioteca de mídia para arquivos novos e atualize os metadados.", + "TaskRefreshLibrary": "Escanear a Biblioteca de Mídia", + "TaskRefreshChapterImagesDescription": "Criar miniaturas para vídeos que tem capítulos.", + "TaskRefreshChapterImages": "Extrair imagens dos capítulos", + "TaskCleanCacheDescription": "Deletar arquivos temporários que não são mais necessários para o sistema.", + "TaskCleanCache": "Limpar Arquivos Temporários", + "TasksChannelsCategory": "Canais da Internet", + "TasksApplicationCategory": "Aplicativo", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Manutenção" } diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index b12d391c17..ebf35c4920 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "O servidor Jellyfin está a iniciar. Tente novamente mais tarde.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas a partir de {0} para {1}", - "SubtitlesDownloadedForItem": "Transferidas legendas para {0}", "Sync": "Sincronização", "System": "Sistema", "TvShows": "Programas TV", diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index 9ee3c37a8a..3d5f7cab24 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -31,7 +31,6 @@ "User": "Utilizador", "TvShows": "Séries", "System": "Sistema", - "SubtitlesDownloadedForItem": "Legendas transferidas para {0}", "SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas de {0} para {1}", "StartupEmbyServerIsLoading": "O servidor Jellyfin está a iniciar. Tente novamente dentro de momentos.", "ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciado", diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json index 71bffffc6b..699dd26daa 100644 --- a/Emby.Server.Implementations/Localization/Core/ro.json +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -17,7 +17,6 @@ "TvShows": "Spectacole TV", "System": "Sistem", "Sync": "Sincronizare", - "SubtitlesDownloadedForItem": "Subtitrari descarcate pentru {0}", "SubtitleDownloadFailureFromForItem": "Subtitrările nu au putut fi descărcate de la {0} pentru {1}", "StartupEmbyServerIsLoading": "Se încarcă serverul Jellyfin. Încercați din nou în scurt timp.", "Songs": "Melodii", @@ -92,5 +91,27 @@ "Artists": "Artiști", "Application": "Aplicație", "AppDeviceValues": "Aplicație: {0}, Dispozitiv: {1}", - "Albums": "Albume" + "Albums": "Albume", + "TaskDownloadMissingSubtitlesDescription": "Caută pe internet subtitrările lipsă pe baza configurației metadatelor.", + "TaskDownloadMissingSubtitles": "Descarcă subtitrările lipsă", + "TaskRefreshChannelsDescription": "Actualizează informațiile despre canalul de internet.", + "TaskRefreshChannels": "Actualizează canale", + "TaskCleanTranscodeDescription": "Șterge fișierele de transcodare mai vechi de o zi.", + "TaskCleanTranscode": "Curățați directorul de transcodare", + "TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru pluginuri care sunt configurate să se actualizeze automat.", + "TaskUpdatePlugins": "Actualizați plugin-uri", + "TaskRefreshPeopleDescription": "Actualizează metadatele pentru actori și regizori din biblioteca media.", + "TaskRefreshPeople": "Actualizează oamenii", + "TaskCleanLogsDescription": "Șterge fișierele jurnal care au mai mult de {0} zile.", + "TaskCleanLogs": "Curățare director jurnal", + "TaskRefreshLibraryDescription": "Scanează biblioteca media pentru fișiere noi și reîmprospătează metadatele.", + "TaskRefreshLibrary": "Scanează Biblioteca Media", + "TaskRefreshChapterImagesDescription": "Creează miniaturi pentru videourile care au capitole.", + "TaskRefreshChapterImages": "Extrage Imaginile de Capitol", + "TaskCleanCacheDescription": "Șterge fișierele cache care nu mai sunt necesare sistemului.", + "TaskCleanCache": "Curățați directorul cache", + "TasksChannelsCategory": "Canale de pe Internet", + "TasksApplicationCategory": "Aplicație", + "TasksLibraryCategory": "Librărie", + "TasksMaintenanceCategory": "Mentenanță" } diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 7cf957a945..c46aa5c30d 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.", "SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить", "SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}", - "SubtitlesDownloadedForItem": "Субтитры к {0} загружены", "Sync": "Синхро", "System": "Система", "TvShows": "ТВ", diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index 1988bda526..0ee652637c 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server sa spúšťa. Prosím, skúste to o chvíľu znova.", "SubtitleDownloadFailureForItem": "Sťahovanie titulkov pre {0} zlyhalo", "SubtitleDownloadFailureFromForItem": "Sťahovanie titulkov z {0} pre {1} zlyhalo", - "SubtitlesDownloadedForItem": "Titulky pre {0} stiahnuté", "Sync": "Synchronizácia", "System": "Systém", "TvShows": "TV seriály", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} ukončil prehrávanie {1} na {2}", "ValueHasBeenAddedToLibrary": "{0} bol pridané do vašej knižnice médií", "ValueSpecialEpisodeName": "Špeciál - {0}", - "VersionNumber": "Verzia {0}" + "VersionNumber": "Verzia {0}", + "TaskDownloadMissingSubtitlesDescription": "Vyhľadá na internete chýbajúce titulky podľa toho, ako sú nakonfigurované metadáta.", + "TaskDownloadMissingSubtitles": "Stiahnuť chýbajúce titulky", + "TaskRefreshChannelsDescription": "Obnoví informácie o internetových kanáloch.", + "TaskRefreshChannels": "Obnoviť kanály", + "TaskCleanTranscodeDescription": "Vymaže súbory transkódovania, ktoré sú staršie ako jeden deň.", + "TaskCleanTranscode": "Vyčistiť priečinok pre transkódovanie", + "TaskUpdatePluginsDescription": "Stiahne a nainštaluje aktualizácie pre zásuvné moduly, ktoré sú nastavené tak, aby sa aktualizovali automaticky.", + "TaskUpdatePlugins": "Aktualizovať zásuvné moduly", + "TaskRefreshPeopleDescription": "Aktualizuje metadáta pre hercov a režisérov vo vašej mediálnej knižnici.", + "TaskRefreshPeople": "Obnoviť osoby", + "TaskCleanLogsDescription": "Vymaže log súbory, ktoré su staršie ako {0} deň/dni/dní.", + "TaskCleanLogs": "Vyčistiť priečinok s logmi", + "TaskRefreshLibraryDescription": "Hľadá vo vašej mediálnej knižnici nové súbory a obnovuje metadáta.", + "TaskRefreshLibrary": "Prehľadávať knižnicu medií", + "TaskRefreshChapterImagesDescription": "Vytvorí náhľady pre videá, ktoré majú kapitoly.", + "TaskRefreshChapterImages": "Extrahovať obrázky kapitol", + "TaskCleanCacheDescription": "Vymaže cache súbory, ktoré nie sú už potrebné pre systém.", + "TaskCleanCache": "Vyčistiť Cache priečinok", + "TasksChannelsCategory": "Internetové kanály", + "TasksApplicationCategory": "Aplikácia", + "TasksLibraryCategory": "Knižnica", + "TasksMaintenanceCategory": "Údržba" } diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index 0fc8379def..b60dd33bd5 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server se nalaga. Poskusi ponovno kasneje.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Neuspešen prenos podnapisov iz {0} za {1}", - "SubtitlesDownloadedForItem": "Podnapisi preneseni za {0}", "Sync": "Sinhroniziraj", "System": "System", "TvShows": "TV serije", diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index da0088991b..9d3445ba6e 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -17,7 +17,6 @@ "TvShows": "ТВ серије", "System": "Систем", "Sync": "Усклади", - "SubtitlesDownloadedForItem": "Титлови преузети за {0}", "SubtitleDownloadFailureFromForItem": "Неуспело преузимање титлова за {1} са {0}", "StartupEmbyServerIsLoading": "Џелифин сервер се подиже. Покушајте поново убрзо.", "Songs": "Песме", diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index b2934545d3..96891f9945 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Server arbetar. Pröva igen snart.", "SubtitleDownloadFailureForItem": "Nerladdning av undertexter för {0} misslyckades", "SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} för {1}", - "SubtitlesDownloadedForItem": "Undertexter har laddats ner till {0}", "Sync": "Synk", "System": "System", "TvShows": "TV-serier", diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index d3552225b4..1d13b03541 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi", - "SubtitlesDownloadedForItem": "{0} için altyazılar indirildi", "Sync": "Eşitle", "System": "Sistem", "TvShows": "Diziler", diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 85ebce0f5d..9d23f60cc5 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -3,11 +3,11 @@ "AppDeviceValues": "应用: {0}, 设备: {1}", "Application": "应用程序", "Artists": "艺术家", - "AuthenticationSucceededWithUserName": "{0} 验证成功", + "AuthenticationSucceededWithUserName": "成功验证{0} ", "Books": "书籍", "CameraImageUploadedFrom": "新的相机图像已从 {0} 上传", "Channels": "频道", - "ChapterNameValue": "第 {0} 章", + "ChapterNameValue": "第 {0} 集", "Collections": "合集", "DeviceOfflineWithName": "{0} 已断开", "DeviceOnlineWithName": "{0} 已连接", @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin 服务器加载中。请稍后再试。", "SubtitleDownloadFailureForItem": "为 {0} 下载字幕失败", "SubtitleDownloadFailureFromForItem": "无法从 {0} 下载 {1} 的字幕", - "SubtitlesDownloadedForItem": "已为 {0} 下载了字幕", "Sync": "同步", "System": "系统", "TvShows": "电视剧", @@ -93,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}", "ValueHasBeenAddedToLibrary": "{0} 已添加至您的媒体库中", "ValueSpecialEpisodeName": "特典 - {0}", - "VersionNumber": "版本 {0}" + "VersionNumber": "版本 {0}", + "TaskUpdatePluginsDescription": "为已设置为自动更新的插件下载和安装更新。", + "TaskRefreshPeople": "刷新人员", + "TasksChannelsCategory": "互联网频道", + "TasksLibraryCategory": "媒体库", + "TaskDownloadMissingSubtitlesDescription": "根据元数据设置在互联网上搜索缺少的字幕。", + "TaskDownloadMissingSubtitles": "下载缺少的字幕", + "TaskRefreshChannelsDescription": "刷新互联网频道信息。", + "TaskRefreshChannels": "刷新频道", + "TaskCleanTranscodeDescription": "删除存在超过 1 天的转码文件。", + "TaskCleanTranscode": "清理转码目录", + "TaskUpdatePlugins": "更新插件", + "TaskRefreshPeopleDescription": "更新媒体库中演员和导演的元数据。", + "TaskCleanLogsDescription": "删除存在超过 {0} 天的的日志文件。", + "TaskCleanLogs": "清理日志目录", + "TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。", + "TaskRefreshLibrary": "扫描媒体库", + "TaskRefreshChapterImagesDescription": "为包含剧集的视频提取缩略图。", + "TaskRefreshChapterImages": "提取剧集图片", + "TaskCleanCacheDescription": "删除系统不再需要的缓存文件。", + "TaskCleanCache": "清理缓存目录", + "TasksApplicationCategory": "应用程序", + "TasksMaintenanceCategory": "维护" } diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index f3d9e5fce8..224748e611 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -76,7 +76,6 @@ "StartupEmbyServerIsLoading": "Jellyfin 伺服器載入中,請稍後再試。", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕", - "SubtitlesDownloadedForItem": "已為 {0} 下載了字幕", "Sync": "同步", "System": "System", "TvShows": "電視節目", diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index acd211f22a..21034b76f3 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -72,7 +72,6 @@ "Shows": "節目", "Songs": "歌曲", "StartupEmbyServerIsLoading": "Jellyfin Server正在啟動,請稍後再試一次。", - "SubtitlesDownloadedForItem": "已為 {0} 下載字幕", "Sync": "同步", "System": "系統", "TvShows": "電視節目", diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 840aca7a6b..677d68b4c9 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -26,14 +26,20 @@ namespace Emby.Server.Implementations.MediaEncoder private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; + /// + /// The first chapter ticks. + /// + private static readonly long _firstChapterTicks = TimeSpan.FromSeconds(15).Ticks; + public EncodingManager( + ILogger logger, IFileSystem fileSystem, - ILoggerFactory loggerFactory, IMediaEncoder encoder, - IChapterManager chapterManager, ILibraryManager libraryManager) + IChapterManager chapterManager, + ILibraryManager libraryManager) { + _logger = logger; _fileSystem = fileSystem; - _logger = loggerFactory.CreateLogger(nameof(EncodingManager)); _encoder = encoder; _chapterManager = chapterManager; _libraryManager = libraryManager; @@ -97,12 +103,7 @@ namespace Emby.Server.Implementations.MediaEncoder return video.DefaultVideoStreamIndex.HasValue; } - /// - /// The first chapter ticks - /// - private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks; - - public async Task RefreshChapterImages(Video video, IDirectoryService directoryService, List chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) + public async Task RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) { if (!IsEligibleForChapterImageExtraction(video)) { @@ -135,7 +136,7 @@ namespace Emby.Server.Implementations.MediaEncoder try { // Add some time for the first chapter to make sure we don't end up with a black image - var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); + var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(_firstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); var protocol = MediaProtocol.File; @@ -152,9 +153,9 @@ namespace Emby.Server.Implementations.MediaEncoder { _fileSystem.DeleteFile(tempFile); } - catch + catch (IOException ex) { - + _logger.LogError(ex, "Error deleting temporary chapter image encoding file {Path}", tempFile); } chapter.ImagePath = path; @@ -184,7 +185,7 @@ namespace Emby.Server.Implementations.MediaEncoder if (saveChapters && changesMade) { - _chapterManager.SaveChapters(video.Id.ToString(), chapters); + _chapterManager.SaveChapters(video.Id, chapters); } DeleteDeadImages(currentImages, chapters); @@ -199,22 +200,21 @@ namespace Emby.Server.Implementations.MediaEncoder return Path.Combine(GetChapterImagesPath(video), filename); } - private static List GetSavedChapterImages(Video video, IDirectoryService directoryService) + private static IReadOnlyList GetSavedChapterImages(Video video, IDirectoryService directoryService) { var path = GetChapterImagesPath(video); if (!Directory.Exists(path)) { - return new List(); + return Array.Empty(); } try { - return directoryService.GetFilePaths(path) - .ToList(); + return directoryService.GetFilePaths(path); } catch (IOException) { - return new List(); + return Array.Empty(); } } @@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.MediaEncoder foreach (var image in deadImages) { - _logger.LogDebug("Deleting dead chapter image {path}", image); + _logger.LogDebug("Deleting dead chapter image {Path}", image); try { @@ -235,7 +235,7 @@ namespace Emby.Server.Implementations.MediaEncoder } catch (IOException ex) { - _logger.LogError(ex, "Error deleting {path}.", image); + _logger.LogError(ex, "Error deleting {Path}.", image); } } } diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 1d8d3cf395..b3e88b6675 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -500,7 +500,7 @@ namespace Emby.Server.Implementations.Networking { if (ip.Address.Equals(address) && ip.IPv4Mask != null) { - return ip.IPv4Mask; + return ip.IPv4Mask; } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 5822c467b7..ea6a70615b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -15,6 +15,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks { @@ -39,11 +40,19 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly IEncodingManager _encodingManager; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// - public ChapterImagesTask(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IItemRepository itemRepo, IApplicationPaths appPaths, IEncodingManager encodingManager, IFileSystem fileSystem) + public ChapterImagesTask( + ILoggerFactory loggerFactory, + ILibraryManager libraryManager, + IItemRepository itemRepo, + IApplicationPaths appPaths, + IEncodingManager encodingManager, + IFileSystem fileSystem, + ILocalizationManager localization) { _logger = loggerFactory.CreateLogger(GetType().Name); _libraryManager = libraryManager; @@ -51,6 +60,7 @@ namespace Emby.Server.Implementations.ScheduledTasks _appPaths = appPaths; _encodingManager = encodingManager; _fileSystem = fileSystem; + _localization = localization; } /// @@ -159,11 +169,11 @@ namespace Emby.Server.Implementations.ScheduledTasks } } - public string Name => "Extract Chapter Images"; + public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages"); - public string Description => "Creates thumbnails for videos that have chapters."; + public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription"); - public string Category => "Library"; + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); public string Key => "RefreshChapterImages"; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index b7668c872a..9df7c538b1 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { @@ -25,6 +26,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks private readonly ILogger _logger; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. @@ -32,11 +34,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks public DeleteCacheFileTask( IApplicationPaths appPaths, ILogger logger, - IFileSystem fileSystem) + IFileSystem fileSystem, + ILocalizationManager localization) { ApplicationPaths = appPaths; _logger = logger; _fileSystem = fileSystem; + _localization = localization; } /// @@ -161,11 +165,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } - public string Name => "Clean Cache Directory"; + public string Name => _localization.GetLocalizedString("TaskCleanCache"); - public string Description => "Deletes cache files no longer needed by the system."; + public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription"); - public string Category => "Maintenance"; + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); public string Key => "DeleteCacheFiles"; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index 9f9c6353a1..3140aa4893 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { @@ -21,15 +22,17 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks private IConfigurationManager ConfigurationManager { get; set; } private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The configuration manager. - public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem) + public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization) { ConfigurationManager = configurationManager; _fileSystem = fileSystem; + _localization = localization; } /// @@ -79,11 +82,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Task.CompletedTask; } - public string Name => "Clean Log Directory"; + public string Name => _localization.GetLocalizedString("TaskCleanLogs"); - public string Description => string.Format("Deletes log files that are more than {0} days old.", ConfigurationManager.CommonConfiguration.LogFileRetentionDays); + public string Description => string.Format(_localization.GetLocalizedString("TaskCleanLogsDescription"), ConfigurationManager.CommonConfiguration.LogFileRetentionDays); - public string Category => "Maintenance"; + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); public string Key => "CleanLogFiles"; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 37136f4386..1d133dcda8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { @@ -19,6 +20,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks private readonly ILogger _logger; private readonly IConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. @@ -26,11 +28,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks public DeleteTranscodeFileTask( ILogger logger, IFileSystem fileSystem, - IConfigurationManager configurationManager) + IConfigurationManager configurationManager, + ILocalizationManager localization) { _logger = logger; _fileSystem = fileSystem; _configurationManager = configurationManager; + _localization = localization; } /// @@ -128,11 +132,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } - public string Name => "Clean Transcode Directory"; + public string Name => _localization.GetLocalizedString("TaskCleanTranscode"); - public string Description => "Deletes transcode files more than one day old."; + public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription"); - public string Category => "Maintenance"; + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); public string Key => "DeleteTranscodeFiles"; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index eaf17aace1..63f867bf6c 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks { @@ -19,16 +20,18 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly ILibraryManager _libraryManager; private readonly IServerApplicationHost _appHost; + private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The library manager. /// The server application host - public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost) + public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost, ILocalizationManager localization) { _libraryManager = libraryManager; _appHost = appHost; + _localization = localization; } /// @@ -57,11 +60,11 @@ namespace Emby.Server.Implementations.ScheduledTasks return _libraryManager.ValidatePeople(cancellationToken, progress); } - public string Name => "Refresh People"; + public string Name => _localization.GetLocalizedString("TaskRefreshPeople"); - public string Description => "Updates metadata for actors and directors in your media library."; + public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription"); - public string Category => "Library"; + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); public string Key => "RefreshPeople"; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index 9d87316e43..588944d0e3 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -8,6 +8,7 @@ using MediaBrowser.Common.Updates; using MediaBrowser.Model.Net; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks { @@ -22,11 +23,13 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly ILogger _logger; private readonly IInstallationManager _installationManager; + private readonly ILocalizationManager _localization; - public PluginUpdateTask(ILogger logger, IInstallationManager installationManager) + public PluginUpdateTask(ILogger logger, IInstallationManager installationManager, ILocalizationManager localization) { _logger = logger; _installationManager = installationManager; + _localization = localization; } /// @@ -96,13 +99,13 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - public string Name => "Update Plugins"; + public string Name => _localization.GetLocalizedString("TaskUpdatePlugins"); /// - public string Description => "Downloads and installs updates for plugins that are configured to update automatically."; + public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription"); /// - public string Category => "Application"; + public string Category => _localization.GetLocalizedString("TasksApplicationCategory"); /// public string Key => "PluginUpdates"; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 0736780191..74cb01444e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -6,6 +6,7 @@ using Emby.Server.Implementations.Library; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks { @@ -19,15 +20,17 @@ namespace Emby.Server.Implementations.ScheduledTasks /// private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; + private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The library manager. - public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config) + public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config, ILocalizationManager localization) { _libraryManager = libraryManager; _config = config; + _localization = localization; } /// @@ -38,7 +41,8 @@ namespace Emby.Server.Implementations.ScheduledTasks { yield return new TaskTriggerInfo { - Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(12).Ticks + Type = TaskTriggerInfo.TriggerInterval, + IntervalTicks = TimeSpan.FromHours(12).Ticks }; } @@ -57,11 +61,11 @@ namespace Emby.Server.Implementations.ScheduledTasks return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken); } - public string Name => "Scan Media Library"; + public string Name => _localization.GetLocalizedString("TaskRefreshLibrary"); - public string Description => "Scans your media library for new files and refreshes metadata."; + public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription"); - public string Category => "Library"; + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); public string Key => "RefreshLibrary"; diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index d963f9043c..e24a95dbb3 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -3,14 +3,27 @@ using System.Collections.Generic; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Services { public delegate object ActionInvokerFn(object intance, object request); + public delegate void VoidActionInvokerFn(object intance, object request); public class ServiceController { + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + public ServiceController(ILogger logger) + { + _logger = logger; + } + public void Init(HttpListenerHost appHost, IEnumerable serviceTypes) { foreach (var serviceType in serviceTypes) @@ -21,6 +34,13 @@ namespace Emby.Server.Implementations.Services public void RegisterService(HttpListenerHost appHost, Type serviceType) { + // Make sure the provided type implements IService + if (!typeof(IService).IsAssignableFrom(serviceType)) + { + _logger.LogWarning("Tried to register a service that does not implement IService: {ServiceType}", serviceType); + return; + } + var processedReqs = new HashSet(); var actions = ServiceExecGeneral.Reset(serviceType); diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index c30f32af94..5177251c3e 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Emby.Server.Implementations.HttpServer; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; -using Emby.Server.Implementations.HttpServer; namespace Emby.Server.Implementations.Services { @@ -241,7 +241,7 @@ namespace Emby.Server.Implementations.Services responses = responses, - security = new [] { apiKeySecurity } + security = new[] { apiKeySecurity } }; } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index dfcd3843c7..de768333d8 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1401,6 +1401,16 @@ namespace Emby.Server.Implementations.Session user = _userManager.GetUserByName(request.Username); } + if (enforcePassword) + { + user = await _userManager.AuthenticateUser( + request.Username, + request.Password, + request.PasswordSha1, + request.RemoteEndPoint, + true).ConfigureAwait(false); + } + if (user == null) { AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); @@ -1413,16 +1423,6 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is not allowed access from this device."); } - if (enforcePassword) - { - user = await _userManager.AuthenticateUser( - request.Username, - request.Password, - request.PasswordSha1, - request.RemoteEndPoint, - true).ConfigureAwait(false); - } - var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); var session = LogSessionActivity( diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index f9ce0bbe12..d0a99e1e28 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -14,7 +14,7 @@ - + diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 2ea690650b..a67118f188 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -308,8 +308,7 @@ namespace Jellyfin.Drawing.Skia if (requiresTransparencyHack || forceCleanBitmap) { - using (var stream = new SKFileStream(NormalizePath(path))) - using (var codec = SKCodec.Create(stream)) + using (var codec = SKCodec.Create(NormalizePath(path))) { if (codec == null) { diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index ed5968ad6f..1d5313c13e 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -4,7 +4,6 @@ using Emby.Server.Implementations; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.IO; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Server diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index 673f0e4155..6f8e4a8ffb 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.Routines diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index e9e852349c..4abdd59aaf 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -13,20 +12,22 @@ 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; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Model.Globalization; +using MediaBrowser.Controller.Extensions; +using MediaBrowser.WebDashboard.Api; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Serilog; -using Serilog.Events; using Serilog.Extensions.Logging; using SQLitePCL; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -112,9 +113,10 @@ namespace Jellyfin.Server // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath); - // Create an instance of the application configuration to use for application startup await InitLoggingConfigFile(appPaths).ConfigureAwait(false); - IConfiguration startupConfig = CreateAppConfiguration(appPaths); + + // Create an instance of the application configuration to use for application startup + IConfiguration startupConfig = CreateAppConfiguration(options, appPaths); // Initialize logging framework InitializeLoggingFramework(startupConfig, appPaths); @@ -183,15 +185,31 @@ namespace Jellyfin.Server new ManagedFileSystem(_loggerFactory.CreateLogger(), appPaths), GetImageEncoder(appPaths), new NetworkManager(_loggerFactory.CreateLogger())); + try { + // If hosting the web client, validate the client content path + if (startupConfig.HostWebClient()) + { + string webContentPath = DashboardService.GetDashboardUIPath(startupConfig, appHost.ServerConfigurationManager); + if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0) + { + 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" + + $"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings."); + } + } + ServiceCollection serviceCollection = new ServiceCollection(); await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false); - var webHost = CreateWebHostBuilder(appHost, serviceCollection, appPaths).Build(); + var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build(); - // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. + // Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection. appHost.ServiceProvider = webHost.Services; + appHost.InitializeServices(); appHost.FindParts(); Migrations.MigrationRunner.Run(appHost, _loggerFactory); @@ -233,7 +251,12 @@ namespace Jellyfin.Server } } - private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection, IApplicationPaths appPaths) + private static IWebHostBuilder CreateWebHostBuilder( + ApplicationHost appHost, + IServiceCollection serviceCollection, + StartupOptions commandLineOpts, + IConfiguration startupConfig, + IApplicationPaths appPaths) { return new WebHostBuilder() .UseKestrel(options => @@ -253,10 +276,11 @@ namespace Jellyfin.Server if (appHost.EnableHttps && appHost.Certificate != null) { - options.Listen( - address, - appHost.HttpsPort, - listenOptions => listenOptions.UseHttps(appHost.Certificate)); + options.Listen(address, appHost.HttpsPort, listenOptions => + { + listenOptions.UseHttps(appHost.Certificate); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); } } } @@ -267,15 +291,16 @@ namespace Jellyfin.Server if (appHost.EnableHttps && appHost.Certificate != null) { - options.ListenAnyIP( - appHost.HttpsPort, - listenOptions => listenOptions.UseHttps(appHost.Certificate)); + options.ListenAnyIP(appHost.HttpsPort, listenOptions => + { + listenOptions.UseHttps(appHost.Certificate); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); } } }) - .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths)) + .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig)) .UseSerilog() - .UseContentRoot(appHost.ContentRoot) .ConfigureServices(services => { // Merge the external ServiceCollection into ASP.NET DI @@ -398,9 +423,8 @@ namespace Jellyfin.Server // webDir // IF --webdir // ELSE IF $JELLYFIN_WEB_DIR - // ELSE use /jellyfin-web + // ELSE /jellyfin-web var webDir = options.WebDir; - if (string.IsNullOrEmpty(webDir)) { webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR"); @@ -471,21 +495,33 @@ namespace Jellyfin.Server await resource.CopyToAsync(dst).ConfigureAwait(false); } - private static IConfiguration CreateAppConfiguration(IApplicationPaths appPaths) + private static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths) { return new ConfigurationBuilder() - .ConfigureAppConfiguration(appPaths) + .ConfigureAppConfiguration(commandLineOpts, appPaths) .Build(); } - private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths) + private static IConfigurationBuilder ConfigureAppConfiguration( + this IConfigurationBuilder config, + StartupOptions commandLineOpts, + IApplicationPaths appPaths, + IConfiguration? startupConfig = null) { + // Use the swagger API page as the default redirect path if not hosting the web client + var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; + if (startupConfig != null && !startupConfig.HostWebClient()) + { + inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html"; + } + return config .SetBasePath(appPaths.ConfigurationDirectoryPath) - .AddInMemoryCollection(ConfigurationOptions.Configuration) + .AddInMemoryCollection(inMemoryDefaultConfig) .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true) .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true) - .AddEnvironmentVariables("JELLYFIN_"); + .AddEnvironmentVariables("JELLYFIN_") + .AddInMemoryCollection(commandLineOpts.ConvertToConfig()); } /// @@ -531,7 +567,7 @@ namespace Jellyfin.Server } catch (Exception ex) { - _logger.LogWarning(ex, "Skia not available. Will fallback to NullIMageEncoder."); + _logger.LogWarning(ex, $"Skia not available. Will fallback to {nameof(NullImageEncoder)}."); } return new NullImageEncoder(); diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json new file mode 100644 index 0000000000..53d9fe1656 --- /dev/null +++ b/Jellyfin.Server/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "Jellyfin.Server": { + "commandName": "Project" + }, + "Jellyfin.Server (nowebclient)": { + "commandName": "Project", + "commandLineArgs": "--nowebclient" + } + } +} diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 1fb1c5af8e..c93577d3ea 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; +using System.Globalization; using CommandLine; using Emby.Server.Implementations; +using MediaBrowser.Controller.Extensions; namespace Jellyfin.Server { @@ -15,6 +18,12 @@ namespace Jellyfin.Server [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] public string? DataDir { get; set; } + /// + /// Gets or sets a value indicating whether the server should not host the web client. + /// + [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. /// @@ -66,5 +75,21 @@ namespace Jellyfin.Server /// [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] public string? RestartArgs { get; set; } + + /// + /// Gets the command line options as a dictionary that can be used in the .NET configuration system. + /// + /// The configuration dictionary. + public Dictionary ConvertToConfig() + { + var config = new Dictionary(); + + if (NoWebClient) + { + config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); + } + + return config; + } } } diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 2b994d2799..112ee8f79a 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -9,8 +9,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Services; using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index 92c32f2ad5..6139ba1560 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Api.UserLibrary; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 322b9805ba..36b03f09ce 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -177,7 +177,7 @@ namespace MediaBrowser.Api } public object Get(GetDefaultDirectoryBrowser request) => - ToOptimizedResult(new DefaultDirectoryBrowserInfo {Path = null}); + ToOptimizedResult(new DefaultDirectoryBrowserInfo { Path = null }); /// /// Gets the specified request. diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index f03e481df4..3348a31875 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -927,61 +927,69 @@ namespace MediaBrowser.Api.Playback.Hls } else { + var gopArg = string.Empty; var keyFrameArg = string.Format( CultureInfo.InvariantCulture, " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"", GetStartNumber(state) * state.SegmentLength, state.SegmentLength); - if (state.TargetFramerate.HasValue) + + var framerate = state.VideoStream?.RealFrameRate; + + if (framerate != null && framerate.HasValue) { // This is to make sure keyframe interval is limited to our segment, // as forcing keyframes is not enough. // Example: we encoded half of desired length, then codec detected // scene cut and inserted a keyframe; next forced keyframe would - // be created outside of segment, which breaks seeking. - keyFrameArg += string.Format( + // be created outside of segment, which breaks seeking + // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe + gopArg = string.Format( CultureInfo.InvariantCulture, - " -g {0} -keyint_min {0}", - (int)(state.SegmentLength * state.TargetFramerate) + " -g {0} -keyint_min {0} -sc_threshold 0", + Math.Ceiling(state.SegmentLength * framerate.Value) ); } - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()); - // Unable to force key frames to h264_qsv transcode - if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + // Unable to force key frames using these hw encoders, set key frames by GOP + if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)) { - Logger.LogInformation("Bug Workaround: Disabling force_key_frames for h264_qsv"); + args += " " + gopArg; } else { - args += " " + keyFrameArg; + args += " " + keyFrameArg + gopArg; } //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; - // Add resolution params, if specified - if (!hasGraphicalSubs) - { - args += EncodingHelper.GetOutputSizeParam(state, encodingOptions, codec, true); - } + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - // This is for internal graphical subs + // This is for graphical subs if (hasGraphicalSubs) { args += EncodingHelper.GetGraphicalSubtitleParam(state, encodingOptions, codec); } + // Add resolution params, if specified + else + { + args += EncodingHelper.GetOutputSizeParam(state, encodingOptions, codec); + } + + // -start_at_zero is necessary to use with -ss when seeking, + // otherwise the target position cannot be determined. + if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) + { + args += " -start_at_zero"; + } //args += " -flags -global_header"; } - if (args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1) - { - args += " -copyts"; - } - if (!string.IsNullOrEmpty(state.OutputVideoSync)) { args += " -vsync " + state.OutputVideoSync; @@ -1025,7 +1033,7 @@ namespace MediaBrowser.Api.Playback.Hls } return string.Format( - "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f hls -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"", + "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), threads, diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 2aa5e2df1f..d74ec3ca63 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -5,8 +5,8 @@ using System; using System.Buffers; using System.Globalization; -using System.Text.Json; using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; @@ -470,7 +470,7 @@ namespace MediaBrowser.Api.Playback else { options.MaxBitrate = GetMaxBitrate(maxBitrate, user); - + if (item is Audio) { if (!user.Policy.EnableAudioPlaybackTranscoding) @@ -486,10 +486,10 @@ namespace MediaBrowser.Api.Playback } } - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) - ? streamBuilder.BuildAudioItem(options) - : streamBuilder.BuildVideoItem(options); + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) + ? streamBuilder.BuildAudioItem(options) + : streamBuilder.BuildVideoItem(options); if (streamInfo == null || !streamInfo.IsDirectStream) { @@ -516,7 +516,7 @@ namespace MediaBrowser.Api.Playback { if (streamInfo != null) { - streamInfo.PlaySessionId = playSessionId; + streamInfo.PlaySessionId = playSessionId; streamInfo.StartPositionTicks = startTimeTicks; mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; @@ -526,10 +526,10 @@ namespace MediaBrowser.Api.Playback } mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - + // Do this after the above so that StartPositionTicks is set SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - } + } } else { diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index b843f7096f..334d1db512 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -12,8 +12,8 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Services; using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index 5bdea7d8b3..870b90796c 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -1,3 +1,5 @@ +using MediaBrowser.Model.Configuration; + namespace MediaBrowser.Common.Configuration { /// @@ -12,9 +14,12 @@ namespace MediaBrowser.Common.Configuration 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. + /// + /// This value is not relevant if the server is configured to not host any static web content. Additionally, + /// the value for takes precedence over this one. + /// string WebPath { get; } /// diff --git a/MediaBrowser.Common/Cryptography/Extensions.cs b/MediaBrowser.Common/Cryptography/CryptoExtensions.cs similarity index 97% rename from MediaBrowser.Common/Cryptography/Extensions.cs rename to MediaBrowser.Common/Cryptography/CryptoExtensions.cs index 1e32a6d1a9..157b0ed100 100644 --- a/MediaBrowser.Common/Cryptography/Extensions.cs +++ b/MediaBrowser.Common/Cryptography/CryptoExtensions.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Common.Cryptography /// /// Class containing extension methods for working with Jellyfin cryptography objects. /// - public static class Extensions + public static class CryptoExtensions { /// /// Creates a new instance. diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 548c214dd5..3b03478020 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -30,7 +30,7 @@ - + diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 51962001ec..38274a80e4 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Common.Net RequestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); CacheMode = CacheMode.None; - DecompressionMethod = CompressionMethod.Deflate; + DecompressionMethod = CompressionMethods.Deflate; } /// @@ -29,7 +29,7 @@ namespace MediaBrowser.Common.Net /// The URL. public string Url { get; set; } - public CompressionMethod DecompressionMethod { get; set; } + public CompressionMethods DecompressionMethod { get; set; } /// /// Gets or sets the accept header. @@ -83,8 +83,6 @@ namespace MediaBrowser.Common.Net public string RequestContent { get; set; } - public byte[] RequestContentBytes { get; set; } - public bool BufferContent { get; set; } public bool LogErrorResponseBody { get; set; } @@ -112,7 +110,7 @@ namespace MediaBrowser.Common.Net } [Flags] - public enum CompressionMethod + public enum CompressionMethods { None = 0b00000001, Deflate = 0b00000010, diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs index d7f7a56229..d4fee6c78e 100644 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Common.Net /// /// Class HttpResponseInfo. /// - public class HttpResponseInfo : IDisposable + public sealed class HttpResponseInfo : IDisposable { #pragma warning disable CS1591 public HttpResponseInfo() @@ -22,7 +22,6 @@ namespace MediaBrowser.Common.Net } #pragma warning restore CS1591 -#pragma warning restore SA1600 /// /// Gets or sets the type of the content. diff --git a/MediaBrowser.Common/System/OperatingSystem.cs b/MediaBrowser.Common/System/OperatingSystem.cs index 7d38ddb6e5..5f673d3208 100644 --- a/MediaBrowser.Common/System/OperatingSystem.cs +++ b/MediaBrowser.Common/System/OperatingSystem.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Common.System case OperatingSystemId.Linux: return "Linux"; case OperatingSystemId.Darwin: return "macOS"; case OperatingSystemId.Windows: return "Windows"; - default: throw new Exception($"Unknown OS {Id}"); + default: throw new PlatformNotSupportedException($"Unknown OS {Id}"); } } } @@ -53,20 +53,20 @@ namespace MediaBrowser.Common.System default: { string osDescription = RuntimeInformation.OSDescription; - if (osDescription.IndexOf("linux", StringComparison.OrdinalIgnoreCase) != -1) + if (osDescription.Contains("linux", StringComparison.OrdinalIgnoreCase)) { return OperatingSystemId.Linux; } - else if (osDescription.IndexOf("darwin", StringComparison.OrdinalIgnoreCase) != -1) + else if (osDescription.Contains("darwin", StringComparison.OrdinalIgnoreCase)) { return OperatingSystemId.Darwin; } - else if (osDescription.IndexOf("bsd", StringComparison.OrdinalIgnoreCase) != -1) + else if (osDescription.Contains("bsd", StringComparison.OrdinalIgnoreCase)) { return OperatingSystemId.BSD; } - throw new Exception($"Can't resolve OS with description: '{osDescription}'"); + throw new PlatformNotSupportedException($"Can't resolve OS with description: '{osDescription}'"); } } } diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs index d061898a14..f82e5b41a2 100644 --- a/MediaBrowser.Controller/Chapters/IChapterManager.cs +++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs @@ -1,16 +1,17 @@ +using System; using System.Collections.Generic; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Chapters { /// - /// Interface IChapterManager + /// Interface IChapterManager. /// public interface IChapterManager { /// /// Saves the chapters. /// - void SaveChapters(string itemId, List chapters); + void SaveChapters(Guid itemId, IReadOnlyList chapters); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index a9ec19e2fd..7380e6da15 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -15,7 +15,6 @@ using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index c72bd487e7..bb48605e55 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; @@ -28,7 +30,6 @@ namespace MediaBrowser.Controller.Entities /// public class Folder : BaseItem { - public static IUserManager UserManager { get; set; } public static IUserViewManager UserViewManager { get; set; } /// @@ -620,7 +621,6 @@ namespace MediaBrowser.Controller.Entities { EnableImages = false } - }).TotalRecordCount; } @@ -1713,7 +1713,6 @@ namespace MediaBrowser.Controller.Entities { EnableImages = false } - }); double unplayedCount = unplayedQueryResult.TotalRecordCount; diff --git a/MediaBrowser.Controller/Entities/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs index 89b5dfee36..8ef5c8d961 100644 --- a/MediaBrowser.Controller/Entities/IItemByName.cs +++ b/MediaBrowser.Controller/Entities/IItemByName.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { /// - /// Marker interface + /// Marker interface. /// public interface IItemByName { diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 64e216e69f..9e4f9d47ed 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Text.Json.Serialization; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities diff --git a/MediaBrowser.Controller/Entities/PersonInfo.cs b/MediaBrowser.Controller/Entities/PersonInfo.cs index e90c55a8ac..f3ec73b322 100644 --- a/MediaBrowser.Controller/Entities/PersonInfo.cs +++ b/MediaBrowser.Controller/Entities/PersonInfo.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Collections.Generic; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 48316499a4..c0043c0efa 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 web client content. + /// + public const string HostWebClientKey = "hostwebclient"; + /// /// The key for the FFmpeg probe size option. /// @@ -22,6 +28,15 @@ namespace MediaBrowser.Controller.Extensions /// public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates"; + /// + /// Gets a value indicating whether the application should 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 HostWebClient(this IConfiguration configuration) + => configuration.GetValue(HostWebClientKey); + /// /// Gets the FFmpeg probe size from the . /// diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 25f0905eb8..608ffc61c2 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -82,6 +82,11 @@ namespace MediaBrowser.Controller /// The local API URL. string GetLocalApiUrl(IPAddress address); + /// + /// Open a URL in an external browser window. + /// + /// The URL to open. + /// is false. void LaunchUrl(string url); void EnableLoopback(string appName); diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs index ec7798551b..5bf4acebb4 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs index 6b0b7e53ac..24d0347e9d 100644 --- a/MediaBrowser.Controller/Library/NameExtensions.cs +++ b/MediaBrowser.Controller/Library/NameExtensions.cs @@ -1,6 +1,6 @@ using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; using MediaBrowser.Controller.Extensions; namespace MediaBrowser.Controller.Library diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 342c764146..ce576a6c3b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -460,16 +460,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.IsVideoRequest && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - var hwOutputFormat = "vaapi"; - - if (hasGraphicalSubs) - { - hwOutputFormat = "yuv420p"; - } - - arg.Append("-hwaccel vaapi -hwaccel_output_format ") - .Append(hwOutputFormat) + arg.Append("-hwaccel vaapi -hwaccel_output_format vaapi") .Append(" -vaapi_device ") .Append(encodingOptions.VaapiDevice) .Append(' '); @@ -480,20 +471,26 @@ namespace MediaBrowser.Controller.MediaEncoding { var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); var outputVideoCodec = GetVideoEncoder(state, encodingOptions); + + var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - if (encodingOptions.EnableHardwareEncoding && outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase)) + if (!hasTextSubs) { - if (!string.IsNullOrEmpty(videoDecoder) && videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)) + // While using QSV encoder + if ((outputVideoCodec ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1) { - arg.Append("-hwaccel qsv "); - } - else - { - arg.Append("-init_hw_device qsv=hw -filter_hw_device hw "); + // While using QSV decoder + if ((videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1) + { + arg.Append("-hwaccel qsv "); + } + // While using SW decoder + else + { + arg.Append("-init_hw_device qsv=hw -filter_hw_device hw "); + } } } - - arg.Append(videoDecoder + " "); } arg.Append("-i ") @@ -503,17 +500,6 @@ namespace MediaBrowser.Controller.MediaEncoding && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) { - if (state.VideoStream != null && state.VideoStream.Width.HasValue) - { - // This is hacky but not sure how to get the exact subtitle resolution - int height = Convert.ToInt32(state.VideoStream.Width.Value / 16.0 * 9.0); - - arg.Append(" -canvas_size ") - .Append(state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture)) - .Append(':') - .Append(height.ToString(CultureInfo.InvariantCulture)); - } - var subtitlePath = state.SubtitleStream.Path; if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase)) @@ -1546,9 +1532,12 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Gets the internal graphical subtitle param. + /// Gets the graphical subtitle param. /// - public string GetGraphicalSubtitleParam(EncodingJobInfo state, EncodingOptions options, string outputVideoCodec) + public string GetGraphicalSubtitleParam( + EncodingJobInfo state, + EncodingOptions options, + string outputVideoCodec) { var outputSizeParam = string.Empty; @@ -1562,53 +1551,77 @@ namespace MediaBrowser.Controller.MediaEncoding { outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase); + if (index != -1) { - var index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - outputSizeParam = "," + outputSizeParam.Substring(index); - } + outputSizeParam = "," + outputSizeParam.Substring(index); } else { - var index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); + index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); if (index != -1) { outputSizeParam = "," + outputSizeParam.Substring(index); } - } - } - - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - && outputSizeParam.Length == 0) - { - outputSizeParam = ",format=nv12|vaapi,hwupload"; - - // Add parameters to use VAAPI with burn-in subttiles (GH issue #642) - if (state.SubtitleStream != null - && state.SubtitleStream.IsTextSubtitleStream - && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { - outputSizeParam += ",hwmap=mode=read+write+direct"; + else + { + index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase); + if (index != -1) + { + outputSizeParam = "," + outputSizeParam.Substring(index); + } + else + { + index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); + if (index != -1) + { + outputSizeParam = "," + outputSizeParam.Substring(index); + } + } + } } } var videoSizeParam = string.Empty; + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options); // Setup subtitle scaling if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) { + // force_original_aspect_ratio=decrease + // Enable decreasing output video width or height if necessary to keep the original aspect ratio videoSizeParam = string.Format( CultureInfo.InvariantCulture, - "scale={0}:{1}", + "scale={0}:{1}:force_original_aspect_ratio=decrease", state.VideoStream.Width.Value, state.VideoStream.Height.Value); - //For QSV, feed it into hardware encoder now + // For QSV, feed it into hardware encoder now if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { videoSizeParam += ",hwupload=extra_hw_frames=64"; } + + // For VAAPI and CUVID decoder + // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video, + // thus needs to be manually adjusted. + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + || (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) + { + var videoStream = state.VideoStream; + var inputWidth = videoStream?.Width; + var inputHeight = videoStream?.Height; + var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); + + if (width.HasValue && height.HasValue) + { + videoSizeParam = string.Format( + CultureInfo.InvariantCulture, + "scale={0}:{1}:force_original_aspect_ratio=decrease", + width.Value, + height.Value); + } + } } var mapPrefix = state.SubtitleStream.IsExternal ? @@ -1619,12 +1632,34 @@ namespace MediaBrowser.Controller.MediaEncoding ? 0 : state.SubtitleStream.Index; - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options); - // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference) var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\""; - if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + // When the input may or may not be hardware VAAPI decodable + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding) + { + /* + [base]: HW scaling video to OutputSize + [sub]: SW scaling subtitle to FixedOutputSize + [base][sub]: SW overlay + */ + outputSizeParam = outputSizeParam.TrimStart(','); + retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; + } + + // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first + else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding) + { + /* + [base]: SW scaling video to OutputSize + [sub]: SW scaling subtitle to FixedOutputSize + [base][sub]: SW overlay + */ + outputSizeParam = outputSizeParam.TrimStart(','); + retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; + } + + else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { /* QSV in FFMpeg can now setup hardware overlay for transcodes. @@ -1688,7 +1723,8 @@ namespace MediaBrowser.Controller.MediaEncoding return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); } - public List GetScalingFilters(int? videoWidth, + public List GetScalingFilters(EncodingJobInfo state, + int? videoWidth, int? videoHeight, Video3DFormat? threedFormat, string videoDecoder, @@ -1707,7 +1743,9 @@ namespace MediaBrowser.Controller.MediaEncoding requestedMaxWidth, requestedMaxHeight); - if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + + if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs) && width.HasValue && height.HasValue) { @@ -1737,7 +1775,7 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add(string.Format(CultureInfo.InvariantCulture, "scale_{0}=format=nv12", vaapi_or_qsv)); } } - else if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 + else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 && width.HasValue && height.HasValue) { @@ -1941,8 +1979,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetOutputSizeParam( EncodingJobInfo state, EncodingOptions options, - string outputVideoCodec, - bool allowTimeStampCopy = true) + string outputVideoCodec) { // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ @@ -1951,42 +1988,57 @@ namespace MediaBrowser.Controller.MediaEncoding var videoStream = state.VideoStream; var filters = new List(); - // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - var hwType = options.HardwareAccelerationType ?? string.Empty; - if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding ) - { - filters.Add("hwdownload"); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options); + var inputWidth = videoStream?.Width; + var inputHeight = videoStream?.Height; + var threeDFormat = state.MediaSource.Video3DFormat; - // 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)) - { - filters.Add("format=p010le"); - filters.Add("format=nv12"); - } - else - { - filters.Add("format=nv12"); - } - } + var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + // When the input may or may not be hardware VAAPI decodable + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); } - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options); - - // If we are software decoding, and hardware encoding - if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) - && (string.IsNullOrEmpty(videoDecoder) || !videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase))) + // When the input may or may not be hardware QSV decodable + else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding) { - filters.Add("format=nv12|qsv"); - filters.Add("hwupload=extra_hw_frames=64"); + if (!hasTextSubs) + { + filters.Add("format=nv12|qsv"); + filters.Add("hwupload=extra_hw_frames=64"); + } } + // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first + + else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding) + { + var codec = videoStream.Codec.ToLowerInvariant(); + var pixelFormat = videoStream.PixelFormat.ToLowerInvariant(); + + // Assert 10-bit hardware VAAPI decodable + if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1 + && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))) + { + filters.Add("hwdownload"); + filters.Add("format=p010le"); + filters.Add("format=nv12"); + } + + // Assert 8-bit hardware VAAPI decodable + else if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) == -1) + { + filters.Add("hwdownload"); + filters.Add("format=nv12"); + } + } + + // Add hardware deinterlace filter before scaling filter if (state.DeInterlace("h264", true)) { if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) @@ -1995,17 +2047,23 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { - filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv")); + if (!hasTextSubs) + { + filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv")); + } } } - if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) - && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + // Add software deinterlace filter before scaling filter + if (((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) + && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + || (hasTextSubs && state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))) { var inputFramerate = videoStream?.RealFrameRate; // If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle - if (string.Equals(options.DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30) + if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30) { filters.Add("yadif=1:-1:0"); } @@ -2015,11 +2073,21 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var inputWidth = videoStream?.Width; - var inputHeight = videoStream?.Height; - var threeDFormat = state.MediaSource.Video3DFormat; + // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr + filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); - filters.AddRange(GetScalingFilters(inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); + // Add parameters to use VAAPI with burn-in text subttiles (GH issue #642) + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding) + { + if (state.SubtitleStream != null + && state.SubtitleStream.IsTextSubtitleStream + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + { + // Test passed on Intel and AMD gfx + filters.Add("hwmap=mode=read+write"); + filters.Add("format=nv12"); + } + } var output = string.Empty; @@ -2037,11 +2105,6 @@ namespace MediaBrowser.Controller.MediaEncoding { filters.Add("hwmap"); } - - if (allowTimeStampCopy) - { - output += " -copyts"; - } } if (filters.Count > 0) @@ -2218,7 +2281,7 @@ namespace MediaBrowser.Controller.MediaEncoding { inputModifier += " " + videoDecoder; - if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1) + if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) { var videoStream = state.VideoStream; var inputWidth = videoStream?.Width; @@ -2227,7 +2290,7 @@ namespace MediaBrowser.Controller.MediaEncoding var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 + if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 && width.HasValue && height.HasValue) { @@ -2525,6 +2588,12 @@ namespace MediaBrowser.Controller.MediaEncoding case "h264": if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) { + // cuvid decoder does not support 10-bit input + if ((videoStream.BitDepth ?? 8) > 8) + { + encodingOptions.HardwareDecodingCodecs = Array.Empty(); + return null; + } return "-c:v h264_cuvid "; } break; @@ -2637,7 +2706,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - if(Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1)) + if (Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1)) return "-hwaccel d3d11va"; else return "-hwaccel dxva2"; @@ -2772,14 +2841,27 @@ namespace MediaBrowser.Controller.MediaEncoding var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasCopyTs = false; + // Add resolution params, if specified if (!hasGraphicalSubs) { var outputSizeParam = GetOutputSizeParam(state, encodingOptions, videoCodec); + args += outputSizeParam; + hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; } + // This is for graphical subs + if (hasGraphicalSubs) + { + var graphicalSubtitleParam = GetGraphicalSubtitleParam(state, encodingOptions, videoCodec); + + args += graphicalSubtitleParam; + + hasCopyTs = graphicalSubtitleParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; + } + if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) { if (!hasCopyTs) @@ -2787,13 +2869,12 @@ namespace MediaBrowser.Controller.MediaEncoding args += " -copyts"; } - args += " -avoid_negative_ts disabled -start_at_zero"; - } + args += " -avoid_negative_ts disabled"; - // This is for internal graphical subs - if (hasGraphicalSubs) - { - args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec); + if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) + { + args += " -start_at_zero"; + } } var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset); @@ -2899,6 +2980,5 @@ namespace MediaBrowser.Controller.MediaEncoding string.Empty, string.Empty).Trim(); } - } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 38ef33cafd..1127a08ded 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -9,8 +9,8 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Session; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; namespace MediaBrowser.Controller.MediaEncoding { diff --git a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs index e560999e8f..15a2580afd 100644 --- a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs +++ b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -12,6 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Refreshes the chapter images. /// - Task RefreshChapterImages(Video video, IDirectoryService directoryService, List chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken); + Task RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index 46933c0465..806478864d 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Net /// /// Inits this instance. /// - void Init(IEnumerable services, IEnumerable listener, IEnumerable urlPrefixes); + void Init(IEnumerable serviceTypes, IEnumerable listener, IEnumerable urlPrefixes); /// /// If set, all requests will respond with this message diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 5a5b7f58f0..eb5a8ded84 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.Persistence /// /// Saves the chapters. /// - void SaveChapters(Guid id, List chapters); + void SaveChapters(Guid id, IReadOnlyList chapters); /// /// Gets the media streams. diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index ca470872b1..b7640c2050 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -66,12 +66,10 @@ namespace MediaBrowser.Controller.Providers return file; } - public List GetFilePaths(string path) - { - return GetFilePaths(path, false); - } + public IReadOnlyList GetFilePaths(string path) + => GetFilePaths(path, false); - public List GetFilePaths(string path, bool clearCache) + public IReadOnlyList GetFilePaths(string path, bool clearCache) { if (clearCache || !_filePathCache.TryGetValue(path, out List result)) { diff --git a/MediaBrowser.Controller/Providers/IDirectoryService.cs b/MediaBrowser.Controller/Providers/IDirectoryService.cs index b304fc3356..949a17740b 100644 --- a/MediaBrowser.Controller/Providers/IDirectoryService.cs +++ b/MediaBrowser.Controller/Providers/IDirectoryService.cs @@ -11,8 +11,8 @@ namespace MediaBrowser.Controller.Providers FileSystemMetadata GetFile(string path); - List GetFilePaths(string path); + IReadOnlyList GetFilePaths(string path); - List GetFilePaths(string path, bool clearCache); + IReadOnlyList GetFilePaths(string path, bool clearCache); } } diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs index f5ee574a2d..2a68f46789 100644 --- a/MediaBrowser.Controller/Sorting/SortExtensions.cs +++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace MediaBrowser.Controller.Sorting { diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 48240d5b62..8d27c33c01 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -1,6 +1,6 @@ using System; -using System.Diagnostics; using System.Collections.Concurrent; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index b3a45991e7..4042e64f63 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -19,8 +19,8 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using System.Diagnostics; namespace MediaBrowser.MediaEncoding.Encoder @@ -426,7 +426,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); + return new ProbeResultNormalizer(_logger, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); } } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index f8047af426..b24d97f4ef 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -9,7 +9,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; @@ -19,13 +18,11 @@ namespace MediaBrowser.MediaEncoding.Probing { private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; - public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, ILocalizationManager localization) + public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization) { _logger = logger; - _fileSystem = fileSystem; _localization = localization; } @@ -40,7 +37,7 @@ namespace MediaBrowser.MediaEncoding.Probing FFProbeHelpers.NormalizeFFProbeResult(data); SetSize(data, info); - var internalStreams = data.Streams ?? new MediaStreamInfo[] { }; + var internalStreams = data.Streams ?? Array.Empty(); info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.Format)) .Where(i => i != null) @@ -539,7 +536,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString)) { - attachment.CodecTag = streamInfo.CodecTagString; + attachment.CodecTag = streamInfo.CodecTagString; } if (streamInfo.Tags != null) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index a6982be5b6..b1397d79db 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -732,6 +732,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles { var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName; + // UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding + if ((path.EndsWith(".ass") || path.EndsWith(".ssa")) + && (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase) + || string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase))) + { + charset = ""; + } + _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path); return charset; @@ -754,10 +762,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles return _httpClient.Get(opts); - case MediaProtocol.File: - return Task.FromResult(File.OpenRead(path)); - default: - throw new ArgumentOutOfRangeException(nameof(protocol)); + case MediaProtocol.File: + return Task.FromResult(File.OpenRead(path)); + default: + throw new ArgumentOutOfRangeException(nameof(protocol)); } } } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index eaf9c4ecbb..648568fd70 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -40,6 +40,7 @@ namespace MediaBrowser.Model.Configuration VaapiDevice = "/dev/dri/renderD128"; H264Crf = 23; H265Crf = 28; + DeinterlaceMethod = "yadif"; EnableHardwareEncoding = true; EnableSubtitleExtraction = true; HardwareDecodingCodecs = new string[] { "h264", "vc1" }; diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index dfb5631807..3107ec2426 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -148,9 +148,9 @@ namespace MediaBrowser.Model.Configuration public bool EnableDashboardResponseCaching { get; set; } /// - /// Allows the dashboard to be served from a custom path. + /// Gets or sets a custom path to serve the dashboard from. /// - /// The dashboard source path. + /// The dashboard source path, or null if the default path should be used. public string DashboardSourcePath { get; set; } /// diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index 6a58b4adcb..8235b72d13 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -1,7 +1,6 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Dlna { diff --git a/MediaBrowser.Model/Dlna/SearchCriteria.cs b/MediaBrowser.Model/Dlna/SearchCriteria.cs index dc6d201aed..394fb9af95 100644 --- a/MediaBrowser.Model/Dlna/SearchCriteria.cs +++ b/MediaBrowser.Model/Dlna/SearchCriteria.cs @@ -2,7 +2,6 @@ using System; using System.Text.RegularExpressions; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Dlna { diff --git a/MediaBrowser.Model/Entities/ChapterInfo.cs b/MediaBrowser.Model/Entities/ChapterInfo.cs index 2903ef61b4..bea7ec1dba 100644 --- a/MediaBrowser.Model/Entities/ChapterInfo.cs +++ b/MediaBrowser.Model/Entities/ChapterInfo.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.Model.Entities /// /// The image path. public string ImagePath { get; set; } + public DateTime ImageDateModified { get; set; } public string ImageTag { get; set; } diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferences.cs index 499baa0588..2cd8bd3065 100644 --- a/MediaBrowser.Model/Entities/DisplayPreferences.cs +++ b/MediaBrowser.Model/Entities/DisplayPreferences.cs @@ -101,7 +101,7 @@ namespace MediaBrowser.Model.Entities /// /// true if [show sidebar]; otherwise, false. public bool ShowSidebar { get; set; } - + /// /// Gets or sets the client /// diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 37f9d7c1ad..e7e8d7cecd 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -69,9 +69,9 @@ namespace MediaBrowser.Model.Entities } } - public string localizedUndefined { get; set; } - public string localizedDefault { get; set; } - public string localizedForced { get; set; } + public string localizedUndefined { get; set; } + public string localizedDefault { get; set; } + public string localizedForced { get; set; } public string DisplayTitle { diff --git a/MediaBrowser.Model/Entities/SeriesStatus.cs b/MediaBrowser.Model/Entities/SeriesStatus.cs index 51351c1355..c77c4a8ad9 100644 --- a/MediaBrowser.Model/Entities/SeriesStatus.cs +++ b/MediaBrowser.Model/Entities/SeriesStatus.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Model.Entities /// The continuing. /// Continuing, - + /// /// The ended. /// diff --git a/MediaBrowser.Model/Entities/SortOrder.cs b/MediaBrowser.Model/Entities/SortOrder.cs index e6cb6fd09e..f3abc06f33 100644 --- a/MediaBrowser.Model/Entities/SortOrder.cs +++ b/MediaBrowser.Model/Entities/SortOrder.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Model.Entities /// The ascending. /// Ascending, - + /// /// The descending. /// diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs index 45e87f137e..3cbfe7d4d7 100644 --- a/MediaBrowser.Providers/Chapters/ChapterManager.cs +++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs @@ -1,36 +1,26 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using MediaBrowser.Controller.Chapters; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; -using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Chapters { public class ChapterManager : IChapterManager { - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IServerConfigurationManager _config; private readonly IItemRepository _itemRepo; - public ChapterManager( - ILibraryManager libraryManager, - ILoggerFactory loggerFactory, - IServerConfigurationManager config, - IItemRepository itemRepo) + public ChapterManager(IItemRepository itemRepo) { - _libraryManager = libraryManager; - _logger = loggerFactory.CreateLogger(nameof(ChapterManager)); - _config = config; _itemRepo = itemRepo; } - public void SaveChapters(string itemId, List chapters) + /// + public void SaveChapters(Guid itemId, IReadOnlyList chapters) { - _itemRepo.SaveChapters(new Guid(itemId), chapters); + _itemRepo.SaveChapters(itemId, chapters); } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index e7b349f67b..7125f34c55 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -1,5 +1,9 @@ +#pragma warning disable CS1591 + using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -897,7 +901,10 @@ namespace MediaBrowser.Providers.Manager return new ExternalUrl { Name = i.Name, - Url = string.Format(i.UrlFormatString, value) + Url = string.Format( + CultureInfo.InvariantCulture, + i.UrlFormatString, + value) }; }).Where(i => i != null).Concat(item.GetRelatedUrls()); @@ -911,11 +918,10 @@ namespace MediaBrowser.Providers.Manager Name = i.Name, Key = i.Key, UrlFormatString = i.UrlFormatString - }); } - private Dictionary _activeRefreshes = new Dictionary(); + private ConcurrentDictionary _activeRefreshes = new ConcurrentDictionary(); public Dictionary GetRefreshQueue() { @@ -927,66 +933,54 @@ namespace MediaBrowser.Providers.Manager { dict[item.Item1] = item.Item1; } + return dict; } } public void OnRefreshStart(BaseItem item) { - //_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); - var id = item.Id; - - lock (_activeRefreshes) - { - _activeRefreshes[id] = 0; - } - + _logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); + _activeRefreshes[item.Id] = 0; RefreshStarted?.Invoke(this, new GenericEventArgs(item)); } public void OnRefreshComplete(BaseItem item) { - //_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); - lock (_activeRefreshes) - { - _activeRefreshes.Remove(item.Id); - } + _logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); + + _activeRefreshes.Remove(item.Id, out _); RefreshCompleted?.Invoke(this, new GenericEventArgs(item)); } public double? GetRefreshProgress(Guid id) { - lock (_activeRefreshes) + if (_activeRefreshes.TryGetValue(id, out double value)) { - if (_activeRefreshes.TryGetValue(id, out double value)) - { - return value; - } - - return null; + return value; } + + return null; } public void OnRefreshProgress(BaseItem item, double progress) { - //_logger.LogInformation("OnRefreshProgress {0} {1}", item.Id.ToString("N", CultureInfo.InvariantCulture), progress); var id = item.Id; + _logger.LogInformation("OnRefreshProgress {0} {1}", id.ToString("N", CultureInfo.InvariantCulture), progress); - lock (_activeRefreshes) - { - if (_activeRefreshes.ContainsKey(id)) - { - _activeRefreshes[id] = progress; + // TODO: Need to hunt down the conditions for this happening + _activeRefreshes.AddOrUpdate( + id, + (_) => throw new Exception( + string.Format( + CultureInfo.InvariantCulture, + "Cannot update refresh progress of item '{0}' ({1}) because a refresh for this item is not running", + item.GetType().Name, + item.Id.ToString("N", CultureInfo.InvariantCulture))), + (_, __) => progress); - RefreshProgress?.Invoke(this, new GenericEventArgs>(new Tuple(item, progress))); - } - else - { - // TODO: Need to hunt down the conditions for this happening - //throw new Exception(string.Format("Refresh for item {0} {1} is not in progress", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture))); - } - } + RefreshProgress?.Invoke(this, new GenericEventArgs>(new Tuple(item, progress))); } private readonly SimplePriorityQueue> _refreshQueue = @@ -1040,10 +1034,9 @@ namespace MediaBrowser.Providers.Manager // Try to throttle this a little bit. await Task.Delay(100).ConfigureAwait(false); - var artist = item as MusicArtist; - var task = artist == null - ? RefreshItem(item, refreshItem.Item2, cancellationToken) - : RefreshArtist(artist, refreshItem.Item2, cancellationToken); + var task = item is MusicArtist artist + ? RefreshArtist(artist, refreshItem.Item2, cancellationToken) + : RefreshItem(item, refreshItem.Item2, cancellationToken); await task.ConfigureAwait(false); } @@ -1125,8 +1118,7 @@ namespace MediaBrowser.Providers.Manager } } - public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, - CancellationToken cancellationToken) + public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken) { return RefreshItem(item, options, cancellationToken); } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 9a366364db..330a4d1e53 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -24,6 +24,18 @@ true + + + + + + + + + + ../jellyfin.ruleset + + diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index db6e49634c..3999025d8e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -194,7 +194,19 @@ namespace MediaBrowser.Providers.MediaInfo FetchShortcutInfo(item); } - var prober = new FFProbeVideoInfo(_logger, _mediaSourceManager, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager, _chapterManager, _libraryManager); + var prober = new FFProbeVideoInfo( + _logger, + _mediaSourceManager, + _mediaEncoder, + _itemRepo, + _blurayExaminer, + _localization, + _encodingManager, + _fileSystem, + _config, + _subtitleManager, + _chapterManager, + _libraryManager); return prober.ProbeVideo(item, options, cancellationToken); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 2b178d4d4e..d2e98a5a94 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; @@ -25,7 +27,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo @@ -33,13 +34,10 @@ namespace MediaBrowser.Providers.MediaInfo public class FFProbeVideoInfo { private readonly ILogger _logger; - private readonly IIsoManager _isoManager; private readonly IMediaEncoder _mediaEncoder; private readonly IItemRepository _itemRepo; private readonly IBlurayExaminer _blurayExaminer; private readonly ILocalizationManager _localization; - private readonly IApplicationPaths _appPaths; - private readonly IJsonSerializer _json; private readonly IEncodingManager _encodingManager; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; @@ -48,16 +46,27 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; - public FFProbeVideoInfo(ILogger logger, IMediaSourceManager mediaSourceManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager) + private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; + + public FFProbeVideoInfo( + ILogger logger, + IMediaSourceManager mediaSourceManager, + IMediaEncoder mediaEncoder, + IItemRepository itemRepo, + IBlurayExaminer blurayExaminer, + ILocalizationManager localization, + IEncodingManager encodingManager, + IFileSystem fileSystem, + IServerConfigurationManager config, + ISubtitleManager subtitleManager, + IChapterManager chapterManager, + ILibraryManager libraryManager) { _logger = logger; - _isoManager = isoManager; _mediaEncoder = mediaEncoder; _itemRepo = itemRepo; _blurayExaminer = blurayExaminer; _localization = localization; - _appPaths = appPaths; - _json = json; _encodingManager = encodingManager; _fileSystem = fileSystem; _config = config; @@ -159,7 +168,7 @@ namespace MediaBrowser.Providers.MediaInfo { List mediaStreams; IReadOnlyList mediaAttachments; - List chapters; + ChapterInfo[] chapters; if (mediaInfo != null) { @@ -177,6 +186,7 @@ namespace MediaBrowser.Providers.MediaInfo { video.RunTimeTicks = mediaInfo.RunTimeTicks; } + video.Size = mediaInfo.Size; if (video.VideoType == VideoType.VideoFile) @@ -189,19 +199,20 @@ namespace MediaBrowser.Providers.MediaInfo { video.Container = null; } + video.Container = mediaInfo.Container; - chapters = mediaInfo.Chapters == null ? new List() : mediaInfo.Chapters.ToList(); + chapters = mediaInfo.Chapters == null ? Array.Empty() : mediaInfo.Chapters; if (blurayInfo != null) { - FetchBdInfo(video, chapters, mediaStreams, blurayInfo); + FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo); } } else { mediaStreams = new List(); mediaAttachments = Array.Empty(); - chapters = new List(); + chapters = Array.Empty(); } await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); @@ -231,9 +242,9 @@ namespace MediaBrowser.Providers.MediaInfo if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || options.MetadataRefreshMode == MetadataRefreshMode.Default) { - if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video)) + if (chapters.Length == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video)) { - AddDummyChapters(video, chapters); + chapters = CreateDummyChapters(video); } NormalizeChapterNames(chapters); @@ -246,28 +257,29 @@ namespace MediaBrowser.Providers.MediaInfo await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false); - _chapterManager.SaveChapters(video.Id.ToString(), chapters); + _chapterManager.SaveChapters(video.Id, chapters); } } - private void NormalizeChapterNames(List chapters) + private void NormalizeChapterNames(ChapterInfo[] chapters) { - var index = 1; - - foreach (var chapter in chapters) + for (int i = 0; i < chapters.Length; i++) { + string name = chapters[i].Name; // Check if the name is empty and/or if the name is a time // Some ripping programs do that. - if (string.IsNullOrWhiteSpace(chapter.Name) || - TimeSpan.TryParse(chapter.Name, out var time)) + if (string.IsNullOrWhiteSpace(name) || + TimeSpan.TryParse(name, out _)) { - chapter.Name = string.Format(_localization.GetLocalizedString("ChapterNameValue"), index.ToString(CultureInfo.InvariantCulture)); + chapters[i].Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("ChapterNameValue"), + (i + 1).ToString(CultureInfo.InvariantCulture)); } - index++; } } - private void FetchBdInfo(BaseItem item, List chapters, List mediaStreams, BlurayDiscInfo blurayInfo) + private void FetchBdInfo(BaseItem item, ref ChapterInfo[] chapters, List mediaStreams, BlurayDiscInfo blurayInfo) { var video = (Video)item; @@ -301,13 +313,15 @@ namespace MediaBrowser.Providers.MediaInfo if (blurayInfo.Chapters != null) { - chapters.Clear(); - - chapters.AddRange(blurayInfo.Chapters.Select(c => new ChapterInfo + double[] brChapter = blurayInfo.Chapters; + chapters = new ChapterInfo[brChapter.Length]; + for (int i = 0; i < brChapter.Length; i++) { - StartPositionTicks = TimeSpan.FromSeconds(c).Ticks - - })); + chapters[i] = new ChapterInfo + { + StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks + }; + } } videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); @@ -495,17 +509,17 @@ namespace MediaBrowser.Providers.MediaInfo var libraryOptions = _libraryManager.GetLibraryOptions(video); string[] subtitleDownloadLanguages; - bool SkipIfEmbeddedSubtitlesPresent; - bool SkipIfAudioTrackMatches; - bool RequirePerfectMatch; + bool skipIfEmbeddedSubtitlesPresent; + bool skipIfAudioTrackMatches; + bool requirePerfectMatch; bool enabled; if (libraryOptions.SubtitleDownloadLanguages == null) { subtitleDownloadLanguages = subtitleOptions.DownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches; - RequirePerfectMatch = subtitleOptions.RequirePerfectMatch; + skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches; + requirePerfectMatch = subtitleOptions.RequirePerfectMatch; enabled = (subtitleOptions.DownloadEpisodeSubtitles && video is Episode) || (subtitleOptions.DownloadMovieSubtitles && @@ -514,9 +528,9 @@ namespace MediaBrowser.Providers.MediaInfo else { subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; enabled = true; } @@ -526,9 +540,9 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager) .DownloadSubtitles(video, currentStreams.Concat(externalSubtitleStreams).ToList(), - SkipIfEmbeddedSubtitlesPresent, - SkipIfAudioTrackMatches, - RequirePerfectMatch, + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, subtitleDownloadLanguages, libraryOptions.DisabledSubtitleFetchers, libraryOptions.SubtitleFetcherOrder, @@ -547,43 +561,45 @@ namespace MediaBrowser.Providers.MediaInfo } /// - /// The dummy chapter duration - /// - private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; - - /// - /// Adds the dummy chapters. + /// Creates dummy chapters. /// /// The video. - /// The chapters. - private void AddDummyChapters(Video video, List chapters) + /// An array of dummy chapters. + private ChapterInfo[] CreateDummyChapters(Video video) { var runtime = video.RunTimeTicks ?? 0; if (runtime < 0) { - throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} has invalid runtime of {1}", + video.Name, + runtime)); } if (runtime < _dummyChapterDuration) { - return; + return Array.Empty(); } - long currentChapterTicks = 0; - var index = 1; - // Limit to 100 chapters just in case there's some incorrect metadata here - while (currentChapterTicks < runtime && index < 100) + int chapterCount = (int)Math.Min(runtime / _dummyChapterDuration, 100); + var chapters = new ChapterInfo[chapterCount]; + + long currentChapterTicks = 0; + for (int i = 0; i < chapterCount; i++) { - chapters.Add(new ChapterInfo + chapters[i] = new ChapterInfo { StartPositionTicks = currentChapterTicks - }); + }; - index++; currentChapterTicks += _dummyChapterDuration; } + + return chapters; } private string[] FetchFromDvdLib(Video item) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 3a936632a0..2615f2dbbb 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -14,6 +14,7 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using MediaBrowser.Model.Globalization; namespace MediaBrowser.Providers.MediaInfo { @@ -25,6 +26,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; private readonly IJsonSerializer _json; + private readonly ILocalizationManager _localization; public SubtitleScheduledTask( ILibraryManager libraryManager, @@ -32,7 +34,8 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, - IMediaSourceManager mediaSourceManager) + IMediaSourceManager mediaSourceManager, + ILocalizationManager localization) { _libraryManager = libraryManager; _config = config; @@ -40,6 +43,7 @@ namespace MediaBrowser.Providers.MediaInfo _logger = logger; _mediaSourceManager = mediaSourceManager; _json = json; + _localization = localization; } private SubtitleOptions GetOptions() @@ -204,11 +208,11 @@ namespace MediaBrowser.Providers.MediaInfo }; } - public string Name => "Download missing subtitles"; + public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles"); - public string Description => "Searches the internet for missing subtitles based on metadata configuration."; + public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription"); - public string Category => "Library"; + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); public string Key => "DownloadSubtitles"; diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index f90a631c65..5a30260a52 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs @@ -31,10 +31,10 @@ namespace MediaBrowser.Providers.Music { return item.IsAccessedByName ? item.GetTaggedItems(new InternalItemsQuery - { - Recursive = true, - IsFolder = false - }) + { + Recursive = true, + IsFolder = false + }) : item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder); } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs index bc973dee5a..31cdaf616a 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -775,7 +776,7 @@ namespace MediaBrowser.Providers.Music _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); _stopWatchMusicBrainz.Restart(); - response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); + response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false); // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs index 6910b4bb44..5843b0c7d9 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs @@ -32,7 +32,11 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz { if (value < Plugin.DefaultRateLimit && _server == Plugin.DefaultServer) { - RateLimit = Plugin.DefaultRateLimit; + _rateLimit = Plugin.DefaultRateLimit; + } + else + { + _rateLimit = value; } } } diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs index a12b4d3ada..b73834155c 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs @@ -60,21 +60,21 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb CancellationToken cancellationToken) { var cacheKey = GenerateKey("series", name, language); - return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken)); + return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken)); } public Task> GetSeriesByIdAsync(int tvdbId, string language, CancellationToken cancellationToken) { var cacheKey = GenerateKey("series", tvdbId, language); - return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetAsync(tvdbId, cancellationToken)); + return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetAsync(tvdbId, cancellationToken)); } public Task> GetEpisodesAsync(int episodeTvdbId, string language, CancellationToken cancellationToken) { var cacheKey = GenerateKey("episode", episodeTvdbId, language); - return TryGetValue(cacheKey, language,() => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken)); + return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken)); } public async Task> GetAllEpisodesAsync(int tvdbId, string language, @@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb CancellationToken cancellationToken) { var cacheKey = GenerateKey("series", imdbId, language); - return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken)); + return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken)); } public Task> GetSeriesByZap2ItIdAsync( diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs index f58c58a2ea..08c2a74d2c 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs @@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb continue; } - var roles = new List {currentActor.Substring(roleStartIndex + 1)}; + var roles = new List { currentActor.Substring(roleStartIndex + 1) }; // Fetch all roles for (var j = i + 1; j < episode.GuestStars.Length; ++j) diff --git a/MediaBrowser.Providers/Tmdb/Models/General/Profile.cs b/MediaBrowser.Providers/Tmdb/Models/General/Profile.cs index 73a049c732..f87d14850c 100644 --- a/MediaBrowser.Providers/Tmdb/Models/General/Profile.cs +++ b/MediaBrowser.Providers/Tmdb/Models/General/Profile.cs @@ -2,10 +2,10 @@ namespace MediaBrowser.Providers.Tmdb.Models.General { public class Profile { - public string File_Path { get; set; } - public int Width { get; set; } - public int Height { get; set; } - public object Iso_639_1 { get; set; } - public double Aspect_Ratio { get; set; } + public string File_Path { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public object Iso_639_1 { get; set; } + public double Aspect_Ratio { get; set; } } } diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs index fbb87d25dd..e2fd5b9e30 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; @@ -36,7 +37,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; private readonly ILogger _logger; - private readonly ILocalizationManager _localization; private readonly ILibraryManager _libraryManager; private readonly IApplicationHost _appHost; @@ -48,7 +48,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger, - ILocalizationManager localization, ILibraryManager libraryManager, IApplicationHost appHost) { @@ -57,7 +56,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies _fileSystem = fileSystem; _configurationManager = configurationManager; _logger = logger; - _localization = localization; _libraryManager = libraryManager; _appHost = appHost; Current = this; @@ -409,15 +407,15 @@ namespace MediaBrowser.Providers.Tmdb.Movies private static long _lastRequestTicks; // The limit is 40 requests per 10 seconds - private static int requestIntervalMs = 300; + private const int RequestIntervalMs = 300; /// /// Gets the movie db response. /// internal async Task GetMovieDbResponse(HttpRequestOptions options) { - var delayTicks = (requestIntervalMs * 10000) - (DateTime.UtcNow.Ticks - _lastRequestTicks); - var delayMs = Math.Min(delayTicks / 10000, requestIntervalMs); + var delayTicks = (RequestIntervalMs * 10000) - (DateTime.UtcNow.Ticks - _lastRequestTicks); + var delayMs = Math.Min(delayTicks / 10000, RequestIntervalMs); if (delayMs > 0) { @@ -430,11 +428,13 @@ namespace MediaBrowser.Providers.Tmdb.Movies options.BufferContent = true; options.UserAgent = _appHost.ApplicationUserAgent; - return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); + return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false); } + /// public int Order => 1; + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClient.GetResponse(new HttpRequestOptions diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 99d8d044f3..133a35527d 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -12,12 +12,14 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Services; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace MediaBrowser.WebDashboard.Api @@ -102,6 +104,7 @@ namespace MediaBrowser.WebDashboard.Api /// The HTTP result factory. private readonly IHttpResultFactory _resultFactory; private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _appConfig; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IFileSystem _fileSystem; private readonly IResourceFileManager _resourceFileManager; @@ -111,6 +114,7 @@ namespace MediaBrowser.WebDashboard.Api /// /// The logger. /// The application host. + /// The application configuration. /// The resource file manager. /// The server configuration manager. /// The file system. @@ -118,6 +122,7 @@ namespace MediaBrowser.WebDashboard.Api public DashboardService( ILogger logger, IServerApplicationHost appHost, + IConfiguration appConfig, IResourceFileManager resourceFileManager, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, @@ -125,6 +130,7 @@ namespace MediaBrowser.WebDashboard.Api { _logger = logger; _appHost = appHost; + _appConfig = appConfig; _resourceFileManager = resourceFileManager; _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; @@ -138,20 +144,30 @@ namespace MediaBrowser.WebDashboard.Api public IRequest Request { get; set; } /// - /// Gets the path for the web interface. + /// Gets the path of the directory containing the static web interface content, or null if the server is not + /// hosting the web client. /// - /// The path for the web interface. - public string DashboardUIPath - { - get - { - if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.DashboardSourcePath)) - { - return _serverConfigurationManager.Configuration.DashboardSourcePath; - } + public string DashboardUIPath => GetDashboardUIPath(_appConfig, _serverConfigurationManager); - return _serverConfigurationManager.ApplicationPaths.WebPath; + /// + /// Gets the path of the directory containing the static web interface content. + /// + /// The app configuration. + /// The server configuration manager. + /// The directory path, or null if the server is not hosting the web client. + public static string GetDashboardUIPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager) + { + if (!appConfig.HostWebClient()) + { + return null; } + + if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath)) + { + return serverConfigManager.Configuration.DashboardSourcePath; + } + + return serverConfigManager.ApplicationPaths.WebPath; } [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] @@ -209,7 +225,7 @@ namespace MediaBrowser.WebDashboard.Api return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); } - return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersionString, null)); + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => PackageCreator.ModifyHtml(false, stream, null, _appHost.ApplicationVersionString, null)); } throw new ResourceNotFoundException(); @@ -307,6 +323,11 @@ namespace MediaBrowser.WebDashboard.Api /// System.Object. public async Task Get(GetDashboardResource request) { + if (!_appConfig.HostWebClient() || DashboardUIPath == null) + { + throw new ResourceNotFoundException(); + } + var path = request.ResourceName; var contentType = MimeTypes.GetMimeType(path); @@ -378,6 +399,11 @@ namespace MediaBrowser.WebDashboard.Api public async Task Get(GetDashboardPackage request) { + if (!_appConfig.HostWebClient() || DashboardUIPath == null) + { + throw new ResourceNotFoundException(); + } + var mode = request.Mode; var inputPath = string.IsNullOrWhiteSpace(mode) ? diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index ad996c5a9b..b7c15a840e 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -31,7 +31,8 @@ namespace MediaBrowser.WebDashboard.Api if (resourceStream != null && IsCoreHtml(virtualPath)) { - resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); + bool isMainIndexPage = string.Equals(virtualPath, "index.html", StringComparison.OrdinalIgnoreCase); + resourceStream = await ModifyHtml(isMainIndexPage, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); } return resourceStream; @@ -47,16 +48,25 @@ namespace MediaBrowser.WebDashboard.Api return string.Equals(Path.GetExtension(path), ".html", StringComparison.OrdinalIgnoreCase); } - // Modifies the HTML by adding common meta tags, css and js. - public async Task ModifyHtml( - string path, + /// + /// Modifies the source HTML stream by adding common meta tags, css and js. + /// + /// True if the stream contains content for the main index page. + /// The stream whose content should be modified. + /// The client mode ('cordova', 'android', etc). + /// The application version. + /// The localization culture. + /// + /// A task that represents the async operation to read and modify the input stream. + /// The task result contains a stream containing the modified HTML content. + /// + public static async Task ModifyHtml( + bool isMainIndexPage, Stream sourceStream, string mode, string appVersion, string localizationCulture) { - var isMainIndexPage = string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase); - string html; using (var reader = new StreamReader(sourceStream, Encoding.UTF8)) { diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs index bb36229c40..39589f0225 100644 --- a/RSSDP/DisposableManagedObjectBase.cs +++ b/RSSDP/DisposableManagedObjectBase.cs @@ -72,7 +72,7 @@ namespace Rssdp.Infrastructure /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes. /// /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification="We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")] public void Dispose() { IsDisposed = true; diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 0aa985a261..18097ef241 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -8,9 +8,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; -using Microsoft.Extensions.Logging; -using MediaBrowser.Model.Net; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Net; +using Microsoft.Extensions.Logging; namespace Rssdp.Infrastructure { diff --git a/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs index 84cdbe360d..e40af703f9 100644 --- a/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy { var fixture = new Fixture().Customize(new AutoMoqCustomization()); _configurationManagerMock = fixture.Freeze>(); - _requirements = new List {new FirstTimeSetupOrElevatedRequirement()}; + _requirements = new List { new FirstTimeSetupOrElevatedRequirement() }; _sut = fixture.Create(); } @@ -58,7 +58,7 @@ namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy private static ClaimsPrincipal SetupUser(string role) { - var claims = new[] {new Claim(ClaimTypes.Role, role)}; + var claims = new[] { new Claim(ClaimTypes.Role, role) }; var identity = new ClaimsIdentity(claims); return new ClaimsPrincipal(identity); } diff --git a/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs index e2beea1ad8..cd05a8328d 100644 --- a/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs @@ -23,9 +23,9 @@ namespace Jellyfin.Api.Tests.Auth.RequiresElevationPolicy [InlineData(UserRoles.Guest, false)] public async Task ShouldHandleRolesCorrectly(string role, bool shouldSucceed) { - var requirements = new List {new RequiresElevationRequirement()}; + var requirements = new List { new RequiresElevationRequirement() }; - var claims = new[] {new Claim(ClaimTypes.Role, role)}; + var claims = new[] { new Claim(ClaimTypes.Role, role) }; var identity = new ClaimsIdentity(claims); var user = new ClaimsPrincipal(identity); diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 77b0561efd..bb0357c067 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 86bb11bd4c..c81b820d9a 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index c63f2e8c63..06c10afe1b 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index b5e4a1287b..52d28206d8 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 9602d9e58a..4a583bcc76 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -7,7 +7,7 @@ - +