From ad51f4f95d09f8ed64fed6013a47f87c01fcca5f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 18:37:14 -0500 Subject: [PATCH 01/68] Add LiveTv configuration extension --- .../LiveTvConfigurationExtensions.cs | 18 ++++++++++++ src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs | 28 ++++++++----------- src/Jellyfin.LiveTv/LiveTvManager.cs | 25 +++++++---------- .../RefreshGuideScheduledTask.cs | 9 ++---- .../TunerHosts/BaseTunerHost.cs | 9 ++---- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 3 +- 6 files changed, 46 insertions(+), 46 deletions(-) create mode 100644 src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs diff --git a/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs new file mode 100644 index 0000000000..67d0e5295b --- /dev/null +++ b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs @@ -0,0 +1,18 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.LiveTv; + +namespace Jellyfin.LiveTv.Configuration; + +/// +/// extensions for Live TV. +/// +public static class LiveTvConfigurationExtensions +{ + /// + /// Gets the . + /// + /// The . + /// The . + public static LiveTvOptions GetLiveTvConfiguration(this IConfigurationManager configurationManager) + => configurationManager.GetConfiguration("livetv"); +} diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs index 439ed965b0..532e1c897d 100644 --- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs +++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs @@ -17,6 +17,7 @@ using System.Xml; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.Extensions; +using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -126,7 +127,7 @@ namespace Jellyfin.LiveTv.EmbyTV { get { - var path = GetConfiguration().RecordingPath; + var path = _config.GetLiveTvConfiguration().RecordingPath; return string.IsNullOrWhiteSpace(path) ? DefaultRecordingPath @@ -189,7 +190,7 @@ namespace Jellyfin.LiveTv.EmbyTV pathsAdded.AddRange(pathsToCreate); } - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); var pathsToRemove = config.MediaLocationsCreated .Except(recordingFolders.SelectMany(i => i.Locations)) @@ -831,7 +832,7 @@ namespace Jellyfin.LiveTv.EmbyTV public Task GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null) { - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); var defaults = new SeriesTimerInfo() { @@ -932,7 +933,7 @@ namespace Jellyfin.LiveTv.EmbyTV private List> GetListingProviders() { - return GetConfiguration().ListingProviders + return _config.GetLiveTvConfiguration().ListingProviders .Select(i => { var provider = _liveTvManager.ListingProviders.FirstOrDefault(l => string.Equals(l.Type, i.Type, StringComparison.OrdinalIgnoreCase)); @@ -1076,7 +1077,7 @@ namespace Jellyfin.LiveTv.EmbyTV private string GetRecordingPath(TimerInfo timer, RemoteSearchResult metadata, out string seriesPath) { var recordPath = RecordingPath; - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); seriesPath = null; if (timer.IsProgramSeries) @@ -1596,7 +1597,7 @@ namespace Jellyfin.LiveTv.EmbyTV private void PostProcessRecording(TimerInfo timer, string path) { - var options = GetConfiguration(); + var options = _config.GetLiveTvConfiguration(); if (string.IsNullOrWhiteSpace(options.RecordingPostProcessor)) { return; @@ -1777,7 +1778,7 @@ namespace Jellyfin.LiveTv.EmbyTV program.AddGenre("News"); } - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); if (config.SaveRecordingNFO) { @@ -2128,11 +2129,6 @@ namespace Jellyfin.LiveTv.EmbyTV return _libraryManager.GetItemList(query).Cast().FirstOrDefault(); } - private LiveTvOptions GetConfiguration() - { - return _config.GetConfiguration("livetv"); - } - private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer) { if (timer.IsManual) @@ -2519,7 +2515,7 @@ namespace Jellyfin.LiveTv.EmbyTV }; } - var customPath = GetConfiguration().MovieRecordingPath; + var customPath = _config.GetLiveTvConfiguration().MovieRecordingPath; if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath)) { yield return new VirtualFolderInfo @@ -2530,7 +2526,7 @@ namespace Jellyfin.LiveTv.EmbyTV }; } - customPath = GetConfiguration().SeriesRecordingPath; + customPath = _config.GetLiveTvConfiguration().SeriesRecordingPath; if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath)) { yield return new VirtualFolderInfo @@ -2546,7 +2542,7 @@ namespace Jellyfin.LiveTv.EmbyTV { var list = new List(); - var configuredDeviceIds = GetConfiguration().TunerHosts + var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) .Select(i => i.DeviceId) .ToList(); @@ -2579,7 +2575,7 @@ namespace Jellyfin.LiveTv.EmbyTV { var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); - var configuredDevices = GetConfiguration().TunerHosts + var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase)) .ToList(); diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs index 4fc9956538..0b3d35731a 100644 --- a/src/Jellyfin.LiveTv/LiveTvManager.cs +++ b/src/Jellyfin.LiveTv/LiveTvManager.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; -using MediaBrowser.Common.Configuration; +using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; @@ -108,11 +108,6 @@ namespace Jellyfin.LiveTv public IReadOnlyList ListingProviders => _listingProviders; - private LiveTvOptions GetConfiguration() - { - return _config.GetConfiguration("livetv"); - } - public string GetEmbyTvActiveRecordingPath(string id) { return EmbyTV.EmbyTV.Current.GetActiveRecordingPath(id); @@ -1302,7 +1297,7 @@ namespace Jellyfin.LiveTv private double GetGuideDays() { - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); if (config.GuideDays.HasValue) { @@ -2125,7 +2120,7 @@ namespace Jellyfin.LiveTv private bool IsLiveTvEnabled(User user) { - return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0); + return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || _config.GetLiveTvConfiguration().TunerHosts.Length > 0); } public IEnumerable GetEnabledUsers() @@ -2187,7 +2182,7 @@ namespace Jellyfin.LiveTv await configurable.Validate(info).ConfigureAwait(false); } - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); var list = config.TunerHosts.ToList(); var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); @@ -2232,7 +2227,7 @@ namespace Jellyfin.LiveTv await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false); - LiveTvOptions config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); var list = config.ListingProviders.ToList(); int index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); @@ -2257,7 +2252,7 @@ namespace Jellyfin.LiveTv public void DeleteListingsProvider(string id) { - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray(); @@ -2267,7 +2262,7 @@ namespace Jellyfin.LiveTv public async Task SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber) { - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase)); listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray(); @@ -2327,7 +2322,7 @@ namespace Jellyfin.LiveTv public Task> GetLineups(string providerType, string providerId, string country, string location) { - var config = GetConfiguration(); + var config = _config.GetLiveTvConfiguration(); if (string.IsNullOrWhiteSpace(providerId)) { @@ -2357,13 +2352,13 @@ namespace Jellyfin.LiveTv public Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken) { - var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); + var info = _config.GetLiveTvConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); return EmbyTV.EmbyTV.Current.GetChannelsForListingsProvider(info, cancellationToken); } public Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken) { - var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); + var info = _config.GetLiveTvConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase)); return provider.GetChannels(info, cancellationToken); } diff --git a/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs b/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs index e58296a70a..18bd61d999 100644 --- a/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs +++ b/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Tasks; namespace Jellyfin.LiveTv @@ -38,7 +38,7 @@ namespace Jellyfin.LiveTv public string Category => "Live TV"; /// - public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0; + public bool IsHidden => _liveTvManager.Services.Count == 1 && _config.GetLiveTvConfiguration().TunerHosts.Length == 0; /// public bool IsEnabled => true; @@ -66,10 +66,5 @@ namespace Jellyfin.LiveTv new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } }; } - - private LiveTvOptions GetConfiguration() - { - return _config.GetConfiguration("livetv"); - } } } diff --git a/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs index 769f196bdf..afc2e4f9ce 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; +using Jellyfin.LiveTv.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -69,7 +69,7 @@ namespace Jellyfin.LiveTv.TunerHosts protected virtual IList GetTunerHosts() { - return GetConfiguration().TunerHosts + return Config.GetLiveTvConfiguration().TunerHosts .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)) .ToList(); } @@ -228,10 +228,5 @@ namespace Jellyfin.LiveTv.TunerHosts return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); } - - protected LiveTvOptions GetConfiguration() - { - return Config.GetConfiguration("livetv"); - } } } diff --git a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index b1b08e992b..a56af65a6f 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -16,6 +16,7 @@ using System.Threading.Tasks; using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; +using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -278,7 +279,7 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun { var list = new List(); - foreach (var host in GetConfiguration().TunerHosts + foreach (var host in Config.GetLiveTvConfiguration().TunerHosts .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))) { try From 063168fc1c5942e1e386eaa6f13755192a0527c4 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 18:38:23 -0500 Subject: [PATCH 02/68] Move LiveTvConfigurationFactory to Configuration folder --- .../LiveTvConfigurationFactory.cs | 24 ++++++++++++++++++ .../LiveTvConfigurationFactory.cs | 25 ------------------- 2 files changed, 24 insertions(+), 25 deletions(-) create mode 100644 src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationFactory.cs delete mode 100644 src/Jellyfin.LiveTv/LiveTvConfigurationFactory.cs diff --git a/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationFactory.cs b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationFactory.cs new file mode 100644 index 0000000000..258afbb056 --- /dev/null +++ b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationFactory.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.LiveTv; + +namespace Jellyfin.LiveTv.Configuration; + +/// +/// implementation for . +/// +public class LiveTvConfigurationFactory : IConfigurationFactory +{ + /// + public IEnumerable GetConfigurations() + { + return new[] + { + new ConfigurationStore + { + ConfigurationType = typeof(LiveTvOptions), + Key = "livetv" + } + }; + } +} diff --git a/src/Jellyfin.LiveTv/LiveTvConfigurationFactory.cs b/src/Jellyfin.LiveTv/LiveTvConfigurationFactory.cs deleted file mode 100644 index ddbf6345c5..0000000000 --- a/src/Jellyfin.LiveTv/LiveTvConfigurationFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.LiveTv; - -namespace Jellyfin.LiveTv -{ - /// - /// implementation for . - /// - public class LiveTvConfigurationFactory : IConfigurationFactory - { - /// - public IEnumerable GetConfigurations() - { - return new ConfigurationStore[] - { - new ConfigurationStore - { - ConfigurationType = typeof(LiveTvOptions), - Key = "livetv" - } - }; - } - } -} From 449365182cb710e5d0d18b1d599a53f76b814dd8 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 19:07:44 -0500 Subject: [PATCH 03/68] Move LiveTV service registration to extension method --- Jellyfin.Server/CoreAppHost.cs | 9 ------- Jellyfin.Server/Startup.cs | 2 ++ .../LiveTvServiceCollectionExtensions.cs | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 5192b9e21b..d5b6e93b8e 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -7,7 +7,6 @@ using Jellyfin.Api.WebSocketListeners; using Jellyfin.Drawing; using Jellyfin.Drawing.Skia; using Jellyfin.LiveTv; -using Jellyfin.LiveTv.Channels; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Devices; @@ -18,18 +17,15 @@ using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.BaseItemManager; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Lyrics; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Trickplay; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.IO; using MediaBrowser.Providers.Lyric; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -101,11 +97,6 @@ namespace Jellyfin.Server serviceCollection.AddScoped(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - foreach (var type in GetExportTypes()) { serviceCollection.AddSingleton(typeof(ILyricProvider), type); diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 1030c6f5fc..7d5f22545d 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -5,6 +5,7 @@ using System.Net.Http.Headers; using System.Net.Mime; using System.Text; using Jellyfin.Api.Middleware; +using Jellyfin.LiveTv.Extensions; using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.Networking; using Jellyfin.Networking.HappyEyeballs; @@ -121,6 +122,7 @@ namespace Jellyfin.Server .AddCheck>(nameof(JellyfinDbContext)); services.AddHlsPlaylistGenerator(); + services.AddLiveTvServices(); services.AddHostedService(); } diff --git a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs new file mode 100644 index 0000000000..5865e88af0 --- /dev/null +++ b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs @@ -0,0 +1,25 @@ +using Jellyfin.LiveTv.Channels; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.DependencyInjection; + +namespace Jellyfin.LiveTv.Extensions; + +/// +/// Live TV extensions for . +/// +public static class LiveTvServiceCollectionExtensions +{ + /// + /// Adds Live TV services to the . + /// + /// The to add services to. + public static void AddLiveTvServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + } +} From 9c2c066e6f62fb713d5bad0fcf5a0b3dcf58e6e1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 20:45:05 -0500 Subject: [PATCH 04/68] Add ITunerHostManager service --- .../ApplicationHost.cs | 2 +- Jellyfin.Api/Controllers/LiveTvController.cs | 19 +- .../LiveTv/ILiveTvManager.cs | 15 +- .../LiveTv/ITunerHostManager.cs | 47 +++++ src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs | 89 +-------- .../LiveTvServiceCollectionExtensions.cs | 6 + src/Jellyfin.LiveTv/LiveTvManager.cs | 76 +------- .../TunerHosts/TunerHostManager.cs | 181 ++++++++++++++++++ 8 files changed, 258 insertions(+), 177 deletions(-) create mode 100644 MediaBrowser.Controller/LiveTv/ITunerHostManager.cs create mode 100644 src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 48d5d8c6a9..5870fed761 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -695,7 +695,7 @@ namespace Emby.Server.Implementations GetExports(), GetExports()); - Resolve().AddParts(GetExports(), GetExports(), GetExports()); + Resolve().AddParts(GetExports(), GetExports()); Resolve().AddParts(GetExports()); } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 5502836239..1a2a3caae8 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -10,7 +10,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; -using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; @@ -43,6 +42,7 @@ namespace Jellyfin.Api.Controllers; public class LiveTvController : BaseJellyfinApiController { private readonly ILiveTvManager _liveTvManager; + private readonly ITunerHostManager _tunerHostManager; private readonly IUserManager _userManager; private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryManager _libraryManager; @@ -55,6 +55,7 @@ public class LiveTvController : BaseJellyfinApiController /// Initializes a new instance of the class. /// /// Instance of the interface. + /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. @@ -64,6 +65,7 @@ public class LiveTvController : BaseJellyfinApiController /// Instance of the interface. public LiveTvController( ILiveTvManager liveTvManager, + ITunerHostManager tunerHostManager, IUserManager userManager, IHttpClientFactory httpClientFactory, ILibraryManager libraryManager, @@ -73,6 +75,7 @@ public class LiveTvController : BaseJellyfinApiController ITranscodeManager transcodeManager) { _liveTvManager = liveTvManager; + _tunerHostManager = tunerHostManager; _userManager = userManager; _httpClientFactory = httpClientFactory; _libraryManager = libraryManager; @@ -951,9 +954,7 @@ public class LiveTvController : BaseJellyfinApiController [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo) - { - return await _liveTvManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false); - } + => await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false); /// /// Deletes a tuner host. @@ -1130,10 +1131,8 @@ public class LiveTvController : BaseJellyfinApiController [HttpGet("TunerHosts/Types")] [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetTunerHostTypes() - { - return _liveTvManager.GetTunerHostTypes(); - } + public IEnumerable GetTunerHostTypes() + => _tunerHostManager.GetTunerHostTypes(); /// /// Discover tuners. @@ -1146,9 +1145,7 @@ public class LiveTvController : BaseJellyfinApiController [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> DiscoverTuners([FromQuery] bool newDevicesOnly = false) - { - return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false); - } + => await _tunerHostManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false); /// /// Gets a live tv recording stream. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 4206159e7b..26f9fe42d3 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -71,9 +71,8 @@ namespace MediaBrowser.Controller.LiveTv /// Adds the parts. /// /// The services. - /// The tuner hosts. /// The listing providers. - void AddParts(IEnumerable services, IEnumerable tunerHosts, IEnumerable listingProviders); + void AddParts(IEnumerable services, IEnumerable listingProviders); /// /// Gets the timer. @@ -253,14 +252,6 @@ namespace MediaBrowser.Controller.LiveTv /// Task. Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList fields, User user = null); - /// - /// Saves the tuner host. - /// - /// Turner host to save. - /// Option to specify that data source has changed. - /// Tuner host information wrapped in a task. - Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true); - /// /// Saves the listing provider. /// @@ -298,10 +289,6 @@ namespace MediaBrowser.Controller.LiveTv Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); - List GetTunerHostTypes(); - - Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); - string GetEmbyTvActiveRecordingPath(string id); ActiveRecordingInfo GetActiveRecordingInfo(string path); diff --git a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs new file mode 100644 index 0000000000..7e4caaf136 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; + +namespace MediaBrowser.Controller.LiveTv; + +/// +/// Service responsible for managing the s. +/// +public interface ITunerHostManager +{ + /// + /// Gets the available s. + /// + IReadOnlyList TunerHosts { get; } + + /// + /// Gets the s for the available s. + /// + /// The s. + IEnumerable GetTunerHostTypes(); + + /// + /// Saves the tuner host. + /// + /// Turner host to save. + /// Option to specify that data source has changed. + /// Tuner host information wrapped in a task. + Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true); + + /// + /// Discovers the available tuners. + /// + /// A value indicating whether to only return new devices. + /// The to use. + /// The s. + Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); + + /// + /// Scans for tuner devices that have changed URLs. + /// + /// The to use. + /// A task that represents the scanning operation. + Task ScanForTunerDeviceChanges(CancellationToken cancellationToken); +} diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs index 532e1c897d..625451fa37 100644 --- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs +++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs @@ -44,8 +44,6 @@ namespace Jellyfin.LiveTv.EmbyTV { public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; - private const int TunerDiscoveryDurationMs = 3000; - private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; private readonly IServerConfigurationManager _config; @@ -54,6 +52,7 @@ namespace Jellyfin.LiveTv.EmbyTV private readonly TimerManager _timerProvider; private readonly LiveTvManager _liveTvManager; + private readonly ITunerHostManager _tunerHostManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _libraryMonitor; @@ -80,6 +79,7 @@ namespace Jellyfin.LiveTv.EmbyTV IHttpClientFactory httpClientFactory, IServerConfigurationManager config, ILiveTvManager liveTvManager, + ITunerHostManager tunerHostManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, @@ -97,6 +97,7 @@ namespace Jellyfin.LiveTv.EmbyTV _providerManager = providerManager; _mediaEncoder = mediaEncoder; _liveTvManager = (LiveTvManager)liveTvManager; + _tunerHostManager = tunerHostManager; _mediaSourceManager = mediaSourceManager; _streamHelper = streamHelper; @@ -310,7 +311,7 @@ namespace Jellyfin.LiveTv.EmbyTV { var list = new List(); - foreach (var hostInstance in _liveTvManager.TunerHosts) + foreach (var hostInstance in _tunerHostManager.TunerHosts) { try { @@ -510,7 +511,7 @@ namespace Jellyfin.LiveTv.EmbyTV { var list = new List(); - foreach (var hostInstance in _liveTvManager.TunerHosts) + foreach (var hostInstance in _tunerHostManager.TunerHosts) { try { @@ -966,7 +967,7 @@ namespace Jellyfin.LiveTv.EmbyTV return result; } - foreach (var hostInstance in _liveTvManager.TunerHosts) + foreach (var hostInstance in _tunerHostManager.TunerHosts) { try { @@ -998,7 +999,7 @@ namespace Jellyfin.LiveTv.EmbyTV throw new ArgumentNullException(nameof(channelId)); } - foreach (var hostInstance in _liveTvManager.TunerHosts) + foreach (var hostInstance in _tunerHostManager.TunerHosts) { try { @@ -2537,81 +2538,5 @@ namespace Jellyfin.LiveTv.EmbyTV }; } } - - public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) - { - var list = new List(); - - var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts - .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) - .Select(i => i.DeviceId) - .ToList(); - - foreach (var host in _liveTvManager.TunerHosts) - { - var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); - - if (newDevicesOnly) - { - discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase)) - .ToList(); - } - - list.AddRange(discoveredDevices); - } - - return list; - } - - public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken) - { - foreach (var host in _liveTvManager.TunerHosts) - { - await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false); - } - } - - private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken) - { - var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); - - var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts - .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - foreach (var device in discoveredDevices) - { - var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase)); - - if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase)) - { - _logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url); - - configuredDevice.Url = device.Url; - await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false); - } - } - } - - private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken) - { - try - { - var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false); - - foreach (var device in discoveredDevices) - { - _logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url); - } - - return discoveredDevices; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error discovering tuner devices"); - - return new List(); - } - } } } diff --git a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs index 5865e88af0..5490547ec3 100644 --- a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs +++ b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ using Jellyfin.LiveTv.Channels; +using Jellyfin.LiveTv.TunerHosts; +using Jellyfin.LiveTv.TunerHosts.HdHomerun; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.IO; @@ -21,5 +23,9 @@ public static class LiveTvServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); } } diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs index 0b3d35731a..71822f3762 100644 --- a/src/Jellyfin.LiveTv/LiveTvManager.cs +++ b/src/Jellyfin.LiveTv/LiveTvManager.cs @@ -57,9 +57,9 @@ namespace Jellyfin.LiveTv private readonly IFileSystem _fileSystem; private readonly IChannelManager _channelManager; private readonly LiveTvDtoService _tvDtoService; + private readonly ITunerHostManager _tunerHostManager; private ILiveTvService[] _services = Array.Empty(); - private ITunerHost[] _tunerHosts = Array.Empty(); private IListingsProvider[] _listingProviders = Array.Empty(); public LiveTvManager( @@ -74,7 +74,8 @@ namespace Jellyfin.LiveTv ILocalizationManager localization, IFileSystem fileSystem, IChannelManager channelManager, - LiveTvDtoService liveTvDtoService) + LiveTvDtoService liveTvDtoService, + ITunerHostManager tunerHostManager) { _config = config; _logger = logger; @@ -88,6 +89,7 @@ namespace Jellyfin.LiveTv _userDataManager = userDataManager; _channelManager = channelManager; _tvDtoService = liveTvDtoService; + _tunerHostManager = tunerHostManager; } public event EventHandler> SeriesTimerCancelled; @@ -104,8 +106,6 @@ namespace Jellyfin.LiveTv /// The services. public IReadOnlyList Services => _services; - public IReadOnlyList TunerHosts => _tunerHosts; - public IReadOnlyList ListingProviders => _listingProviders; public string GetEmbyTvActiveRecordingPath(string id) @@ -113,16 +113,10 @@ namespace Jellyfin.LiveTv return EmbyTV.EmbyTV.Current.GetActiveRecordingPath(id); } - /// - /// Adds the parts. - /// - /// The services. - /// The tuner hosts. - /// The listing providers. - public void AddParts(IEnumerable services, IEnumerable tunerHosts, IEnumerable listingProviders) + /// + public void AddParts(IEnumerable services, IEnumerable listingProviders) { _services = services.ToArray(); - _tunerHosts = tunerHosts.Where(i => i.IsSupported).ToArray(); _listingProviders = listingProviders.ToArray(); @@ -154,20 +148,6 @@ namespace Jellyfin.LiveTv })); } - public List GetTunerHostTypes() - { - return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair - { - Name = i.Name, - Id = i.Type - }).ToList(); - } - - public Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) - { - return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken); - } - public QueryResult GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { var user = query.UserId.Equals(default) @@ -1029,7 +1009,7 @@ namespace Jellyfin.LiveTv { await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false); - await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false); + await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false); var numComplete = 0; double progressPerService = _services.Length == 0 @@ -2166,48 +2146,6 @@ namespace Jellyfin.LiveTv return _libraryManager.GetNamedView(name, CollectionType.livetv, name); } - public async Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) - { - info = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(info)); - - var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); - - if (provider is null) - { - throw new ResourceNotFoundException(); - } - - if (provider is IConfigurableTunerHost configurable) - { - await configurable.Validate(info).ConfigureAwait(false); - } - - var config = _config.GetLiveTvConfiguration(); - - var list = config.TunerHosts.ToList(); - var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); - - if (index == -1 || string.IsNullOrWhiteSpace(info.Id)) - { - info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - list.Add(info); - config.TunerHosts = list.ToArray(); - } - else - { - config.TunerHosts[index] = info; - } - - _config.SaveConfiguration("livetv", config); - - if (dataSourceChanged) - { - _taskManager.CancelIfRunningAndQueue(); - } - - return info; - } - public async Task SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings) { // Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs new file mode 100644 index 0000000000..04eb8293a1 --- /dev/null +++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions; +using Jellyfin.LiveTv.Configuration; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.LiveTv.TunerHosts; + +/// +public class TunerHostManager : ITunerHostManager +{ + private const int TunerDiscoveryDurationMs = 3000; + + private readonly ILogger _logger; + private readonly IConfigurationManager _config; + private readonly ITaskManager _taskManager; + private readonly ITunerHost[] _tunerHosts; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + /// The . + public TunerHostManager( + ILogger logger, + IConfigurationManager config, + ITaskManager taskManager, + IEnumerable tunerHosts) + { + _logger = logger; + _config = config; + _taskManager = taskManager; + _tunerHosts = tunerHosts.Where(t => t.IsSupported).ToArray(); + } + + /// + public IReadOnlyList TunerHosts => _tunerHosts; + + /// + public IEnumerable GetTunerHostTypes() + => _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair + { + Name = i.Name, + Id = i.Type + }); + + /// + public async Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) + { + info = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(info))!; + + var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); + + if (provider is null) + { + throw new ResourceNotFoundException(); + } + + if (provider is IConfigurableTunerHost configurable) + { + await configurable.Validate(info).ConfigureAwait(false); + } + + var config = _config.GetLiveTvConfiguration(); + + var list = config.TunerHosts.ToList(); + var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); + + if (index == -1 || string.IsNullOrWhiteSpace(info.Id)) + { + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + list.Add(info); + config.TunerHosts = list.ToArray(); + } + else + { + config.TunerHosts[index] = info; + } + + _config.SaveConfiguration("livetv", config); + + if (dataSourceChanged) + { + _taskManager.CancelIfRunningAndQueue(); + } + + return info; + } + + /// + public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) + { + var list = new List(); + + var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts + .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) + .Select(i => i.DeviceId) + .ToList(); + + foreach (var host in _tunerHosts) + { + var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); + + if (newDevicesOnly) + { + discoveredDevices = discoveredDevices + .Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + + list.AddRange(discoveredDevices); + } + + return list; + } + + /// + public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken) + { + foreach (var host in _tunerHosts) + { + await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false); + } + } + + private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken) + { + var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); + + var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts + .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var device in discoveredDevices) + { + var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase)); + + if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url); + + configuredDevice.Url = device.Url; + await SaveTunerHost(configuredDevice).ConfigureAwait(false); + } + } + } + + private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken) + { + try + { + var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false); + + foreach (var device in discoveredDevices) + { + _logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url); + } + + return discoveredDevices; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error discovering tuner devices"); + + return new List(); + } + } +} From 3ce16713dd013b5aabdedebafd025f2224d2475f Mon Sep 17 00:00:00 2001 From: JPVenson Date: Sun, 14 Jan 2024 16:50:09 +0100 Subject: [PATCH 05/68] Fixed disposable not being called (#10613) * Fixed disposable not being called * PulledUp usage of IAsyncDisposable for sessioninfo Co-authored-by: Patrick Barron --- .../Session/SessionManager.cs | 12 ++++---- .../Session/ISessionManager.cs | 3 +- .../Session/SessionInfo.cs | 30 +++++-------------- 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6f599e4c7c..f457a78b3b 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -189,7 +189,7 @@ namespace Emby.Server.Implementations.Session _logger); } - private void OnSessionEnded(SessionInfo info) + private async ValueTask OnSessionEnded(SessionInfo info) { EventHelper.QueueEventIfNotNull( SessionEnded, @@ -202,7 +202,7 @@ namespace Emby.Server.Implementations.Session _eventManager.Publish(new SessionEndedEventArgs(info)); - info.Dispose(); + await info.DisposeAsync().ConfigureAwait(false); } /// @@ -301,12 +301,12 @@ namespace Emby.Server.Implementations.Session await _mediaSourceManager.CloseLiveStream(session.PlayState.LiveStreamId).ConfigureAwait(false); } - OnSessionEnded(session); + await OnSessionEnded(session).ConfigureAwait(false); } } /// - public void ReportSessionEnded(string sessionId) + public async ValueTask ReportSessionEnded(string sessionId) { CheckDisposed(); var session = GetSession(sessionId, false); @@ -317,7 +317,7 @@ namespace Emby.Server.Implementations.Session _activeConnections.TryRemove(key, out _); - OnSessionEnded(session); + await OnSessionEnded(session).ConfigureAwait(false); } } @@ -1590,7 +1590,7 @@ namespace Emby.Server.Implementations.Session { try { - ReportSessionEnded(session.Id); + await ReportSessionEnded(session.Id).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 53df7133b5..5a47236f92 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -111,7 +111,8 @@ namespace MediaBrowser.Controller.Session /// Reports the session ended. /// /// The session identifier. - void ReportSessionEnded(string sessionId); + /// Task. + ValueTask ReportSessionEnded(string sessionId); /// /// Sends the general command. diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 3e30c8dc45..3a12a56f1e 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Session /// /// Class SessionInfo. /// - public sealed class SessionInfo : IAsyncDisposable, IDisposable + public sealed class SessionInfo : IAsyncDisposable { // 1 second private const long ProgressIncrement = 10000000; @@ -374,8 +374,7 @@ namespace MediaBrowser.Controller.Session } } - /// - public void Dispose() + public async ValueTask DisposeAsync() { _disposed = true; @@ -386,30 +385,17 @@ namespace MediaBrowser.Controller.Session foreach (var controller in controllers) { - if (controller is IDisposable disposable) + if (controller is IAsyncDisposable disposableAsync) + { + _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name); + await disposableAsync.DisposeAsync().ConfigureAwait(false); + } + else if (controller is IDisposable disposable) { _logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name); disposable.Dispose(); } } } - - public async ValueTask DisposeAsync() - { - _disposed = true; - - StopAutomaticProgress(); - - var controllers = SessionControllers.ToList(); - - foreach (var controller in controllers) - { - if (controller is IAsyncDisposable disposableAsync) - { - _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name); - await disposableAsync.DisposeAsync().ConfigureAwait(false); - } - } - } } } From c23a038ba8f275e061c148ea27e458174a9a7cbe Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 20:56:58 -0500 Subject: [PATCH 06/68] Remove unnecessary allocations in TunerHostManager --- Jellyfin.Api/Controllers/LiveTvController.cs | 4 +-- .../LiveTv/ITunerHostManager.cs | 3 +-- .../TunerHosts/TunerHostManager.cs | 25 +++++++------------ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 1a2a3caae8..27eb88b60f 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1144,8 +1144,8 @@ public class LiveTvController : BaseJellyfinApiController [HttpGet("Tuners/Discover")] [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> DiscoverTuners([FromQuery] bool newDevicesOnly = false) - => await _tunerHostManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false); + public IAsyncEnumerable DiscoverTuners([FromQuery] bool newDevicesOnly = false) + => _tunerHostManager.DiscoverTuners(newDevicesOnly); /// /// Gets a live tv recording stream. diff --git a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs index 7e4caaf136..3df6066f66 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs @@ -34,9 +34,8 @@ public interface ITunerHostManager /// Discovers the available tuners. /// /// A value indicating whether to only return new devices. - /// The to use. /// The s. - Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); + IAsyncEnumerable DiscoverTuners(bool newDevicesOnly); /// /// Scans for tuner devices that have changed URLs. diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs index 04eb8293a1..3e4b0e13fc 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Extensions; using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -101,10 +100,8 @@ public class TunerHostManager : ITunerHostManager } /// - public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) + public async IAsyncEnumerable DiscoverTuners(bool newDevicesOnly) { - var list = new List(); - var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) .Select(i => i.DeviceId) @@ -112,19 +109,15 @@ public class TunerHostManager : ITunerHostManager foreach (var host in _tunerHosts) { - var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); - - if (newDevicesOnly) + var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, CancellationToken.None).ConfigureAwait(false); + foreach (var tuner in discoveredDevices) { - discoveredDevices = discoveredDevices - .Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase)) - .ToList(); + if (!newDevicesOnly || !configuredDeviceIds.Contains(tuner.DeviceId, StringComparer.OrdinalIgnoreCase)) + { + yield return tuner; + } } - - list.AddRange(discoveredDevices); } - - return list; } /// @@ -158,7 +151,7 @@ public class TunerHostManager : ITunerHostManager } } - private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken) + private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken) { try { @@ -175,7 +168,7 @@ public class TunerHostManager : ITunerHostManager { _logger.LogError(ex, "Error discovering tuner devices"); - return new List(); + return Array.Empty(); } } } From 7cd60aefb50d2868eb584c60967d459cdca8f80a Mon Sep 17 00:00:00 2001 From: Martin Vandenbussche Date: Mon, 15 Jan 2024 16:19:47 +0100 Subject: [PATCH 07/68] Adding support for proper trailer STRM URL format, along with the deprecated format --- .../Parsers/BaseNfoParser.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 70e5b66c1e..5408fb6409 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -460,10 +460,28 @@ namespace MediaBrowser.XbmcMetadata.Parsers var trailer = reader.ReadNormalizedString(); if (!string.IsNullOrEmpty(trailer)) { - item.AddTrailerUrl(trailer.Replace( - "plugin://plugin.video.youtube/?action=play_video&videoid=", - BaseNfoSaver.YouTubeWatchUrl, - StringComparison.OrdinalIgnoreCase)); + if (trailer.StartsWith("plugin://plugin.video.youtube/?action=play_video&videoid=", StringComparison.OrdinalIgnoreCase)) + { + // Deprecated format + item.AddTrailerUrl(trailer.Replace( + "plugin://plugin.video.youtube/?action=play_video&videoid=", + BaseNfoSaver.YouTubeWatchUrl, + StringComparison.OrdinalIgnoreCase)); + + var suggested_url = trailer.Replace( + "plugin://plugin.video.youtube/?action=play_video&videoid=", + "plugin://plugin.video.youtube/play/?video_id=", + StringComparison.OrdinalIgnoreCase); + Logger.LogWarning("Trailer URL uses a deprecated format : {URL}. Using {URL_NEW} instead is advised.", [trailer, suggested_url]); + } + else if (trailer.StartsWith("plugin://plugin.video.youtube/play/?video_id=", StringComparison.OrdinalIgnoreCase)) + { + // Proper format + item.AddTrailerUrl(trailer.Replace( + "plugin://plugin.video.youtube/play/?video_id=", + BaseNfoSaver.YouTubeWatchUrl, + StringComparison.OrdinalIgnoreCase)); + } } break; From c03f5ca6c32d14681921fa084618ca89fbe953ab Mon Sep 17 00:00:00 2001 From: Martin Vandenbussche Date: Mon, 15 Jan 2024 16:23:15 +0100 Subject: [PATCH 08/68] Updating CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4e45fd24ad..3752ba1b06 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -175,6 +175,7 @@ - [Chris-Codes-It] (https://github.com/Chris-Codes-It) - [Pithaya](https://github.com/Pithaya) - [Çağrı Sakaoğlu](https://github.com/ilovepilav) + _ [Barasingha](https://github.com/MaVdbussche) # Emby Contributors From c101d287f24cf53bce0674bf70d88ae61da67ed9 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 12 Jan 2024 21:16:08 -0500 Subject: [PATCH 09/68] Remove unused Live TV code --- .../Channels/IChannelManager.cs | 7 - .../LiveTv/ILiveTvService.cs | 13 -- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 7 - .../LiveTv/LiveTvServiceStatusInfo.cs | 54 ----- .../LiveTv/LiveTvTunerInfo.cs | 77 ------- .../LiveTv/RecordingInfo.cs | 210 ------------------ .../LiveTv/RecordingStatusChangedEventArgs.cs | 16 -- MediaBrowser.Model/IO/IStreamHelper.cs | 2 - .../LiveTv/LiveTvTunerStatus.cs | 12 - .../Channels/ChannelManager.cs | 34 --- src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs | 5 - src/Jellyfin.LiveTv/StreamHelper.cs | 30 --- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 186 +--------------- .../TunerHosts/M3UTunerHost.cs | 16 -- 14 files changed, 11 insertions(+), 658 deletions(-) delete mode 100644 MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs delete mode 100644 MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs delete mode 100644 MediaBrowser.Controller/LiveTv/RecordingInfo.cs delete mode 100644 MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs delete mode 100644 MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 8eb27888ab..c8b432ecb2 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -95,12 +95,5 @@ namespace MediaBrowser.Controller.Channels /// The cancellation token. /// The item media sources. IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); - - /// - /// Whether the item supports media probe. - /// - /// The item. - /// Whether media probe should be enabled. - bool EnableMediaProbe(BaseItem item); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index ce34954e3f..52fb156481 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -140,14 +140,6 @@ namespace MediaBrowser.Controller.LiveTv /// Task. Task CloseLiveStream(string id, CancellationToken cancellationToken); - /// - /// Records the live stream. - /// - /// The identifier. - /// The cancellation token. - /// Task. - Task RecordLiveStream(string id, CancellationToken cancellationToken); - /// /// Resets the tuner. /// @@ -180,9 +172,4 @@ namespace MediaBrowser.Controller.LiveTv { Task GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List currentLiveStreams, CancellationToken cancellationToken); } - - public interface ISupportsUpdatingDefaults - { - Task UpdateTimerDefaults(SeriesTimerInfo info, CancellationToken cancellationToken); - } } diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index b983091588..3689a2adf6 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -35,13 +35,6 @@ namespace MediaBrowser.Controller.LiveTv /// Task<IEnumerable<ChannelInfo>>. Task> GetChannels(bool enableCache, CancellationToken cancellationToken); - /// - /// Gets the tuner infos. - /// - /// The cancellation token. - /// Task<List<LiveTvTunerInfo>>. - Task> GetTunerInfos(CancellationToken cancellationToken); - /// /// Gets the channel stream. /// diff --git a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs deleted file mode 100644 index eb3babc180..0000000000 --- a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs +++ /dev/null @@ -1,54 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Controller.LiveTv -{ - public class LiveTvServiceStatusInfo - { - public LiveTvServiceStatusInfo() - { - Tuners = new List(); - IsVisible = true; - } - - /// - /// Gets or sets the status. - /// - /// The status. - public LiveTvServiceStatus Status { get; set; } - - /// - /// Gets or sets the status message. - /// - /// The status message. - public string StatusMessage { get; set; } - - /// - /// Gets or sets the version. - /// - /// The version. - public string Version { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has update available. - /// - /// true if this instance has update available; otherwise, false. - public bool HasUpdateAvailable { get; set; } - - /// - /// Gets or sets the tuners. - /// - /// The tuners. - public List Tuners { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is visible. - /// - /// true if this instance is visible; otherwise, false. - public bool IsVisible { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs deleted file mode 100644 index aa5eb59d16..0000000000 --- a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs +++ /dev/null @@ -1,77 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Controller.LiveTv -{ - public class LiveTvTunerInfo - { - public LiveTvTunerInfo() - { - Clients = new List(); - } - - /// - /// Gets or sets the type of the source. - /// - /// The type of the source. - public string SourceType { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - public LiveTvTunerStatus Status { get; set; } - - /// - /// Gets or sets the channel identifier. - /// - /// The channel identifier. - public string ChannelId { get; set; } - - /// - /// Gets or sets the recording identifier. - /// - /// The recording identifier. - public string RecordingId { get; set; } - - /// - /// Gets or sets the name of the program. - /// - /// The name of the program. - public string ProgramName { get; set; } - - /// - /// Gets or sets the clients. - /// - /// The clients. - public List Clients { get; set; } - - /// - /// Gets or sets a value indicating whether this instance can reset. - /// - /// true if this instance can reset; otherwise, false. - public bool CanReset { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs deleted file mode 100644 index 1dcf7a58fe..0000000000 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ /dev/null @@ -1,210 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Controller.LiveTv -{ - public class RecordingInfo - { - public RecordingInfo() - { - Genres = new List(); - } - - /// - /// Gets or sets the id of the recording. - /// - public string Id { get; set; } - - /// - /// Gets or sets the series timer identifier. - /// - /// The series timer identifier. - public string SeriesTimerId { get; set; } - - /// - /// Gets or sets the timer identifier. - /// - /// The timer identifier. - public string TimerId { get; set; } - - /// - /// Gets or sets the channelId of the recording. - /// - public string ChannelId { get; set; } - - /// - /// Gets or sets the type of the channel. - /// - /// The type of the channel. - public ChannelType ChannelType { get; set; } - - /// - /// Gets or sets the name of the recording. - /// - public string Name { get; set; } - - /// - /// Gets or sets the path. - /// - /// The path. - public string Path { get; set; } - - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - - /// - /// Gets or sets the overview. - /// - /// The overview. - public string Overview { get; set; } - - /// - /// Gets or sets the start date of the recording, in UTC. - /// - public DateTime StartDate { get; set; } - - /// - /// Gets or sets the end date of the recording, in UTC. - /// - public DateTime EndDate { get; set; } - - /// - /// Gets or sets the program identifier. - /// - /// The program identifier. - public string ProgramId { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - public RecordingStatus Status { get; set; } - - /// - /// Gets or sets the genre of the program. - /// - public List Genres { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is repeat. - /// - /// true if this instance is repeat; otherwise, false. - public bool IsRepeat { get; set; } - - /// - /// Gets or sets the episode title. - /// - /// The episode title. - public string EpisodeTitle { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is hd. - /// - /// true if this instance is hd; otherwise, false. - public bool? IsHD { get; set; } - - /// - /// Gets or sets the audio. - /// - /// The audio. - public ProgramAudio? Audio { get; set; } - - /// - /// Gets or sets the original air date. - /// - /// The original air date. - public DateTime? OriginalAirDate { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is movie. - /// - /// true if this instance is movie; otherwise, false. - public bool IsMovie { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is sports. - /// - /// true if this instance is sports; otherwise, false. - public bool IsSports { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is series. - /// - /// true if this instance is series; otherwise, false. - public bool IsSeries { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is live. - /// - /// true if this instance is live; otherwise, false. - public bool IsLive { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is news. - /// - /// true if this instance is news; otherwise, false. - public bool IsNews { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is kids. - /// - /// true if this instance is kids; otherwise, false. - public bool IsKids { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is premiere. - /// - /// true if this instance is premiere; otherwise, false. - public bool IsPremiere { get; set; } - - /// - /// Gets or sets the official rating. - /// - /// The official rating. - public string OfficialRating { get; set; } - - /// - /// Gets or sets the community rating. - /// - /// The community rating. - public float? CommunityRating { get; set; } - - /// - /// Gets or sets the image path if it can be accessed directly from the file system. - /// - /// The image path. - public string ImagePath { get; set; } - - /// - /// Gets or sets the image url if it can be downloaded. - /// - /// The image URL. - public string ImageUrl { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has image. - /// - /// null if [has image] contains no value, true if [has image]; otherwise, false. - public bool? HasImage { get; set; } - - /// - /// Gets or sets the show identifier. - /// - /// The show identifier. - public string ShowId { get; set; } - - /// - /// Gets or sets the date last updated. - /// - /// The date last updated. - public DateTime DateLastUpdated { get; set; } - } -} diff --git a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs b/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs deleted file mode 100644 index 0b943c9396..0000000000 --- a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using MediaBrowser.Model.LiveTv; - -namespace MediaBrowser.Controller.LiveTv -{ - public class RecordingStatusChangedEventArgs : EventArgs - { - public string RecordingId { get; set; } - - public RecordingStatus NewStatus { get; set; } - } -} diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs index f900da5567..034a6bf8bf 100644 --- a/MediaBrowser.Model/IO/IStreamHelper.cs +++ b/MediaBrowser.Model/IO/IStreamHelper.cs @@ -13,8 +13,6 @@ namespace MediaBrowser.Model.IO Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken); - Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken); - Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs b/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs deleted file mode 100644 index 80a6461957..0000000000 --- a/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs +++ /dev/null @@ -1,12 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.LiveTv -{ - public enum LiveTvTunerStatus - { - Available = 0, - Disabled = 1, - RecordingTv = 2, - LiveTv = 3 - } -} diff --git a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs index f5ce75ff4d..51abb503eb 100644 --- a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs +++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs @@ -113,15 +113,6 @@ namespace Jellyfin.LiveTv.Channels return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item); } - /// - public bool EnableMediaProbe(BaseItem item) - { - var internalChannel = _libraryManager.GetItemById(item.ChannelId); - var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); - - return channel is ISupportsMediaProbe; - } - /// public Task DeleteItem(BaseItem item) { @@ -562,18 +553,6 @@ namespace Jellyfin.LiveTv.Channels return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures()); } - /// - /// Checks whether the provided Guid supports external transfer. - /// - /// The Guid. - /// Whether or not the provided Guid supports external transfer. - public bool SupportsExternalTransfer(Guid channelId) - { - var channelProvider = GetChannelProvider(channelId); - - return channelProvider.GetChannelFeatures().SupportsContentDownloading; - } - /// /// Gets the provided channel's supported features. /// @@ -1215,19 +1194,6 @@ namespace Jellyfin.LiveTv.Channels return result; } - internal IChannel GetChannelProvider(Guid internalChannelId) - { - var result = GetAllChannels() - .FirstOrDefault(i => internalChannelId.Equals(GetInternalChannelId(i.Name))); - - if (result is null) - { - throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId); - } - - return result; - } - /// public void Dispose() { diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs index 625451fa37..9eb3aa2fd6 100644 --- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs +++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs @@ -1023,11 +1023,6 @@ namespace Jellyfin.LiveTv.EmbyTV return Task.CompletedTask; } - public Task RecordLiveStream(string id, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - public Task ResetTuner(string id, CancellationToken cancellationToken) { return Task.CompletedTask; diff --git a/src/Jellyfin.LiveTv/StreamHelper.cs b/src/Jellyfin.LiveTv/StreamHelper.cs index ab4b6e9b15..e9644e95e7 100644 --- a/src/Jellyfin.LiveTv/StreamHelper.cs +++ b/src/Jellyfin.LiveTv/StreamHelper.cs @@ -81,36 +81,6 @@ namespace Jellyfin.LiveTv } } - public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) - { - byte[] buffer = ArrayPool.Shared.Rent(IODefaults.CopyToBufferSize); - try - { - int bytesRead; - - while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) - { - var bytesToWrite = Math.Min(bytesRead, copyLength); - - if (bytesToWrite > 0) - { - await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false); - } - - copyLength -= bytesToWrite; - - if (copyLength <= 0) - { - break; - } - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - public async Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken) { byte[] buffer = ArrayPool.Shared.Rent(bufferSize); diff --git a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index a56af65a6f..fef84dd000 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -16,7 +15,6 @@ using System.Threading.Tasks; using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; -using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -164,152 +162,6 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun } } - private async Task> GetTunerInfosHttp(TunerHostInfo info, CancellationToken cancellationToken) - { - var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - - using var response = await _httpClientFactory.CreateClient(NamedClient.Default) - .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken) - .ConfigureAwait(false); - var tuners = new List(); - var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using (stream.ConfigureAwait(false)) - { - using var sr = new StreamReader(stream, System.Text.Encoding.UTF8); - await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false)) - { - string stripedLine = StripXML(line); - if (stripedLine.Contains("Channel", StringComparison.Ordinal)) - { - LiveTvTunerStatus status; - var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); - var name = stripedLine.Substring(0, index - 1); - var currentChannel = stripedLine.Substring(index + 7); - if (string.Equals(currentChannel, "none", StringComparison.Ordinal)) - { - status = LiveTvTunerStatus.LiveTv; - } - else - { - status = LiveTvTunerStatus.Available; - } - - tuners.Add(new LiveTvTunerInfo - { - Name = name, - SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber, - ProgramName = currentChannel, - Status = status - }); - } - } - } - - return tuners; - } - - private static string StripXML(string source) - { - if (string.IsNullOrEmpty(source)) - { - return string.Empty; - } - - char[] buffer = new char[source.Length]; - int bufferIndex = 0; - bool inside = false; - - for (int i = 0; i < source.Length; i++) - { - char let = source[i]; - if (let == '<') - { - inside = true; - continue; - } - - if (let == '>') - { - inside = false; - continue; - } - - if (!inside) - { - buffer[bufferIndex++] = let; - } - } - - return new string(buffer, 0, bufferIndex); - } - - private async Task> GetTunerInfosUdp(TunerHostInfo info, CancellationToken cancellationToken) - { - var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - - var tuners = new List(model.TunerCount); - - var uri = new Uri(GetApiUrl(info)); - - using (var manager = new HdHomerunManager()) - { - // Legacy HdHomeruns are IPv4 only - var ipInfo = IPAddress.Parse(uri.Host); - - for (int i = 0; i < model.TunerCount; i++) - { - var name = string.Format(CultureInfo.InvariantCulture, "Tuner {0}", i + 1); - var currentChannel = "none"; // TODO: Get current channel and map back to Station Id - var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false); - var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv; - tuners.Add(new LiveTvTunerInfo - { - Name = name, - SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber, - ProgramName = currentChannel, - Status = status - }); - } - } - - return tuners; - } - - public async Task> GetTunerInfos(CancellationToken cancellationToken) - { - var list = new List(); - - foreach (var host in Config.GetLiveTvConfiguration().TunerHosts - .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))) - { - try - { - list.AddRange(await GetTunerInfos(host, cancellationToken).ConfigureAwait(false)); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting tuner info"); - } - } - - return list; - } - - public async Task> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken) - { - // TODO Need faster way to determine UDP vs HTTP - var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false); - - var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo; - - if (hdHomerunChannelInfo is null || hdHomerunChannelInfo.IsLegacyTuner) - { - return await GetTunerInfosUdp(info, cancellationToken).ConfigureAwait(false); - } - - return await GetTunerInfosHttp(info, cancellationToken).ConfigureAwait(false); - } - private static string GetApiUrl(TunerHostInfo info) { var url = info.Url; @@ -575,40 +427,24 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun _streamHelper); } - var enableHttpStream = true; - if (enableHttpStream) + mediaSource.Protocol = MediaProtocol.Http; + + var httpUrl = channel.Path; + + // If raw was used, the tuner doesn't support params + if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) { - mediaSource.Protocol = MediaProtocol.Http; - - var httpUrl = channel.Path; - - // If raw was used, the tuner doesn't support params - if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) - { - httpUrl += "?transcode=" + profile; - } - - mediaSource.Path = httpUrl; - - return new SharedHttpStream( - mediaSource, - tunerHost, - streamId, - FileSystem, - _httpClientFactory, - Logger, - Config, - _appHost, - _streamHelper); + httpUrl += "?transcode=" + profile; } - return new HdHomerunUdpStream( + mediaSource.Path = httpUrl; + + return new SharedHttpStream( mediaSource, tunerHost, streamId, - new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), - modelInfo.TunerCount, FileSystem, + _httpClientFactory, Logger, Config, _appHost, diff --git a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs index 7235e65b64..3666d342ed 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs @@ -80,22 +80,6 @@ namespace Jellyfin.LiveTv.TunerHosts .ConfigureAwait(false); } - public Task> GetTunerInfos(CancellationToken cancellationToken) - { - var list = GetTunerHosts() - .Select(i => new LiveTvTunerInfo() - { - Name = Name, - SourceType = Type, - Status = LiveTvTunerStatus.Available, - Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture), - Url = i.Url - }) - .ToList(); - - return Task.FromResult(list); - } - protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList currentLiveStreams, CancellationToken cancellationToken) { var tunerCount = tunerHost.TunerCount; From 70feba6540c1c4f6d9fbfacc974fc18d1da050b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:33:08 -0700 Subject: [PATCH 10/68] chore(deps): update dependency xunit to v2.6.6 (#10867) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 294414ee12..3343971fe4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -85,6 +85,6 @@ - + From fa9b1d5f65d135ff3021cce60ce3ac2867837268 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:33:28 -0700 Subject: [PATCH 11/68] chore(deps): update dependency diacritics to v3.3.27 (#10862) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3343971fe4..b88e43f9db 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,7 +12,7 @@ - + From 021bfd1ecd3b72013926fe7602c03ac9b6b89692 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:34:18 -0700 Subject: [PATCH 12/68] chore(deps): update dependency svg.skia to v1.0.0.10 (#10480) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b88e43f9db..bc87f9fc96 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -71,7 +71,7 @@ - + From 9ff9c8f0c784044119e021ac798b6e40e104de91 Mon Sep 17 00:00:00 2001 From: Martin Vandenbussche <26136934+MaVdbussche@users.noreply.github.com> Date: Tue, 16 Jan 2024 08:50:39 +0100 Subject: [PATCH 13/68] Apply suggestions from code review Co-authored-by: Cody Robibero --- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 5408fb6409..ec2bdb1e72 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -468,11 +468,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers BaseNfoSaver.YouTubeWatchUrl, StringComparison.OrdinalIgnoreCase)); - var suggested_url = trailer.Replace( + var suggestedUrl = trailer.Replace( "plugin://plugin.video.youtube/?action=play_video&videoid=", "plugin://plugin.video.youtube/play/?video_id=", StringComparison.OrdinalIgnoreCase); - Logger.LogWarning("Trailer URL uses a deprecated format : {URL}. Using {URL_NEW} instead is advised.", [trailer, suggested_url]); + Logger.LogWarning("Trailer URL uses a deprecated format : {Url}. Using {NewUrl} instead is advised.", [trailer, suggestedUrl]); } else if (trailer.StartsWith("plugin://plugin.video.youtube/play/?video_id=", StringComparison.OrdinalIgnoreCase)) { From d4ca845a260b0975b6c47287bec8ccfa1e545ed5 Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Tue, 16 Jan 2024 09:00:34 +0000 Subject: [PATCH 14/68] Translated using Weblate (Chinese (Traditional, Hong Kong)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant_HK/ --- Emby.Server.Implementations/Localization/Core/zh-HK.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 66cbee3948..3ab9774c27 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -83,13 +83,13 @@ "UserDeletedWithName": "用戶 {0} 已被移除", "UserDownloadingItemWithValues": "{0} 正在下載 {1}", "UserLockedOutWithName": "用戶 {0} 已被封鎖", - "UserOfflineFromDevice": "{0} 從 {1} 斷開連接", + "UserOfflineFromDevice": "{0} 終止了 {1} 的連接", "UserOnlineFromDevice": "{0} 從 {1} 連線", - "UserPasswordChangedWithName": "{0} 的密碼已被變改", + "UserPasswordChangedWithName": "{0} 的密碼已被更改", "UserPolicyUpdatedWithName": "使用條款已更新為 {0}", "UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}", - "UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 上播放 {1}", - "ValueHasBeenAddedToLibrary": "已添加 {0} 到你的媒體庫", + "UserStoppedPlayingItemWithValues": "{0} 停止在 {2} 上播放 {1}", + "ValueHasBeenAddedToLibrary": "{0} 已被加入至你的媒體庫", "ValueSpecialEpisodeName": "特典 - {0}", "VersionNumber": "版本 {0}", "TaskDownloadMissingSubtitles": "下載欠缺字幕", From bf57faff6506df82ba2302ae08e5f1da5698798e Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 16 Jan 2024 16:20:37 +0000 Subject: [PATCH 15/68] Translated using Weblate (Georgian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ka/ --- Emby.Server.Implementations/Localization/Core/ka.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ka.json b/Emby.Server.Implementations/Localization/Core/ka.json index 5368b8eb6e..2d02522fea 100644 --- a/Emby.Server.Implementations/Localization/Core/ka.json +++ b/Emby.Server.Implementations/Localization/Core/ka.json @@ -4,9 +4,9 @@ "HeaderFavoriteAlbums": "რჩეული ალბომები", "TasksApplicationCategory": "აპლიკაცია", "Albums": "ალბომები", - "AppDeviceValues": "აპი: {0}, მოწყობილობა: {1}", + "AppDeviceValues": "აპლიკაცია: {0}, მოწყობილობა: {1}", "Application": "აპლიკაცია", - "Artists": "შემსრულებლები", + "Artists": "არტისტი", "AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია", "Books": "წიგნები", "Forced": "ძალით", From 244a739675a140be4c6ad88ee8028cdadec4ac58 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 16 Jan 2024 20:24:28 +0100 Subject: [PATCH 16/68] Fix incorrect path check in CleanupCollectionAndPlaylistPathsTask --- .../Tasks/CleanupCollectionAndPlaylistPathsTask.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs index acd4bf9056..812df81922 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs @@ -115,9 +115,10 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask List? itemsToRemove = null; foreach (var linkedChild in folder.LinkedChildren) { - if (!File.Exists(folder.Path)) + var path = linkedChild.Path; + if (!File.Exists(path)) { - _logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, linkedChild.Path); + _logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, path); (itemsToRemove ??= new List()).Add(linkedChild); } } From 59c2ae944ddc0b4231f4e99863cf4c2f2a16e66f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Jan 2024 09:50:35 -0500 Subject: [PATCH 17/68] Add IGuideManager service --- Jellyfin.Api/Controllers/LiveTvController.cs | 8 +- .../LiveTv/IGuideManager.cs | 26 + .../LiveTv/ILiveTvManager.cs | 6 - .../LiveTvServiceCollectionExtensions.cs | 2 + src/Jellyfin.LiveTv/Guide/GuideManager.cs | 713 ++++++++++++++++++ src/Jellyfin.LiveTv/LiveTvManager.cs | 668 +--------------- .../RefreshGuideScheduledTask.cs | 14 +- 7 files changed, 755 insertions(+), 682 deletions(-) create mode 100644 MediaBrowser.Controller/LiveTv/IGuideManager.cs create mode 100644 src/Jellyfin.LiveTv/Guide/GuideManager.cs diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 27eb88b60f..35cb970474 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -42,6 +42,7 @@ namespace Jellyfin.Api.Controllers; public class LiveTvController : BaseJellyfinApiController { private readonly ILiveTvManager _liveTvManager; + private readonly IGuideManager _guideManager; private readonly ITunerHostManager _tunerHostManager; private readonly IUserManager _userManager; private readonly IHttpClientFactory _httpClientFactory; @@ -55,6 +56,7 @@ public class LiveTvController : BaseJellyfinApiController /// Initializes a new instance of the class. /// /// Instance of the interface. + /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. @@ -65,6 +67,7 @@ public class LiveTvController : BaseJellyfinApiController /// Instance of the interface. public LiveTvController( ILiveTvManager liveTvManager, + IGuideManager guideManager, ITunerHostManager tunerHostManager, IUserManager userManager, IHttpClientFactory httpClientFactory, @@ -75,6 +78,7 @@ public class LiveTvController : BaseJellyfinApiController ITranscodeManager transcodeManager) { _liveTvManager = liveTvManager; + _guideManager = guideManager; _tunerHostManager = tunerHostManager; _userManager = userManager; _httpClientFactory = httpClientFactory; @@ -940,9 +944,7 @@ public class LiveTvController : BaseJellyfinApiController [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetGuideInfo() - { - return _liveTvManager.GetGuideInfo(); - } + => _guideManager.GetGuideInfo(); /// /// Adds a tuner host. diff --git a/MediaBrowser.Controller/LiveTv/IGuideManager.cs b/MediaBrowser.Controller/LiveTv/IGuideManager.cs new file mode 100644 index 0000000000..9883b9283c --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/IGuideManager.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.LiveTv; + +namespace MediaBrowser.Controller.LiveTv; + +/// +/// Service responsible for managing the Live TV guide. +/// +public interface IGuideManager +{ + /// + /// Gets the guide information. + /// + /// The . + GuideInfo GetGuideInfo(); + + /// + /// Refresh the guide. + /// + /// The to use. + /// The to use. + /// Task representing the refresh operation. + Task RefreshGuide(IProgress progress, CancellationToken cancellationToken); +} diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 26f9fe42d3..2dbc2cf82e 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -174,12 +174,6 @@ namespace MediaBrowser.Controller.LiveTv /// Task. Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken); - /// - /// Gets the guide information. - /// - /// GuideInfo. - GuideInfo GetGuideInfo(); - /// /// Gets the recommended programs. /// diff --git a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs index 5490547ec3..21dab69e05 100644 --- a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs +++ b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Jellyfin.LiveTv.Channels; +using Jellyfin.LiveTv.Guide; using Jellyfin.LiveTv.TunerHosts; using Jellyfin.LiveTv.TunerHosts.HdHomerun; using MediaBrowser.Controller.Channels; @@ -24,6 +25,7 @@ public static class LiveTvServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs new file mode 100644 index 0000000000..21b41e9ccd --- /dev/null +++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs @@ -0,0 +1,713 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.LiveTv.Configuration; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Progress; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.LiveTv; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.LiveTv.Guide; + +/// +public class GuideManager : IGuideManager +{ + private const int MaxGuideDays = 14; + private const string EtagKey = "ProgramEtag"; + private const string ExternalServiceTag = "ExternalServiceId"; + + private readonly ILogger _logger; + private readonly IConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly IItemRepository _itemRepo; + private readonly ILibraryManager _libraryManager; + private readonly ILiveTvManager _liveTvManager; + private readonly ITunerHostManager _tunerHostManager; + private readonly LiveTvDtoService _tvDtoService; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + public GuideManager( + ILogger logger, + IConfigurationManager config, + IFileSystem fileSystem, + IItemRepository itemRepo, + ILibraryManager libraryManager, + ILiveTvManager liveTvManager, + ITunerHostManager tunerHostManager, + LiveTvDtoService tvDtoService) + { + _logger = logger; + _config = config; + _fileSystem = fileSystem; + _itemRepo = itemRepo; + _libraryManager = libraryManager; + _liveTvManager = liveTvManager; + _tunerHostManager = tunerHostManager; + _tvDtoService = tvDtoService; + } + + /// + public GuideInfo GetGuideInfo() + { + var startDate = DateTime.UtcNow; + var endDate = startDate.AddDays(GetGuideDays()); + + return new GuideInfo + { + StartDate = startDate, + EndDate = endDate + }; + } + + /// + public async Task RefreshGuide(IProgress progress, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(progress); + + await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false); + + await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false); + + var numComplete = 0; + double progressPerService = _liveTvManager.Services.Count == 0 + ? 0 + : 1.0 / _liveTvManager.Services.Count; + + var newChannelIdList = new List(); + var newProgramIdList = new List(); + + var cleanDatabase = true; + + foreach (var service in _liveTvManager.Services) + { + cancellationToken.ThrowIfCancellationRequested(); + + _logger.LogDebug("Refreshing guide from {Name}", service.Name); + + try + { + var innerProgress = new ActionableProgress(); + innerProgress.RegisterAction(p => progress.Report(p * progressPerService)); + + var idList = await RefreshChannelsInternal(service, innerProgress, cancellationToken).ConfigureAwait(false); + + newChannelIdList.AddRange(idList.Item1); + newProgramIdList.AddRange(idList.Item2); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + cleanDatabase = false; + _logger.LogError(ex, "Error refreshing channels for service"); + } + + numComplete++; + double percent = numComplete; + percent /= _liveTvManager.Services.Count; + + progress.Report(100 * percent); + } + + if (cleanDatabase) + { + CleanDatabase(newChannelIdList.ToArray(), [BaseItemKind.LiveTvChannel], progress, cancellationToken); + CleanDatabase(newProgramIdList.ToArray(), [BaseItemKind.LiveTvProgram], progress, cancellationToken); + } + + var coreService = _liveTvManager.Services.OfType().FirstOrDefault(); + if (coreService is not null) + { + await coreService.RefreshSeriesTimers(cancellationToken).ConfigureAwait(false); + await coreService.RefreshTimers(cancellationToken).ConfigureAwait(false); + } + + // Load these now which will prefetch metadata + var dtoOptions = new DtoOptions(); + var fields = dtoOptions.Fields.ToList(); + dtoOptions.Fields = fields.ToArray(); + + progress.Report(100); + } + + private double GetGuideDays() + { + var config = _config.GetLiveTvConfiguration(); + + return config.GuideDays.HasValue + ? Math.Max(1, Math.Min(config.GuideDays.Value, MaxGuideDays)) + : 7; + } + + private async Task, List>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress progress, CancellationToken cancellationToken) + { + progress.Report(10); + + var allChannelsList = (await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false)) + .Select(i => new Tuple(service.Name, i)) + .ToList(); + + var list = new List(); + + var numComplete = 0; + var parentFolder = _liveTvManager.GetInternalLiveTvFolder(cancellationToken); + + foreach (var channelInfo in allChannelsList) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken).ConfigureAwait(false); + + list.Add(item); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting channel information for {Name}", channelInfo.Item2.Name); + } + + numComplete++; + double percent = numComplete; + percent /= allChannelsList.Count; + + progress.Report((5 * percent) + 10); + } + + progress.Report(15); + + numComplete = 0; + var programs = new List(); + var channels = new List(); + + var guideDays = GetGuideDays(); + + _logger.LogInformation("Refreshing guide with {0} days of guide data", guideDays); + + foreach (var currentChannel in list) + { + cancellationToken.ThrowIfCancellationRequested(); + channels.Add(currentChannel.Id); + + try + { + var start = DateTime.UtcNow.AddHours(-1); + var end = start.AddDays(guideDays); + + var isMovie = false; + var isSports = false; + var isNews = false; + var isKids = false; + var isSeries = false; + + var channelPrograms = (await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false)).ToList(); + + var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.LiveTvProgram], + ChannelIds = new[] { currentChannel.Id }, + DtoOptions = new DtoOptions(true) + }).Cast().ToDictionary(i => i.Id); + + var newPrograms = new List(); + var updatedPrograms = new List(); + + foreach (var program in channelPrograms) + { + var (programItem, isNew, isUpdated) = GetProgram(program, existingPrograms, currentChannel); + if (isNew) + { + newPrograms.Add(programItem); + } + else if (isUpdated) + { + updatedPrograms.Add(programItem); + } + + programs.Add(programItem.Id); + + isMovie |= program.IsMovie; + isSeries |= program.IsSeries; + isSports |= program.IsSports; + isNews |= program.IsNews; + isKids |= program.IsKids; + } + + _logger.LogDebug("Channel {0} has {1} new programs and {2} updated programs", currentChannel.Name, newPrograms.Count, updatedPrograms.Count); + + if (newPrograms.Count > 0) + { + _libraryManager.CreateItems(newPrograms, null, cancellationToken); + } + + if (updatedPrograms.Count > 0) + { + await _libraryManager.UpdateItemsAsync( + updatedPrograms, + currentChannel, + ItemUpdateType.MetadataImport, + cancellationToken).ConfigureAwait(false); + } + + currentChannel.IsMovie = isMovie; + currentChannel.IsNews = isNews; + currentChannel.IsSports = isSports; + currentChannel.IsSeries = isSeries; + + if (isKids) + { + currentChannel.AddTag("Kids"); + } + + await currentChannel.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + await currentChannel.RefreshMetadata( + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ForceSave = true + }, + cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting programs for channel {Name}", currentChannel.Name); + } + + numComplete++; + double percent = numComplete / (double)allChannelsList.Count; + + progress.Report((85 * percent) + 15); + } + + progress.Report(100); + return new Tuple, List>(channels, programs); + } + + private void CleanDatabase(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress progress, CancellationToken cancellationToken) + { + var list = _itemRepo.GetItemIdsList(new InternalItemsQuery + { + IncludeItemTypes = validTypes, + DtoOptions = new DtoOptions(false) + }); + + var numComplete = 0; + + foreach (var itemId in list) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (itemId.Equals(default)) + { + // Somehow some invalid data got into the db. It probably predates the boundary checking + continue; + } + + if (!currentIdList.Contains(itemId)) + { + var item = _libraryManager.GetItemById(itemId); + + if (item is not null) + { + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false, + DeleteFromExternalProvider = false + }, + false); + } + } + + numComplete++; + double percent = numComplete / (double)list.Count; + + progress.Report(100 * percent); + } + } + + private async Task GetChannel( + ChannelInfo channelInfo, + string serviceName, + BaseItem parentFolder, + CancellationToken cancellationToken) + { + var parentFolderId = parentFolder.Id; + var isNew = false; + var forceUpdate = false; + + var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id); + + if (_libraryManager.GetItemById(id) is not LiveTvChannel item) + { + item = new LiveTvChannel + { + Name = channelInfo.Name, + Id = id, + DateCreated = DateTime.UtcNow + }; + + isNew = true; + } + + if (channelInfo.Tags is not null) + { + if (!channelInfo.Tags.SequenceEqual(item.Tags, StringComparer.OrdinalIgnoreCase)) + { + isNew = true; + } + + item.Tags = channelInfo.Tags; + } + + if (!item.ParentId.Equals(parentFolderId)) + { + isNew = true; + } + + item.ParentId = parentFolderId; + + item.ChannelType = channelInfo.ChannelType; + item.ServiceName = serviceName; + + if (!string.Equals(item.GetProviderId(ExternalServiceTag), serviceName, StringComparison.OrdinalIgnoreCase)) + { + forceUpdate = true; + } + + item.SetProviderId(ExternalServiceTag, serviceName); + + if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal)) + { + forceUpdate = true; + } + + item.ExternalId = channelInfo.Id; + + if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal)) + { + forceUpdate = true; + } + + item.Number = channelInfo.Number; + + if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal)) + { + forceUpdate = true; + } + + item.Name = channelInfo.Name; + + if (!item.HasImage(ImageType.Primary)) + { + if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath)) + { + item.SetImagePath(ImageType.Primary, channelInfo.ImagePath); + forceUpdate = true; + } + else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl)) + { + item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl); + forceUpdate = true; + } + } + + if (isNew) + { + _libraryManager.CreateItem(item, parentFolder); + } + else if (forceUpdate) + { + await _libraryManager.UpdateItemAsync(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + } + + return item; + } + + private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram( + ProgramInfo info, + Dictionary allExistingPrograms, + LiveTvChannel channel) + { + var id = _tvDtoService.GetInternalProgramId(info.Id); + + var isNew = false; + var forceUpdate = false; + + if (!allExistingPrograms.TryGetValue(id, out var item)) + { + isNew = true; + item = new LiveTvProgram + { + Name = info.Name, + Id = id, + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow + }; + + if (!string.IsNullOrEmpty(info.Etag)) + { + item.SetProviderId(EtagKey, info.Etag); + } + } + + if (!string.Equals(info.ShowId, item.ShowId, StringComparison.OrdinalIgnoreCase)) + { + item.ShowId = info.ShowId; + forceUpdate = true; + } + + var seriesId = info.SeriesId; + + if (!item.ParentId.Equals(channel.Id)) + { + forceUpdate = true; + } + + item.ParentId = channel.Id; + + item.Audio = info.Audio; + item.ChannelId = channel.Id; + item.CommunityRating ??= info.CommunityRating; + if ((item.CommunityRating ?? 0).Equals(0)) + { + item.CommunityRating = null; + } + + item.EpisodeTitle = info.EpisodeTitle; + item.ExternalId = info.Id; + + if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal)) + { + forceUpdate = true; + } + + item.ExternalSeriesId = seriesId; + + var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle); + + if (isSeries || !string.IsNullOrEmpty(info.EpisodeTitle)) + { + item.SeriesName = info.Name; + } + + var tags = new List(); + if (info.IsLive) + { + tags.Add("Live"); + } + + if (info.IsPremiere) + { + tags.Add("Premiere"); + } + + if (info.IsNews) + { + tags.Add("News"); + } + + if (info.IsSports) + { + tags.Add("Sports"); + } + + if (info.IsKids) + { + tags.Add("Kids"); + } + + if (info.IsRepeat) + { + tags.Add("Repeat"); + } + + if (info.IsMovie) + { + tags.Add("Movie"); + } + + if (isSeries) + { + tags.Add("Series"); + } + + item.Tags = tags.ToArray(); + + item.Genres = info.Genres.ToArray(); + + if (info.IsHD ?? false) + { + item.Width = 1280; + item.Height = 720; + } + + item.IsMovie = info.IsMovie; + item.IsRepeat = info.IsRepeat; + + if (item.IsSeries != isSeries) + { + forceUpdate = true; + } + + item.IsSeries = isSeries; + + item.Name = info.Name; + item.OfficialRating ??= info.OfficialRating; + item.Overview ??= info.Overview; + item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; + item.ProviderIds = info.ProviderIds; + + foreach (var providerId in info.SeriesProviderIds) + { + info.ProviderIds["Series" + providerId.Key] = providerId.Value; + } + + if (item.StartDate != info.StartDate) + { + forceUpdate = true; + } + + item.StartDate = info.StartDate; + + if (item.EndDate != info.EndDate) + { + forceUpdate = true; + } + + item.EndDate = info.EndDate; + + item.ProductionYear = info.ProductionYear; + + if (!isSeries || info.IsRepeat) + { + item.PremiereDate = info.OriginalAirDate; + } + + item.IndexNumber = info.EpisodeNumber; + item.ParentIndexNumber = info.SeasonNumber; + + if (!item.HasImage(ImageType.Primary)) + { + if (!string.IsNullOrWhiteSpace(info.ImagePath)) + { + item.SetImage( + new ItemImageInfo + { + Path = info.ImagePath, + Type = ImageType.Primary + }, + 0); + } + else if (!string.IsNullOrWhiteSpace(info.ImageUrl)) + { + item.SetImage( + new ItemImageInfo + { + Path = info.ImageUrl, + Type = ImageType.Primary + }, + 0); + } + } + + if (!item.HasImage(ImageType.Thumb)) + { + if (!string.IsNullOrWhiteSpace(info.ThumbImageUrl)) + { + item.SetImage( + new ItemImageInfo + { + Path = info.ThumbImageUrl, + Type = ImageType.Thumb + }, + 0); + } + } + + if (!item.HasImage(ImageType.Logo)) + { + if (!string.IsNullOrWhiteSpace(info.LogoImageUrl)) + { + item.SetImage( + new ItemImageInfo + { + Path = info.LogoImageUrl, + Type = ImageType.Logo + }, + 0); + } + } + + if (!item.HasImage(ImageType.Backdrop)) + { + if (!string.IsNullOrWhiteSpace(info.BackdropImageUrl)) + { + item.SetImage( + new ItemImageInfo + { + Path = info.BackdropImageUrl, + Type = ImageType.Backdrop + }, + 0); + } + } + + var isUpdated = false; + if (isNew) + { + } + else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag)) + { + isUpdated = true; + } + else + { + var etag = info.Etag; + + if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase)) + { + item.SetProviderId(EtagKey, etag); + isUpdated = true; + } + } + + if (isNew || isUpdated) + { + item.OnMetadataChanged(); + } + + return (item, isNew, isUpdated); + } +} diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs index 71822f3762..e8fb02c4f8 100644 --- a/src/Jellyfin.LiveTv/LiveTvManager.cs +++ b/src/Jellyfin.LiveTv/LiveTvManager.cs @@ -14,20 +14,16 @@ using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; @@ -40,24 +36,16 @@ namespace Jellyfin.LiveTv /// public class LiveTvManager : ILiveTvManager { - private const int MaxGuideDays = 14; - private const string ExternalServiceTag = "ExternalServiceId"; - - private const string EtagKey = "ProgramEtag"; - private readonly IServerConfigurationManager _config; private readonly ILogger _logger; - private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; private readonly ILocalizationManager _localization; - private readonly IFileSystem _fileSystem; private readonly IChannelManager _channelManager; private readonly LiveTvDtoService _tvDtoService; - private readonly ITunerHostManager _tunerHostManager; private ILiveTvService[] _services = Array.Empty(); private IListingsProvider[] _listingProviders = Array.Empty(); @@ -65,31 +53,25 @@ namespace Jellyfin.LiveTv public LiveTvManager( IServerConfigurationManager config, ILogger logger, - IItemRepository itemRepo, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, - IFileSystem fileSystem, IChannelManager channelManager, - LiveTvDtoService liveTvDtoService, - ITunerHostManager tunerHostManager) + LiveTvDtoService liveTvDtoService) { _config = config; _logger = logger; - _itemRepo = itemRepo; _userManager = userManager; _libraryManager = libraryManager; _taskManager = taskManager; _localization = localization; - _fileSystem = fileSystem; _dtoService = dtoService; _userDataManager = userDataManager; _channelManager = channelManager; _tvDtoService = liveTvDtoService; - _tunerHostManager = tunerHostManager; } public event EventHandler> SeriesTimerCancelled; @@ -400,355 +382,6 @@ namespace Jellyfin.LiveTv } } - private async Task GetChannelAsync(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken) - { - var parentFolderId = parentFolder.Id; - var isNew = false; - var forceUpdate = false; - - var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id); - - var item = _libraryManager.GetItemById(id) as LiveTvChannel; - - if (item is null) - { - item = new LiveTvChannel - { - Name = channelInfo.Name, - Id = id, - DateCreated = DateTime.UtcNow - }; - - isNew = true; - } - - if (channelInfo.Tags is not null) - { - if (!channelInfo.Tags.SequenceEqual(item.Tags, StringComparer.OrdinalIgnoreCase)) - { - isNew = true; - } - - item.Tags = channelInfo.Tags; - } - - if (!item.ParentId.Equals(parentFolderId)) - { - isNew = true; - } - - item.ParentId = parentFolderId; - - item.ChannelType = channelInfo.ChannelType; - item.ServiceName = serviceName; - - if (!string.Equals(item.GetProviderId(ExternalServiceTag), serviceName, StringComparison.OrdinalIgnoreCase)) - { - forceUpdate = true; - } - - item.SetProviderId(ExternalServiceTag, serviceName); - - if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal)) - { - forceUpdate = true; - } - - item.ExternalId = channelInfo.Id; - - if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal)) - { - forceUpdate = true; - } - - item.Number = channelInfo.Number; - - if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal)) - { - forceUpdate = true; - } - - item.Name = channelInfo.Name; - - if (!item.HasImage(ImageType.Primary)) - { - if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath)) - { - item.SetImagePath(ImageType.Primary, channelInfo.ImagePath); - forceUpdate = true; - } - else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl)) - { - item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl); - forceUpdate = true; - } - } - - if (isNew) - { - _libraryManager.CreateItem(item, parentFolder); - } - else if (forceUpdate) - { - await _libraryManager.UpdateItemAsync(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); - } - - return item; - } - - private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram(ProgramInfo info, Dictionary allExistingPrograms, LiveTvChannel channel) - { - var id = _tvDtoService.GetInternalProgramId(info.Id); - - var isNew = false; - var forceUpdate = false; - - if (!allExistingPrograms.TryGetValue(id, out LiveTvProgram item)) - { - isNew = true; - item = new LiveTvProgram - { - Name = info.Name, - Id = id, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow - }; - - if (!string.IsNullOrEmpty(info.Etag)) - { - item.SetProviderId(EtagKey, info.Etag); - } - } - - if (!string.Equals(info.ShowId, item.ShowId, StringComparison.OrdinalIgnoreCase)) - { - item.ShowId = info.ShowId; - forceUpdate = true; - } - - var seriesId = info.SeriesId; - - if (!item.ParentId.Equals(channel.Id)) - { - forceUpdate = true; - } - - item.ParentId = channel.Id; - - item.Audio = info.Audio; - item.ChannelId = channel.Id; - item.CommunityRating ??= info.CommunityRating; - if ((item.CommunityRating ?? 0).Equals(0)) - { - item.CommunityRating = null; - } - - item.EpisodeTitle = info.EpisodeTitle; - item.ExternalId = info.Id; - - if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal)) - { - forceUpdate = true; - } - - item.ExternalSeriesId = seriesId; - - var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle); - - if (isSeries || !string.IsNullOrEmpty(info.EpisodeTitle)) - { - item.SeriesName = info.Name; - } - - var tags = new List(); - if (info.IsLive) - { - tags.Add("Live"); - } - - if (info.IsPremiere) - { - tags.Add("Premiere"); - } - - if (info.IsNews) - { - tags.Add("News"); - } - - if (info.IsSports) - { - tags.Add("Sports"); - } - - if (info.IsKids) - { - tags.Add("Kids"); - } - - if (info.IsRepeat) - { - tags.Add("Repeat"); - } - - if (info.IsMovie) - { - tags.Add("Movie"); - } - - if (isSeries) - { - tags.Add("Series"); - } - - item.Tags = tags.ToArray(); - - item.Genres = info.Genres.ToArray(); - - if (info.IsHD ?? false) - { - item.Width = 1280; - item.Height = 720; - } - - item.IsMovie = info.IsMovie; - item.IsRepeat = info.IsRepeat; - - if (item.IsSeries != isSeries) - { - forceUpdate = true; - } - - item.IsSeries = isSeries; - - item.Name = info.Name; - item.OfficialRating ??= info.OfficialRating; - item.Overview ??= info.Overview; - item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; - item.ProviderIds = info.ProviderIds; - - foreach (var providerId in info.SeriesProviderIds) - { - info.ProviderIds["Series" + providerId.Key] = providerId.Value; - } - - if (item.StartDate != info.StartDate) - { - forceUpdate = true; - } - - item.StartDate = info.StartDate; - - if (item.EndDate != info.EndDate) - { - forceUpdate = true; - } - - item.EndDate = info.EndDate; - - item.ProductionYear = info.ProductionYear; - - if (!isSeries || info.IsRepeat) - { - item.PremiereDate = info.OriginalAirDate; - } - - item.IndexNumber = info.EpisodeNumber; - item.ParentIndexNumber = info.SeasonNumber; - - if (!item.HasImage(ImageType.Primary)) - { - if (!string.IsNullOrWhiteSpace(info.ImagePath)) - { - item.SetImage( - new ItemImageInfo - { - Path = info.ImagePath, - Type = ImageType.Primary - }, - 0); - } - else if (!string.IsNullOrWhiteSpace(info.ImageUrl)) - { - item.SetImage( - new ItemImageInfo - { - Path = info.ImageUrl, - Type = ImageType.Primary - }, - 0); - } - } - - if (!item.HasImage(ImageType.Thumb)) - { - if (!string.IsNullOrWhiteSpace(info.ThumbImageUrl)) - { - item.SetImage( - new ItemImageInfo - { - Path = info.ThumbImageUrl, - Type = ImageType.Thumb - }, - 0); - } - } - - if (!item.HasImage(ImageType.Logo)) - { - if (!string.IsNullOrWhiteSpace(info.LogoImageUrl)) - { - item.SetImage( - new ItemImageInfo - { - Path = info.LogoImageUrl, - Type = ImageType.Logo - }, - 0); - } - } - - if (!item.HasImage(ImageType.Backdrop)) - { - if (!string.IsNullOrWhiteSpace(info.BackdropImageUrl)) - { - item.SetImage( - new ItemImageInfo - { - Path = info.BackdropImageUrl, - Type = ImageType.Backdrop - }, - 0); - } - } - - var isUpdated = false; - if (isNew) - { - } - else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag)) - { - isUpdated = true; - } - else - { - var etag = info.Etag; - - if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase)) - { - item.SetProviderId(EtagKey, etag); - isUpdated = true; - } - } - - if (isNew || isUpdated) - { - item.OnMetadataChanged(); - } - - return (item, isNew, isUpdated); - } - public async Task GetProgram(string id, CancellationToken cancellationToken, User user = null) { var program = _libraryManager.GetItemById(id); @@ -1000,293 +633,6 @@ namespace Jellyfin.LiveTv } } - internal Task RefreshChannels(IProgress progress, CancellationToken cancellationToken) - { - return RefreshChannelsInternal(progress, cancellationToken); - } - - private async Task RefreshChannelsInternal(IProgress progress, CancellationToken cancellationToken) - { - await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false); - - await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false); - - var numComplete = 0; - double progressPerService = _services.Length == 0 - ? 0 - : 1.0 / _services.Length; - - var newChannelIdList = new List(); - var newProgramIdList = new List(); - - var cleanDatabase = true; - - foreach (var service in _services) - { - cancellationToken.ThrowIfCancellationRequested(); - - _logger.LogDebug("Refreshing guide from {Name}", service.Name); - - try - { - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report(p * progressPerService)); - - var idList = await RefreshChannelsInternal(service, innerProgress, cancellationToken).ConfigureAwait(false); - - newChannelIdList.AddRange(idList.Item1); - newProgramIdList.AddRange(idList.Item2); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - cleanDatabase = false; - _logger.LogError(ex, "Error refreshing channels for service"); - } - - numComplete++; - double percent = numComplete; - percent /= _services.Length; - - progress.Report(100 * percent); - } - - if (cleanDatabase) - { - CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { BaseItemKind.LiveTvChannel }, progress, cancellationToken); - CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { BaseItemKind.LiveTvProgram }, progress, cancellationToken); - } - - var coreService = _services.OfType().FirstOrDefault(); - - if (coreService is not null) - { - await coreService.RefreshSeriesTimers(cancellationToken).ConfigureAwait(false); - await coreService.RefreshTimers(cancellationToken).ConfigureAwait(false); - } - - // Load these now which will prefetch metadata - var dtoOptions = new DtoOptions(); - var fields = dtoOptions.Fields.ToList(); - dtoOptions.Fields = fields.ToArray(); - - progress.Report(100); - } - - private async Task, List>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress progress, CancellationToken cancellationToken) - { - progress.Report(10); - - var allChannelsList = (await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false)) - .Select(i => new Tuple(service.Name, i)) - .ToList(); - - var list = new List(); - - var numComplete = 0; - var parentFolder = GetInternalLiveTvFolder(cancellationToken); - - foreach (var channelInfo in allChannelsList) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var item = await GetChannelAsync(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken).ConfigureAwait(false); - - list.Add(item); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting channel information for {Name}", channelInfo.Item2.Name); - } - - numComplete++; - double percent = numComplete; - percent /= allChannelsList.Count; - - progress.Report((5 * percent) + 10); - } - - progress.Report(15); - - numComplete = 0; - var programs = new List(); - var channels = new List(); - - var guideDays = GetGuideDays(); - - _logger.LogInformation("Refreshing guide with {0} days of guide data", guideDays); - - cancellationToken.ThrowIfCancellationRequested(); - - foreach (var currentChannel in list) - { - channels.Add(currentChannel.Id); - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var start = DateTime.UtcNow.AddHours(-1); - var end = start.AddDays(guideDays); - - var isMovie = false; - var isSports = false; - var isNews = false; - var isKids = false; - var iSSeries = false; - - var channelPrograms = (await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false)).ToList(); - - var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, - ChannelIds = new Guid[] { currentChannel.Id }, - DtoOptions = new DtoOptions(true) - }).Cast().ToDictionary(i => i.Id); - - var newPrograms = new List(); - var updatedPrograms = new List(); - - foreach (var program in channelPrograms) - { - var programTuple = GetProgram(program, existingPrograms, currentChannel); - var programItem = programTuple.Item; - - if (programTuple.IsNew) - { - newPrograms.Add(programItem); - } - else if (programTuple.IsUpdated) - { - updatedPrograms.Add(programItem); - } - - programs.Add(programItem.Id); - - isMovie |= program.IsMovie; - iSSeries |= program.IsSeries; - isSports |= program.IsSports; - isNews |= program.IsNews; - isKids |= program.IsKids; - } - - _logger.LogDebug("Channel {0} has {1} new programs and {2} updated programs", currentChannel.Name, newPrograms.Count, updatedPrograms.Count); - - if (newPrograms.Count > 0) - { - _libraryManager.CreateItems(newPrograms, null, cancellationToken); - } - - if (updatedPrograms.Count > 0) - { - await _libraryManager.UpdateItemsAsync( - updatedPrograms, - currentChannel, - ItemUpdateType.MetadataImport, - cancellationToken).ConfigureAwait(false); - } - - currentChannel.IsMovie = isMovie; - currentChannel.IsNews = isNews; - currentChannel.IsSports = isSports; - currentChannel.IsSeries = iSSeries; - - if (isKids) - { - currentChannel.AddTag("Kids"); - } - - await currentChannel.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); - await currentChannel.RefreshMetadata( - new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - ForceSave = true - }, - cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting programs for channel {Name}", currentChannel.Name); - } - - numComplete++; - double percent = numComplete / (double)allChannelsList.Count; - - progress.Report((85 * percent) + 15); - } - - progress.Report(100); - return new Tuple, List>(channels, programs); - } - - private void CleanDatabaseInternal(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress progress, CancellationToken cancellationToken) - { - var list = _itemRepo.GetItemIdsList(new InternalItemsQuery - { - IncludeItemTypes = validTypes, - DtoOptions = new DtoOptions(false) - }); - - var numComplete = 0; - - foreach (var itemId in list) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (itemId.Equals(default)) - { - // Somehow some invalid data got into the db. It probably predates the boundary checking - continue; - } - - if (!currentIdList.Contains(itemId)) - { - var item = _libraryManager.GetItemById(itemId); - - if (item is not null) - { - _libraryManager.DeleteItem( - item, - new DeleteOptions - { - DeleteFileLocation = false, - DeleteFromExternalProvider = false - }, - false); - } - } - - numComplete++; - double percent = numComplete / (double)list.Count; - - progress.Report(100 * percent); - } - } - - private double GetGuideDays() - { - var config = _config.GetLiveTvConfiguration(); - - if (config.GuideDays.HasValue) - { - return Math.Max(1, Math.Min(config.GuideDays.Value, MaxGuideDays)); - } - - return 7; - } - private async Task> GetEmbyRecordingsAsync(RecordingQuery query, DtoOptions dtoOptions, User user) { if (user is null) @@ -2056,18 +1402,6 @@ namespace Jellyfin.LiveTv await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false); } - public GuideInfo GetGuideInfo() - { - var startDate = DateTime.UtcNow; - var endDate = startDate.AddDays(GetGuideDays()); - - return new GuideInfo - { - StartDate = startDate, - EndDate = endDate - }; - } - private LiveTvServiceInfo[] GetServiceInfos() { return Services.Select(GetServiceInfo).ToArray(); diff --git a/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs b/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs index 18bd61d999..798ababc27 100644 --- a/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs +++ b/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs @@ -15,16 +15,22 @@ namespace Jellyfin.LiveTv public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask { private readonly ILiveTvManager _liveTvManager; + private readonly IGuideManager _guideManager; private readonly IConfigurationManager _config; /// /// Initializes a new instance of the class. /// /// The live tv manager. + /// The guide manager. /// The configuration manager. - public RefreshGuideScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config) + public RefreshGuideScheduledTask( + ILiveTvManager liveTvManager, + IGuideManager guideManager, + IConfigurationManager config) { _liveTvManager = liveTvManager; + _guideManager = guideManager; _config = config; } @@ -51,11 +57,7 @@ namespace Jellyfin.LiveTv /// public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) - { - var manager = (LiveTvManager)_liveTvManager; - - return manager.RefreshChannels(progress, cancellationToken); - } + => _guideManager.RefreshGuide(progress, cancellationToken); /// public IEnumerable GetDefaultTriggers() From 3e32f94fb3ab8f817a74e7dd27981174869a0c45 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Jan 2024 09:57:38 -0500 Subject: [PATCH 18/68] Move RefreshGuideScheduledTask to Guide folder --- .../Guide/RefreshGuideScheduledTask.cs | 71 ++++++++++++++++++ src/Jellyfin.LiveTv/LiveTvManager.cs | 1 + .../RefreshGuideScheduledTask.cs | 72 ------------------- .../TunerHosts/TunerHostManager.cs | 1 + 4 files changed, 73 insertions(+), 72 deletions(-) create mode 100644 src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs delete mode 100644 src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs diff --git a/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs b/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs new file mode 100644 index 0000000000..1c79d6ab3d --- /dev/null +++ b/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.LiveTv.Configuration; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Tasks; + +namespace Jellyfin.LiveTv.Guide; + +/// +/// The "Refresh Guide" scheduled task. +/// +public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask +{ + private readonly ILiveTvManager _liveTvManager; + private readonly IGuideManager _guideManager; + private readonly IConfigurationManager _config; + + /// + /// Initializes a new instance of the class. + /// + /// The live tv manager. + /// The guide manager. + /// The configuration manager. + public RefreshGuideScheduledTask( + ILiveTvManager liveTvManager, + IGuideManager guideManager, + IConfigurationManager config) + { + _liveTvManager = liveTvManager; + _guideManager = guideManager; + _config = config; + } + + /// + public string Name => "Refresh Guide"; + + /// + public string Description => "Downloads channel information from live tv services."; + + /// + public string Category => "Live TV"; + + /// + public bool IsHidden => _liveTvManager.Services.Count == 1 && _config.GetLiveTvConfiguration().TunerHosts.Length == 0; + + /// + public bool IsEnabled => true; + + /// + public bool IsLogged => true; + + /// + public string Key => "RefreshGuide"; + + /// + public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + => _guideManager.RefreshGuide(progress, cancellationToken); + + /// + public IEnumerable GetDefaultTriggers() + { + return new[] + { + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } + }; + } +} diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs index e8fb02c4f8..aa3be2048a 100644 --- a/src/Jellyfin.LiveTv/LiveTvManager.cs +++ b/src/Jellyfin.LiveTv/LiveTvManager.cs @@ -13,6 +13,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.LiveTv.Configuration; +using Jellyfin.LiveTv.Guide; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; diff --git a/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs b/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs deleted file mode 100644 index 798ababc27..0000000000 --- a/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Jellyfin.LiveTv.Configuration; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Tasks; - -namespace Jellyfin.LiveTv -{ - /// - /// The "Refresh Guide" scheduled task. - /// - public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask - { - private readonly ILiveTvManager _liveTvManager; - private readonly IGuideManager _guideManager; - private readonly IConfigurationManager _config; - - /// - /// Initializes a new instance of the class. - /// - /// The live tv manager. - /// The guide manager. - /// The configuration manager. - public RefreshGuideScheduledTask( - ILiveTvManager liveTvManager, - IGuideManager guideManager, - IConfigurationManager config) - { - _liveTvManager = liveTvManager; - _guideManager = guideManager; - _config = config; - } - - /// - public string Name => "Refresh Guide"; - - /// - public string Description => "Downloads channel information from live tv services."; - - /// - public string Category => "Live TV"; - - /// - public bool IsHidden => _liveTvManager.Services.Count == 1 && _config.GetLiveTvConfiguration().TunerHosts.Length == 0; - - /// - public bool IsEnabled => true; - - /// - public bool IsLogged => true; - - /// - public string Key => "RefreshGuide"; - - /// - public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) - => _guideManager.RefreshGuide(progress, cancellationToken); - - /// - public IEnumerable GetDefaultTriggers() - { - return new[] - { - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } - }; - } - } -} diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs index 3e4b0e13fc..60be19c68a 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.LiveTv.Configuration; +using Jellyfin.LiveTv.Guide; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.LiveTv; From 27ab3ef029cc65d3a60812615350de8cd2f5fda4 Mon Sep 17 00:00:00 2001 From: Martin Vandenbussche Date: Wed, 17 Jan 2024 16:46:04 +0100 Subject: [PATCH 19/68] Removing unnecessary array initialization --- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index ec2bdb1e72..97cdc68545 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -472,7 +472,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers "plugin://plugin.video.youtube/?action=play_video&videoid=", "plugin://plugin.video.youtube/play/?video_id=", StringComparison.OrdinalIgnoreCase); - Logger.LogWarning("Trailer URL uses a deprecated format : {Url}. Using {NewUrl} instead is advised.", [trailer, suggestedUrl]); + Logger.LogWarning("Trailer URL uses a deprecated format : {Url}. Using {NewUrl} instead is advised.", trailer, suggestedUrl); } else if (trailer.StartsWith("plugin://plugin.video.youtube/play/?video_id=", StringComparison.OrdinalIgnoreCase)) { From e7b8d45bbb0f2b832245dae7ac0d401c56cb10a4 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Wed, 17 Jan 2024 08:51:39 -0700 Subject: [PATCH 20/68] Use helper function to compare guid (#10825) --- .../Data/SqliteItemRepository.cs | 32 +++++++++---------- .../EntryPoints/LibraryChangedNotifier.cs | 3 +- .../Library/LibraryManager.cs | 30 ++++++++--------- .../Library/MediaSourceManager.cs | 5 +-- .../Library/MusicManager.cs | 3 +- .../Library/SearchEngine.cs | 4 +-- .../Library/UserViewManager.cs | 5 +-- .../Playlists/PlaylistManager.cs | 3 +- .../Session/SessionManager.cs | 22 ++++++------- Emby.Server.Implementations/SyncPlay/Group.cs | 3 +- .../TV/TVSeriesManager.cs | 7 ++-- .../Updates/InstallationManager.cs | 3 +- .../DefaultAuthorizationHandler.cs | 3 +- .../FirstTimeSetupHandler.cs | 3 +- Jellyfin.Api/Controllers/ArtistsController.cs | 7 ++-- .../Controllers/ChannelsController.cs | 5 +-- Jellyfin.Api/Controllers/FilterController.cs | 5 +-- Jellyfin.Api/Controllers/GenresController.cs | 5 +-- .../Controllers/InstantMixController.cs | 15 +++++---- Jellyfin.Api/Controllers/ItemsController.cs | 5 +-- Jellyfin.Api/Controllers/LibraryController.cs | 26 +++++++-------- Jellyfin.Api/Controllers/LiveTvController.cs | 25 ++++++++------- Jellyfin.Api/Controllers/MoviesController.cs | 3 +- .../Controllers/MusicGenresController.cs | 5 +-- Jellyfin.Api/Controllers/PersonsController.cs | 5 +-- .../Controllers/PlaylistsController.cs | 3 +- Jellyfin.Api/Controllers/SearchController.cs | 2 +- Jellyfin.Api/Controllers/SessionController.cs | 7 ++-- Jellyfin.Api/Controllers/StudiosController.cs | 5 +-- .../Controllers/SuggestionsController.cs | 3 +- Jellyfin.Api/Controllers/TvShowsController.cs | 10 +++--- Jellyfin.Api/Controllers/UserController.cs | 3 +- .../Controllers/UserLibraryController.cs | 19 +++++------ Jellyfin.Api/Controllers/VideosController.cs | 7 ++-- Jellyfin.Api/Controllers/YearsController.cs | 6 ++-- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 3 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 3 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- .../Users/UserManager.cs | 3 +- .../Entities/AggregateFolder.cs | 3 +- .../Entities/Audio/MusicArtist.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 16 +++++----- MediaBrowser.Controller/Entities/Folder.cs | 9 +++--- .../Entities/TV/Episode.cs | 11 ++++--- MediaBrowser.Controller/Entities/TV/Season.cs | 5 +-- MediaBrowser.Controller/Entities/UserView.cs | 8 ++--- .../Entities/UserViewBuilder.cs | 2 +- MediaBrowser.Controller/Entities/Video.cs | 2 +- .../Transcoding/TranscodeManager.cs | 3 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 3 +- .../Manager/ProviderManager.cs | 4 +-- src/Jellyfin.Extensions/GuidExtensions.cs | 26 +++++++++++++++ .../Converters/JsonNullableGuidConverter.cs | 2 +- .../Channels/ChannelManager.cs | 8 ++--- src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs | 6 ++-- src/Jellyfin.LiveTv/LiveTvDtoService.cs | 5 +-- src/Jellyfin.LiveTv/LiveTvManager.cs | 7 ++-- .../AuthHelper.cs | 3 +- 58 files changed, 249 insertions(+), 184 deletions(-) create mode 100644 src/Jellyfin.Extensions/GuidExtensions.cs diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index d0772654ce..a6336f1451 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -699,7 +699,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@EndDate"); } - saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); + saveItemStatement.TryBind("@ChannelId", item.ChannelId.IsEmpty() ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); if (item is IHasProgramAttributes hasProgramAttributes) { @@ -729,7 +729,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); var parentId = item.ParentId; - if (parentId.Equals(default)) + if (parentId.IsEmpty()) { saveItemStatement.TryBindNull("@ParentId"); } @@ -925,7 +925,7 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBind("@SeasonName", episode.SeasonName); - var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId; + var nullableSeasonId = episode.SeasonId.IsEmpty() ? (Guid?)null : episode.SeasonId; saveItemStatement.TryBind("@SeasonId", nullableSeasonId); } @@ -937,7 +937,7 @@ namespace Emby.Server.Implementations.Data if (item is IHasSeries hasSeries) { - var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId; + var nullableSeriesId = hasSeries.SeriesId.IsEmpty() ? (Guid?)null : hasSeries.SeriesId; saveItemStatement.TryBind("@SeriesId", nullableSeriesId); saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); @@ -1010,7 +1010,7 @@ namespace Emby.Server.Implementations.Data } Guid ownerId = item.OwnerId; - if (ownerId.Equals(default)) + if (ownerId.IsEmpty()) { saveItemStatement.TryBindNull("@OwnerId"); } @@ -1266,7 +1266,7 @@ namespace Emby.Server.Implementations.Data /// is . public BaseItem RetrieveItem(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(id)); } @@ -1970,7 +1970,7 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentNullException(nameof(id)); } @@ -3230,7 +3230,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add($"ChannelId in ({inClause})"); } - if (!query.ParentId.Equals(default)) + if (!query.ParentId.IsEmpty()) { whereClauses.Add("ParentId=@ParentId"); statement?.TryBind("@ParentId", query.ParentId); @@ -4452,7 +4452,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type public void DeleteItem(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentNullException(nameof(id)); } @@ -4583,13 +4583,13 @@ AND Type = @InternalPersonType)"); statement?.TryBind("@UserId", query.User.InternalId); } - if (!query.ItemId.Equals(default)) + if (!query.ItemId.IsEmpty()) { whereClauses.Add("ItemId=@ItemId"); statement?.TryBind("@ItemId", query.ItemId); } - if (!query.AppearsInItemId.Equals(default)) + if (!query.AppearsInItemId.IsEmpty()) { whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)"); statement?.TryBind("@AppearsInItemId", query.AppearsInItemId); @@ -4640,7 +4640,7 @@ AND Type = @InternalPersonType)"); private void UpdateAncestors(Guid itemId, List ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement) { - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { throw new ArgumentNullException(nameof(itemId)); } @@ -5156,7 +5156,7 @@ AND Type = @InternalPersonType)"); private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db) { - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { throw new ArgumentNullException(nameof(itemId)); } @@ -5228,7 +5228,7 @@ AND Type = @InternalPersonType)"); public void UpdatePeople(Guid itemId, List people) { - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { throw new ArgumentNullException(nameof(itemId)); } @@ -5378,7 +5378,7 @@ AND Type = @InternalPersonType)"); { CheckDisposed(); - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentNullException(nameof(id)); } @@ -5758,7 +5758,7 @@ AND Type = @InternalPersonType)"); CancellationToken cancellationToken) { CheckDisposed(); - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentException("Guid can't be empty.", nameof(id)); } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index a83d7a4105..83e7b230df 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -241,7 +242,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint { var userIds = _sessionManager.Sessions .Select(i => i.UserId) - .Where(i => !i.Equals(default)) + .Where(i => !i.IsEmpty()) .Distinct() .ToArray(); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a79ffd9cb1..8ae913dad8 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -732,7 +732,7 @@ namespace Emby.Server.Implementations.Library Path = path }; - if (folder.Id.Equals(default)) + if (folder.Id.IsEmpty()) { if (string.IsNullOrEmpty(folder.Path)) { @@ -1219,7 +1219,7 @@ namespace Emby.Server.Implementations.Library /// is null. public BaseItem GetItemById(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(id)); } @@ -1241,7 +1241,7 @@ namespace Emby.Server.Implementations.Library public List GetItemList(InternalItemsQuery query, bool allowExternalContent) { - if (query.Recursive && !query.ParentId.Equals(default)) + if (query.Recursive && !query.ParentId.IsEmpty()) { var parent = GetItemById(query.ParentId); if (parent is not null) @@ -1272,7 +1272,7 @@ namespace Emby.Server.Implementations.Library public int GetCount(InternalItemsQuery query) { - if (query.Recursive && !query.ParentId.Equals(default)) + if (query.Recursive && !query.ParentId.IsEmpty()) { var parent = GetItemById(query.ParentId); if (parent is not null) @@ -1430,7 +1430,7 @@ namespace Emby.Server.Implementations.Library public QueryResult GetItemsResult(InternalItemsQuery query) { - if (query.Recursive && !query.ParentId.Equals(default)) + if (query.Recursive && !query.ParentId.IsEmpty()) { var parent = GetItemById(query.ParentId); if (parent is not null) @@ -1486,7 +1486,7 @@ namespace Emby.Server.Implementations.Library private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) { if (query.AncestorIds.Length == 0 && - query.ParentId.Equals(default) && + query.ParentId.IsEmpty() && query.ChannelIds.Count == 0 && query.TopParentIds.Length == 0 && string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) && @@ -1520,7 +1520,7 @@ namespace Emby.Server.Implementations.Library } // Translate view into folders - if (!view.DisplayParentId.Equals(default)) + if (!view.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(view.DisplayParentId); if (displayParent is not null) @@ -1531,7 +1531,7 @@ namespace Emby.Server.Implementations.Library return Array.Empty(); } - if (!view.ParentId.Equals(default)) + if (!view.ParentId.IsEmpty()) { var displayParent = GetItemById(view.ParentId); if (displayParent is not null) @@ -2137,7 +2137,7 @@ namespace Emby.Server.Implementations.Library return null; } - while (!item.ParentId.Equals(default)) + while (!item.ParentId.IsEmpty()) { var parent = item.GetParent(); if (parent is null || parent is AggregateFolder) @@ -2215,7 +2215,7 @@ namespace Emby.Server.Implementations.Library CollectionType? viewType, string sortName) { - var parentIdString = parentId.Equals(default) + var parentIdString = parentId.IsEmpty() ? null : parentId.ToString("N", CultureInfo.InvariantCulture); var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty); @@ -2251,7 +2251,7 @@ namespace Emby.Server.Implementations.Library var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && !item.DisplayParentId.Equals(default)) + if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2315,7 +2315,7 @@ namespace Emby.Server.Implementations.Library var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && !item.DisplayParentId.Equals(default)) + if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2345,7 +2345,7 @@ namespace Emby.Server.Implementations.Library { ArgumentException.ThrowIfNullOrEmpty(name); - var parentIdString = parentId.Equals(default) + var parentIdString = parentId.IsEmpty() ? null : parentId.ToString("N", CultureInfo.InvariantCulture); var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty); @@ -2391,7 +2391,7 @@ namespace Emby.Server.Implementations.Library var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && !item.DisplayParentId.Equals(default)) + if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2419,7 +2419,7 @@ namespace Emby.Server.Implementations.Library return GetItemById(parentId.Value); } - if (userId.HasValue && !userId.Equals(default)) + if (!userId.IsNullOrEmpty()) { return GetUserRootFolder(); } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 68eccf311d..c38f1af912 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -13,6 +13,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -524,10 +525,10 @@ namespace Emby.Server.Implementations.Library _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource); var clone = JsonSerializer.Deserialize(json, _jsonOptions); - if (!request.UserId.Equals(default)) + if (!request.UserId.IsEmpty()) { var user = _userManager.GetUserById(request.UserId); - var item = request.ItemId.Equals(default) + var item = request.ItemId.IsEmpty() ? null : _libraryManager.GetItemById(request.ItemId); SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index b2439a87e1..078f4ad219 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library { return Guid.Empty; } - }).Where(i => !i.Equals(default)).ToArray(); + }).Where(i => !i.IsEmpty()).ToArray(); return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index b916b91708..020cb517d1 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library public QueryResult GetSearchHints(SearchQuery query) { User user = null; - if (!query.UserId.Equals(default)) + if (!query.UserId.IsEmpty()) { user = _userManager.GetUserById(query.UserId); } @@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.Library if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist) { - if (!searchQuery.ParentId.Equals(default)) + if (!searchQuery.ParentId.IsEmpty()) { searchQuery.AncestorIds = new[] { searchQuery.ParentId }; searchQuery.ParentId = Guid.Empty; diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 1d662ed8dd..83a66c8e47 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -151,7 +152,7 @@ namespace Emby.Server.Implementations.Library var index = Array.IndexOf(orders, i.Id); if (index == -1 && i is UserView view - && !view.DisplayParentId.Equals(default)) + && !view.DisplayParentId.IsEmpty()) { index = Array.IndexOf(orders, view.DisplayParentId); } @@ -253,7 +254,7 @@ namespace Emby.Server.Implementations.Library var parents = new List(); - if (!parentId.Equals(default)) + if (!parentId.IsEmpty()) { var parentItem = _libraryManager.GetItemById(parentId); if (parentItem is Channel) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index d2e2fd7d56..aea8d65322 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.Playlists public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection itemIds, Guid userId) { - var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index f457a78b3b..bbb3938dcf 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -337,7 +337,7 @@ namespace Emby.Server.Implementations.Session info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); } - if (!info.ItemId.Equals(default) && info.Item is null && libraryItem is not null) + if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null) { var current = session.NowPlayingItem; @@ -529,7 +529,7 @@ namespace Emby.Server.Implementations.Session { var users = new List(); - if (session.UserId.Equals(default)) + if (session.UserId.IsEmpty()) { return users; } @@ -690,7 +690,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = info.ItemId.Equals(default) + var libraryItem = info.ItemId.IsEmpty() ? null : GetNowPlayingItem(session, info.ItemId); @@ -784,7 +784,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = info.ItemId.Equals(default) + var libraryItem = info.ItemId.IsEmpty() ? null : GetNowPlayingItem(session, info.ItemId); @@ -923,7 +923,7 @@ namespace Emby.Server.Implementations.Session session.StopAutomaticProgress(); - var libraryItem = info.ItemId.Equals(default) + var libraryItem = info.ItemId.IsEmpty() ? null : GetNowPlayingItem(session, info.ItemId); @@ -933,7 +933,7 @@ namespace Emby.Server.Implementations.Session info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); } - if (!info.ItemId.Equals(default) && info.Item is null && libraryItem is not null) + if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null) { var current = session.NowPlayingItem; @@ -1154,7 +1154,7 @@ namespace Emby.Server.Implementations.Session var session = GetSessionToRemoteControl(sessionId); - var user = session.UserId.Equals(default) ? null : _userManager.GetUserById(session.UserId); + var user = session.UserId.IsEmpty() ? null : _userManager.GetUserById(session.UserId); List items; @@ -1223,7 +1223,7 @@ namespace Emby.Server.Implementations.Session { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - if (!controllingSession.UserId.Equals(default)) + if (!controllingSession.UserId.IsEmpty()) { command.ControllingUserId = controllingSession.UserId; } @@ -1342,7 +1342,7 @@ namespace Emby.Server.Implementations.Session { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - if (!controllingSession.UserId.Equals(default)) + if (!controllingSession.UserId.IsEmpty()) { command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture); } @@ -1463,7 +1463,7 @@ namespace Emby.Server.Implementations.Session ArgumentException.ThrowIfNullOrEmpty(request.AppVersion); User user = null; - if (!request.UserId.Equals(default)) + if (!request.UserId.IsEmpty()) { user = _userManager.GetUserById(request.UserId); } @@ -1766,7 +1766,7 @@ namespace Emby.Server.Implementations.Session { ArgumentNullException.ThrowIfNull(info); - var user = info.UserId.Equals(default) + var user = info.UserId.IsEmpty() ? null : _userManager.GetUserById(info.UserId); diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index da8f949326..a7821c0e0e 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; @@ -553,7 +554,7 @@ namespace Emby.Server.Implementations.SyncPlay if (playingItemRemoved) { var itemId = PlayQueue.GetPlayingItemId(); - if (!itemId.Equals(default)) + if (!itemId.IsEmpty()) { var item = _libraryManager.GetItemById(itemId); RunTimeTicks = item.RunTimeTicks ?? 0; diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index ef890aeb4f..34c9e86f26 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -41,7 +42,7 @@ namespace Emby.Server.Implementations.TV } string? presentationUniqueKey = null; - if (query.SeriesId.HasValue && !query.SeriesId.Value.Equals(default)) + if (!query.SeriesId.IsNullOrEmpty()) { if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series) { @@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.TV string? presentationUniqueKey = null; int? limit = null; - if (request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default)) + if (!request.SeriesId.IsNullOrEmpty()) { if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series) { @@ -146,7 +147,7 @@ namespace Emby.Server.Implementations.TV // If viewing all next up for all series, remove first episodes // But if that returns empty, keep those first episodes (avoid completely empty view) - var alwaysEnableFirstEpisode = request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default); + var alwaysEnableFirstEpisode = !request.SeriesId.IsNullOrEmpty(); var anyFound = false; return allNextUp diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 15c4cfdf02..ce3d6cab88 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -11,6 +11,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.Updates availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); } - if (!id.Equals(default)) + if (!id.IsEmpty()) { availablePackages = availablePackages.Where(x => x.Id.Equals(id)); } diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs index cf3cb69052..7d0fe55898 100644 --- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs @@ -2,6 +2,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; @@ -41,7 +42,7 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy var isApiKey = context.User.GetIsApiKey(); var userId = context.User.GetUserId(); // This likely only happens during the wizard, so skip the default checks and let any other handlers do it - if (!isApiKey && userId.Equals(default)) + if (!isApiKey && userId.IsEmpty()) { return Task.CompletedTask; } diff --git a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs index 688a13bc0b..965b7e7e60 100644 --- a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs +++ b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; @@ -46,7 +47,7 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy } var userId = contextUser.GetUserId(); - if (userId.Equals(default)) + if (userId.IsEmpty()) { context.Fail(); return Task.CompletedTask; diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index e7d3e694ab..8b931f1621 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -126,7 +127,7 @@ public class ArtistsController : BaseJellyfinApiController User? user = null; BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { user = _userManager.GetUserById(userId.Value); } @@ -330,7 +331,7 @@ public class ArtistsController : BaseJellyfinApiController User? user = null; BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { user = _userManager.GetUserById(userId.Value); } @@ -469,7 +470,7 @@ public class ArtistsController : BaseJellyfinApiController var item = _libraryManager.GetArtist(name, dtoOptions); - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index fdc16ee23f..f83c71b578 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -126,7 +127,7 @@ public class ChannelsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -201,7 +202,7 @@ public class ChannelsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index baeb8b81a0..d6e043e6a1 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -3,6 +3,7 @@ using System.Linq; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -53,7 +54,7 @@ public class FilterController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -146,7 +147,7 @@ public class FilterController : BaseJellyfinApiController [FromQuery] bool? recursive) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 6cb1993e46..54d48aec21 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -95,7 +96,7 @@ public class GenresController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -172,7 +173,7 @@ public class GenresController : BaseJellyfinApiController item ??= new Genre(); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 4dc2a4253d..e7ff1f9868 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -76,7 +77,7 @@ public class InstantMixController : BaseJellyfinApiController { var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -113,7 +114,7 @@ public class InstantMixController : BaseJellyfinApiController { var album = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -150,7 +151,7 @@ public class InstantMixController : BaseJellyfinApiController { var playlist = (Playlist)_libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -186,7 +187,7 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -223,7 +224,7 @@ public class InstantMixController : BaseJellyfinApiController { var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -260,7 +261,7 @@ public class InstantMixController : BaseJellyfinApiController { var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } @@ -334,7 +335,7 @@ public class InstantMixController : BaseJellyfinApiController { var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index a1fc8e11b2..d10fba920c 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -245,7 +246,7 @@ public class ItemsController : BaseJellyfinApiController var isApiKey = User.GetIsApiKey(); // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method userId = RequestHelpers.GetUserId(User, userId); - var user = !isApiKey && !userId.Value.Equals(default) + var user = !isApiKey && !userId.IsNullOrEmpty() ? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException() : null; @@ -840,7 +841,7 @@ public class ItemsController : BaseJellyfinApiController var ancestorIds = Array.Empty(); var excludeFolderIds = user.GetPreferenceValues(PreferenceKind.LatestItemExcludes); - if (parentIdGuid.Equals(default) && excludeFolderIds.Length > 0) + if (parentIdGuid.IsEmpty() && excludeFolderIds.Length > 0) { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index de057bbab9..a0bbc961f0 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -146,12 +146,12 @@ public class LibraryController : BaseJellyfinApiController [FromQuery] bool inheritFromParent = false) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = itemId.Equals(default) - ? (userId.Value.Equals(default) + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); @@ -213,12 +213,12 @@ public class LibraryController : BaseJellyfinApiController [FromQuery] bool inheritFromParent = false) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = itemId.Equals(default) - ? (userId.Value.Equals(default) + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); @@ -339,7 +339,7 @@ public class LibraryController : BaseJellyfinApiController { var isApiKey = User.GetIsApiKey(); var userId = User.GetUserId(); - var user = !isApiKey && !userId.Equals(default) + var user = !isApiKey && !userId.IsEmpty() ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() : null; if (!isApiKey && user is null) @@ -382,7 +382,7 @@ public class LibraryController : BaseJellyfinApiController { var isApiKey = User.GetIsApiKey(); var userId = User.GetUserId(); - var user = !isApiKey && !userId.Equals(default) + var user = !isApiKey && !userId.IsEmpty() ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() : null; @@ -428,7 +428,7 @@ public class LibraryController : BaseJellyfinApiController [FromQuery] bool? isFavorite) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -471,7 +471,7 @@ public class LibraryController : BaseJellyfinApiController var baseItemDtos = new List(); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -702,8 +702,8 @@ public class LibraryController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) { userId = RequestHelpers.GetUserId(User, userId); - var item = itemId.Equals(default) - ? (userId.Value.Equals(default) + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); @@ -718,7 +718,7 @@ public class LibraryController : BaseJellyfinApiController return new QueryResult(); } - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 27eb88b60f..1b2f5750f6 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -15,6 +15,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LiveTvDtos; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -182,7 +183,7 @@ public class LiveTvController : BaseJellyfinApiController dtoOptions, CancellationToken.None); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -214,10 +215,10 @@ public class LiveTvController : BaseJellyfinApiController public ActionResult GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = channelId.Equals(default) + var item = channelId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(channelId); @@ -387,7 +388,7 @@ public class LiveTvController : BaseJellyfinApiController public async Task>> GetRecordingFolders([FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false); @@ -410,10 +411,10 @@ public class LiveTvController : BaseJellyfinApiController public ActionResult GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = recordingId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId); + var item = recordingId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId); var dtoOptions = new DtoOptions() .AddClientFields(User); @@ -567,7 +568,7 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] bool enableTotalRecordCount = true) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -594,7 +595,7 @@ public class LiveTvController : BaseJellyfinApiController GenreIds = genreIds }; - if (librarySeriesId.HasValue && !librarySeriesId.Equals(default)) + if (!librarySeriesId.IsNullOrEmpty()) { query.IsSeries = true; @@ -623,7 +624,7 @@ public class LiveTvController : BaseJellyfinApiController [Authorize(Policy = Policies.LiveTvAccess)] public async Task>> GetPrograms([FromBody] GetProgramsDto body) { - var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId); + var user = body.UserId.IsEmpty() ? null : _userManager.GetUserById(body.UserId); var query = new InternalItemsQuery(user) { @@ -648,7 +649,7 @@ public class LiveTvController : BaseJellyfinApiController GenreIds = body.GenreIds }; - if (!body.LibrarySeriesId.Equals(default)) + if (!body.LibrarySeriesId.IsEmpty()) { query.IsSeries = true; @@ -707,7 +708,7 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] bool enableTotalRecordCount = true) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -746,7 +747,7 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index e1145481fa..471bcd096e 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -7,6 +7,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -69,7 +70,7 @@ public class MoviesController : BaseJellyfinApiController [FromQuery] int itemLimit = 8) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 69b9042646..5411baa3e7 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -95,7 +96,7 @@ public class MusicGenresController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -164,7 +165,7 @@ public class MusicGenresController : BaseJellyfinApiController return NotFound(); } - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index b4c6f490a0..6ca3086015 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -83,7 +84,7 @@ public class PersonsController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -129,7 +130,7 @@ public class PersonsController : BaseJellyfinApiController return NotFound(); } - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); return _dtoService.GetBaseItemDto(item, dtoOptions, user); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index c4c89ccde0..921cc6031f 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -9,6 +9,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.PlaylistDtos; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; @@ -188,7 +189,7 @@ public class PlaylistsController : BaseJellyfinApiController return NotFound(); } - var user = userId.Equals(default) + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 5b45941657..413b7b8340 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -209,7 +209,7 @@ public class SearchController : BaseJellyfinApiController break; } - if (!item.ChannelId.Equals(default)) + if (!item.ChannelId.IsEmpty()) { var channel = _libraryManager.GetItemById(item.ChannelId); result.ChannelName = channel?.Name; diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 083515a949..52b58b8f10 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -10,6 +10,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.SessionDtos; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; @@ -71,7 +72,7 @@ public class SessionController : BaseJellyfinApiController result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); } - if (controllableByUserId.HasValue && !controllableByUserId.Equals(default)) + if (!controllableByUserId.IsNullOrEmpty()) { result = result.Where(i => i.SupportsRemoteControl); @@ -83,12 +84,12 @@ public class SessionController : BaseJellyfinApiController if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) { - result = result.Where(i => i.UserId.Equals(default) || i.ContainsUser(controllableByUserId.Value)); + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableByUserId.Value)); } if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) { - result = result.Where(i => !i.UserId.Equals(default)); + result = result.Where(i => !i.UserId.IsEmpty()); } result = result.Where(i => diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index f434f60f51..708fc7436f 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -91,7 +92,7 @@ public class StudiosController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -144,7 +145,7 @@ public class StudiosController : BaseJellyfinApiController var dtoOptions = new DtoOptions().AddClientFields(User); var item = _libraryManager.GetStudio(name); - if (!userId.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index 675757fc51..2aa6d25a79 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -62,7 +63,7 @@ public class SuggestionsController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] bool enableTotalRecordCount = false) { - var user = userId.Equals(default) + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 55a30d4692..3d84b61bf4 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -111,7 +111,7 @@ public class TvShowsController : BaseJellyfinApiController }, options); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -150,7 +150,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] bool? enableUserData) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -222,7 +222,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] ItemSortBy? sortBy) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); @@ -284,7 +284,7 @@ public class TvShowsController : BaseJellyfinApiController } // This must be the last filter - if (adjacentTo.HasValue && !adjacentTo.Value.Equals(default)) + if (!adjacentTo.IsNullOrEmpty()) { episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList(); } @@ -339,7 +339,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] bool? enableUserData) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index f9f27f1480..ea10ee24f9 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.UserDtos; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -532,7 +533,7 @@ public class UserController : BaseJellyfinApiController public ActionResult GetCurrentUser() { var userId = User.GetUserId(); - if (userId.Equals(default)) + if (userId.IsEmpty()) { return BadRequest(); } diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 2c4fe91862..264e0a3dbd 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -84,7 +85,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -145,7 +146,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -185,7 +186,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -221,7 +222,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -257,7 +258,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -294,7 +295,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -330,7 +331,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -375,7 +376,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); @@ -558,7 +559,7 @@ public class UserLibraryController : BaseJellyfinApiController return NotFound(); } - var item = itemId.Equals(default) + var item = itemId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index c231c147fc..e6c3198698 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -11,6 +11,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -96,12 +97,12 @@ public class VideosController : BaseJellyfinApiController public ActionResult> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - var user = userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = itemId.Equals(default) - ? (userId.Value.Equals(default) + var item = itemId.IsEmpty() + ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index ca46c38c59..e4aa0ea42d 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -90,7 +90,7 @@ public class YearsController : BaseJellyfinApiController .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - User? user = userId.Value.Equals(default) + User? user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); @@ -110,7 +110,7 @@ public class YearsController : BaseJellyfinApiController { var folder = (Folder)parentItem; - if (userId.Equals(default)) + if (userId.IsNullOrEmpty()) { items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList(); } @@ -182,7 +182,7 @@ public class YearsController : BaseJellyfinApiController var dtoOptions = new DtoOptions() .AddClientFields(User); - if (!userId.Value.Equals(default)) + if (!userId.IsNullOrEmpty()) { var user = _userManager.GetUserById(userId.Value); return _dtoService.GetBaseItemDto(item, dtoOptions, user); diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 321987ca74..6a24ad32ab 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -86,7 +87,7 @@ public class MediaInfoHelper string? mediaSourceId = null, string? liveStreamId = null) { - var user = userId is null || userId.Value.Equals(default) + var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); var item = _libraryManager.GetItemById(id); diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index be3d4dfb67..429e972132 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -7,6 +7,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -67,7 +68,7 @@ public static class RequestHelpers var authenticatedUserId = claimsPrincipal.GetUserId(); // UserId not provided, fall back to authenticated user id. - if (userId is null || userId.Value.Equals(default)) + if (userId.IsNullOrEmpty()) { return authenticatedUserId; } diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 78943f7b58..7a3842a9f7 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -82,7 +82,7 @@ public static class StreamingHelpers }; var userId = httpContext.User.GetUserId(); - if (!userId.Equals(default)) + if (!userId.IsEmpty()) { state.User = userManager.GetUserById(userId); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index cc464a7a3d..c4a2bfdb87 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -11,6 +11,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.Data.Events.Users; +using Jellyfin.Extensions; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -117,7 +118,7 @@ namespace Jellyfin.Server.Implementations.Users /// public User? GetUserById(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(id)); } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index d789033f16..b225f22df0 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -184,7 +185,7 @@ namespace MediaBrowser.Controller.Entities /// The id is empty. public BaseItem FindVirtualChild(Guid id) { - if (id.Equals(default)) + if (id.IsEmpty()) { throw new ArgumentNullException(nameof(id)); } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 18d948a62f..11cdf84445 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Entities.Audio public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo { [JsonIgnore] - public bool IsAccessedByName => ParentId.Equals(default); + public bool IsAccessedByName => ParentId.IsEmpty(); [JsonIgnore] public override bool IsFolder => !IsAccessedByName; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index fdbceac961..ddcc994a0b 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -240,7 +240,7 @@ namespace MediaBrowser.Controller.Entities { get { - if (!ChannelId.Equals(default)) + if (!ChannelId.IsEmpty()) { return SourceType.Channel; } @@ -530,7 +530,7 @@ namespace MediaBrowser.Controller.Entities get { var id = DisplayParentId; - if (id.Equals(default)) + if (id.IsEmpty()) { return null; } @@ -746,7 +746,7 @@ namespace MediaBrowser.Controller.Entities public virtual bool StopRefreshIfLocalMetadataFound => true; [JsonIgnore] - protected virtual bool SupportsOwnedItems => !ParentId.Equals(default) && IsFileProtocol; + protected virtual bool SupportsOwnedItems => !ParentId.IsEmpty() && IsFileProtocol; [JsonIgnore] public virtual bool SupportsPeople => false; @@ -823,7 +823,7 @@ namespace MediaBrowser.Controller.Entities public BaseItem GetOwner() { var ownerId = OwnerId; - return ownerId.Equals(default) ? null : LibraryManager.GetItemById(ownerId); + return ownerId.IsEmpty() ? null : LibraryManager.GetItemById(ownerId); } public bool CanDelete(User user, List allCollectionFolders) @@ -968,7 +968,7 @@ namespace MediaBrowser.Controller.Entities public BaseItem GetParent() { var parentId = ParentId; - if (parentId.Equals(default)) + if (parentId.IsEmpty()) { return null; } @@ -1361,7 +1361,7 @@ namespace MediaBrowser.Controller.Entities var tasks = extras.Select(i => { var subOptions = new MetadataRefreshOptions(options); - if (!i.OwnerId.Equals(ownerId) || !i.ParentId.Equals(default)) + if (!i.OwnerId.Equals(ownerId) || !i.ParentId.IsEmpty()) { i.OwnerId = ownerId; i.ParentId = Guid.Empty; @@ -1673,7 +1673,7 @@ namespace MediaBrowser.Controller.Entities // First get using the cached Id if (info.ItemId.HasValue) { - if (info.ItemId.Value.Equals(default)) + if (info.ItemId.Value.IsEmpty()) { return null; } @@ -2439,7 +2439,7 @@ namespace MediaBrowser.Controller.Entities return Task.FromResult(true); } - if (video.OwnerId.Equals(default)) + if (video.OwnerId.IsEmpty()) { video.OwnerId = this.Id; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index e707eedbf6..74eb089de3 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Collections; @@ -198,7 +199,7 @@ namespace MediaBrowser.Controller.Entities { item.SetParent(this); - if (item.Id.Equals(default)) + if (item.Id.IsEmpty()) { item.Id = LibraryManager.GetNewItemId(item.Path, item.GetType()); } @@ -697,7 +698,7 @@ namespace MediaBrowser.Controller.Entities if (this is not UserRootFolder && this is not AggregateFolder - && query.ParentId.Equals(default)) + && query.ParentId.IsEmpty()) { query.Parent = this; } @@ -840,7 +841,7 @@ namespace MediaBrowser.Controller.Entities return true; } - if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default)) + if (!query.AdjacentTo.IsNullOrEmpty()) { Logger.LogDebug("Query requires post-filtering due to AdjacentTo"); return true; @@ -987,7 +988,7 @@ namespace MediaBrowser.Controller.Entities #pragma warning restore CA1309 // This must be the last filter - if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default)) + if (!query.AdjacentTo.IsNullOrEmpty()) { items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value); } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index bf31508c1d..37e2414142 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -74,12 +75,12 @@ namespace MediaBrowser.Controller.Entities.TV get { var seriesId = SeriesId; - if (seriesId.Equals(default)) + if (seriesId.IsEmpty()) { seriesId = FindSeriesId(); } - return seriesId.Equals(default) ? null : (LibraryManager.GetItemById(seriesId) as Series); + return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series); } } @@ -89,12 +90,12 @@ namespace MediaBrowser.Controller.Entities.TV get { var seasonId = SeasonId; - if (seasonId.Equals(default)) + if (seasonId.IsEmpty()) { seasonId = FindSeasonId(); } - return seasonId.Equals(default) ? null : (LibraryManager.GetItemById(seasonId) as Season); + return seasonId.IsEmpty() ? null : (LibraryManager.GetItemById(seasonId) as Season); } } @@ -271,7 +272,7 @@ namespace MediaBrowser.Controller.Entities.TV var seasonId = SeasonId; - if (!seasonId.Equals(default) && !list.Contains(seasonId)) + if (!seasonId.IsEmpty() && !list.Contains(seasonId)) { list.Add(seasonId); } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 0a040a3c28..c29cefc15e 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Querying; @@ -48,12 +49,12 @@ namespace MediaBrowser.Controller.Entities.TV get { var seriesId = SeriesId; - if (seriesId.Equals(default)) + if (seriesId.IsEmpty()) { seriesId = FindSeriesId(); } - return seriesId.Equals(default) ? null : (LibraryManager.GetItemById(seriesId) as Series); + return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series); } } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index eb026deb4f..c93488a85e 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -70,11 +70,11 @@ namespace MediaBrowser.Controller.Entities /// public override IEnumerable GetIdsForAncestorQuery() { - if (!DisplayParentId.Equals(default)) + if (!DisplayParentId.IsEmpty()) { yield return DisplayParentId; } - else if (!ParentId.Equals(default)) + else if (!ParentId.IsEmpty()) { yield return ParentId; } @@ -95,11 +95,11 @@ namespace MediaBrowser.Controller.Entities { var parent = this as Folder; - if (!DisplayParentId.Equals(default)) + if (!DisplayParentId.IsEmpty()) { parent = LibraryManager.GetItemById(DisplayParentId) as Folder ?? parent; } - else if (!ParentId.Equals(default)) + else if (!ParentId.IsEmpty()) { parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index a3525c8623..4af000557e 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -433,7 +433,7 @@ namespace MediaBrowser.Controller.Entities var user = query.User; // This must be the last filter - if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default)) + if (!query.AdjacentTo.IsNullOrEmpty()) { items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value); } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index be2eb4d288..5adadec390 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -456,7 +456,7 @@ namespace MediaBrowser.Controller.Entities foreach (var child in LinkedAlternateVersions) { // Reset the cached value - if (child.ItemId.HasValue && child.ItemId.Value.Equals(default)) + if (child.ItemId.IsNullOrEmpty()) { child.ItemId = null; } diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index 483d0a1d82..d79e4441aa 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -9,6 +9,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -400,7 +401,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { - var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index bf18d46dcd..da683a17e6 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; @@ -1536,7 +1537,7 @@ namespace MediaBrowser.Model.Dlna private static void ValidateMediaOptions(MediaOptions options, bool isMediaSource) { - if (options.ItemId.Equals(default)) + if (options.ItemId.IsEmpty()) { ArgumentException.ThrowIfNullOrEmpty(options.DeviceId); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 4ba8844182..b530b9de3f 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -706,7 +706,7 @@ namespace MediaBrowser.Providers.Manager { BaseItem? referenceItem = null; - if (!searchInfo.ItemId.Equals(default)) + if (!searchInfo.ItemId.IsEmpty()) { referenceItem = _libraryManager.GetItemById(searchInfo.ItemId); } @@ -944,7 +944,7 @@ namespace MediaBrowser.Providers.Manager public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority) { ArgumentNullException.ThrowIfNull(itemId); - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(itemId)); } diff --git a/src/Jellyfin.Extensions/GuidExtensions.cs b/src/Jellyfin.Extensions/GuidExtensions.cs new file mode 100644 index 0000000000..95c591a82e --- /dev/null +++ b/src/Jellyfin.Extensions/GuidExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Jellyfin.Extensions; + +/// +/// Guid specific extensions. +/// +public static class GuidExtensions +{ + /// + /// Determine whether the guid is default. + /// + /// The guid. + /// Whether the guid is the default value. + public static bool IsEmpty(this Guid guid) + => guid.Equals(default); + + /// + /// Determine whether the guid is null or default. + /// + /// The guid. + /// Whether the guid is null or the default valueF. + public static bool IsNullOrEmpty([NotNullWhen(false)] this Guid? guid) + => guid is null || guid.Value.IsEmpty(); +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs index 656e3c3dab..0a50b5c3b9 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Extensions.Json.Converters { // null got handled higher up the call stack var val = value!.Value; - if (val.Equals(default)) + if (val.IsEmpty()) { writer.WriteNullValue(); } diff --git a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs index 51abb503eb..bc968f8ee1 100644 --- a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs +++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs @@ -150,7 +150,7 @@ namespace Jellyfin.LiveTv.Channels /// public async Task> GetChannelsInternalAsync(ChannelQuery query) { - var user = query.UserId.Equals(default) + var user = query.UserId.IsEmpty() ? null : _userManager.GetUserById(query.UserId); @@ -263,7 +263,7 @@ namespace Jellyfin.LiveTv.Channels /// public async Task> GetChannelsAsync(ChannelQuery query) { - var user = query.UserId.Equals(default) + var user = query.UserId.IsEmpty() ? null : _userManager.GetUserById(query.UserId); @@ -695,7 +695,7 @@ namespace Jellyfin.LiveTv.Channels // Find the corresponding channel provider plugin var channelProvider = GetChannelProvider(channel); - var parentItem = query.ParentId.Equals(default) + var parentItem = query.ParentId.IsEmpty() ? channel : _libraryManager.GetItemById(query.ParentId); @@ -708,7 +708,7 @@ namespace Jellyfin.LiveTv.Channels cancellationToken) .ConfigureAwait(false); - if (query.ParentId.Equals(default)) + if (query.ParentId.IsEmpty()) { query.Parent = channel; } diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs index 9eb3aa2fd6..e7e927b2d0 100644 --- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs +++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs @@ -1992,7 +1992,7 @@ namespace Jellyfin.LiveTv.EmbyTV await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false); } - var people = item.Id.Equals(default) ? new List() : _libraryManager.GetPeople(item); + var people = item.Id.IsEmpty() ? new List() : _libraryManager.GetPeople(item); var directors = people .Where(i => i.IsType(PersonKind.Director)) @@ -2317,7 +2317,7 @@ namespace Jellyfin.LiveTv.EmbyTV { string channelId = seriesTimer.RecordAnyChannel ? null : seriesTimer.ChannelId; - if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.Equals(default)) + if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.IsEmpty()) { if (!tempChannelCache.TryGetValue(parent.ChannelId, out LiveTvChannel channel)) { @@ -2376,7 +2376,7 @@ namespace Jellyfin.LiveTv.EmbyTV { string channelId = null; - if (!programInfo.ChannelId.Equals(default)) + if (!programInfo.ChannelId.IsEmpty()) { if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out LiveTvChannel channel)) { diff --git a/src/Jellyfin.LiveTv/LiveTvDtoService.cs b/src/Jellyfin.LiveTv/LiveTvDtoService.cs index 7c7c26eb4a..55b056d3d8 100644 --- a/src/Jellyfin.LiveTv/LiveTvDtoService.cs +++ b/src/Jellyfin.LiveTv/LiveTvDtoService.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Drawing; @@ -456,7 +457,7 @@ namespace Jellyfin.LiveTv info.Id = timer.ExternalId; } - if (!dto.ChannelId.Equals(default) && string.IsNullOrEmpty(info.ChannelId)) + if (!dto.ChannelId.IsEmpty() && string.IsNullOrEmpty(info.ChannelId)) { var channel = _libraryManager.GetItemById(dto.ChannelId); @@ -522,7 +523,7 @@ namespace Jellyfin.LiveTv info.Id = timer.ExternalId; } - if (!dto.ChannelId.Equals(default) && string.IsNullOrEmpty(info.ChannelId)) + if (!dto.ChannelId.IsEmpty() && string.IsNullOrEmpty(info.ChannelId)) { var channel = _libraryManager.GetItemById(dto.ChannelId); diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs index 71822f3762..bada4249aa 100644 --- a/src/Jellyfin.LiveTv/LiveTvManager.cs +++ b/src/Jellyfin.LiveTv/LiveTvManager.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using Jellyfin.LiveTv.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -150,7 +151,7 @@ namespace Jellyfin.LiveTv public QueryResult GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { - var user = query.UserId.Equals(default) + var user = query.UserId.IsEmpty() ? null : _userManager.GetUserById(query.UserId); @@ -1245,7 +1246,7 @@ namespace Jellyfin.LiveTv { cancellationToken.ThrowIfCancellationRequested(); - if (itemId.Equals(default)) + if (itemId.IsEmpty()) { // Somehow some invalid data got into the db. It probably predates the boundary checking continue; @@ -1504,7 +1505,7 @@ namespace Jellyfin.LiveTv public async Task> GetRecordingsAsync(RecordingQuery query, DtoOptions options) { - var user = query.UserId.Equals(default) + var user = query.UserId.IsEmpty() ? null : _userManager.GetUserById(query.UserId); diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs index 4e8aec9f19..2f21495044 100644 --- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs +++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.StartupDtos; using Jellyfin.Api.Models.UserDtos; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using MediaBrowser.Model.Dto; using Xunit; @@ -56,7 +57,7 @@ namespace Jellyfin.Server.Integration.Tests public static async Task GetRootFolderDtoAsync(HttpClient client, Guid userId = default) { - if (userId.Equals(default)) + if (userId.IsEmpty()) { var userDto = await GetUserDtoAsync(client); userId = userDto.Id; From 502cbe77b2658cbca1a9f25d5e5e78ad4cd63eab Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Jan 2024 12:10:09 -0500 Subject: [PATCH 21/68] Use Math.Clamp in GetGuideDays --- src/Jellyfin.LiveTv/Guide/GuideManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs index ae0fdb07a1..18831aa4eb 100644 --- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs +++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs @@ -159,7 +159,7 @@ public class GuideManager : IGuideManager var config = _config.GetLiveTvConfiguration(); return config.GuideDays.HasValue - ? Math.Max(1, Math.Min(config.GuideDays.Value, MaxGuideDays)) + ? Math.Clamp(config.GuideDays.Value, 1, MaxGuideDays) : 7; } From 5d3acd43e9aeaf8e050a8c917192d8288725804d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Jan 2024 12:10:42 -0500 Subject: [PATCH 22/68] Use collection expression --- src/Jellyfin.LiveTv/Guide/GuideManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs index 18831aa4eb..f157af5eaa 100644 --- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs +++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs @@ -233,7 +233,7 @@ public class GuideManager : IGuideManager var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.LiveTvProgram], - ChannelIds = new[] { currentChannel.Id }, + ChannelIds = [currentChannel.Id], DtoOptions = new DtoOptions(true) }).Cast().ToDictionary(i => i.Id); From 75c2de110e3d67ac1f9adc684fc26b066a1915ce Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Jan 2024 12:12:24 -0500 Subject: [PATCH 23/68] Remove useless comment --- src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs b/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs index 1c79d6ab3d..a9fde08501 100644 --- a/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs +++ b/src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs @@ -64,8 +64,11 @@ public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledT { return new[] { - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } + new TaskTriggerInfo + { + Type = TaskTriggerInfo.TriggerInterval, + IntervalTicks = TimeSpan.FromHours(24).Ticks + } }; } } From f0a9639c173a8ade72b0e1de4345c7409da1b78f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Jan 2024 12:14:28 -0500 Subject: [PATCH 24/68] Remove pointless code --- src/Jellyfin.LiveTv/Guide/GuideManager.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs index f157af5eaa..bfbc6d4cc6 100644 --- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs +++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs @@ -146,11 +146,6 @@ public class GuideManager : IGuideManager await coreService.RefreshTimers(cancellationToken).ConfigureAwait(false); } - // Load these now which will prefetch metadata - var dtoOptions = new DtoOptions(); - var fields = dtoOptions.Fields.ToList(); - dtoOptions.Fields = fields.ToArray(); - progress.Report(100); } From 08592fb3fec648aff1b5cf64d03d3339b200cac9 Mon Sep 17 00:00:00 2001 From: TelepathicWalrus Date: Wed, 17 Jan 2024 19:36:14 +0000 Subject: [PATCH 25/68] Add ex to catch if cached mediainfo doesnt exist --- .../Library/LiveStreamHelper.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 59d705acef..d41845cdf0 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -48,21 +48,26 @@ namespace Emby.Server.Implementations.Library if (!string.IsNullOrEmpty(cacheKey)) { - FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath); try { - mediaInfo = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath); + + try + { + mediaInfo = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + // _logger.LogDebug("Found cached media info"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deserializing mediainfo cache"); + } - // _logger.LogDebug("Found cached media info"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deserializing mediainfo cache"); - } - finally - { await jsonStream.DisposeAsync().ConfigureAwait(false); } + catch + { + _logger.LogError("Could not open cached media info"); + } } if (mediaInfo is null) From 4962eb5b432c28bc829d915c29becc57db20cf45 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Jan 2024 17:48:56 -0700 Subject: [PATCH 26/68] chore(deps): update github/codeql-action action to v3.23.1 (#10880) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci-codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml index 4b2673e82d..d8c550e704 100644 --- a/.github/workflows/ci-codeql-analysis.yml +++ b/.github/workflows/ci-codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '8.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 + uses: github/codeql-action/init@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 + uses: github/codeql-action/autobuild@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 + uses: github/codeql-action/analyze@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 From a884b1f7869436fb373d71106330ab1db79cee11 Mon Sep 17 00:00:00 2001 From: Gauvino <68083474+Gauvino@users.noreply.github.com> Date: Thu, 18 Jan 2024 02:11:03 +0100 Subject: [PATCH 27/68] Refactor Dockerfile and build (#10603) * Fix fedora * Fix RID Linux * Fix package and image versions * Fix buildling and optimize docker images ``` * Removed find obj * Changed curl command and added gpg * Added to Contributors * Removed apt-transport-https package * Removed RASPI * Update Intel drivers version * Update Dockerfile for CentOS, Fedora, and portable deployments - Changed Jammy docker image to Built-in Jammy Microsoft .NET SDK image - Switched from using "Yum" to "Dnf" for CentOS and Fedora - Added "dnf clean all" and "rm -rf /var/cache/dnf" to the end of CentOS and Fedora Dockerfiles - Added "apt-get clean", "apt-get autoremove", "rm -rf /var/lib/apt/lists/*" to the end of the Debian/Ubuntu Dockerfiles - Added ${DOTNET_VERSION} in every Dockerfile except CentOS/Fedora - Removed previous warning comment for dotnet publish build in parallel - Arranged package installation * Re-arranged Dockerfile package installation * Re-align * Remove curl * Remove curl --- CONTRIBUTORS.md | 3 +- Dockerfile | 68 +++++++++----------- Dockerfile.arm | 56 +++++++--------- Dockerfile.arm64 | 54 ++++++++-------- bump_version | 4 +- debian/rules | 12 ++-- deployment/Dockerfile.centos.amd64 | 29 +++++---- deployment/Dockerfile.debian.amd64 | 17 +++-- deployment/Dockerfile.debian.arm64 | 25 ++++--- deployment/Dockerfile.debian.armhf | 25 ++++--- deployment/Dockerfile.docker.amd64 | 9 ++- deployment/Dockerfile.docker.arm64 | 9 ++- deployment/Dockerfile.docker.armhf | 9 ++- deployment/Dockerfile.fedora.amd64 | 25 ++++--- deployment/Dockerfile.linux.amd64 | 13 +++- deployment/Dockerfile.linux.amd64-musl | 13 +++- deployment/Dockerfile.linux.arm64 | 13 +++- deployment/Dockerfile.linux.armhf | 13 +++- deployment/Dockerfile.linux.musl-linux-arm64 | 15 +++-- deployment/Dockerfile.macos.amd64 | 13 +++- deployment/Dockerfile.macos.arm64 | 13 +++- deployment/Dockerfile.portable | 13 +++- deployment/Dockerfile.ubuntu.amd64 | 19 +++--- deployment/Dockerfile.ubuntu.arm64 | 55 ++++++++-------- deployment/Dockerfile.ubuntu.armhf | 55 ++++++++-------- deployment/Dockerfile.windows.amd64 | 13 +++- deployment/build.centos.amd64 | 6 +- deployment/build.debian.amd64 | 2 +- deployment/build.debian.arm64 | 2 +- deployment/build.debian.armhf | 2 +- deployment/build.fedora.amd64 | 4 +- deployment/build.linux.amd64 | 8 +-- deployment/build.linux.amd64-musl | 8 +-- deployment/build.linux.arm64 | 8 +-- deployment/build.linux.armhf | 8 +-- deployment/build.linux.musl-linux-arm64 | 8 +-- deployment/build.macos.amd64 | 8 +-- deployment/build.macos.arm64 | 8 +-- deployment/build.portable | 8 +-- deployment/build.ubuntu.amd64 | 2 +- deployment/build.ubuntu.arm64 | 2 +- deployment/build.ubuntu.armhf | 2 +- deployment/build.windows.amd64 | 22 +++---- fedora/README.md | 12 ++-- fedora/jellyfin.spec | 16 +---- 45 files changed, 402 insertions(+), 327 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4e45fd24ad..457f59e0f6 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -172,9 +172,10 @@ - [tallbl0nde](https://github.com/tallbl0nde) - [sleepycatcoding](https://github.com/sleepycatcoding) - [scampower3](https://github.com/scampower3) - - [Chris-Codes-It] (https://github.com/Chris-Codes-It) + - [Chris-Codes-It](https://github.com/Chris-Codes-It) - [Pithaya](https://github.com/Pithaya) - [Çağrı Sakaoğlu](https://github.com/ilovepilav) + - [Gauvino](https://github.com/Gauvino) # Emby Contributors diff --git a/Dockerfile b/Dockerfile index d3f10cd12e..550c3203d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,72 +8,68 @@ FROM node:20-alpine as web-builder ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ + && apk del curl \ && cd jellyfin-web-* \ && npm ci --no-audit --unsafe-perm \ && npm run build:production \ && mv dist /dist -FROM debian:stable-slim as app +FROM debian:bookworm-slim as app # https://askubuntu.com/questions/972516/debian-frontend-environment-variable ARG DEBIAN_FRONTEND="noninteractive" # http://stackoverflow.com/questions/48162574/ddg#49462622 ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_VISIBLE_DEVICES="all" ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" -# https://github.com/intel/compute-runtime/releases -ARG GMMLIB_VERSION=22.0.2 -ARG IGC_VERSION=1.0.10395 -ARG NEO_VERSION=22.08.22549 -ARG LEVEL_ZERO_VERSION=1.3.22549 +ENV JELLYFIN_DATA_DIR=/config +ENV JELLYFIN_CACHE_DIR=/cache + +# https://github.com/intel/compute-runtime/releases +ARG GMMLIB_VERSION=22.3.11.ci17757293 +ARG IGC_VERSION=1.0.15136.22 +ARG NEO_VERSION=23.39.27427.23 +ARG LEVEL_ZERO_VERSION=1.3.27427.23 -# Install dependencies: -# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding. -# curl: healthcheck RUN apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget curl \ - && wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \ + && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \ + && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \ && apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y \ - mesa-va-drivers \ - jellyfin-ffmpeg5 \ - openssl \ - locales \ + && apt-get install --no-install-recommends --no-install-suggests -y mesa-va-drivers jellyfin-ffmpeg6 openssl locales \ # Intel VAAPI Tone mapping dependencies: # Prefer NEO to Beignet since the latter one doesn't support Comet Lake or newer for now. # Do not use the intel-opencl-icd package from repo since they will not build with RELEASE_WITH_REGKEYS enabled. && mkdir intel-compute-runtime \ && cd intel-compute-runtime \ - && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \ - && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \ - && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \ - && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \ - && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \ + && curl -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \ + -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \ + -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \ + -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \ + -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/libigdgmm12_${GMMLIB_VERSION}_amd64.deb \ && dpkg -i *.deb \ && cd .. \ && rm -rf intel-compute-runtime \ - && apt-get remove gnupg wget -y \ + && apt-get remove gnupg -y \ && apt-get clean autoclean -y \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /cache /config /media \ - && chmod 777 /cache /config /media \ + && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \ + && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en +ENV LC_ALL=en_US.UTF-8 +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 -# because of changes in docker and systemd we need to not build in parallel at the moment -# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting -RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none + +RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none FROM app @@ -83,11 +79,9 @@ COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web EXPOSE 8096 -VOLUME /cache /config -ENTRYPOINT ["./jellyfin/jellyfin", \ - "--datadir", "/config", \ - "--cachedir", "/cache", \ - "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] +VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} +ENTRYPOINT [ "./jellyfin/jellyfin", \ + "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 + CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 diff --git a/Dockerfile.arm b/Dockerfile.arm index db1acc838a..07039e43b5 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -4,64 +4,58 @@ # https://github.com/multiarch/qemu-user-static#binfmt_misc-register ARG DOTNET_VERSION=8.0 - FROM node:20-alpine as web-builder ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ + && apk del curl \ && cd jellyfin-web-* \ && npm ci --no-audit --unsafe-perm \ && npm run build:production \ && mv dist /dist FROM multiarch/qemu-user-static:x86_64-arm as qemu -FROM arm32v7/debian:stable-slim as app +FROM arm32v7/debian:bookworm-slim as app # https://askubuntu.com/questions/972516/debian-frontend-environment-variable ARG DEBIAN_FRONTEND="noninteractive" # http://stackoverflow.com/questions/48162574/ddg#49462622 ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_VISIBLE_DEVICES="all" ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" +ENV JELLYFIN_DATA_DIR=/config +ENV JELLYFIN_CACHE_DIR=/cache + COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin -# curl: setup & healthcheck RUN apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ - curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ - curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \ - echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \ - echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \ - apt-get update && \ - apt-get install --no-install-recommends --no-install-suggests -y \ - jellyfin-ffmpeg \ - libssl-dev \ - libfontconfig1 \ - libfreetype6 \ - vainfo \ - libva2 \ - locales \ + && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \ + && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \ + && curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | gpg --dearmor -o /etc/apt/trusted.gpg.d/ubuntu-jellyfin.gpg \ + && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \ + && apt-get update \ + && apt-get install --no-install-recommends --no-install-suggests -y \ + jellyfin-ffmpeg6 libssl-dev libfontconfig1 \ + libfreetype6 vainfo libva2 locales \ && apt-get remove gnupg -y \ && apt-get clean autoclean -y \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /cache /config /media \ - && chmod 777 /cache /config /media \ + && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \ + && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en +ENV LC_ALL=en_US.UTF-8 +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 -# Discard objs - may cause failures if exists -RUN find . -type d -name obj | xargs -r rm -r -# Build + RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none FROM app @@ -72,11 +66,9 @@ COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web EXPOSE 8096 -VOLUME /cache /config -ENTRYPOINT ["./jellyfin/jellyfin", \ - "--datadir", "/config", \ - "--cachedir", "/cache", \ - "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] +VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} +ENTRYPOINT [ "/jellyfin/jellyfin", \ + "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 + CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 3eb5f45fc4..54023794fc 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -4,58 +4,58 @@ # https://github.com/multiarch/qemu-user-static#binfmt_misc-register ARG DOTNET_VERSION=8.0 - FROM node:20-alpine as web-builder ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ + && apk del curl \ && cd jellyfin-web-* \ && npm ci --no-audit --unsafe-perm \ && npm run build:production \ && mv dist /dist FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu -FROM arm64v8/debian:stable-slim as app +FROM arm64v8/debian:bookworm-slim as app # https://askubuntu.com/questions/972516/debian-frontend-environment-variable ARG DEBIAN_FRONTEND="noninteractive" # http://stackoverflow.com/questions/48162574/ddg#49462622 ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) +ENV NVIDIA_VISIBLE_DEVICES="all" ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" +ENV JELLYFIN_DATA_DIR=/config +ENV JELLYFIN_CACHE_DIR=/cache + COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin -# curl: healcheck -RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \ - ffmpeg \ - libssl-dev \ - ca-certificates \ - libfontconfig1 \ - libfreetype6 \ - libomxil-bellagio0 \ - libomxil-bellagio-bin \ - locales \ - curl \ +RUN apt-get update \ + && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \ + && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \ + && curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | gpg --dearmor -o /etc/apt/trusted.gpg.d/ubuntu-jellyfin.gpg \ + && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \ + && apt-get update \ + && apt-get install --no-install-recommends --no-install-suggests -y \ + jellyfin-ffmpeg6 locales libssl-dev libfontconfig1 \ + libfreetype6 libomxil-bellagio0 libomxil-bellagio-bin \ + && apt-get remove gnupg -y \ && apt-get clean autoclean -y \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /cache /config /media \ - && chmod 777 /cache /config /media \ + && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \ + && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en +ENV LC_ALL=en_US.UTF-8 +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 -# Discard objs - may cause failures if exists -RUN find . -type d -name obj | xargs -r rm -r -# Build + RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none FROM app @@ -66,11 +66,9 @@ COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web EXPOSE 8096 -VOLUME /cache /config -ENTRYPOINT ["./jellyfin/jellyfin", \ - "--datadir", "/config", \ - "--cachedir", "/cache", \ - "--ffmpeg", "/usr/bin/ffmpeg"] +VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} +ENTRYPOINT [ "/jellyfin/jellyfin", \ + "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 + CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 diff --git a/bump_version b/bump_version index 41d27f5c8a..dd55e62c79 100755 --- a/bump_version +++ b/bump_version @@ -21,7 +21,7 @@ fi shared_version_file="./SharedVersion.cs" build_file="./build.yaml" # csproj files for nuget packages -jellyfin_subprojects=( +jellyfin_subprojects=( MediaBrowser.Common/MediaBrowser.Common.csproj Jellyfin.Data/Jellyfin.Data.csproj MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -97,7 +97,7 @@ cat ${debian_changelog_file} >> ${debian_changelog_temp} # Move into place mv ${debian_changelog_temp} ${debian_changelog_file} -# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting +# Write out a temporary Dnf changelog with our new stuff prepended and some templated formatting fedora_spec_file="fedora/jellyfin.spec" fedora_changelog_temp="$( mktemp )" fedora_spec_temp_dir="$( mktemp -d )" diff --git a/debian/rules b/debian/rules index 069d48aad1..79cd55a15d 100755 --- a/debian/rules +++ b/debian/rules @@ -7,27 +7,27 @@ HOST_ARCH := $(shell arch) BUILD_ARCH := ${DEB_HOST_MULTIARCH} ifeq ($(HOST_ARCH),x86_64) # Building AMD64 - DOTNETRUNTIME := debian-x64 + DOTNETRUNTIME := linux-x64 ifeq ($(BUILD_ARCH),arm-linux-gnueabihf) # Cross-building ARM on AMD64 - DOTNETRUNTIME := debian-arm + DOTNETRUNTIME := linux-arm endif ifeq ($(BUILD_ARCH),aarch64-linux-gnu) # Cross-building ARM on AMD64 - DOTNETRUNTIME := debian-arm64 + DOTNETRUNTIME := linux-arm64 endif endif ifeq ($(HOST_ARCH),armv7l) # Building ARM - DOTNETRUNTIME := debian-arm + DOTNETRUNTIME := linux-arm endif ifeq ($(HOST_ARCH),arm64) # Building ARM - DOTNETRUNTIME := debian-arm64 + DOTNETRUNTIME := linux-arm64 endif ifeq ($(HOST_ARCH),aarch64) # Building ARM - DOTNETRUNTIME := debian-arm64 + DOTNETRUNTIME := linux-arm64 endif export DH_VERBOSE=1 diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64 index 7c9bbf39e9..3db184f494 100644 --- a/deployment/Dockerfile.centos.amd64 +++ b/deployment/Dockerfile.centos.amd64 @@ -1,29 +1,36 @@ -FROM centos:7 +FROM quay.io/centos/centos:stream9 + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV IS_DOCKER=YES # Prepare CentOS environment -RUN yum update -yq \ - && yum install -yq epel-release \ - && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget +RUN dnf update -yq \ + && dnf install -yq epel-release \ + && dnf install -yq \ + rpmdevtools libcurl-devel fontconfig-devel \ + freetype-devel openssl-devel glibc-devel \ + libicu-devel git wget dnf-plugins-core \ + && dnf clean all \ + && rm -rf /var/cache/dnf # Install DotNET SDK RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet # Create symlinks and directories RUN ln -sf ${SOURCE_DIR}/deployment/build.centos.amd64 /build.sh \ - && mkdir -p ${SOURCE_DIR}/SPECS \ - && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ - && mkdir -p ${SOURCE_DIR}/SOURCES \ - && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES + && mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ + && mkdir -p ${SOURCE_DIR}/SOURCES \ + && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES VOLUME ${SOURCE_DIR}/ diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index d344c59646..da0c9dabd3 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -10,11 +14,14 @@ ENV ARCH=amd64 ENV IS_DOCKER=YES # Prepare Debian build environment -RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ +RUN apt-get update -yq \ + && apt-get install --no-install-recommends -yq \ debhelper gnupg devscripts build-essential mmv \ - libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev \ - libssl1.1 liblttng-ust0 + libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev \ + libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yq \ + && apt-get autoremove -yq \ + && rm -rf /var/lib/apt/lists/* # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.amd64 /build.sh diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index 8a5411f059..6c4cb816f5 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,23 +15,26 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts build-essential mmv # Prepare the cross-toolchain RUN dpkg --add-architecture arm64 \ - && apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends cross-gcc-dev \ - && TARGET_LIST="arm64" cross-gcc-gensource 9 \ - && cd cross-gcc-packages-amd64/cross-gcc-9-arm64 \ - && apt-get install -yqq --no-install-recommends \ - gcc-9-source libstdc++-9-dev-arm64-cross \ + && apt-get update -yqq \ + && apt-get install --no-install-recommends -yqq cross-gcc-dev \ + && TARGET_LIST="arm64" cross-gcc-gensource 12 \ + && cd cross-gcc-packages-amd64/cross-gcc-12-arm64 \ + && apt-get install --no-install-recommends -yqq \ + gcc-12-source libstdc++-12-dev-arm64-cross \ binutils-aarch64-linux-gnu bison flex libtool \ gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev \ systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip \ libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 \ libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 \ - libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust0:arm64 libstdc++-9-dev:arm64 + libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust1:arm64 libstdc++-12-dev:arm64 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.arm64 /build.sh diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index e95ba16962..b1fa6cee52 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,24 +15,27 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts build-essential mmv # Prepare the cross-toolchain RUN dpkg --add-architecture armhf \ - && apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends cross-gcc-dev \ - && TARGET_LIST="armhf" cross-gcc-gensource 9 \ - && cd cross-gcc-packages-amd64/cross-gcc-9-armhf \ - && apt-get install -yqq --no-install-recommends\ - gcc-9-source libstdc++-9-dev-armhf-cross \ + && apt-get update -yqq \ + && apt-get install --no-install-recommends -yqq cross-gcc-dev \ + && TARGET_LIST="armhf" cross-gcc-gensource 12 \ + && cd cross-gcc-packages-amd64/cross-gcc-12-armhf \ + && apt-get install --no-install-recommends -yqq \ + gcc-12-source libstdc++-12-dev-armhf-cross \ binutils-aarch64-linux-gnu bison flex libtool gdb \ sharutils netbase libmpc-dev libmpfr-dev libgmp-dev \ systemtap-sdt-dev autogen expect chrpath zlib1g-dev \ zip binutils-arm-linux-gnueabihf libc6-dev:armhf \ linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf \ libfontconfig1-dev:armhf libfreetype6-dev:armhf libssl-dev:armhf \ - liblttng-ust0:armhf libstdc++-9-dev:armhf + liblttng-ust1:armhf libstdc++-12-dev:armhf \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64 index 1749ca563c..ca16a08fbc 100644 --- a/deployment/Dockerfile.docker.amd64 +++ b/deployment/Dockerfile.docker.amd64 @@ -1,13 +1,12 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin WORKDIR ${SOURCE_DIR} COPY . . - ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 -# because of changes in docker and systemd we need to not build in parallel at the moment -# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting -RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none +RUN dotnet publish Jellyfin.Server --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64 index bbddb61e4d..6e0f7d18e9 100644 --- a/deployment/Dockerfile.docker.arm64 +++ b/deployment/Dockerfile.docker.arm64 @@ -1,13 +1,12 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin WORKDIR ${SOURCE_DIR} COPY . . - ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 -# because of changes in docker and systemd we need to not build in parallel at the moment -# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting -RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none +RUN dotnet publish Jellyfin.Server --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf index 3de1d68878..44fb705e6d 100644 --- a/deployment/Dockerfile.docker.armhf +++ b/deployment/Dockerfile.docker.armhf @@ -1,13 +1,12 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin WORKDIR ${SOURCE_DIR} COPY . . - ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 -# because of changes in docker and systemd we need to not build in parallel at the moment -# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting -RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none +RUN dotnet publish Jellyfin.Server --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index 66ead37d7d..75a6d1e649 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -1,7 +1,9 @@ FROM fedora:39 + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -9,21 +11,26 @@ ENV IS_DOCKER=YES # Prepare Fedora environment RUN dnf update -yq \ - && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make + && dnf install -yq \ + @buildsys-build rpmdevtools git \ + dnf-plugins-core libcurl-devel fontconfig-devel \ + freetype-devel openssl-devel glibc-devel \ + libicu-devel systemd wget make \ + && dnf clean all \ + && rm -rf /var/cache/dnf # Install DotNET SDK RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet # Create symlinks and directories RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.amd64 /build.sh \ - && mkdir -p ${SOURCE_DIR}/SPECS \ - && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ - && mkdir -p ${SOURCE_DIR}/SOURCES \ - && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES + && mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ + && mkdir -p ${SOURCE_DIR}/SOURCES \ + && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES VOLUME ${SOURCE_DIR}/ diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 386f7cefe0..6b8de3773f 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,10 +15,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts unzip \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.amd64 /build.sh diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl index 56c8773332..49d98da2ac 100644 --- a/deployment/Dockerfile.linux.amd64-musl +++ b/deployment/Dockerfile.linux.amd64-musl @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,10 +15,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts unzip \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.amd64-musl /build.sh diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64 index c9692c440a..aba33c8b23 100644 --- a/deployment/Dockerfile.linux.arm64 +++ b/deployment/Dockerfile.linux.arm64 @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,10 +15,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts unzip \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.arm64 /build.sh diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf index 2304615560..247f756150 100644 --- a/deployment/Dockerfile.linux.armhf +++ b/deployment/Dockerfile.linux.armhf @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,10 +15,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts unzip \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.armhf /build.sh diff --git a/deployment/Dockerfile.linux.musl-linux-arm64 b/deployment/Dockerfile.linux.musl-linux-arm64 index 240d091869..a6e1ba217e 100644 --- a/deployment/Dockerfile.linux.musl-linux-arm64 +++ b/deployment/Dockerfile.linux.musl-linux-arm64 @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,10 +15,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ - apt-transport-https debhelper gnupg devscripts unzip \ + && apt-get install --no-install-recommends -yqq \ + debhelper gnupg devscripts unzip \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.musl-linux-arm64 /build.sh diff --git a/deployment/Dockerfile.macos.amd64 b/deployment/Dockerfile.macos.amd64 index 1b054dfc47..45980c363e 100644 --- a/deployment/Dockerfile.macos.amd64 +++ b/deployment/Dockerfile.macos.amd64 @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,10 +15,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.macos.amd64 /build.sh diff --git a/deployment/Dockerfile.macos.arm64 b/deployment/Dockerfile.macos.arm64 index 07e18da55a..ee3a813dde 100644 --- a/deployment/Dockerfile.macos.arm64 +++ b/deployment/Dockerfile.macos.arm64 @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,10 +15,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.macos.arm64 /build.sh diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index 36135f7a6e..0ab1b19147 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -10,10 +14,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index 84fa2028e5..2326d3e852 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -1,7 +1,11 @@ -FROM ubuntu:bionic +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-jammy + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,16 +15,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg wget ca-certificates devscripts \ mmv build-essential libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 - -# Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.ubuntu.amd64 /build.sh diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index ca3aa35085..461a287a18 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -1,7 +1,11 @@ -FROM ubuntu:bionic +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-jammy + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,39 +15,36 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg wget ca-certificates devscripts \ mmv build-essential lsb-release -# Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - # Prepare the cross-toolchain RUN rm /etc/apt/sources.list \ - && export CODENAME="$( lsb_release -c -s )" \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ - && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ - && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ - && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ - && dpkg --add-architecture arm64 \ - && apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends cross-gcc-dev \ - && TARGET_LIST="arm64" cross-gcc-gensource 6 \ - && cd cross-gcc-packages-amd64/cross-gcc-6-arm64 \ - && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \ - && apt-get install -yqq --no-install-recommends \ - gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu \ - bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev \ + && export CODENAME="$( lsb_release -c -s )" \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ + && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ + && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ + && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \ + && dpkg --add-architecture arm64 \ + && apt-get update -yqq \ + && apt-get install --no-install-recommends -yqq cross-gcc-dev \ + && TARGET_LIST="arm64" cross-gcc-gensource 12 \ + && cd cross-gcc-packages-amd64/cross-gcc-12-arm64 \ + && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \ + && apt-get install --no-install-recommends -yqq \ + gcc-12-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu \ + bison flex libtool gdb sharutils netbase libmpc-dev \ libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev \ zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 \ - libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64 libssl-dev:arm64 + libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust1:arm64 libstdc++6:arm64 libssl-dev:arm64 \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.ubuntu.arm64 /build.sh diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index e52b7fba35..83fe32acf8 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -1,7 +1,11 @@ -FROM ubuntu:bionic +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-jammy + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,39 +15,36 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg wget ca-certificates devscripts \ mmv build-essential lsb-release -# Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - # Prepare the cross-toolchain RUN rm /etc/apt/sources.list \ - && export CODENAME="$( lsb_release -c -s )" \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ - && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ - && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ - && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ - && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ - && dpkg --add-architecture armhf \ - && apt-get update -yqq \ - && apt-get install -yqq cross-gcc-dev \ - && TARGET_LIST="armhf" cross-gcc-gensource 6 \ - && cd cross-gcc-packages-amd64/cross-gcc-6-armhf \ - && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \ - && apt-get install -yqq --no-install-recommends \ - gcc-6-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf \ - bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev \ + && export CODENAME="$( lsb_release -c -s )" \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ + && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \ + && dpkg --add-architecture armhf \ + && apt-get update -yqq \ + && apt-get install --no-install-recommends -yqq cross-gcc-dev \ + && TARGET_LIST="armhf" cross-gcc-gensource 12 \ + && cd cross-gcc-packages-amd64/cross-gcc-12-armhf \ + && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \ + && apt-get install --no-install-recommends -yqq \ + gcc-12-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf \ + bison flex libtool gdb sharutils netbase libmpc-dev \ libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev \ zip libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf \ - libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust0:armhf libstdc++6:armhf libssl-dev:armhf + libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust1:armhf libstdc++6:armhf libssl-dev:armhf \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index 08587aa7eb..358fb620ac 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -1,7 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim +ARG DOTNET_VERSION=8.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim + # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist + # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -10,10 +14,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update -yqq \ - && apt-get install -yqq --no-install-recommends \ + && apt-get install --no-install-recommends -yqq \ debhelper gnupg devscripts unzip \ mmv libcurl4-openssl-dev libfontconfig1-dev \ - libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 zip + libfreetype6-dev libssl-dev libssl3 liblttng-ust1 zip \ + && apt-get clean autoclean -yqq \ + && apt-get autoremove -yqq \ + && rm -rf /var/lib/apt/lists/* # Link to docker-build script RUN ln -sf ${SOURCE_DIR}/deployment/build.windows.amd64 /build.sh diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64 index a0ab93e4e0..af73e31533 100755 --- a/deployment/build.centos.amd64 +++ b/deployment/build.centos.amd64 @@ -1,6 +1,6 @@ #!/bin/bash -#= CentOS/RHEL 7+ amd64 .rpm +#= CentOS/RHEL 8+ amd64 .rpm set -o errexit set -o xtrace @@ -42,7 +42,7 @@ rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi rm -f fedora/jellyfin*.tar.gz @@ -51,7 +51,7 @@ if [[ ${IS_DOCKER} == YES ]]; then pushd fedora cp -a /tmp/spec.orig jellyfin.spec - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" popd fi diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64 index 1a59d02e91..85776ad6a0 100755 --- a/deployment/build.debian.amd64 +++ b/deployment/build.debian.amd64 @@ -37,7 +37,7 @@ mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64 index e1e30fab4a..d37cc5a64a 100755 --- a/deployment/build.debian.arm64 +++ b/deployment/build.debian.arm64 @@ -38,7 +38,7 @@ mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf index e3e8ae004e..f3505b1478 100755 --- a/deployment/build.debian.armhf +++ b/deployment/build.debian.armhf @@ -38,7 +38,7 @@ mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64 index da345ec08a..21859cbf9f 100755 --- a/deployment/build.fedora.amd64 +++ b/deployment/build.fedora.amd64 @@ -42,7 +42,7 @@ rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi rm -f fedora/jellyfin*.tar.gz @@ -51,7 +51,7 @@ if [[ ${IS_DOCKER} == YES ]]; then pushd fedora cp -a /tmp/spec.orig jellyfin.spec - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" popd fi diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64 index c6baa61f6e..2998d2f9e3 100755 --- a/deployment/build.linux.amd64 +++ b/deployment/build.linux.amd64 @@ -16,16 +16,16 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true -tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version} -rm -rf dist/jellyfin-server_${version} +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true +tar -czf jellyfin-server_"${version}"_linux-amd64.tar.gz -C dist jellyfin-server_"${version}" +rm -rf dist/jellyfin-server_"${version}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.linux.amd64-musl b/deployment/build.linux.amd64-musl index 6523f8319a..0fa1764650 100755 --- a/deployment/build.linux.amd64-musl +++ b/deployment/build.linux.amd64-musl @@ -16,16 +16,16 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true -tar -czf jellyfin-server_${version}_linux-amd64-musl.tar.gz -C dist jellyfin-server_${version} -rm -rf dist/jellyfin-server_${version} +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true +tar -czf jellyfin-server_"${version}"_linux-amd64-musl.tar.gz -C dist jellyfin-server_"${version}" +rm -rf dist/jellyfin-server_"${version}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.linux.arm64 b/deployment/build.linux.arm64 index 6d6a8f803a..dc44ca330d 100755 --- a/deployment/build.linux.arm64 +++ b/deployment/build.linux.arm64 @@ -16,16 +16,16 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true -tar -czf jellyfin-server_${version}_linux-arm64.tar.gz -C dist jellyfin-server_${version} -rm -rf dist/jellyfin-server_${version} +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true +tar -czf jellyfin-server_"${version}"_linux-arm64.tar.gz -C dist jellyfin-server_"${version}" +rm -rf dist/jellyfin-server_"${version}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.linux.armhf b/deployment/build.linux.armhf index 5167dfcb8d..f9de9ff0a3 100755 --- a/deployment/build.linux.armhf +++ b/deployment/build.linux.armhf @@ -16,16 +16,16 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true -tar -czf jellyfin-server_${version}_linux-armhf.tar.gz -C dist jellyfin-server_${version} -rm -rf dist/jellyfin-server_${version} +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true +tar -czf jellyfin-server_"${version}"_linux-armhf.tar.gz -C dist jellyfin-server_"${version}" +rm -rf dist/jellyfin-server_"${version}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.linux.musl-linux-arm64 b/deployment/build.linux.musl-linux-arm64 index 57980314d4..ae9ab010f5 100755 --- a/deployment/build.linux.musl-linux-arm64 +++ b/deployment/build.linux.musl-linux-arm64 @@ -16,16 +16,16 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true -tar -czf jellyfin-server_${version}_linux-arm64-musl.tar.gz -C dist jellyfin-server_${version} -rm -rf dist/jellyfin-server_${version} +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-arm64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true +tar -czf jellyfin-server_"${version}"_linux-arm64-musl.tar.gz -C dist jellyfin-server_"${version}" +rm -rf dist/jellyfin-server_"${version}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.macos.amd64 b/deployment/build.macos.amd64 index c7711e82c7..81e0f43f62 100755 --- a/deployment/build.macos.amd64 +++ b/deployment/build.macos.amd64 @@ -16,16 +16,16 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true -tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version} -rm -rf dist/jellyfin-server_${version} +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true +tar -czf jellyfin-server_"${version}"_macos-amd64.tar.gz -C dist jellyfin-server_"${version}" +rm -rf dist/jellyfin-server_"${version}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.macos.arm64 b/deployment/build.macos.arm64 index b07eaad4e0..0a6f37edea 100755 --- a/deployment/build.macos.arm64 +++ b/deployment/build.macos.arm64 @@ -16,16 +16,16 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true -tar -czf jellyfin-server_${version}_macos-arm64.tar.gz -C dist jellyfin-server_${version} -rm -rf dist/jellyfin-server_${version} +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-arm64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true +tar -czf jellyfin-server_"${version}"_macos-arm64.tar.gz -C dist jellyfin-server_"${version}" +rm -rf dist/jellyfin-server_"${version}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.portable b/deployment/build.portable index ec151d2956..fad14fccfc 100755 --- a/deployment/build.portable +++ b/deployment/build.portable @@ -16,16 +16,16 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=false -tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version} -rm -rf dist/jellyfin-server_${version} +dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=false +tar -czf jellyfin-server_"${version}"_portable.tar.gz -C dist jellyfin-server_"${version}" +rm -rf dist/jellyfin-server_"${version}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64 index 17968a6e93..3658676035 100755 --- a/deployment/build.ubuntu.amd64 +++ b/deployment/build.ubuntu.amd64 @@ -37,7 +37,7 @@ mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64 index ee7da9bb98..8cf24b955f 100755 --- a/deployment/build.ubuntu.arm64 +++ b/deployment/build.ubuntu.arm64 @@ -38,7 +38,7 @@ mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf index 85c993282e..896486dcd4 100755 --- a/deployment/build.ubuntu.armhf +++ b/deployment/build.ubuntu.armhf @@ -38,7 +38,7 @@ mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64 index 20f976365d..cd07f4e0b2 100755 --- a/deployment/build.windows.amd64 +++ b/deployment/build.windows.amd64 @@ -23,30 +23,30 @@ fi output_dir="dist/jellyfin-server_${version}" # Build binary -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output "${output_dir}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true # Prepare addins addin_build_dir="$( mktemp -d )" -wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip -wget ${FFMPEG_URL} -O ${addin_build_dir}/jellyfin-ffmpeg.zip -unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir} -cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe ${output_dir}/nssm.exe -unzip ${addin_build_dir}/jellyfin-ffmpeg.zip -d ${addin_build_dir}/jellyfin-ffmpeg -cp ${addin_build_dir}/jellyfin-ffmpeg/* ${output_dir} -rm -rf ${addin_build_dir} +wget ${NSSM_URL} -O "${addin_build_dir}"/nssm.zip +wget ${FFMPEG_URL} -O "${addin_build_dir}"/jellyfin-ffmpeg.zip +unzip "${addin_build_dir}"/nssm.zip -d "${addin_build_dir}" +cp "${addin_build_dir}"/${NSSM_VERSION}/win64/nssm.exe "${output_dir}"/nssm.exe +unzip "${addin_build_dir}"/jellyfin-ffmpeg.zip -d "${addin_build_dir}"/jellyfin-ffmpeg +cp "${addin_build_dir}"/jellyfin-ffmpeg/* "${output_dir}" +rm -rf "${addin_build_dir}" # Create zip package pushd dist -zip -qr jellyfin-server_${version}.portable.zip jellyfin-server_${version} +zip -qr jellyfin-server_"${version}".portable.zip jellyfin-server_"${version}" popd -rm -rf ${output_dir} +rm -rf "${output_dir}" # Move the artifacts out mkdir -p "${ARTIFACT_DIR}/" mv dist/jellyfin[-_]*.zip "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" + chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi popd diff --git a/fedora/README.md b/fedora/README.md index d449b51c16..6ea87740f2 100644 --- a/fedora/README.md +++ b/fedora/README.md @@ -14,8 +14,10 @@ The RPM package for Fedora/CentOS requires some additional repositories as ffmpe # ffmpeg from RPMfusion free # Fedora $ sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm -# CentOS 7 -$ sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm +# CentOS 8 +$ sudo dnf localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm +# CentOS 9 +$ sudo dnf localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm ``` ## Building with dotnet @@ -26,8 +28,10 @@ Jellyfin is build with `--self-contained` so no dotnet required for runtime. # dotnet required for building the RPM # Fedora $ sudo dnf copr enable @dotnet-sig/dotnet -# CentOS -$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm +# CentOS 8 +$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/8/packages-microsoft-prod.rpm +# CentOS 9 +$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/9/packages-microsoft-prod.rpm ``` ## TODO diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index fb9fb2f7da..5327495ad3 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -1,10 +1,4 @@ %global debug_package %{nil} -# Set the dotnet runtime -%if 0%{?fedora} -%global dotnet_runtime fedora.%{fedora}-x64 -%else -%global dotnet_runtime centos-x64 -%endif Name: jellyfin Version: 10.9.0 @@ -29,12 +23,6 @@ BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, BuildRequires: dotnet-runtime-8.0, dotnet-sdk-8.0 Requires: %{name}-server = %{version}-%{release}, %{name}-web = %{version}-%{release} -# Temporary (hopefully?) fix for https://github.com/jellyfin/jellyfin/issues/7471 -%if 0%{?fedora} >= 36 -%global __requires_exclude ^liblttng-ust\\.so\\.0.*$ -%endif - - %description Jellyfin is a free software media system that puts you in control of managing and streaming your media. @@ -66,14 +54,14 @@ the Jellyfin server to bind to ports 80 and/or 443 for example. export DOTNET_CLI_TELEMETRY_OPTOUT=1 export PATH=$PATH:/usr/local/bin # cannot use --output due to https://github.com/dotnet/sdk/issues/22220 -dotnet publish --configuration Release --self-contained --runtime %{dotnet_runtime} \ +dotnet publish --configuration Release --self-contained --runtime linux-x64 \ -p:DebugSymbols=false -p:DebugType=none Jellyfin.Server %install # Jellyfin files %{__mkdir} -p %{buildroot}%{_libdir}/jellyfin %{buildroot}%{_bindir} -%{__cp} -r Jellyfin.Server/bin/Release/net8.0/%{dotnet_runtime}/publish/* %{buildroot}%{_libdir}/jellyfin +%{__cp} -r Jellyfin.Server/bin/Release/net8.0/linux-x64/publish/* %{buildroot}%{_libdir}/jellyfin %{__install} -D %{SOURCE10} %{buildroot}%{_bindir}/jellyfin sed -i -e 's|/usr/lib64|%{_libdir}|g' %{buildroot}%{_bindir}/jellyfin From 2fa50cceef6dede0b80fc9f528f057553a7928fc Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Wed, 17 Jan 2024 18:21:20 -0700 Subject: [PATCH 28/68] Use NuGetAuthenticate@1 --- .ci/azure-pipelines-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 39f98e063e..b0684c0d4c 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -259,7 +259,7 @@ jobs: publishFeedCredentials: 'NugetOrg' allowPackageConflicts: true # This ignores an error if the version already exists - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 displayName: 'Authenticate to unstable Nuget feed' condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') From 8fea819b5152f6a38febb9435df18c2fa26d3273 Mon Sep 17 00:00:00 2001 From: Attila Szakacs Date: Thu, 18 Jan 2024 16:38:47 +0100 Subject: [PATCH 29/68] Extract all subtitle streams simultaneously Extracting a subtitle stream is a disk I/O bottlenecked operation as ffmpeg has to read through the whole file, but usually there is nothing CPU intensive to do. If a file has multiple subtitle streams, and we want to extract more of them, extracting them one-by-one results in reading the whole file again and again. However ffmpeg can extract multiple streams at once. We can optimize this by extracting the subtitle streams all at once when only one of them gets queried, then we will have all of them cached for later use. It is useful for people switching subtitles during playback. It is even more useful for people who extract all the subtitle streams in advance, for example with the "Subtitle Extract" plugin. In this case we reduce the extraction time significantly based on the number of subtitle streams in the files, which can be 5-10 in many cases. Signed-off-by: Attila Szakacs --- .../Subtitles/SubtitleEncoder.cs | 221 +++++++++++++++--- 1 file changed, 194 insertions(+), 27 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 459d854bf1..0e66565ed0 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -194,36 +195,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles { if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase)) { - string outputFormat; - string outputCodec; + await ExtractAllTextSubtitles(mediaSource, cancellationToken).ConfigureAwait(false); - if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) - || string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase) - || string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = subtitleStream.Codec; - } - else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = "srt"; - } - else - { - // Extract - outputCodec = "srt"; - outputFormat = "srt"; - } - - // Extract + var outputFormat = GetTextSubtitleFormat(subtitleStream); var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat); - await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken) - .ConfigureAwait(false); - return new SubtitleInfo() { Path = outputPath, @@ -467,6 +443,197 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.LogInformation("ffmpeg subtitle conversion succeeded for {Path}", inputPath); } + private string GetTextSubtitleFormat(MediaStream subtitleStream) + { + if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)) + { + return subtitleStream.Codec; + } + else + { + return "srt"; + } + } + + private bool IsCodecCopyable(string codec) + { + return string.Equals(codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "ssa", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "srt", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "subrip", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Extracts all text subtitles. + /// + /// The mediaSource. + /// The cancellation token. + /// Task. + private async Task ExtractAllTextSubtitles(MediaSourceInfo mediaSource, CancellationToken cancellationToken) + { + var semaphores = new List { }; + var extractableStreams = new List { }; + + try + { + var subtitleStreams = mediaSource.MediaStreams + .Where(stream => stream.IsTextSubtitleStream && stream.SupportsExternalStream); + + foreach (var subtitleStream in subtitleStreams) + { + var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream)); + + var semaphore = GetLock(outputPath); + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + if (File.Exists(outputPath)) + { + semaphore.Release(); + continue; + } + + semaphores.Add(semaphore); + extractableStreams.Add(subtitleStream); + } + + if (extractableStreams.Count > 0) + { + await ExtractAllTextSubtitlesInternal(mediaSource, extractableStreams, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Unable to get streams for File:{File}", mediaSource.Path); + } + finally + { + foreach (var semaphore in semaphores) + { + semaphore.Release(); + } + } + } + + private async Task ExtractAllTextSubtitlesInternal( + MediaSourceInfo mediaSource, + List subtitleStreams, + CancellationToken cancellationToken) + { + var inputPath = mediaSource.Path; + var outputPaths = new List { }; + var args = string.Format( + CultureInfo.InvariantCulture, + "-i {0} -copyts", + inputPath); + + foreach (var subtitleStream in subtitleStreams) + { + var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream)); + var outputCodec = IsCodecCopyable(subtitleStream.Codec) ? "copy" : "srt"; + + Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new FileNotFoundException($"Calculated path ({outputPath}) is not valid.")); + + outputPaths.Add(outputPath); + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0} -an -vn -c:s {1} \"{2}\"", + subtitleStream.Index, + outputCodec, + outputPath); + } + + int exitCode; + + using (var process = new Process + { + StartInfo = new ProcessStartInfo + { + CreateNoWindow = true, + UseShellExecute = false, + FileName = _mediaEncoder.EncoderPath, + Arguments = args, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false + }, + EnableRaisingEvents = true + }) + { + _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); + + try + { + process.Start(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error starting ffmpeg"); + + throw; + } + + try + { + await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); + exitCode = process.ExitCode; + } + catch (OperationCanceledException) + { + process.Kill(true); + exitCode = -1; + } + } + + var failed = false; + + if (exitCode == -1) + { + failed = true; + + foreach (var outputPath in outputPaths) + { + try + { + _logger.LogWarning("Deleting extracted subtitle due to failure: {Path}", outputPath); + _fileSystem.DeleteFile(outputPath); + } + catch (FileNotFoundException) + { + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting extracted subtitle {Path}", outputPath); + } + } + } + else + { + foreach (var outputPath in outputPaths) + { + if (!File.Exists(outputPath)) + { + _logger.LogError("ffmpeg subtitle extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); + failed = true; + } + else + { + if (outputPath.EndsWith("ass", StringComparison.OrdinalIgnoreCase)) + { + await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false); + } + + _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath); + } + } + } + + if (failed) + { + throw new FfmpegException( + string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0}", inputPath)); + } + } + /// /// Extracts the text subtitle. /// From 879ac12d433f94f40b2630d2de39cc0896990374 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:00:58 +0000 Subject: [PATCH 30/68] chore(deps): update actions/upload-artifact action to v4.2.0 --- .github/workflows/ci-openapi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index 75ec82c5f1..63268001e0 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -25,7 +25,7 @@ jobs: - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: openapi-head retention-days: 14 @@ -59,7 +59,7 @@ jobs: - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: openapi-base retention-days: 14 From 21ae7a1317255acd0a031e74575f6c08967ce5f3 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 18 Jan 2024 23:11:22 +0000 Subject: [PATCH 31/68] Added ffmpeg version to build --- .../devcontainer.json | 0 .../Dev - Server Ffmpeg/devcontainer.json | 28 +++++++++++++++++++ .../Dev - Server Ffmpeg/install-ffmpeg.sh | 5 ++++ .vscode/extensions.json | 2 +- 4 files changed, 34 insertions(+), 1 deletion(-) rename .devcontainer/{ => Dev - Server Default}/devcontainer.json (100%) create mode 100644 .devcontainer/Dev - Server Ffmpeg/devcontainer.json create mode 100644 .devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/Dev - Server Default/devcontainer.json similarity index 100% rename from .devcontainer/devcontainer.json rename to .devcontainer/Dev - Server Default/devcontainer.json diff --git a/.devcontainer/Dev - Server Ffmpeg/devcontainer.json b/.devcontainer/Dev - Server Ffmpeg/devcontainer.json new file mode 100644 index 0000000000..8d1413d092 --- /dev/null +++ b/.devcontainer/Dev - Server Ffmpeg/devcontainer.json @@ -0,0 +1,28 @@ +{ + "name": "Development Jellyfin Server - FFmpeg", + "image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy", + // restores nuget packages, installs the dotnet workloads and installs the dev https certificate + "postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust; bash ./install-ffmpeg.sh", + // reads the extensions list and installs them + "postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension", + "features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "none", + "dotnetRuntimeVersions": "8.0", + "aspNetCoreRuntimeVersions": "8.0" + }, + "ghcr.io/devcontainers-contrib/features/apt-packages:1": { + "preserve_apt_list": false, + "packages": ["libfontconfig1"] + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "dockerDashComposeVersion": "v2" + }, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {} + }, + "hostRequirements": { + "memory": "8gb", + "cpus": 4 + } +} diff --git a/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh b/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh new file mode 100644 index 0000000000..d2a54b98f3 --- /dev/null +++ b/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +sudo wget https://repo.jellyfin.org/releases/server/ubuntu/versions/jellyfin-ffmpeg/6.0-8/jellyfin-ffmpeg6_6.0-8-focal_amd64.deb -O ffmpeg.deb +sudo apt install -f ./ffmpeg.deb -y +rm ffmpeg.deb \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d738e9fba4..3be946e446 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,7 +2,7 @@ "recommendations": [ "ms-dotnettools.csharp", "editorconfig.editorconfig", - "GitHub.vscode-github-actions", + "github.vscode-github-actions", "ms-dotnettools.vscode-dotnet-runtime", "ms-dotnettools.csdevkit" ], From 23c77706838aa2e5cf83ee036f3853a90acc4a68 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Fri, 19 Jan 2024 00:48:03 +0000 Subject: [PATCH 32/68] Fixed ffmpeg version updated lauch with ffmpeg --- .devcontainer/Dev - Server Ffmpeg/devcontainer.json | 2 +- .devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh | 6 ++++-- .../{Dev - Server Default => }/devcontainer.json | 0 .vscode/launch.json | 12 ++++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) rename .devcontainer/{Dev - Server Default => }/devcontainer.json (100%) diff --git a/.devcontainer/Dev - Server Ffmpeg/devcontainer.json b/.devcontainer/Dev - Server Ffmpeg/devcontainer.json index 8d1413d092..0b848d9f3c 100644 --- a/.devcontainer/Dev - Server Ffmpeg/devcontainer.json +++ b/.devcontainer/Dev - Server Ffmpeg/devcontainer.json @@ -2,7 +2,7 @@ "name": "Development Jellyfin Server - FFmpeg", "image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy", // restores nuget packages, installs the dotnet workloads and installs the dev https certificate - "postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust; bash ./install-ffmpeg.sh", + "postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust; sudo bash \"./.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh\"", // reads the extensions list and installs them "postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension", "features": { diff --git a/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh b/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh index d2a54b98f3..c84e1258fa 100644 --- a/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh +++ b/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh @@ -1,5 +1,7 @@ #!/bin/bash -sudo wget https://repo.jellyfin.org/releases/server/ubuntu/versions/jellyfin-ffmpeg/6.0-8/jellyfin-ffmpeg6_6.0-8-focal_amd64.deb -O ffmpeg.deb +wget https://repo.jellyfin.org/releases/server/ubuntu/versions/jellyfin-ffmpeg/6.0.1-1/jellyfin-ffmpeg6_6.0.1-1-jammy_amd64.deb -O ffmpeg.deb + +sudo apt update sudo apt install -f ./ffmpeg.deb -y -rm ffmpeg.deb \ No newline at end of file +rm ffmpeg.deb diff --git a/.devcontainer/Dev - Server Default/devcontainer.json b/.devcontainer/devcontainer.json similarity index 100% rename from .devcontainer/Dev - Server Default/devcontainer.json rename to .devcontainer/devcontainer.json diff --git a/.vscode/launch.json b/.vscode/launch.json index be55764fd4..2673973dbf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,6 +29,18 @@ "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart" }, + { + "name": "ghcs .NET Launch (nowebclient, ffmpeg)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll", + "args": ["--nowebclient", "--ffmpeg", "/usr/share/jellyfin-ffmpeg/ffmpeg"], + "cwd": "${workspaceFolder}/Jellyfin.Server", + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, { "name": ".NET Attach", "type": "coreclr", From 67b6c4f136fa5bcacb118475eacb140fe40b53d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 04:33:45 +0000 Subject: [PATCH 33/68] chore(deps): update dependency serilog.aspnetcore to v8.0.1 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index bc87f9fc96..687bc19f94 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -57,7 +57,7 @@ - + From 8de70e381485e20378538a58dd7476f10cbab60a Mon Sep 17 00:00:00 2001 From: JPVenson Date: Fri, 19 Jan 2024 06:02:24 +0000 Subject: [PATCH 34/68] Added documentation --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 62ef21334d..3f6ccb7b7c 100644 --- a/README.md +++ b/README.md @@ -145,14 +145,24 @@ cd Jellyfin.Server/bin/Debug/net8.0 # Change into the build output directory ### Running from GH-Codespaces As Jellyfin will run on a container on a github hosted server, JF needs to handle some things differently. + +**NOTE:** Depending on the selected configuration (if you just click 'create codespace' it will create a default configuration one) it might take 20-30 secounds to load all extensions and prepare the enviorment while vscode is already open. Just give it some time and wait until you see `Downloading .NET version(s) 7.0.15~x64 ...... Done!` in the output tab. + **NOTE:** If you want to access the JF instance from outside, like with a WebClient on another PC, remember to set the "ports" in the lower VsCode window to public. -#### FFmpeg installation. -Because sometimes you need FFMPEG to test certain cases, follow the instructions from the wiki on the dev enviorment: -https://jellyfin.org/docs/general/installation/linux/#ffmpeg-installation - **NOTE:** When first opening the server instance with any WebUI, you will be send to the login instead of the setup page. Refresh the login page once and you should be redirected to the Setup. +There are two configurations for you to chose from. +#### Default - Development Jellyfin Server +This creates a container that has everything to run and debug the Jellyfin Media server but does not setup anything else. Each time you create a new container you have to run though the whole setup again. There is also no ffmpeg, webclient or media preloaded. Use the `.NET Launch (nowebclient)` lunch config to start the server. + +> Keep in mind that as this has no web client you have to connect to it via an extenal client. This can be just another codespace container running the WebUI. vuejs does not work from the getgo as it does not support the setup steps. + +#### Development Jellyfin Server ffmpeg +this extens the default server with an default installation of ffmpeg6 though the means described here: https://jellyfin.org/docs/general/installation/linux#repository-manual +If you want to install a specific ffmpeg version, follow the comments embedded in the `.devcontainer/Dev - Server Ffmpeg/install.ffmpeg.sh` file. + + ### Running The Tests This repository also includes unit tests that are used to validate functionality as part of a CI pipeline on Azure. There are several ways to run these tests. From 6ef110b00803e8dad37375d80c98a767859f6c1b Mon Sep 17 00:00:00 2001 From: JPVenson Date: Fri, 19 Jan 2024 06:02:39 +0000 Subject: [PATCH 35/68] Updated to using jf repro --- .../Dev - Server Ffmpeg/install-ffmpeg.sh | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh b/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh index c84e1258fa..c867ef538c 100644 --- a/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh +++ b/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh @@ -1,7 +1,32 @@ #!/bin/bash -wget https://repo.jellyfin.org/releases/server/ubuntu/versions/jellyfin-ffmpeg/6.0.1-1/jellyfin-ffmpeg6_6.0.1-1-jammy_amd64.deb -O ffmpeg.deb +## configure the following for a manuall install of a specific version from the repo -sudo apt update -sudo apt install -f ./ffmpeg.deb -y -rm ffmpeg.deb +# wget https://repo.jellyfin.org/releases/server/ubuntu/versions/jellyfin-ffmpeg/6.0.1-1/jellyfin-ffmpeg6_6.0.1-1-jammy_amd64.deb -O ffmpeg.deb + +# sudo apt update +# sudo apt install -f ./ffmpeg.deb -y +# rm ffmpeg.deb + + +## Add the jellyfin repo +sudo apt install curl gnupg -y +sudo apt-get install software-properties-common -y +sudo add-apt-repository universe -y + +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg +export VERSION_OS="$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release )" +export VERSION_CODENAME="$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )" +export DPKG_ARCHITECTURE="$( dpkg --print-architecture )" +cat < Date: Fri, 19 Jan 2024 07:39:04 +0100 Subject: [PATCH 36/68] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3f6ccb7b7c..878e335ed1 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,8 @@ This creates a container that has everything to run and debug the Jellyfin Media this extens the default server with an default installation of ffmpeg6 though the means described here: https://jellyfin.org/docs/general/installation/linux#repository-manual If you want to install a specific ffmpeg version, follow the comments embedded in the `.devcontainer/Dev - Server Ffmpeg/install.ffmpeg.sh` file. +Use the `ghcs .NET Launch (nowebclient, ffmpeg)` launch config to run with the jellyfin-ffmpeg enabled. + ### Running The Tests From 04a063aa8dc131f6e5f7f6bca2d339d3f6bc3c4a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:50:28 +0000 Subject: [PATCH 37/68] chore(deps): update dependency efcoresecondlevelcacheinterceptor to v4.1.2 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 687bc19f94..d6a9921586 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,7 @@ - + From 38bf59d6e8485c05b6b877043b6e87ff09e80d45 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Fri, 19 Jan 2024 12:25:39 +0100 Subject: [PATCH 38/68] Update .vscode/launch.json Co-authored-by: Nyanmisaka --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2673973dbf..7e50d4f0a4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -35,7 +35,7 @@ "request": "launch", "preLaunchTask": "build", "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll", - "args": ["--nowebclient", "--ffmpeg", "/usr/share/jellyfin-ffmpeg/ffmpeg"], + "args": ["--nowebclient", "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"], "cwd": "${workspaceFolder}/Jellyfin.Server", "console": "internalConsole", "stopAtEntry": false, From 9f945260094f177608e41f80a48e019e01ca1d91 Mon Sep 17 00:00:00 2001 From: jonathan1jansson Date: Thu, 18 Jan 2024 13:43:13 +0000 Subject: [PATCH 39/68] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 97062deece..1fc3cdbaaa 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -43,7 +43,7 @@ "NameInstallFailed": "{0} installationen misslyckades", "NameSeasonNumber": "Säsong {0}", "NameSeasonUnknown": "Okänd säsong", - "NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig att hämta.", + "NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig för nedladdning.", "NotificationOptionApplicationUpdateAvailable": "Ny programversion tillgänglig", "NotificationOptionApplicationUpdateInstalled": "Programuppdatering installerad", "NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats", @@ -74,7 +74,7 @@ "Songs": "Låtar", "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}", + "SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} till {1}", "Sync": "Synk", "System": "System", "TvShows": "TV-serier", From 538f141b4cd7c3537f4a60060d54c51f7e19afcf Mon Sep 17 00:00:00 2001 From: TelepathicWalrus Date: Fri, 19 Jan 2024 17:25:57 +0000 Subject: [PATCH 40/68] Update error handling --- Emby.Server.Implementations/Library/LiveStreamHelper.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index d41845cdf0..5f54c73190 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -64,9 +64,13 @@ namespace Emby.Server.Implementations.Library await jsonStream.DisposeAsync().ConfigureAwait(false); } - catch + catch (IOException) { - _logger.LogError("Could not open cached media info"); + _logger.LogDebug("Could not open cached media info"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error opening cached media info"); } } From 94e3fed3c9816012ebfaab6c8dadbef5d003eca9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 20 Jan 2024 16:23:51 +0000 Subject: [PATCH 41/68] chore(deps): update dependency metabrainz.musicbrainz to v6.1.0 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 687bc19f94..24bed643b1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,7 +22,7 @@ - + From 1ff23692285597e5f751845674edff87bbe71391 Mon Sep 17 00:00:00 2001 From: hulkhaugen Date: Fri, 19 Jan 2024 19:12:59 +0000 Subject: [PATCH 42/68] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Translate-?= =?UTF-8?q?URL:=20https://translate.jellyfin.org/projects/jellyfin/jellyfi?= =?UTF-8?q?n-core/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Localization/Core/nb.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 0362c24179..e64252d897 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -32,10 +32,10 @@ "LabelIpAddressValue": "IP-adresse: {0}", "LabelRunningTimeValue": "Spilletid {0}", "Latest": "Siste", - "MessageApplicationUpdated": "Jellyfin-tjeneren har blitt oppdatert", - "MessageApplicationUpdatedTo": "Jellyfin-tjeneren ble oppdatert til {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Tjenerkonfigurasjonsseksjon {0} har blitt oppdatert", - "MessageServerConfigurationUpdated": "Tjenerkonfigurasjon er oppdatert", + "MessageApplicationUpdated": "Jellyfin-serveren har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-serveren ble oppdatert til {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurasjonsseksjon {0} har blitt oppdatert", + "MessageServerConfigurationUpdated": "Serverkonfigurasjon har blitt oppdatert", "MixedContent": "Blandet innhold", "Movies": "Filmer", "Music": "Musikk", @@ -43,7 +43,7 @@ "NameInstallFailed": "Installasjonen av {0} mislyktes", "NameSeasonNumber": "Sesong {0}", "NameSeasonUnknown": "Ukjent sesong", - "NewVersionIsAvailable": "En ny versjon av Jellyfin-tjeneren er tilgjengelig for nedlasting.", + "NewVersionIsAvailable": "En ny versjon av Jellyfin Server er tilgjengelig for nedlasting.", "NotificationOptionApplicationUpdateAvailable": "En programvareoppdatering er tilgjengelig", "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering installert", "NotificationOptionAudioPlayback": "Lydavspilling startet", @@ -55,7 +55,7 @@ "NotificationOptionPluginInstalled": "Programvareutvidelse installert", "NotificationOptionPluginUninstalled": "Programvareutvidelse avinstallert", "NotificationOptionPluginUpdateInstalled": "Programvareutvidelsesoppdatering installert", - "NotificationOptionServerRestartRequired": "Tjeneromstart er nødvendig", + "NotificationOptionServerRestartRequired": "Serveromstart er nødvendig", "NotificationOptionTaskFailed": "Feil under utføring av planlagt oppgave", "NotificationOptionUserLockedOut": "Bruker er utestengt", "NotificationOptionVideoPlayback": "Videoavspilling startet", @@ -70,9 +70,9 @@ "ScheduledTaskFailedWithName": "{0} mislykkes", "ScheduledTaskStartedWithName": "{0} startet", "ServerNameNeedsToBeRestarted": "{0} må startes på nytt", - "Shows": "Program", + "Shows": "Serier", "Songs": "Sanger", - "StartupEmbyServerIsLoading": "Jellyfin-tjener laster. Prøv igjen snart.", + "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}", "Sync": "Synkroniser", From 5e375888fc19aebd1348d23b28287524f0f0f23b Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 19 Jan 2024 21:23:38 +0000 Subject: [PATCH 43/68] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Translate-?= =?UTF-8?q?URL:=20https://translate.jellyfin.org/projects/jellyfin/jellyfi?= =?UTF-8?q?n-core/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/Core/nb.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index e64252d897..b6c15d871a 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -5,7 +5,7 @@ "Artists": "Artister", "AuthenticationSucceededWithUserName": "{0} har logget inn", "Books": "Bøker", - "CameraImageUploadedFrom": "Et nytt kamerabilde er lastet opp fra {0}", + "CameraImageUploadedFrom": "Et nytt kamerabilde har blitt lastet opp fra {0}", "Channels": "Kanaler", "ChapterNameValue": "Kapittel {0}", "Collections": "Samlinger", @@ -49,7 +49,7 @@ "NotificationOptionAudioPlayback": "Lydavspilling startet", "NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppet", "NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp", - "NotificationOptionInstallationFailed": "Installasjonen feilet", + "NotificationOptionInstallationFailed": "Installasjonsfeil", "NotificationOptionNewLibraryContent": "Nytt innhold lagt til", "NotificationOptionPluginError": "Programvareutvidelsesfeil", "NotificationOptionPluginInstalled": "Programvareutvidelse installert", From 30ab5d5e817fe4626cfe3e9e40e0b2443bae948c Mon Sep 17 00:00:00 2001 From: Gauvino <68083474+Gauvino@users.noreply.github.com> Date: Sun, 21 Jan 2024 18:55:01 +0100 Subject: [PATCH 44/68] Fix action building (#10899) * Fix action building * Added required package --- deployment/Dockerfile.centos.amd64 | 6 +++--- deployment/build.centos.amd64 | 12 ++++++------ deployment/build.debian.amd64 | 8 +------- deployment/build.debian.arm64 | 8 +------- deployment/build.debian.armhf | 8 +------- deployment/build.fedora.amd64 | 2 +- deployment/build.ubuntu.amd64 | 8 +------- deployment/build.ubuntu.arm64 | 8 +------- deployment/build.ubuntu.armhf | 8 +------- 9 files changed, 16 insertions(+), 52 deletions(-) diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64 index 3db184f494..af309b0831 100644 --- a/deployment/Dockerfile.centos.amd64 +++ b/deployment/Dockerfile.centos.amd64 @@ -11,11 +11,11 @@ ENV IS_DOCKER=YES # Prepare CentOS environment RUN dnf update -yq \ - && dnf install -yq epel-release \ && dnf install -yq \ - rpmdevtools libcurl-devel fontconfig-devel \ + @buildsys-build rpmdevtools git \ + dnf-plugins-core libcurl-devel fontconfig-devel \ freetype-devel openssl-devel glibc-devel \ - libicu-devel git wget dnf-plugins-core \ + libicu-devel systemd wget make \ && dnf clean all \ && rm -rf /var/cache/dnf diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64 index af73e31533..26be377f10 100755 --- a/deployment/build.centos.amd64 +++ b/deployment/build.centos.amd64 @@ -1,6 +1,6 @@ #!/bin/bash -#= CentOS/RHEL 8+ amd64 .rpm +#= CentOS/RHEL 9+ amd64 .rpm set -o errexit set -o xtrace @@ -10,7 +10,7 @@ pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove BuildRequires for dotnet, since it's installed manually - pushd fedora + pushd centos cp -a jellyfin.spec /tmp/spec.orig sed -i 's/BuildRequires: dotnet/# BuildRequires: dotnet/' jellyfin.spec @@ -20,7 +20,7 @@ fi # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then - pushd fedora + pushd centos PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) @@ -35,7 +35,7 @@ EOF fi # Build RPM -make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS +make -f centos/Makefile srpm outdir=/root/rpmbuild/SRPMS rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm # Move the artifacts out @@ -45,10 +45,10 @@ if [[ ${IS_DOCKER} == YES ]]; then chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" fi -rm -f fedora/jellyfin*.tar.gz +rm -f centos/jellyfin*.tar.gz if [[ ${IS_DOCKER} == YES ]]; then - pushd fedora + pushd centos cp -a /tmp/spec.orig jellyfin.spec chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}" diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64 index 85776ad6a0..350b22a854 100755 --- a/deployment/build.debian.amd64 +++ b/deployment/build.debian.amd64 @@ -1,6 +1,6 @@ #!/bin/bash -#= Debian 10+ amd64 .deb +#= Debian 12+ amd64 .deb set -o errexit set -o xtrace @@ -8,12 +8,6 @@ set -o xtrace # Move to source directory pushd "${SOURCE_DIR}" -if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-8.0, since it's installed manually - cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-8.0,/d' debian/control -fi - # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then pushd debian diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64 index d37cc5a64a..0dfca0ab49 100755 --- a/deployment/build.debian.arm64 +++ b/deployment/build.debian.arm64 @@ -1,6 +1,6 @@ #!/bin/bash -#= Debian 10+ arm64 .deb +#= Debian 12+ arm64 .deb set -o errexit set -o xtrace @@ -8,12 +8,6 @@ set -o xtrace # Move to source directory pushd "${SOURCE_DIR}" -if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-8.0, since it's installed manually - cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-8.0,/d' debian/control -fi - # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then pushd debian diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf index f3505b1478..0ab9e2f9a4 100755 --- a/deployment/build.debian.armhf +++ b/deployment/build.debian.armhf @@ -1,6 +1,6 @@ #!/bin/bash -#= Debian 10+ arm64 .deb +#= Debian 12+ arm64 .deb set -o errexit set -o xtrace @@ -8,12 +8,6 @@ set -o xtrace # Move to source directory pushd "${SOURCE_DIR}" -if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-8.0, since it's installed manually - cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-8.0,/d' debian/control -fi - # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then pushd debian diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64 index 21859cbf9f..2b4ec2a9c5 100755 --- a/deployment/build.fedora.amd64 +++ b/deployment/build.fedora.amd64 @@ -1,6 +1,6 @@ #!/bin/bash -#= Fedora 29+ amd64 .rpm +#= Fedora 39+ amd64 .rpm set -o errexit set -o xtrace diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64 index 3658676035..6fd87a3aec 100755 --- a/deployment/build.ubuntu.amd64 +++ b/deployment/build.ubuntu.amd64 @@ -1,6 +1,6 @@ #!/bin/bash -#= Ubuntu 18.04+ amd64 .deb +#= Ubuntu 22.04+ amd64 .deb set -o errexit set -o xtrace @@ -8,12 +8,6 @@ set -o xtrace # Move to source directory pushd "${SOURCE_DIR}" -if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-8.0, since it's installed manually - cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-8.0,/d' debian/control -fi - # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then pushd debian diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64 index 8cf24b955f..f783941c73 100755 --- a/deployment/build.ubuntu.arm64 +++ b/deployment/build.ubuntu.arm64 @@ -1,6 +1,6 @@ #!/bin/bash -#= Ubuntu 18.04+ arm64 .deb +#= Ubuntu 22.04+ arm64 .deb set -o errexit set -o xtrace @@ -8,12 +8,6 @@ set -o xtrace # Move to source directory pushd "${SOURCE_DIR}" -if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-8.0, since it's installed manually - cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-8.0,/d' debian/control -fi - # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then pushd debian diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf index 896486dcd4..cde6708c5b 100755 --- a/deployment/build.ubuntu.armhf +++ b/deployment/build.ubuntu.armhf @@ -1,6 +1,6 @@ #!/bin/bash -#= Ubuntu 18.04+ arm64 .deb +#= Ubuntu 22.04+ arm64 .deb set -o errexit set -o xtrace @@ -8,12 +8,6 @@ set -o xtrace # Move to source directory pushd "${SOURCE_DIR}" -if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-8.0, since it's installed manually - cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-8.0,/d' debian/control -fi - # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then pushd debian From 7027bc00fc06314929011735e425763779cb4076 Mon Sep 17 00:00:00 2001 From: Mustafa Date: Sun, 21 Jan 2024 15:49:15 -0500 Subject: [PATCH 45/68] Added translation using Weblate (Urdu) --- Emby.Server.Implementations/Localization/Core/ur.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/ur.json diff --git a/Emby.Server.Implementations/Localization/Core/ur.json b/Emby.Server.Implementations/Localization/Core/ur.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ur.json @@ -0,0 +1 @@ +{} From 1d235205ae0a53fd7e584b6561dd0f0c6437f691 Mon Sep 17 00:00:00 2001 From: TelepathicWalrus Date: Mon, 22 Jan 2024 17:43:35 +0000 Subject: [PATCH 46/68] Log IOException --- Emby.Server.Implementations/Library/LiveStreamHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 5f54c73190..d6530df2dd 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -64,9 +64,9 @@ namespace Emby.Server.Implementations.Library await jsonStream.DisposeAsync().ConfigureAwait(false); } - catch (IOException) + catch (IOException ex) { - _logger.LogDebug("Could not open cached media info"); + _logger.LogDebug(ex, "Could not open cached media info"); } catch (Exception ex) { From 0cf477b4fed45f80e0deb25dd2f3569c4da7ea50 Mon Sep 17 00:00:00 2001 From: Mustafa Date: Sun, 21 Jan 2024 20:50:26 +0000 Subject: [PATCH 47/68] Translated using Weblate (Urdu) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ur/ --- Emby.Server.Implementations/Localization/Core/ur.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ur.json b/Emby.Server.Implementations/Localization/Core/ur.json index 0967ef424b..3766830413 100644 --- a/Emby.Server.Implementations/Localization/Core/ur.json +++ b/Emby.Server.Implementations/Localization/Core/ur.json @@ -1 +1,3 @@ -{} +{ + "Books": "کتابیں" +} From e2b2399ea60c549fc8285bf8c8a9651a1e8ba635 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:05:50 +0000 Subject: [PATCH 48/68] chore(deps): update actions/upload-artifact action to v4.3.0 --- .github/workflows/ci-openapi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index 63268001e0..e43160562f 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -25,7 +25,7 @@ jobs: - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json - uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: openapi-head retention-days: 14 @@ -59,7 +59,7 @@ jobs: - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json - uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: openapi-base retention-days: 14 From 989a80e05238d870f9d5b34d142a73e5845635cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:21:05 +0000 Subject: [PATCH 49/68] chore(deps): update dependency efcoresecondlevelcacheinterceptor to v4.2.0 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index fe9280965c..dcf1834949 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,7 @@ - + From 47ba39062f58f96d9e74ae61293f985729c2b991 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:09:48 +0000 Subject: [PATCH 50/68] chore(deps): update peter-evans/create-or-update-comment action to v4 --- .github/workflows/ci-openapi.yml | 4 ++-- .github/workflows/commands.yml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index e43160562f..e8168470bd 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -112,7 +112,7 @@ jobs: direction: last body-includes: openapi-diff-workflow-comment - name: Reply or edit difference comment (changed) - uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 if: ${{ steps.read-diff.outputs.body != '' }} with: issue-number: ${{ github.event.pull_request.number }} @@ -127,7 +127,7 @@ jobs: - name: Edit difference comment (unchanged) - uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }} with: issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 75b6a73e56..386f8d321b 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify as seen - uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 with: token: ${{ secrets.JF_BOT_TOKEN }} comment-id: ${{ github.event.comment.id }} @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify as seen - uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 if: ${{ github.event.comment != null }} with: token: ${{ secrets.JF_BOT_TOKEN }} @@ -58,7 +58,7 @@ jobs: - name: Notify as running id: comment_running - uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 if: ${{ github.event.comment != null }} with: token: ${{ secrets.JF_BOT_TOKEN }} @@ -93,7 +93,7 @@ jobs: exit ${retcode} - name: Notify with result success - uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 if: ${{ github.event.comment != null && success() }} with: token: ${{ secrets.JF_BOT_TOKEN }} @@ -108,7 +108,7 @@ jobs: reactions: hooray - name: Notify with result failure - uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 if: ${{ github.event.comment != null && failure() }} with: token: ${{ secrets.JF_BOT_TOKEN }} From d1a298138309e637c4012cca24025960074d9e51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:09:55 +0000 Subject: [PATCH 51/68] chore(deps): update peter-evans/find-comment action to v3 --- .github/workflows/ci-openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index e43160562f..7ea3bc3bf4 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -105,7 +105,7 @@ jobs: body="${body//$'\r'/'%0D'}" echo ::set-output name=body::$body - name: Find difference comment - uses: peter-evans/find-comment@a54c31d7fa095754bfef525c0c8e5e5674c4b4b1 # v2.4.0 + uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3.0.0 id: find-comment with: issue-number: ${{ github.event.pull_request.number }} From 2d68e0b7e7038a9ba3746f4ee7b864da4987625b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:56:37 +0000 Subject: [PATCH 52/68] chore(deps): update github/codeql-action action to v3.23.2 --- .github/workflows/ci-codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml index d8c550e704..e92a404d24 100644 --- a/.github/workflows/ci-codeql-analysis.yml +++ b/.github/workflows/ci-codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '8.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/init@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/autobuild@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/analyze@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 From 9323390add9fe23d2e5b71826b59360f9604f086 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 28 Jan 2024 19:40:49 +0800 Subject: [PATCH 53/68] Fix the display aspect ratio of PGSSUB subtitle burn-in Signed-off-by: nyanmisaka --- .../MediaEncoding/EncodingHelper.cs | 88 +++++++++---------- .../Probing/ProbeResultNormalizer.cs | 4 + 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 400e7f40fb..cffc014e87 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -835,30 +835,25 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetGraphicalSubCanvasSize(EncodingJobInfo state) { - // DVBSUB and DVDSUB use the fixed canvas size 720x576 + // DVBSUB uses the fixed canvas size 720x576 if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !state.SubtitleStream.IsTextSubtitleStream - && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase) - && !string.Equals(state.SubtitleStream.Codec, "DVDSUB", StringComparison.OrdinalIgnoreCase)) + && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)) { - var inW = state.VideoStream?.Width; - var inH = state.VideoStream?.Height; - var reqW = state.BaseRequest.Width; - var reqH = state.BaseRequest.Height; - var reqMaxW = state.BaseRequest.MaxWidth; - var reqMaxH = state.BaseRequest.MaxHeight; + var subtitleWidth = state.SubtitleStream?.Width; + var subtitleHeight = state.SubtitleStream?.Height; - // setup a relative small canvas_size for overlay_qsv/vaapi to reduce transfer overhead - var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, 1080); - - if (overlayW.HasValue && overlayH.HasValue) + if (subtitleWidth.HasValue + && subtitleHeight.HasValue + && subtitleWidth.Value > 0 + && subtitleHeight.Value > 0) { return string.Format( CultureInfo.InvariantCulture, " -canvas_size {0}x{1}", - overlayW.Value, - overlayH.Value); + subtitleWidth.Value, + subtitleHeight.Value); } } @@ -2877,7 +2872,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - public static string GetCustomSwScaleFilter( + public static string GetGraphicalSubPreProcessFilters( int? videoWidth, int? videoHeight, int? requestedWidth, @@ -2897,7 +2892,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return string.Format( CultureInfo.InvariantCulture, - "scale=s={0}x{1}:flags=fast_bilinear", + @"scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}", outWidth.Value, outHeight.Value); } @@ -3340,9 +3335,8 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasGraphicalSubs) { - // [0:s]scale=s=1280x720 - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } @@ -3504,9 +3498,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - // scale=s=1280x720,format=yuva420p,hwupload - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); subFilters.Add("format=yuva420p"); } else if (hasTextSubs) @@ -3527,8 +3520,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -3702,9 +3695,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - // scale=s=1280x720,format=yuva420p,hwupload - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); subFilters.Add("format=yuva420p"); } else if (hasTextSubs) @@ -3727,8 +3719,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -3938,10 +3930,9 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - // scale,format=bgra,hwupload - // overlay_qsv can handle overlay scaling, - // add a dummy scale filter to pair with -canvas_size. - subFilters.Add("scale=flags=fast_bilinear"); + // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } else if (hasTextSubs) @@ -3973,8 +3964,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -4158,7 +4149,9 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - subFilters.Add("scale=flags=fast_bilinear"); + // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } else if (hasTextSubs) @@ -4189,8 +4182,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -4425,7 +4418,9 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - subFilters.Add("scale=flags=fast_bilinear"); + // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } else if (hasTextSubs) @@ -4454,8 +4449,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); if (isVaapiEncoder) @@ -4599,9 +4594,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - // scale=s=1280x720,format=bgra,hwupload - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } else if (hasTextSubs) @@ -4815,8 +4809,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); - subFilters.Add(subSwScaleFilter); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); if (isVaapiEncoder) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 629c300603..b532f9a7e3 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -742,6 +742,10 @@ namespace MediaBrowser.MediaEncoding.Probing stream.LocalizedExternal = _localization.GetLocalizedString("External"); stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired"); + // Graphical subtitle may have width and height info + stream.Width = streamInfo.Width; + stream.Height = streamInfo.Height; + if (string.IsNullOrEmpty(stream.Title)) { // mp4 missing track title workaround: fall back to handler_name if populated and not the default "SubtitleHandler" From 92c0ec0c1bc6c25d2dd9e531fcc26a13883bea8a Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 28 Jan 2024 19:29:23 +0800 Subject: [PATCH 54/68] Use video framerate for ASS subtitle HW burn-in Signed-off-by: nyanmisaka --- .../MediaEncoding/EncodingHelper.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index cffc014e87..2a2614e4d7 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2908,7 +2908,7 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedHeight, int? requestedMaxWidth, int? requestedMaxHeight, - int? framerate) + float? framerate) { var reqTicks = state.BaseRequest.StartTimeTicks ?? 0; var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture); @@ -2927,7 +2927,7 @@ namespace MediaBrowser.Controller.MediaEncoding "alphasrc=s={0}x{1}:r={2}:start='{3}'", outWidth.Value, outHeight.Value, - framerate ?? 10, + framerate ?? 25, reqTicks > 0 ? startTime : 0); } @@ -3504,8 +3504,11 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasTextSubs) { + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5); + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=yuva420p"); @@ -3701,8 +3704,11 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasTextSubs) { + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5); + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=yuva420p"); @@ -3937,8 +3943,11 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasTextSubs) { + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4156,7 +4165,10 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasTextSubs) { - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4425,7 +4437,10 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasTextSubs) { - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5); + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4600,7 +4615,10 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasTextSubs) { - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5); + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); From b943d629a1b07315ba7f6d2fe31b2610692c503d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:55:01 +0000 Subject: [PATCH 55/68] chore(deps): update dependency svg.skia to v1.0.0.13 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index dcf1834949..c88b9e8088 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -71,7 +71,7 @@ - + From d9b911ce7f401d325530335038ef494c45660faa Mon Sep 17 00:00:00 2001 From: azam Date: Sun, 28 Jan 2024 05:53:28 +0000 Subject: [PATCH 56/68] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index a07222975b..ebd3f7560b 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -124,5 +124,7 @@ "External": "Luaran", "TaskOptimizeDatabase": "Optimumkan pangkalan data", "TaskKeyframeExtractor": "Ekstrak bingkai kunci", - "TaskKeyframeExtractorDescription": "Ekstrak bingkai kunci dari fail video untuk membina HLS playlist yang lebih tepat. Tugas ini mungkin perlukan masa yang panjang." + "TaskKeyframeExtractorDescription": "Ekstrak bingkai kunci dari fail video untuk membina HLS playlist yang lebih tepat. Tugas ini mungkin perlukan masa yang panjang.", + "TaskRefreshTrickplayImagesDescription": "Jana gambar prebiu Trickplay untuk video dalam perpustakaan.", + "TaskRefreshTrickplayImages": "Jana gambar Trickplay" } From 2b03927e0e9e9632af3384f237c34156ec4b7144 Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Fri, 26 Jan 2024 03:02:35 +0000 Subject: [PATCH 57/68] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index 44ce4ac5b2..e92752c5f7 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -123,5 +123,7 @@ "TaskKeyframeExtractor": "Trích Xuất Khung Hình", "TaskKeyframeExtractorDescription": "Trích xuất khung hình chính từ các tệp video để tạo danh sách phát HLS chính xác hơn. Tác vụ này có thể chạy trong một thời gian dài.", "External": "Bên ngoài", - "HearingImpaired": "Khiếm Thính" + "HearingImpaired": "Khiếm Thính", + "TaskRefreshTrickplayImages": "Tạo Ảnh Xem Trước Trickplay", + "TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật." } From 73a9bd1ae59260e54f7a4531f4a7ebd83b6a764a Mon Sep 17 00:00:00 2001 From: antti202 Date: Fri, 26 Jan 2024 18:45:46 +0000 Subject: [PATCH 58/68] Translated using Weblate (Estonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/ --- Emby.Server.Implementations/Localization/Core/et.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index 081462407d..c78ffa28c3 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -52,7 +52,7 @@ "PluginUninstalledWithName": "{0} eemaldati", "PluginInstalledWithName": "{0} paigaldati", "Plugin": "Plugin", - "Playlists": "Pleilistid", + "Playlists": "Esitusloendid", "Photos": "Fotod", "NotificationOptionVideoPlaybackStopped": "Video taasesitus lõppes", "NotificationOptionVideoPlayback": "Video taasesitus algas", @@ -123,5 +123,7 @@ "External": "Väline", "HearingImpaired": "Kuulmispuudega", "TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadreid, et luua täpsemaid HLS-i esitusloendeid. See ülesanne võib kesta pikka aega.", - "TaskKeyframeExtractor": "Võtmekaadri ekstraktor" + "TaskKeyframeExtractor": "Võtmekaadri ekstraktor", + "TaskRefreshTrickplayImages": "Loo eelvaate pildid", + "TaskRefreshTrickplayImagesDescription": "Loob eelvaated videotele, kus lubatud." } From a3fb24233c55f5980d171af846ad68e37b8666e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 00:17:06 +0000 Subject: [PATCH 59/68] chore(deps): update dependency blurhashsharp to v1.3.2 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c88b9e8088..d1298d9e50 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,7 +9,7 @@ - + From fd116e76160e10585958049867f443a7879c42e2 Mon Sep 17 00:00:00 2001 From: LesDomen Date: Mon, 29 Jan 2024 10:23:01 +0000 Subject: [PATCH 60/68] Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sl/ --- Emby.Server.Implementations/Localization/Core/sl-SI.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index 1944e072cb..110af11b71 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -124,5 +124,7 @@ "TaskKeyframeExtractor": "Ekstraktor ključnih sličic", "External": "Zunanji", "TaskKeyframeExtractorDescription": "Iz video datoteke Izvleče ključne sličice, da ustvari bolj natančne sezname predvajanja HLS. Proces lahko traja dolgo časa.", - "HearingImpaired": "Oslabljen sluh" + "HearingImpaired": "Oslabljen sluh", + "TaskRefreshTrickplayImages": "Ustvari Trickplay slike", + "TaskRefreshTrickplayImagesDescription": "Ustvari trickplay predoglede za posnetke v omogočenih knjižnicah." } From ce81e2aeab942538a7d5640b7ad88a50398b10d5 Mon Sep 17 00:00:00 2001 From: Attila Szakacs Date: Thu, 18 Jan 2024 17:00:00 +0100 Subject: [PATCH 61/68] Add alltilla to CONTRIBUTORS.md Signed-off-by: Attila Szakacs --- CONTRIBUTORS.md | 1 + .../Subtitles/SubtitleEncoder.cs | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 457f59e0f6..5dcb6daa39 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -4,6 +4,7 @@ - [97carmine](https://github.com/97carmine) - [Abbe98](https://github.com/Abbe98) - [agrenott](https://github.com/agrenott) + - [alltilla](https://github.com/alltilla) - [AndreCarvalho](https://github.com/AndreCarvalho) - [anthonylavado](https://github.com/anthonylavado) - [Artiume](https://github.com/Artiume) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 0e66565ed0..8fd1f9fc1e 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -472,8 +472,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// Task. private async Task ExtractAllTextSubtitles(MediaSourceInfo mediaSource, CancellationToken cancellationToken) { - var semaphores = new List { }; - var extractableStreams = new List { }; + var semaphores = new List(); + var extractableStreams = new List(); try { @@ -498,9 +498,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles } if (extractableStreams.Count > 0) - { - await ExtractAllTextSubtitlesInternal(mediaSource, extractableStreams, cancellationToken).ConfigureAwait(false); - } + { + await ExtractAllTextSubtitlesInternal(mediaSource, extractableStreams, cancellationToken).ConfigureAwait(false); + } } catch (Exception ex) { @@ -521,7 +521,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles CancellationToken cancellationToken) { var inputPath = mediaSource.Path; - var outputPaths = new List { }; + var outputPaths = new List(); var args = string.Format( CultureInfo.InvariantCulture, "-i {0} -copyts", @@ -531,6 +531,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles { var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream)); var outputCodec = IsCodecCopyable(subtitleStream.Codec) ? "copy" : "srt"; + var streamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream); + + if (streamIndex == -1) + { + _logger.LogError("Cannot find subtitle stream index for {InputPath} ({Index}), skipping this stream", inputPath, subtitleStream.Index); + continue; + } Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new FileNotFoundException($"Calculated path ({outputPath}) is not valid.")); @@ -538,7 +545,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles args += string.Format( CultureInfo.InvariantCulture, " -map 0:{0} -an -vn -c:s {1} \"{2}\"", - subtitleStream.Index, + streamIndex, outputCodec, outputPath); } @@ -614,16 +621,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles { _logger.LogError("ffmpeg subtitle extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); failed = true; + continue; } - else - { - if (outputPath.EndsWith("ass", StringComparison.OrdinalIgnoreCase)) - { - await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false); - } - _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath); + if (outputPath.EndsWith("ass", StringComparison.OrdinalIgnoreCase)) + { + await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false); } + + _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath); } } From e45a2f7e10570301a7c78ef6700cba65649468e6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 23:14:55 +0000 Subject: [PATCH 62/68] chore(deps): update dependency blurhashsharp.skiasharp to v1.3.2 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d1298d9e50..8b4813f566 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,7 +8,7 @@ - + From 7d6a03bad6b8baacb83625be32e708090722fe10 Mon Sep 17 00:00:00 2001 From: TelepathicWalrus Date: Thu, 1 Feb 2024 07:14:25 +0000 Subject: [PATCH 63/68] Change nested try catch to using statement --- Emby.Server.Implementations/Library/LiveStreamHelper.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index d6530df2dd..d4aeae41a5 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -52,17 +52,11 @@ namespace Emby.Server.Implementations.Library { FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath); - try + await using (jsonStream.ConfigureAwait(false)) { mediaInfo = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); // _logger.LogDebug("Found cached media info"); } - catch (Exception ex) - { - _logger.LogError(ex, "Error deserializing mediainfo cache"); - } - - await jsonStream.DisposeAsync().ConfigureAwait(false); } catch (IOException ex) { From d423efd2eac8bb3d392b9d6c98b172cc05b64a5a Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 20 Dec 2023 13:56:28 +0800 Subject: [PATCH 64/68] Add a new HWA type RKMPP Signed-off-by: nyanmisaka --- MediaBrowser.Model/Session/HardwareEncodingType.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Session/HardwareEncodingType.cs b/MediaBrowser.Model/Session/HardwareEncodingType.cs index f5753467a1..058875cd3b 100644 --- a/MediaBrowser.Model/Session/HardwareEncodingType.cs +++ b/MediaBrowser.Model/Session/HardwareEncodingType.cs @@ -33,6 +33,11 @@ /// /// Video ToolBox. /// - VideoToolBox = 5 + VideoToolBox = 5, + + /// + /// Rockchip Media Process Platform (RKMPP). + /// + RKMPP = 6 } } From 52da00c3c7ba30d04feacb3956b4df0bbc08c76b Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 20 Dec 2023 13:58:09 +0800 Subject: [PATCH 65/68] Register RKMPP HW codecs and filters Signed-off-by: nyanmisaka --- .../Encoder/EncoderValidator.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 0d1d27ae8b..fdca283908 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -45,7 +45,15 @@ namespace MediaBrowser.MediaEncoding.Encoder "mpeg4_cuvid", "vp8_cuvid", "vp9_cuvid", - "av1_cuvid" + "av1_cuvid", + "h264_rkmpp", + "hevc_rkmpp", + "mpeg1_rkmpp", + "mpeg2_rkmpp", + "mpeg4_rkmpp", + "vp8_rkmpp", + "vp9_rkmpp", + "av1_rkmpp" }; private static readonly string[] _requiredEncoders = new[] @@ -82,7 +90,9 @@ namespace MediaBrowser.MediaEncoding.Encoder "av1_vaapi", "h264_v4l2m2m", "h264_videotoolbox", - "hevc_videotoolbox" + "hevc_videotoolbox", + "h264_rkmpp", + "hevc_rkmpp" }; private static readonly string[] _requiredFilters = new[] @@ -116,9 +126,12 @@ namespace MediaBrowser.MediaEncoding.Encoder "libplacebo", "scale_vulkan", "overlay_vulkan", - "hwupload_vaapi", // videotoolbox - "yadif_videotoolbox" + "yadif_videotoolbox", + // rkrga + "scale_rkrga", + "vpp_rkrga", + "overlay_rkrga" }; private static readonly Dictionary _filterOptionsDict = new Dictionary From e62dab627e7eab650d594ca9ca9236e504863bbe Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Mon, 29 Jan 2024 19:46:17 +0800 Subject: [PATCH 66/68] Add full HWA transcoding pipeline for RKMPP Signed-off-by: nyanmisaka --- .../MediaEncoding/EncodingHelper.cs | 464 +++++++++++++++++- 1 file changed, 459 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2a2614e4d7..1c95192f18 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -30,6 +30,7 @@ namespace MediaBrowser.Controller.MediaEncoding private const string VaapiAlias = "va"; private const string D3d11vaAlias = "dx11"; private const string VideotoolboxAlias = "vt"; + private const string RkmppAlias = "rk"; private const string OpenclAlias = "ocl"; private const string CudaAlias = "cu"; private const string DrmAlias = "dr"; @@ -161,6 +162,7 @@ namespace MediaBrowser.Controller.MediaEncoding { "vaapi", hwEncoder + "_vaapi" }, { "videotoolbox", hwEncoder + "_videotoolbox" }, { "v4l2m2m", hwEncoder + "_v4l2m2m" }, + { "rkmpp", hwEncoder + "_rkmpp" }, }; if (!string.IsNullOrEmpty(hwType) @@ -217,6 +219,14 @@ namespace MediaBrowser.Controller.MediaEncoding && _mediaEncoder.SupportsFilter("hwupload_vaapi"); } + private bool IsRkmppFullSupported() + { + return _mediaEncoder.SupportsHwaccel("rkmpp") + && _mediaEncoder.SupportsFilter("scale_rkrga") + && _mediaEncoder.SupportsFilter("vpp_rkrga") + && _mediaEncoder.SupportsFilter("overlay_rkrga"); + } + private bool IsOpenclFullSupported() { return _mediaEncoder.SupportsHwaccel("opencl") @@ -696,6 +706,14 @@ namespace MediaBrowser.Controller.MediaEncoding return codec.ToLowerInvariant(); } + private string GetRkmppDeviceArgs(string alias) + { + alias ??= RkmppAlias; + + // device selection in rk is not supported. + return " -init_hw_device rkmpp=" + alias; + } + private string GetVideoToolboxDeviceArgs(string alias) { alias ??= VideotoolboxAlias; @@ -1056,6 +1074,33 @@ namespace MediaBrowser.Controller.MediaEncoding // no videotoolbox hw filter. args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias)); } + else if (string.Equals(optHwaccelType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp")) + { + return string.Empty; + } + + var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + if (!isRkmppDecoder && !isRkmppEncoder) + { + return string.Empty; + } + + args.Append(GetRkmppDeviceArgs(RkmppAlias)); + + var filterDevArgs = string.Empty; + var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported(); + + if (doOclTonemap && !isRkmppDecoder) + { + args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias)); + filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias); + } + + args.Append(filterDevArgs); + } if (!string.IsNullOrEmpty(vidDecoder)) { @@ -1472,8 +1517,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase) @@ -1913,20 +1960,22 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "constrained_baseline"; } - // libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case. + // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this case. if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)) && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase)) { profile = "baseline"; } - // libx264, h264_qsv, h264_nvenc and h264_vaapi does not support Constrained High profile, force High in this case. + // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case. if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)) && profile.Contains("high", StringComparison.OrdinalIgnoreCase)) { profile = "high"; @@ -2010,6 +2059,11 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -level " + level; } } + else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)) + { + param += " -level " + level; + } else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; @@ -2828,6 +2882,48 @@ namespace MediaBrowser.Controller.MediaEncoding return (outputWidth, outputHeight); } + public static bool IsScaleRatioSupported( + int? videoWidth, + int? videoHeight, + int? requestedWidth, + int? requestedHeight, + int? requestedMaxWidth, + int? requestedMaxHeight, + double? maxScaleRatio) + { + var (outWidth, outHeight) = GetFixedOutputSize( + videoWidth, + videoHeight, + requestedWidth, + requestedHeight, + requestedMaxWidth, + requestedMaxHeight); + + if (!videoWidth.HasValue + || !videoHeight.HasValue + || !outWidth.HasValue + || !outHeight.HasValue + || !maxScaleRatio.HasValue + || (maxScaleRatio.Value < 1.0f)) + { + return false; + } + + var minScaleRatio = 1.0f / maxScaleRatio; + var scaleRatioW = (double)outWidth / (double)videoWidth; + var scaleRatioH = (double)outHeight / (double)videoHeight; + + if (scaleRatioW < minScaleRatio + || scaleRatioW > maxScaleRatio + || scaleRatioH < minScaleRatio + || scaleRatioH > maxScaleRatio) + { + return false; + } + + return true; + } + public static string GetHwScaleFilter( string hwScaleSuffix, string videoFormat, @@ -4910,6 +5006,237 @@ namespace MediaBrowser.Controller.MediaEncoding return (newfilters, swFilterChain.SubFilters, swFilterChain.OverlayFilters); } + /// + /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain. + /// + /// Encoding state. + /// Encoding options. + /// Video encoder to use. + /// The tuple contains three lists: main, sub and overlay filters. + public (List MainFilters, List SubFilters, List OverlayFilters) GetRkmppVidFilterChain( + EncodingJobInfo state, + EncodingOptions options, + string vidEncoder) + { + if (!string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + return (null, null, null); + } + + var isLinux = OperatingSystem.IsLinux(); + var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(vidDecoder); + var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported(); + + if ((isSwDecoder && isSwEncoder) + || !isRkmppOclSupported + || !_mediaEncoder.SupportsFilter("alphasrc")) + { + return GetSwVidFilterChain(state, options, vidEncoder); + } + + // prefered rkmpp + rkrga + opencl filters pipeline + if (isRkmppOclSupported) + { + return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder); + } + + return (null, null, null); + } + + public (List MainFilters, List SubFilters, List OverlayFilters) GetRkmppVidFiltersPrefered( + EncodingJobInfo state, + EncodingOptions options, + string vidDecoder, + string vidEncoder) + { + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + var threeDFormat = state.MediaSource.Video3DFormat; + + var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase); + var isSwDecoder = !isRkmppDecoder; + var isSwEncoder = !isRkmppEncoder; + var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder; + + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var doDeintH2645 = doDeintH264 || doDeintHevc; + var doOclTonemap = IsHwTonemapAvailable(state, options); + + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; + var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var hasAssSubs = hasSubs + && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + + /* Make main filters for video stream */ + var mainFilters = new List(); + + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap)); + + if (isSwDecoder) + { + // INPUT sw surface(memory) + // sw deint + if (doDeintH2645) + { + var swDeintFilter = GetSwDeinterlaceFilter(state, options); + mainFilters.Add(swDeintFilter); + } + + var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + if (!string.IsNullOrEmpty(swScaleFilter)) + { + swScaleFilter += ":flags=fast_bilinear"; + } + + // sw scale + mainFilters.Add(swScaleFilter); + mainFilters.Add("format=" + outFormat); + + // keep video at memory except ocl tonemap, + // since the overhead caused by hwupload >>> using sw filter. + // sw => hw + if (doOclTonemap) + { + mainFilters.Add("hwupload=derive_device=opencl"); + } + } + else if (isRkmppDecoder) + { + // INPUT rkmpp/drm surface(gem/dma-heap) + + var isFullAfbcPipeline = isDrmInDrmOut && !doOclTonemap; + var outFormat = doOclTonemap ? "p010" : "nv12"; + var hwScaleFilter = GetHwScaleFilter("rkrga", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter2 = GetHwScaleFilter("rkrga", string.Empty, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + + if (!hasSubs + || !isFullAfbcPipeline + || !string.IsNullOrEmpty(hwScaleFilter2)) + { + // try enabling AFBC to save DDR bandwidth + if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline) + { + hwScaleFilter += ":afbc=1"; + } + + // hw scale + mainFilters.Add(hwScaleFilter); + } + } + + if (doOclTonemap && isRkmppDecoder) + { + // map from rkmpp/drm to opencl via drm-opencl interop. + mainFilters.Add("hwmap=derive_device=opencl:mode=read"); + } + + // ocl tonemap + if (doOclTonemap) + { + var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12"); + // enable tradeoffs for performance + if (!string.IsNullOrEmpty(tonemapFilter)) + { + tonemapFilter += ":tradeoff=1"; + } + + mainFilters.Add(tonemapFilter); + } + + var memoryOutput = false; + var isUploadForOclTonemap = isSwDecoder && doOclTonemap; + if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap) + { + memoryOutput = true; + + // OUTPUT nv12 surface(memory) + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + } + + // OUTPUT nv12 surface(memory) + if (isSwDecoder && isRkmppEncoder) + { + memoryOutput = true; + } + + if (memoryOutput) + { + // text subtitles + if (hasTextSubs) + { + var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false); + mainFilters.Add(textSubtitlesFilter); + } + } + + if (isDrmInDrmOut) + { + if (doOclTonemap) + { + // OUTPUT drm(nv12) surface(gem/dma-heap) + // reverse-mapping via drm-opencl interop. + mainFilters.Add("hwmap=derive_device=rkmpp:mode=write:reverse=1"); + mainFilters.Add("format=drm_prime"); + } + } + + /* Make sub and overlay filters for subtitle stream */ + var subFilters = new List(); + var overlayFilters = new List(); + if (isDrmInDrmOut) + { + if (hasSubs) + { + if (hasGraphicalSubs) + { + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); + subFilters.Add("format=bgra"); + } + else if (hasTextSubs) + { + var framerate = state.VideoStream?.RealFrameRate; + var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; + + // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload + var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); + var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); + subFilters.Add(alphaSrcFilter); + subFilters.Add("format=bgra"); + subFilters.Add(subTextSubtitlesFilter); + } + + subFilters.Add("hwupload=derive_device=rkmpp"); + + // try enabling AFBC to save DDR bandwidth + overlayFilters.Add("overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12:afbc=1"); + } + } + else if (memoryOutput) + { + if (hasGraphicalSubs) + { + var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + subFilters.Add(subPreProcFilters); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); + } + } + + return (mainFilters, subFilters, overlayFilters); + } + /// /// Gets the parameter of video processing filters. /// @@ -4956,6 +5283,10 @@ namespace MediaBrowser.Controller.MediaEncoding { (mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec); } + else if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + (mainFilters, subFilters, overlayFilters) = GetRkmppVidFilterChain(state, options, outputVideoCodec); + } else { (mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec); @@ -5087,18 +5418,21 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)) { return 8; } if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) { return 10; } if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) { return 12; @@ -5151,7 +5485,12 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))) { - return null; + // One exception is that RKMPP decoder can handle H.264 High 10. + if (!(string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase) + && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))) + { + return null; + } } if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) @@ -5178,6 +5517,11 @@ namespace MediaBrowser.Controller.MediaEncoding { return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth); } + + if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + return GetRkmppVidDecoder(state, options, videoStream, bitDepth); + } } var whichCodec = videoStream.Codec; @@ -5243,6 +5587,11 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + return isCodecAvailable ? (" -c:v " + decoderName) : null; } @@ -5265,6 +5614,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported(); var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv"); var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox"); + var isRkmppSupported = isLinux && IsRkmppFullSupported(); var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase); var ffmpegVersion = _mediaEncoder.EncoderVersion; @@ -5367,6 +5717,14 @@ namespace MediaBrowser.Controller.MediaEncoding return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty); } + // Rockchip rkmpp + if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase) + && isRkmppSupported + && isCodecAvailable) + { + return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime" : string.Empty); + } + return null; } @@ -5673,6 +6031,102 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) + { + var isLinux = OperatingSystem.IsLinux(); + + if (!isLinux + || !string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var inW = state.VideoStream?.Width; + var inH = state.VideoStream?.Height; + var reqW = state.BaseRequest.Width; + var reqH = state.BaseRequest.Height; + var reqMaxW = state.BaseRequest.MaxWidth; + var reqMaxH = state.BaseRequest.MaxHeight; + + // rkrga RGA2e supports range from 1/16 to 16 + if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f)) + { + return null; + } + + var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported(); + var hwSurface = isRkmppOclSupported + && _mediaEncoder.SupportsFilter("alphasrc"); + + // rkrga RGA3 supports range from 1/8 to 8 + var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f); + + // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool + var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase) + || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); + var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp; + + // nv15 and nv20 are bit-stream only formats + if (is10bitSwFormatsRkmpp && !hwSurface) + { + return null; + } + + if (is8bitSwFormatsRkmpp) + { + if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface); + } + + if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface); + } + + if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface); + } + + if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface); + } + } + + if (is8_10bitSwFormatsRkmpp) + { + if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)) + { + var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface); + return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty); + } + + if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)) + { + var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface); + return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty); + } + + if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)) + { + var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface); + return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty); + } + + if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "av1", bitDepth, hwSurface); + } + } + + return null; + } + /// /// Gets the number of threads. /// From 5a66741963a00588cd44a299e68113d70077074f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 20:19:22 +0000 Subject: [PATCH 67/68] chore(deps): update github/codeql-action action to v3.24.0 --- .github/workflows/ci-codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml index e92a404d24..839bebb96a 100644 --- a/.github/workflows/ci-codeql-analysis.yml +++ b/.github/workflows/ci-codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '8.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 + uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 + uses: github/codeql-action/autobuild@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 + uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 From e4f715bbee26985ae7666e4c80282ac52e7fce73 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Sat, 3 Feb 2024 00:26:49 -0500 Subject: [PATCH 68/68] Added translation using Weblate (Kyrgyz) --- Emby.Server.Implementations/Localization/Core/ky.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/ky.json diff --git a/Emby.Server.Implementations/Localization/Core/ky.json b/Emby.Server.Implementations/Localization/Core/ky.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ky.json @@ -0,0 +1 @@ +{}