From b0e853070ba368099fdc78d925d4d5675eb00d64 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 4 Feb 2025 15:38:25 +0800 Subject: [PATCH 01/19] Don't use RETURNING clause with EFCore The RETURNING clause helps with performance and is now default of EFCore. However, EFCore cannot automatically perform retry when the table was locked/busy. Disable it as a workaround for the locking issues of very huge databases. --- .../JellyfinDbContext.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Jellyfin.Server.Implementations/JellyfinDbContext.cs b/Jellyfin.Server.Implementations/JellyfinDbContext.cs index 34d9e3960d..43ea2bd3c2 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbContext.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbContext.cs @@ -4,6 +4,8 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Interfaces; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations; @@ -271,4 +273,23 @@ public class JellyfinDbContext(DbContextOptions options, ILog // Configuration for each entity is in its own class inside 'ModelConfiguration'. modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDbContext).Assembly); } + + /// + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Conventions.Add(_ => new DoNotUseReturningClauseConvention()); + } + + private class DoNotUseReturningClauseConvention : IModelFinalizingConvention + { + public void ProcessModelFinalizing( + IConventionModelBuilder modelBuilder, + IConventionContext context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + { + entityType.UseSqlReturningClause(false); + } + } + } } From a0ab0eb8758a2b51b905771e4ef2c30a733047cb Mon Sep 17 00:00:00 2001 From: Jxiced Date: Fri, 14 Feb 2025 17:01:01 +0000 Subject: [PATCH 02/19] Update ThrowIfInvalidUsername to include whitespaces. --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index c7ae0f4dbe..f54355f2a7 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -735,7 +735,7 @@ namespace Jellyfin.Server.Implementations.Users internal static void ThrowIfInvalidUsername(string name) { - if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name)) + if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name) && !char.IsWhiteSpace(name[0]) && !char.IsWhiteSpace(name[^1])) { return; } From 237c1d9b976f58a8b1dec1018626debe334246c6 Mon Sep 17 00:00:00 2001 From: Jxiced Date: Fri, 14 Feb 2025 17:46:23 +0000 Subject: [PATCH 03/19] Update regex and revert previous changes to ThrowIfInvalidUsername. --- Jellyfin.Server.Implementations/Users/UserManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index f54355f2a7..3ff725d65d 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -113,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Users // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( ) - [GeneratedRegex(@"^[\w\ \-'._@+]+$")] + [GeneratedRegex(@"^(?!\s)[\w \-'._@]+(? @@ -735,7 +735,7 @@ namespace Jellyfin.Server.Implementations.Users internal static void ThrowIfInvalidUsername(string name) { - if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name) && !char.IsWhiteSpace(name[0]) && !char.IsWhiteSpace(name[^1])) + if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name)) { return; } From b5fcbfc15eef9845f21ecbeaea954d44394b0a5c Mon Sep 17 00:00:00 2001 From: Jxiced Date: Fri, 14 Feb 2025 17:49:25 +0000 Subject: [PATCH 04/19] Update test cases. --- .../Users/UserManagerTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs index 665afe1118..4cea53bd3d 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs @@ -23,6 +23,10 @@ namespace Jellyfin.Server.Implementations.Tests.Users [InlineData(" ")] [InlineData("")] [InlineData("special characters like & $ ? are not allowed")] + [InlineData("thishasaspaceontheend ")] + [InlineData(" thishasaspaceatthestart")] + [InlineData(" thishasaspaceatbothends ")] + [InlineData(" this has a space at both ends and inbetween ")] public void ThrowIfInvalidUsername_WhenInvalidUsername_ThrowsArgumentException(string username) { Assert.Throws(() => UserManager.ThrowIfInvalidUsername(username)); From 84450bb2972c00307c9eb17111df35a9316de4af Mon Sep 17 00:00:00 2001 From: Jxiced <48179642+Jxiced@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:13:05 +0000 Subject: [PATCH 05/19] Update Jellyfin.Server.Implementations/Users/UserManager.cs Co-authored-by: gnattu --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 3ff725d65d..fba8923f89 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -113,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Users // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( ) - [GeneratedRegex(@"^(?!\s)[\w \-'._@]+(? From 523123dd36e2d4bc473453889a181543f5edffc0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 16 Feb 2025 16:26:43 +0000 Subject: [PATCH 06/19] Update dependency z440.atl.core to 6.17.0 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 999e4ba745..854c5a6df8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -79,7 +79,7 @@ - + From a085b90e051df713e95e5924031671bb0b3e6c9e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 12:53:13 +0000 Subject: [PATCH 07/19] Update appleboy/ssh-action action to v1.2.1 --- .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 e82988200d..ee9df30f44 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -172,7 +172,7 @@ jobs: strip_components: 1 target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}" - name: Move openapi.json (unstable) into place - uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0 + uses: appleboy/ssh-action@8faa84277b88b6cd1455986f459aa66cf72bc8a3 # v1.2.1 with: host: "${{ secrets.REPO_HOST }}" username: "${{ secrets.REPO_USER }}" @@ -234,7 +234,7 @@ jobs: strip_components: 1 target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}" - name: Move openapi.json (stable) into place - uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0 + uses: appleboy/ssh-action@8faa84277b88b6cd1455986f459aa66cf72bc8a3 # v1.2.1 with: host: "${{ secrets.REPO_HOST }}" username: "${{ secrets.REPO_USER }}" From 7ca09c4081d5953bc5b9f4673de66b9118c90389 Mon Sep 17 00:00:00 2001 From: theguymadmax <171496228+theguymadmax@users.noreply.github.com> Date: Fri, 21 Feb 2025 05:39:38 -0500 Subject: [PATCH 08/19] Backport pull request #13594 from jellyfin/release-10.10.z Fix 4K filtering when grouping movies into collections Original-merge: 317d7a9f4f76dbd964e2649ed4b7d03d3e68ca9d Merged-by: crobibero Backported-by: Bond_009 --- MediaBrowser.Controller/Entities/Folder.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 957e8b3190..af6348e462 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1202,6 +1202,11 @@ namespace MediaBrowser.Controller.Entities return false; } + if (request.Is4K.HasValue) + { + return false; + } + if (request.IsHD.HasValue) { return false; From a4aefc8a802d89c2f1406261d492bfc298351bb7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Feb 2025 20:25:39 +0000 Subject: [PATCH 09/19] Update CI dependencies --- .github/workflows/ci-codeql-analysis.yml | 6 +++--- .github/workflows/ci-compat.yml | 4 ++-- .github/workflows/ci-openapi.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml index 00f7e9e6d2..1f166d10c8 100644 --- a/.github/workflows/ci-codeql-analysis.yml +++ b/.github/workflows/ci-codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '9.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 diff --git a/.github/workflows/ci-compat.yml b/.github/workflows/ci-compat.yml index 07e61024ee..1975a9f03a 100644 --- a/.github/workflows/ci-compat.yml +++ b/.github/workflows/ci-compat.yml @@ -26,7 +26,7 @@ jobs: dotnet build Jellyfin.Server -o ./out - name: Upload Head - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: abi-head retention-days: 14 @@ -65,7 +65,7 @@ jobs: dotnet build Jellyfin.Server -o ./out - name: Upload Head - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: abi-base retention-days: 14 diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index ee9df30f44..bc30256568 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -27,7 +27,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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: openapi-head retention-days: 14 @@ -61,7 +61,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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: openapi-base retention-days: 14 From 83b2c472378410a3d0e7e8065cb627d2520d99d4 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 22 Feb 2025 10:23:33 +0100 Subject: [PATCH 10/19] Remove deprecated GetWakeOnLanInfo endpoint --- Jellyfin.Api/Controllers/SystemController.cs | 16 ------- MediaBrowser.Model/System/WakeOnLanInfo.cs | 47 -------------------- 2 files changed, 63 deletions(-) delete mode 100644 MediaBrowser.Model/System/WakeOnLanInfo.cs diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 6c5ce47158..0ee11c0704 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -212,20 +212,4 @@ public class SystemController : BaseJellyfinApiController FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); return File(stream, "text/plain; charset=utf-8"); } - - /// - /// Gets wake on lan information. - /// - /// Information retrieved. - /// An with the WakeOnLan infos. - [HttpGet("WakeOnLanInfo")] - [Authorize] - [Obsolete("This endpoint is obsolete.")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetWakeOnLanInfo() - { - var result = _networkManager.GetMacAddresses() - .Select(i => new WakeOnLanInfo(i)); - return Ok(result); - } } diff --git a/MediaBrowser.Model/System/WakeOnLanInfo.cs b/MediaBrowser.Model/System/WakeOnLanInfo.cs deleted file mode 100644 index aba19a6baf..0000000000 --- a/MediaBrowser.Model/System/WakeOnLanInfo.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Net.NetworkInformation; - -namespace MediaBrowser.Model.System -{ - /// - /// Provides the MAC address and port for wake-on-LAN functionality. - /// - public class WakeOnLanInfo - { - /// - /// Initializes a new instance of the class. - /// - /// The MAC address. - public WakeOnLanInfo(PhysicalAddress macAddress) : this(macAddress.ToString()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The MAC address. - public WakeOnLanInfo(string macAddress) : this() - { - MacAddress = macAddress; - } - - /// - /// Initializes a new instance of the class. - /// - public WakeOnLanInfo() - { - Port = 9; - } - - /// - /// Gets the MAC address of the device. - /// - /// The MAC address. - public string? MacAddress { get; } - - /// - /// Gets or sets the wake-on-LAN port. - /// - /// The wake-on-LAN port. - public int Port { get; set; } - } -} From d18066f0f23f819efdef145a74ffb173df636b58 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 22 Feb 2025 10:27:42 +0100 Subject: [PATCH 11/19] Remove GetMacAddresses from NetworkManager --- MediaBrowser.Common/Net/INetworkManager.cs | 6 ----- .../Manager/NetworkManager.cs | 23 ------------------- 2 files changed, 29 deletions(-) diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 78a391d36d..d838144ff6 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -94,12 +94,6 @@ namespace MediaBrowser.Common.Net /// IP address to use, or loopback address if all else fails. string GetBindAddress(string source, out int? port); - /// - /// Get a list of all the MAC addresses associated with active interfaces. - /// - /// List of MAC addresses. - IReadOnlyList GetMacAddresses(); - /// /// Returns true if the address is part of the user defined LAN. /// diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index dd01e9533b..2fbcbf79ce 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -49,11 +49,6 @@ public class NetworkManager : INetworkManager, IDisposable /// private bool _eventfire; - /// - /// List of all interface MAC addresses. - /// - private IReadOnlyList _macAddresses; - /// /// Dictionary containing interface addresses and their subnets. /// @@ -91,7 +86,6 @@ public class NetworkManager : INetworkManager, IDisposable _startupConfig = startupConfig; _initLock = new(); _interfaces = new List(); - _macAddresses = new List(); _publishedServerUrls = new List(); _networkEventLock = new(); _remoteAddressFilter = new List(); @@ -215,7 +209,6 @@ public class NetworkManager : INetworkManager, IDisposable /// /// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state. - /// Generate a list of all active mac addresses that aren't loopback addresses. /// private void InitializeInterfaces() { @@ -224,7 +217,6 @@ public class NetworkManager : INetworkManager, IDisposable _logger.LogDebug("Refreshing interfaces."); var interfaces = new List(); - var macAddresses = new List(); try { @@ -236,13 +228,6 @@ public class NetworkManager : INetworkManager, IDisposable try { var ipProperties = adapter.GetIPProperties(); - var mac = adapter.GetPhysicalAddress(); - - // Populate MAC list - if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && !PhysicalAddress.None.Equals(mac)) - { - macAddresses.Add(mac); - } // Populate interface list foreach (var info in ipProperties.UnicastAddresses) @@ -302,7 +287,6 @@ public class NetworkManager : INetworkManager, IDisposable _logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count); _logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString())); - _macAddresses = macAddresses; _interfaces = interfaces; } } @@ -711,13 +695,6 @@ public class NetworkManager : INetworkManager, IDisposable return true; } - /// - public IReadOnlyList GetMacAddresses() - { - // Populated in construction - so always has values. - return _macAddresses; - } - /// public IReadOnlyList GetLoopbacks() { From 068bc687647ed894a2ae05d5b1e24770415bc8ec Mon Sep 17 00:00:00 2001 From: millallo Date: Sat, 22 Feb 2025 12:31:20 +0000 Subject: [PATCH 12/19] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 297b3abce7..e05afbabeb 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -58,8 +58,8 @@ "NotificationOptionServerRestartRequired": "Riavvio del server necessario", "NotificationOptionTaskFailed": "Operazione pianificata fallita", "NotificationOptionUserLockedOut": "Utente bloccato", - "NotificationOptionVideoPlayback": "La riproduzione video è iniziata", - "NotificationOptionVideoPlaybackStopped": "La riproduzione video è stata interrotta", + "NotificationOptionVideoPlayback": "Riproduzione video iniziata", + "NotificationOptionVideoPlaybackStopped": "Riproduzione video interrotta", "Photos": "Foto", "Playlists": "Playlist", "Plugin": "Plugin", From 06be4998e189729170a6f6dbbca68a0790c72d43 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 25 Feb 2025 15:26:47 -0500 Subject: [PATCH 13/19] Backport pull request #13611 from jellyfin/release-10.10.z Remove empty ParentIndexNumber workaround Original-merge: 1daf761aece5114a6ac3b7b938f114e70b83d99e Merged-by: Bond-009 Backported-by: Bond_009 --- Emby.Server.Implementations/Library/LibraryManager.cs | 9 --------- .../Plugins/Omdb/OmdbEpisodeProvider.cs | 5 ++--- .../Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs | 6 +++--- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 10 +++++----- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c483f3c61f..8568104941 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2630,15 +2630,6 @@ namespace Emby.Server.Implementations.Library { episode.ParentIndexNumber = season.IndexNumber; } - else - { - /* - Anime series don't generally have a season in their file name, however, - TVDb needs a season to correctly get the metadata. - Hence, a null season needs to be filled with something. */ - // FIXME perhaps this would be better for TVDb parser to ask for season 1 if no season is specified - episode.ParentIndexNumber = 1; - } if (episode.ParentIndexNumber.HasValue) { diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs index d8b33a799f..ccff31ebaa 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs @@ -55,13 +55,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId) && !string.IsNullOrEmpty(seriesImdbId) - && info.IndexNumber.HasValue - && info.ParentIndexNumber.HasValue) + && info.IndexNumber.HasValue) { result.HasMetadata = await _omdbProvider.FetchEpisodeData( result, info.IndexNumber.Value, - info.ParentIndexNumber.Value, + info.ParentIndexNumber ?? 1, info.GetProviderId(MetadataProvider.Imdb), seriesImdbId, info.MetadataLanguage, diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index d1fec7cb13..7de0e430f2 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -63,10 +63,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return Enumerable.Empty(); } - var seasonNumber = episode.ParentIndexNumber; + var seasonNumber = episode.ParentIndexNumber ?? 1; var episodeNumber = episode.IndexNumber; - if (!seasonNumber.HasValue || !episodeNumber.HasValue) + if (!episodeNumber.HasValue) { return Enumerable.Empty(); } @@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken) .ConfigureAwait(false); var stills = episodeResult?.Images?.Stills; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index e628abde55..c93dabb66c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) { // The search query must either provide an episode number or date - if (!searchInfo.IndexNumber.HasValue || !searchInfo.ParentIndexNumber.HasValue) + if (!searchInfo.IndexNumber.HasValue) { return Enumerable.Empty(); } @@ -96,10 +96,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return metadataResult; } - var seasonNumber = info.ParentIndexNumber; + var seasonNumber = info.ParentIndexNumber ?? 1; var episodeNumber = info.IndexNumber; - if (!seasonNumber.HasValue || !episodeNumber.HasValue) + if (!episodeNumber.HasValue) { return metadataResult; } @@ -112,7 +112,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV List? result = null; for (int? episode = startindex; episode <= endindex; episode++) { - var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false); + var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false); if (episodeInfo is not null) { (result ??= new List()).Add(episodeInfo); @@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV else { episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .ConfigureAwait(false); } From 33e8c18136f4dd380650962a18ebcb3bf2e7f087 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 25 Feb 2025 15:32:02 -0500 Subject: [PATCH 14/19] Backport pull request #13593 from jellyfin/release-10.10.z Wait for ffmpeg to exit on Windows before we try deleting the concat file Original-merge: 346f36bc09eb6989d7cd6439175e46b699162cbb Merged-by: Bond-009 Backported-by: Bond_009 --- .../Tasks/AudioNormalizationTask.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs index 031d147765..8d1d509ff7 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs @@ -116,6 +116,7 @@ public partial class AudioNormalizationTask : IScheduledTask { a.LUFS = await CalculateLUFSAsync( string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile), + OperatingSystem.IsWindows(), // Wait for process to exit on Windows before we try deleting the concat file cancellationToken).ConfigureAwait(false); } finally @@ -142,7 +143,10 @@ public partial class AudioNormalizationTask : IScheduledTask continue; } - t.LUFS = await CalculateLUFSAsync(string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)), cancellationToken).ConfigureAwait(false); + t.LUFS = await CalculateLUFSAsync( + string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)), + false, + cancellationToken).ConfigureAwait(false); } _itemRepository.SaveItems(tracks, cancellationToken); @@ -162,7 +166,7 @@ public partial class AudioNormalizationTask : IScheduledTask ]; } - private async Task CalculateLUFSAsync(string inputArgs, CancellationToken cancellationToken) + private async Task CalculateLUFSAsync(string inputArgs, bool waitForExit, CancellationToken cancellationToken) { var args = $"-hide_banner {inputArgs} -af ebur128=framelog=verbose -f null -"; @@ -189,18 +193,28 @@ public partial class AudioNormalizationTask : IScheduledTask } using var reader = process.StandardError; + float? lufs = null; await foreach (var line in reader.ReadAllLinesAsync(cancellationToken)) { Match match = LUFSRegex().Match(line); - if (match.Success) { - return float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat); + lufs = float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat); + break; } } - _logger.LogError("Failed to find LUFS value in output"); - return null; + if (lufs is null) + { + _logger.LogError("Failed to find LUFS value in output"); + } + + if (waitForExit) + { + await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + } + + return lufs; } } } From 1131b051d8ec9f5cb0d0b3d42852c1a022a04932 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 25 Feb 2025 15:32:03 -0500 Subject: [PATCH 15/19] Backport pull request #13601 from jellyfin/release-10.10.z Delete children from cache on parent delete Original-merge: 767a5e61930897d6151bf2b5b6c4940a288deb41 Merged-by: Bond-009 Backported-by: Bond_009 --- Emby.Server.Implementations/Library/LibraryManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 8568104941..cc2092e21e 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -457,6 +457,7 @@ namespace Emby.Server.Implementations.Library foreach (var child in children) { _itemRepository.DeleteItem(child.Id); + _cache.TryRemove(child.Id, out _); } _cache.TryRemove(item.Id, out _); From c38e887ea5235a6a485417ff5224a6da80209d68 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 00:31:28 +0000 Subject: [PATCH 16/19] Update actions/download-artifact action to v4.1.9 --- .github/workflows/ci-compat.yml | 4 ++-- .github/workflows/ci-openapi.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-compat.yml b/.github/workflows/ci-compat.yml index 1975a9f03a..ca505790cc 100644 --- a/.github/workflows/ci-compat.yml +++ b/.github/workflows/ci-compat.yml @@ -85,13 +85,13 @@ jobs: steps: - name: Download abi-head - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 with: name: abi-head path: abi-head - name: Download abi-base - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 with: name: abi-base path: abi-base diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index bc30256568..85a7a33bcd 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -80,12 +80,12 @@ jobs: - openapi-base steps: - name: Download openapi-head - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 with: name: openapi-head path: openapi-head - name: Download openapi-base - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 with: name: openapi-base path: openapi-base @@ -158,7 +158,7 @@ jobs: run: |- echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV - name: Download openapi-head - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 with: name: openapi-head path: openapi-head @@ -220,7 +220,7 @@ jobs: run: |- echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV - name: Download openapi-head - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 with: name: openapi-head path: openapi-head From 0803600afdf06a10b1bb0d40c992bb3aa0cda4f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Tue, 25 Feb 2025 10:21:33 +0000 Subject: [PATCH 17/19] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/ --- Emby.Server.Implementations/Localization/Core/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index f205e8b64c..1a9c3ee8be 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -13,7 +13,7 @@ "DeviceOnlineWithName": "{0} belépett", "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet innen: {0}", "Favorites": "Kedvencek", - "Folders": "Könyvtárak", + "Folders": "Mappák", "Genres": "Műfajok", "HeaderAlbumArtists": "Albumelőadók", "HeaderContinueWatching": "Megtekintés folytatása", From 93dd5551df280f53eaa441156cc6016ee384e554 Mon Sep 17 00:00:00 2001 From: Dominik Krivohlavek Date: Sat, 1 Mar 2025 07:00:52 +0100 Subject: [PATCH 18/19] Add support for reading and storing Recording MBIDs from file metadata (#12173) * Add recording metadata provider * Add recording MBID * Save recording MBID during probing * Set recording ID in probe result normalizer * Add recording external media type * Reimplement after changes in upstream * Rename variable * Rename variable * Revert "Set recording ID in probe result normalizer" This reverts commit 9dd18c8aba3f970a5816a13a33acf3d58b0e440f. * Fix setting provider ID * Simplify code * Fix comment * Add missing using --- .../Entities/MetadataProvider.cs | 7 ++++- .../Providers/ExternalIdMediaType.cs | 7 ++++- .../MediaInfo/AudioFileProber.cs | 19 +++++++++++++ .../MusicBrainz/MusicBrainzRecordingId.cs | 27 +++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs index dcc4ae88c5..65337b60fd 100644 --- a/MediaBrowser.Model/Entities/MetadataProvider.cs +++ b/MediaBrowser.Model/Entities/MetadataProvider.cs @@ -84,6 +84,11 @@ namespace MediaBrowser.Model.Entities /// /// The TvMaze provider. /// - TvMaze = 19 + TvMaze = 19, + + /// + /// The MusicBrainz recording provider. + /// + MusicBrainzRecording = 20, } } diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs index ef518369cc..71a131bb80 100644 --- a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs +++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs @@ -71,6 +71,11 @@ namespace MediaBrowser.Model.Providers /// /// A book. /// - Book = 13 + Book = 13, + + /// + /// A music recording. + /// + Recording = 14 } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index a0481a6426..963b611515 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -19,6 +19,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; +using static Jellyfin.Extensions.StringExtensions; namespace MediaBrowser.Providers.MediaInfo { @@ -400,6 +401,24 @@ namespace MediaBrowser.Providers.MediaInfo } } + if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzRecording, out _)) + { + if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_TRACKID", out var recordingMbId) + || track.AdditionalFields.TryGetValue("MusicBrainz Track Id", out recordingMbId)) + && !string.IsNullOrEmpty(recordingMbId)) + { + audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, recordingMbId); + } + else if (track.AdditionalFields.TryGetValue("UFID", out var ufIdValue) && !string.IsNullOrEmpty(ufIdValue)) + { + // If tagged with MB Picard, the format is 'http://musicbrainz.org\0' + if (ufIdValue.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase)) + { + audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, ufIdValue.AsSpan().RightPart('\0').ToString()); + } + } + } + // Save extracted lyrics if they exist, // and if the audio doesn't yet have lyrics. var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs new file mode 100644 index 0000000000..d2af628067 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs @@ -0,0 +1,27 @@ +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz; + +/// +/// MusicBrainz recording id. +/// +public class MusicBrainzRecordingId : IExternalId +{ + /// + public string ProviderName => "MusicBrainz"; + + /// + public string Key => MetadataProvider.MusicBrainzRecording.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Recording; + + /// + public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/recording/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio; +} From f035b1162528ed4c351ae03f2c217499e46bdd1a Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Sat, 1 Mar 2025 00:01:21 -0600 Subject: [PATCH 19/19] Better exception message when folders or folder items are missing (#13632) Emit the not-found Id in the exception for easier diagnosis --- Emby.Server.Implementations/Collections/CollectionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index e414792ba0..4a0662e16a 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.Collections { if (_libraryManager.GetItemById(collectionId) is not BoxSet collection) { - throw new ArgumentException("No collection exists with the supplied Id"); + throw new ArgumentException("No collection exists with the supplied collectionId " + collectionId); } List? itemList = null; @@ -218,7 +218,7 @@ namespace Emby.Server.Implementations.Collections if (item is null) { - throw new ArgumentException("No item exists with the supplied Id"); + throw new ArgumentException("No item exists with the supplied Id " + id); } if (!currentLinkedChildrenIds.Contains(id))