From 016a7e5542a6b2a9532341f0d8e567929d138eeb Mon Sep 17 00:00:00 2001 From: Jellyfin Release Bot Date: Sat, 26 Oct 2024 13:32:50 -0400 Subject: [PATCH 01/66] Bump version to 10.10.0 From c6629aebf871af861b42f711f12ff920117f4bce Mon Sep 17 00:00:00 2001 From: Benedikt Date: Mon, 28 Oct 2024 14:29:15 +0100 Subject: [PATCH 02/66] Fix TMDB import failing when no IMDB ID is set for a movie (#12891) --- CONTRIBUTORS.md | 1 + MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a9deb1c4a2..bcc428abbd 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -192,6 +192,7 @@ - [jaina heartles](https://github.com/heartles) - [oxixes](https://github.com/oxixes) - [elfalem](https://github.com/elfalem) + - [benedikt257](https://github.com/benedikt257) # Emby Contributors diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 8d68e2dcfe..eef08b251f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -198,7 +198,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies }; movie.SetProviderId(MetadataProvider.Tmdb, tmdbId); - movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId); + movie.TrySetProviderId(MetadataProvider.Imdb, movieResult.ImdbId); if (movieResult.BelongsToCollection is not null) { movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(CultureInfo.InvariantCulture)); From 312ff4f3d875b9e5c738ee20273cd9f3bd32a58c Mon Sep 17 00:00:00 2001 From: JPVenson Date: Wed, 30 Oct 2024 10:05:52 +0000 Subject: [PATCH 03/66] Fixed disabled providers not beeing returned --- .../MediaSegments/MediaSegmentManager.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index d641f521b9..9d38167ac6 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -141,6 +141,19 @@ public class MediaSegmentManager : IMediaSegmentManager /// public async Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter) { + var baseItem = _libraryManager.GetItemById(itemId); + + if (baseItem is null) + { + _logger.LogError("Tried to request segments for an invalid item"); + return []; + } + + var libraryOptions = _libraryManager.GetLibraryOptions(baseItem); + var providers = _segmentProviders + .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) + .ToArray(); + using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); var query = db.MediaSegments @@ -151,6 +164,9 @@ public class MediaSegmentManager : IMediaSegmentManager query = query.Where(e => typeFilter.Contains(e.Type)); } + var providerIds = providers.Select(f => GetProviderId(f.Name)).ToArray(); + query = query.Where(e => providerIds.Contains(e.SegmentProviderId)); + return query .OrderBy(e => e.StartTicks) .AsNoTracking() From c08d1d5b7f45b41ef200e9604fa38096c1560a85 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Wed, 30 Oct 2024 10:09:39 +0000 Subject: [PATCH 04/66] Added parameter to enable or disable library filter --- .../MediaSegments/MediaSegmentManager.cs | 18 ++++++++++-------- .../MediaSegements/IMediaSegmentManager.cs | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index 9d38167ac6..4abf5f9f8a 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -139,7 +139,7 @@ public class MediaSegmentManager : IMediaSegmentManager } /// - public async Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter) + public async Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter, bool filterByProvider = true) { var baseItem = _libraryManager.GetItemById(itemId); @@ -149,11 +149,6 @@ public class MediaSegmentManager : IMediaSegmentManager return []; } - var libraryOptions = _libraryManager.GetLibraryOptions(baseItem); - var providers = _segmentProviders - .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) - .ToArray(); - using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); var query = db.MediaSegments @@ -164,8 +159,15 @@ public class MediaSegmentManager : IMediaSegmentManager query = query.Where(e => typeFilter.Contains(e.Type)); } - var providerIds = providers.Select(f => GetProviderId(f.Name)).ToArray(); - query = query.Where(e => providerIds.Contains(e.SegmentProviderId)); + if (filterByProvider) + { + var libraryOptions = _libraryManager.GetLibraryOptions(baseItem); + var providers = _segmentProviders + .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) + .ToArray(); + var providerIds = providers.Select(f => GetProviderId(f.Name)).ToArray(); + query = query.Where(e => providerIds.Contains(e.SegmentProviderId)); + } return query .OrderBy(e => e.StartTicks) diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs index 010d7edb4f..7980a38261 100644 --- a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs +++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs @@ -50,8 +50,9 @@ public interface IMediaSegmentManager /// /// The id of the . /// filteres all media segments of the given type to be included. If null all types are included. + /// When set filteres the segments to only return those that which providers are currently enabled on their library. /// An enumerator of 's. - Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter); + Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter, bool filterByProvider = true); /// /// Gets information about any media segments stored for the given itemId. From aa4dd04b992bdcb7f40a82ca09bc5ebc70d23df7 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Wed, 30 Oct 2024 10:10:55 +0000 Subject: [PATCH 05/66] Added fast fail for no provider selected segment query --- .../MediaSegments/MediaSegmentManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index 4abf5f9f8a..453b8e03dd 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -165,6 +165,11 @@ public class MediaSegmentManager : IMediaSegmentManager var providers = _segmentProviders .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) .ToArray(); + if (providers.Length == 0) + { + return []; + } + var providerIds = providers.Select(f => GetProviderId(f.Name)).ToArray(); query = query.Where(e => providerIds.Contains(e.SegmentProviderId)); } From 013058015172d596ae67327e203ca03e20658813 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Wed, 30 Oct 2024 10:25:57 +0000 Subject: [PATCH 06/66] Fixed interface definition --- .../Controllers/MediaSegmentsController.cs | 2 +- .../MediaSegments/MediaSegmentManager.cs | 14 ++++++++++---- .../MediaSegements/IMediaSegmentManager.cs | 9 +++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs index 3dc5167a2e..2d1d4e2c8a 100644 --- a/Jellyfin.Api/Controllers/MediaSegmentsController.cs +++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs @@ -55,7 +55,7 @@ public class MediaSegmentsController : BaseJellyfinApiController return NotFound(); } - var items = await _mediaSegmentManager.GetSegmentsAsync(item.Id, includeSegmentTypes).ConfigureAwait(false); + var items = await _mediaSegmentManager.GetSegmentsAsync(item, includeSegmentTypes).ConfigureAwait(false); return Ok(new QueryResult(items.ToArray())); } } diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index 453b8e03dd..1b9e7a17f0 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -139,20 +139,26 @@ public class MediaSegmentManager : IMediaSegmentManager } /// - public async Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter, bool filterByProvider = true) + public Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter, bool filterByProvider = true) { var baseItem = _libraryManager.GetItemById(itemId); if (baseItem is null) { _logger.LogError("Tried to request segments for an invalid item"); - return []; + return Task.FromResult>([]); } + return GetSegmentsAsync(baseItem, typeFilter, filterByProvider); + } + + /// + public async Task> GetSegmentsAsync(BaseItem item, IEnumerable? typeFilter, bool filterByProvider = true) + { using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); var query = db.MediaSegments - .Where(e => e.ItemId.Equals(itemId)); + .Where(e => e.ItemId.Equals(item.Id)); if (typeFilter is not null) { @@ -161,7 +167,7 @@ public class MediaSegmentManager : IMediaSegmentManager if (filterByProvider) { - var libraryOptions = _libraryManager.GetLibraryOptions(baseItem); + var libraryOptions = _libraryManager.GetLibraryOptions(item); var providers = _segmentProviders .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) .ToArray(); diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs index 7980a38261..72bd1da8aa 100644 --- a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs +++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs @@ -54,6 +54,15 @@ public interface IMediaSegmentManager /// An enumerator of 's. Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter, bool filterByProvider = true); + /// + /// Obtains all segments accociated with the itemId. + /// + /// The . + /// filteres all media segments of the given type to be included. If null all types are included. + /// When set filteres the segments to only return those that which providers are currently enabled on their library. + /// An enumerator of 's. + Task> GetSegmentsAsync(BaseItem itemId, IEnumerable? typeFilter, bool filterByProvider = true); + /// /// Gets information about any media segments stored for the given itemId. /// From 54a6a33c017fb50812cb8c849b50843b25fc782d Mon Sep 17 00:00:00 2001 From: JPVenson Date: Wed, 30 Oct 2024 10:31:10 +0000 Subject: [PATCH 07/66] renamed param --- .../MediaSegements/IMediaSegmentManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs index 72bd1da8aa..672f27eca2 100644 --- a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs +++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs @@ -57,11 +57,11 @@ public interface IMediaSegmentManager /// /// Obtains all segments accociated with the itemId. /// - /// The . + /// The . /// filteres all media segments of the given type to be included. If null all types are included. /// When set filteres the segments to only return those that which providers are currently enabled on their library. /// An enumerator of 's. - Task> GetSegmentsAsync(BaseItem itemId, IEnumerable? typeFilter, bool filterByProvider = true); + Task> GetSegmentsAsync(BaseItem item, IEnumerable? typeFilter, bool filterByProvider = true); /// /// Gets information about any media segments stored for the given itemId. From fe9c6fb8ae7d8b5c4b6362902b6e3acbc2e6df55 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 31 Oct 2024 07:40:47 +0000 Subject: [PATCH 08/66] Fixed enumerable --- .../MediaSegments/MediaSegmentManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index 1b9e7a17f0..33879dbf40 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -183,8 +183,9 @@ public class MediaSegmentManager : IMediaSegmentManager return query .OrderBy(e => e.StartTicks) .AsNoTracking() - .ToImmutableList() - .Select(Map); + .AsEnumerable() + .Select(Map) + .ToImmutableArray(); } private static MediaSegmentDto Map(MediaSegment segment) From f99e0407fd67358fc07c30ac4cbfa736be5f4daa Mon Sep 17 00:00:00 2001 From: "Mikal S." <7761729+revam@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:40:03 +0100 Subject: [PATCH 09/66] Don't try to prune images for virtual episodes. (#12909) --- CONTRIBUTORS.md | 1 + MediaBrowser.Providers/Manager/ItemImageProvider.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index bcc428abbd..e44608135c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -193,6 +193,7 @@ - [oxixes](https://github.com/oxixes) - [elfalem](https://github.com/elfalem) - [benedikt257](https://github.com/benedikt257) + - [revam](https://github.com/revam) # Emby Contributors diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 36a7c2fabe..9b738ce6f3 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -387,8 +387,8 @@ namespace MediaBrowser.Providers.Manager item.RemoveImages(images); - // Cleanup old metadata directory for episodes if empty - if (item is Episode) + // Cleanup old metadata directory for episodes if empty, as long as it's not a virtual item + if (item is Episode && !item.IsVirtualItem) { var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata"); if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDirectory).Any()) From 3592c629e78e80c9d2fc9e368c5d61a11c1bf688 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 31 Oct 2024 16:40:48 +0100 Subject: [PATCH 10/66] Fixed possible NullReferenceException in SessionManager (#12915) --- Emby.Server.Implementations/Session/SessionManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6a8ad2bdc5..fe2c3d24f6 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1938,7 +1938,11 @@ namespace Emby.Server.Implementations.Session // Don't report acceleration type for non-admin users. result = result.Select(r => { - r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; + if (r.TranscodingInfo is not null) + { + r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; + } + return r; }); } From 584be05e9340b11adf9cf49bec674f4c280116c1 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 31 Oct 2024 17:51:56 +0000 Subject: [PATCH 11/66] reduced providerid build --- .../MediaSegments/MediaSegmentManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index 33879dbf40..b7cf2c6655 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -168,15 +168,15 @@ public class MediaSegmentManager : IMediaSegmentManager if (filterByProvider) { var libraryOptions = _libraryManager.GetLibraryOptions(item); - var providers = _segmentProviders + var providerIds = _segmentProviders .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) + .Select(f => GetProviderId(f.Name)) .ToArray(); - if (providers.Length == 0) + if (providerIds.Length == 0) { return []; } - var providerIds = providers.Select(f => GetProviderId(f.Name)).ToArray(); query = query.Where(e => providerIds.Contains(e.SegmentProviderId)); } From b0f44f1d5ac05f70e0a690418165cf5d1d70ac62 Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 1 Nov 2024 05:49:31 +0800 Subject: [PATCH 12/66] Set AudioCodec when building stream This was not set at least since 10.9 and the transcoding behavior is close to "undefined" and in 10.10 this will not work at all. This will make the returned transcoding url from PlayBackInfo to correctly specify the desired transcoding codec. If the client wants to use the HLS controller directly it should be responsible to provide valid container and codec in the parameters. --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index a25ddc367d..f91c32aa84 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -208,6 +208,10 @@ namespace MediaBrowser.Model.Dlna var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate); playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); + if (playlistItem.AudioCodecs.Count == 0 && !string.IsNullOrWhiteSpace(transcodingProfile.AudioCodec)) + { + playlistItem.AudioCodecs = [transcodingProfile.AudioCodec]; + } } playlistItem.TranscodeReasons = transcodeReasons; From 096e1b29701c957e13dd482f343afc6537cb1c9a Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 1 Nov 2024 07:09:16 +0800 Subject: [PATCH 13/66] Add comments noting that comma separated codec list is not supported in pure audio transcoding for now --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index f91c32aa84..767e012029 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -208,6 +208,10 @@ namespace MediaBrowser.Model.Dlna var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate); playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); + + // Pure audio transcoding does not support comma separated list of transcoding codec at the moment. + // So just use the AudioCodec as is would be safe enough as the _transcoderSupport.CanEncodeToAudioCodec + // would fail so this profile will not even be picked up. if (playlistItem.AudioCodecs.Count == 0 && !string.IsNullOrWhiteSpace(transcodingProfile.AudioCodec)) { playlistItem.AudioCodecs = [transcodingProfile.AudioCodec]; From 74d2c2addfd61a514c7ef04d9c08efd1f1bdb660 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 2 Nov 2024 17:15:00 +0800 Subject: [PATCH 14/66] Remove DynamicImageResponse local image after saved to metadata folder Previously, local images provided by DynamicImageResponse were never cleaned up until the server was restarted. This issue has become more severe in 10.10, as the default is now set to use the system's native temp folder, which might be a RAM backed tmpfs. This behavior could lead to resource starvation for long-running servers performing multiple library scans. Metadata plugins prefer the old behavior should do its own backup. --- MediaBrowser.Providers/Manager/ItemImageProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 9b738ce6f3..b371e10bff 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -232,6 +232,8 @@ namespace MediaBrowser.Providers.Manager var stream = AsyncFile.OpenRead(response.Path); await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false); + + File.Delete(response.Path); } } From 469bf9d514e1b1a0e8285d2853ff053fc2a20490 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 3 Nov 2024 02:51:11 +0800 Subject: [PATCH 15/66] Move the remove source implementation into ProviderManager --- .../Providers/IProviderManager.cs | 3 ++- MediaBrowser.Providers/Manager/ItemImageProvider.cs | 6 +----- MediaBrowser.Providers/Manager/ProviderManager.cs | 12 ++++++++++-- .../Manager/ItemImageProviderTests.cs | 3 +++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 38fc5f2cca..0d3a334dfb 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -77,7 +77,8 @@ namespace MediaBrowser.Controller.Providers Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken); /// - /// Saves the image. + /// Saves the image by giving the image path on filesystem. + /// This method will remove the image on the source path after saving it to the destination. /// /// Image to save. /// Source of image. diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index b371e10bff..64954818a5 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -229,11 +229,7 @@ namespace MediaBrowser.Providers.Manager { var mimeType = MimeTypes.GetMimeType(response.Path); - var stream = AsyncFile.OpenRead(response.Path); - - await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false); - - File.Delete(response.Path); + await _providerManager.SaveImage(item, response.Path, mimeType, imageType, null, null, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 81a9af68be..3c65d49a86 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -251,7 +251,7 @@ namespace MediaBrowser.Providers.Manager } /// - public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken) + public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(source)) { @@ -259,7 +259,15 @@ namespace MediaBrowser.Providers.Manager } var fileStream = AsyncFile.OpenRead(source); - return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); + await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); + try + { + File.Delete(source); + } + catch (IOException ex) + { + _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source); + } } /// diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs index 0c7d2487cb..0d99e9af0e 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs @@ -292,6 +292,9 @@ namespace Jellyfin.Providers.Tests.Manager providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) .Callback((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) .Returns(Task.CompletedTask); + providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, null, It.IsAny())) + .Callback((callbackItem, _, _, callbackType, _, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) + .Returns(Task.CompletedTask); var itemImageProvider = GetItemImageProvider(providerManager.Object, null); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); From 3aefbf8cf6062e7f82f58cc6110e22e42be556b5 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 3 Nov 2024 03:02:35 +0800 Subject: [PATCH 16/66] Don't do double remove in BaseDynamicImageProvider --- Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 82db7c46b3..0a3d740ccf 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -122,7 +122,6 @@ namespace Emby.Server.Implementations.Images } await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false); - File.Delete(outputPath); return ItemUpdateType.ImageUpdate; } From e9ee0ef1f5848170f3de2dbde78c3b7d1f811eaa Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 3 Nov 2024 04:11:41 +0800 Subject: [PATCH 17/66] Remove temp file even when saving failed --- .../Manager/ProviderManager.cs | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 3c65d49a86..bfc8ee3e15 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -258,15 +258,37 @@ namespace MediaBrowser.Providers.Manager throw new ArgumentNullException(nameof(source)); } - var fileStream = AsyncFile.OpenRead(source); - await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); + Exception? saveException = null; + try { - File.Delete(source); + var fileStream = AsyncFile.OpenRead(source); + await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); } - catch (IOException ex) + catch (Exception ex) { - _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source); + saveException = ex; + _logger.LogError(ex, "Unable to save image {Source}", source); + } + finally + { + try + { + File.Delete(source); + } + catch (IOException ex) + { + _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source); + } + catch (Exception ex) + { + saveException ??= ex; + } + } + + if (saveException is not null) + { + throw saveException; } } From bb30d26ffb05301dd3ac3255fab64431c3394463 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 3 Nov 2024 04:28:48 +0800 Subject: [PATCH 18/66] Use ExceptionDispatchInfo --- MediaBrowser.Providers/Manager/ProviderManager.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index bfc8ee3e15..220436bf12 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Mime; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using AsyncKeyedLock; @@ -258,7 +259,7 @@ namespace MediaBrowser.Providers.Manager throw new ArgumentNullException(nameof(source)); } - Exception? saveException = null; + ExceptionDispatchInfo? saveException = null; try { @@ -267,7 +268,7 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - saveException = ex; + saveException = ExceptionDispatchInfo.Capture(ex); _logger.LogError(ex, "Unable to save image {Source}", source); } finally @@ -282,14 +283,11 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - saveException ??= ex; + saveException ??= ExceptionDispatchInfo.Capture(ex); } } - if (saveException is not null) - { - throw saveException; - } + saveException?.Throw(); } /// From 03271c43a7f7f6c1ed9dca4703bd8596fd3e441e Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 3 Nov 2024 16:10:17 +0800 Subject: [PATCH 19/66] Throw the exception as is --- MediaBrowser.Providers/Manager/ProviderManager.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 220436bf12..cc4a7ef12c 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -259,18 +259,11 @@ namespace MediaBrowser.Providers.Manager throw new ArgumentNullException(nameof(source)); } - ExceptionDispatchInfo? saveException = null; - try { var fileStream = AsyncFile.OpenRead(source); await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); } - catch (Exception ex) - { - saveException = ExceptionDispatchInfo.Capture(ex); - _logger.LogError(ex, "Unable to save image {Source}", source); - } finally { try @@ -281,13 +274,7 @@ namespace MediaBrowser.Providers.Manager { _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source); } - catch (Exception ex) - { - saveException ??= ExceptionDispatchInfo.Capture(ex); - } } - - saveException?.Throw(); } /// From 5769d5ca911d0b6fce7e765bfcc7c2f30602ba3f Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 3 Nov 2024 23:25:11 +0800 Subject: [PATCH 20/66] Catch all exceptions for file removal --- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index cc4a7ef12c..c5689550d4 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -270,7 +270,7 @@ namespace MediaBrowser.Providers.Manager { File.Delete(source); } - catch (IOException ex) + catch (Exception ex) { _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source); } From f6f4cdf9e788ac522ca6d43eac4570c1fa607da4 Mon Sep 17 00:00:00 2001 From: Jellyfin Release Bot Date: Sun, 3 Nov 2024 10:57:46 -0500 Subject: [PATCH 21/66] Bump version to 10.10.1 --- Emby.Naming/Emby.Naming.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- SharedVersion.cs | 4 ++-- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 7eb131575d..4df181a699 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -36,7 +36,7 @@ Jellyfin Contributors Jellyfin.Naming - 10.10.0 + 10.10.1 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index e24e37740d..944e270bea 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -18,7 +18,7 @@ Jellyfin Contributors Jellyfin.Data - 10.10.0 + 10.10.1 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index c1945bf931..80f63c8bf6 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Common - 10.10.0 + 10.10.1 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 1ef2eb343d..7940e1d98b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Controller - 10.10.0 + 10.10.1 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 9489fe1905..e31731bb86 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Model - 10.10.0 + 10.10.1 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/SharedVersion.cs b/SharedVersion.cs index f98cfbc74e..150708df6b 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.10.0")] -[assembly: AssemblyFileVersion("10.10.0")] +[assembly: AssemblyVersion("10.10.1")] +[assembly: AssemblyFileVersion("10.10.1")] diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index f786cc3b40..8f84b7f957 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -15,7 +15,7 @@ Jellyfin Contributors Jellyfin.Extensions - 10.10.0 + 10.10.1 https://github.com/jellyfin/jellyfin GPL-3.0-only From 954950dc145db4edf85cc2c1e3ce068274097b71 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 4 Nov 2024 22:59:23 +0800 Subject: [PATCH 22/66] Add a small tolerance value to remux fps check (#12947) --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 28f0d1fff7..eaae34cad2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2196,7 +2196,10 @@ namespace MediaBrowser.Controller.MediaEncoding { var videoFrameRate = videoStream.ReferenceFrameRate; - if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value) + // Add a little tolerance to the framerate check because some videos might record a framerate + // that is slightly higher than the intended framerate, but the device can still play it correctly. + // 0.05 fps tolerance should be safe enough. + if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f) { return false; } From 3089e9e40aea4bfe2b99d8b8bd5fdf1dd9d37984 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 4 Nov 2024 23:04:04 +0800 Subject: [PATCH 23/66] Fix json array string writer in JsonDelimitedArrayConverter (#12949) --- .../Json/Converters/JsonDelimitedArrayConverter.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs index 936a5a97c4..5e0ddb8667 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -71,24 +71,11 @@ namespace Jellyfin.Extensions.Json.Converters writer.WriteStartArray(); if (value.Length > 0) { - var toWrite = value.Length - 1; foreach (var it in value) { - var wrote = false; if (it is not null) { writer.WriteStringValue(it.ToString()); - wrote = true; - } - - if (toWrite > 0) - { - if (wrote) - { - writer.WriteStringValue(Delimiter.ToString()); - } - - toWrite--; } } } From c8ca0c72e187655ccc36c2db8f7a55f505ffe09b Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 5 Nov 2024 00:06:39 +0800 Subject: [PATCH 24/66] Fix trickplay images never being replaced The Refresh API controller did not pass the query parameter from the client to MetadataRefreshOptions and the old trickplay files never got replaced. --- Jellyfin.Api/Controllers/ItemRefreshController.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/ItemRefreshController.cs b/Jellyfin.Api/Controllers/ItemRefreshController.cs index d7a8c37c4b..7effe61e49 100644 --- a/Jellyfin.Api/Controllers/ItemRefreshController.cs +++ b/Jellyfin.Api/Controllers/ItemRefreshController.cs @@ -50,6 +50,7 @@ public class ItemRefreshController : BaseJellyfinApiController /// (Optional) Specifies the image refresh mode. /// (Optional) Determines if metadata should be replaced. Only applicable if mode is FullRefresh. /// (Optional) Determines if images should be replaced. Only applicable if mode is FullRefresh. + /// (Optional) Determines if trickplay images should be replaced. Only applicable if mode is FullRefresh. /// Item metadata refresh queued. /// Item to refresh not found. /// An on success, or a if the item could not be found. @@ -62,7 +63,8 @@ public class ItemRefreshController : BaseJellyfinApiController [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None, [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None, [FromQuery] bool replaceAllMetadata = false, - [FromQuery] bool replaceAllImages = false) + [FromQuery] bool replaceAllImages = false, + [FromQuery] bool regenerateTrickplay = false) { var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) @@ -81,7 +83,8 @@ public class ItemRefreshController : BaseJellyfinApiController || replaceAllImages || replaceAllMetadata, IsAutomated = false, - RemoveOldMetadata = replaceAllMetadata + RemoveOldMetadata = replaceAllMetadata, + RegenerateTrickplay = regenerateTrickplay }; _providerManager.QueueRefresh(item.Id, refreshOptions, RefreshPriority.High); From 2354cd45d4b5a1cb010106cbe6991787bcf95bf7 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 5 Nov 2024 10:17:15 +0800 Subject: [PATCH 25/66] Fix height of imported trickplay tiles fixes c56dbc1 Signed-off-by: nyanmisaka --- Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs index cfe385106a..af57bc134d 100644 --- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs +++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs @@ -238,7 +238,7 @@ public class TrickplayManager : ITrickplayManager foreach (var tile in existingFiles) { var image = _imageEncoder.GetImageSize(tile); - localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, image.Height); + localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, (int)Math.Ceiling((double)image.Height / localTrickplayInfo.TileHeight)); var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tile).Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000)); localTrickplayInfo.Bandwidth = Math.Max(localTrickplayInfo.Bandwidth, bitrate); } From aa08d3f2bf155d55f748bff1f0a0c7f071f79ae7 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Wed, 6 Nov 2024 21:37:47 +0800 Subject: [PATCH 26/66] Fix pixel format in HEVC RExt SDR transcoding (#12973) --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index eaae34cad2..e1d0ed0a08 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -4131,7 +4131,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isD3d11vaDecoder || isQsvDecoder) { var isRext = IsVideoStreamHevcRext(state); - var twoPassVppTonemap = isRext; + var twoPassVppTonemap = false; var doVppFullRangeOut = isMjpegEncoder && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption; var doVppScaleModeHq = isMjpegEncoder @@ -4140,6 +4140,12 @@ namespace MediaBrowser.Controller.MediaEncoding var procampParams = string.Empty; if (doVppTonemap) { + if (isRext) + { + // VPP tonemap requires p010 input + twoPassVppTonemap = true; + } + if (options.VppTonemappingBrightness != 0 && options.VppTonemappingBrightness >= -100 && options.VppTonemappingBrightness <= 100) From 97dc02b1632c3c329a181c816ff2c6dc84319732 Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 6 Nov 2024 21:38:00 +0800 Subject: [PATCH 27/66] Always consider null char as delimiter for ID3v2 (#12962) --- MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs index 4a814f22a3..b088cfb53b 100644 --- a/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs +++ b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs @@ -18,7 +18,7 @@ public static class LibraryOptionsExtension { ArgumentNullException.ThrowIfNull(options); - return options.CustomTagDelimiters.Select(x => + var delimiterList = options.CustomTagDelimiters.Select(x => { var isChar = char.TryParse(x, out var c); if (isChar) @@ -27,6 +27,8 @@ public static class LibraryOptionsExtension } return null; - }).Where(x => x is not null).Select(x => x!.Value).ToArray(); + }).Where(x => x is not null).Select(x => x!.Value).ToList(); + delimiterList.Add('\0'); + return delimiterList.ToArray(); } } From 25321d7f80a3b065a8d3061a93adb78d701b7412 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sun, 10 Nov 2024 02:31:59 +0800 Subject: [PATCH 28/66] Fix InvariantCulture in VPP tonemap options (#12989) --- .../MediaEncoding/EncodingHelper.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e1d0ed0a08..92ceb2c542 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3321,24 +3321,25 @@ namespace MediaBrowser.Controller.MediaEncoding && options.VppTonemappingBrightness >= -100 && options.VppTonemappingBrightness <= 100) { - procampParams += $"=b={options.VppTonemappingBrightness}"; + procampParams += "procamp_vaapi=b={0}"; doVaVppProcamp = true; } if (options.VppTonemappingContrast > 1 && options.VppTonemappingContrast <= 10) { - procampParams += doVaVppProcamp ? ":" : "="; - procampParams += $"c={options.VppTonemappingContrast}"; + procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}"; doVaVppProcamp = true; } - args = "{0}tonemap_vaapi=format={1}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32"; + args = "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32"; return string.Format( CultureInfo.InvariantCulture, args, - doVaVppProcamp ? $"procamp_vaapi{procampParams}," : string.Empty, + options.VppTonemappingBrightness, + options.VppTonemappingContrast, + doVaVppProcamp ? "," : string.Empty, videoFormat ?? "nv12"); } else @@ -4138,6 +4139,7 @@ namespace MediaBrowser.Controller.MediaEncoding && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption; var doVppProcamp = false; var procampParams = string.Empty; + var procampParamsString = string.Empty; if (doVppTonemap) { if (isRext) @@ -4150,18 +4152,26 @@ namespace MediaBrowser.Controller.MediaEncoding && options.VppTonemappingBrightness >= -100 && options.VppTonemappingBrightness <= 100) { - procampParams += $":brightness={options.VppTonemappingBrightness}"; + procampParamsString += ":brightness={0}"; twoPassVppTonemap = doVppProcamp = true; } if (options.VppTonemappingContrast > 1 && options.VppTonemappingContrast <= 10) { - procampParams += $":contrast={options.VppTonemappingContrast}"; + procampParamsString += ":contrast={1}"; twoPassVppTonemap = doVppProcamp = true; } - procampParams += doVppProcamp ? ":procamp=1:async_depth=2" : string.Empty; + if (doVppProcamp) + { + procampParamsString += ":procamp=1:async_depth=2"; + procampParams = string.Format( + CultureInfo.InvariantCulture, + procampParamsString, + options.VppTonemappingBrightness, + options.VppTonemappingContrast); + } } var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12"; From d292fde9e29609b58278e46e4edb155698b2fe1c Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 10 Nov 2024 02:33:27 +0800 Subject: [PATCH 29/66] Use invariant culture for tonemap options (#12991) --- .../MediaEncoding/EncodingHelper.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 92ceb2c542..21c4798af6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3527,20 +3527,29 @@ namespace MediaBrowser.Controller.MediaEncoding { // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat; - - var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format={tonemapFormat}"; + var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}"; if (options.TonemappingParam != 0) { - tonemapArgs += $":param={options.TonemappingParam}"; + tonemapArgString += ":param={4}"; } var range = options.TonemappingRange; if (range == TonemappingRange.tv || range == TonemappingRange.pc) { - tonemapArgs += $":range={options.TonemappingRange}"; + tonemapArgString += ":range={5}"; } + var tonemapArgs = string.Format( + CultureInfo.InvariantCulture, + tonemapArgString, + options.TonemappingAlgorithm, + options.TonemappingDesat, + options.TonemappingPeak, + tonemapFormat, + options.TonemappingParam, + options.TonemappingRange); + mainFilters.Add(tonemapArgs); } else From 9e61a6fd729b2980832014ae42bd4f7d1f3afb69 Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 15 Nov 2024 08:00:59 +0800 Subject: [PATCH 30/66] Always cleanup trickplay temp for ffmpeg failures (#13030) --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 826ffd0b7e..a34238cd68 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -1035,6 +1035,16 @@ namespace MediaBrowser.MediaEncoding.Encoder if (exitCode == -1) { _logger.LogError("ffmpeg image extraction failed for {ProcessDescription}", processDescription); + // Cleanup temp folder here, because the targetDirectory is not returned and the cleanup for failed ffmpeg process is not possible for caller. + // Ideally the ffmpeg should not write any files if it fails, but it seems like it is not guaranteed. + try + { + Directory.Delete(targetDirectory, true); + } + catch (Exception e) + { + _logger.LogError(e, "Failed to delete ffmpeg temp directory {TargetDirectory}", targetDirectory); + } throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", processDescription)); } From e2434d38c54b90070bc4eaffa7e3c5cdd9934602 Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 15 Nov 2024 08:01:48 +0800 Subject: [PATCH 31/66] Only set first MusicBrainz ID for audio tags (#13003) --- .../MediaInfo/AudioFileProber.cs | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 27f6d120f9..7f1fdbcb85 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -347,7 +347,8 @@ namespace MediaBrowser.Providers.MediaInfo || track.AdditionalFields.TryGetValue("MusicBrainz Artist Id", out musicBrainzArtistTag)) && !string.IsNullOrEmpty(musicBrainzArtistTag)) { - audio.TrySetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzArtistTag); + var id = GetFirstMusicBrainzId(musicBrainzArtistTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); + audio.TrySetProviderId(MetadataProvider.MusicBrainzArtist, id); } } @@ -357,7 +358,8 @@ namespace MediaBrowser.Providers.MediaInfo || track.AdditionalFields.TryGetValue("MusicBrainz Album Artist Id", out musicBrainzReleaseArtistIdTag)) && !string.IsNullOrEmpty(musicBrainzReleaseArtistIdTag)) { - audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbumArtist, musicBrainzReleaseArtistIdTag); + var id = GetFirstMusicBrainzId(musicBrainzReleaseArtistIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); + audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbumArtist, id); } } @@ -367,7 +369,8 @@ namespace MediaBrowser.Providers.MediaInfo || track.AdditionalFields.TryGetValue("MusicBrainz Album Id", out musicBrainzReleaseIdTag)) && !string.IsNullOrEmpty(musicBrainzReleaseIdTag)) { - audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbum, musicBrainzReleaseIdTag); + var id = GetFirstMusicBrainzId(musicBrainzReleaseIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); + audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbum, id); } } @@ -377,7 +380,8 @@ namespace MediaBrowser.Providers.MediaInfo || track.AdditionalFields.TryGetValue("MusicBrainz Release Group Id", out musicBrainzReleaseGroupIdTag)) && !string.IsNullOrEmpty(musicBrainzReleaseGroupIdTag)) { - audio.TrySetProviderId(MetadataProvider.MusicBrainzReleaseGroup, musicBrainzReleaseGroupIdTag); + var id = GetFirstMusicBrainzId(musicBrainzReleaseGroupIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); + audio.TrySetProviderId(MetadataProvider.MusicBrainzReleaseGroup, id); } } @@ -387,7 +391,8 @@ namespace MediaBrowser.Providers.MediaInfo || track.AdditionalFields.TryGetValue("MusicBrainz Release Track Id", out trackMbId)) && !string.IsNullOrEmpty(trackMbId)) { - audio.TrySetProviderId(MetadataProvider.MusicBrainzTrack, trackMbId); + var id = GetFirstMusicBrainzId(trackMbId, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); + audio.TrySetProviderId(MetadataProvider.MusicBrainzTrack, id); } } @@ -441,5 +446,18 @@ namespace MediaBrowser.Providers.MediaInfo return items; } + + // MusicBrainz IDs are multi-value tags, so we need to split them + // However, our current provider can only have one single ID, which means we need to pick the first one + private string? GetFirstMusicBrainzId(string tag, bool useCustomTagDelimiters, char[] tagDelimiters, string[] whitelist) + { + var val = tag.Split(InternalValueSeparator).FirstOrDefault(); + if (val is not null && useCustomTagDelimiters) + { + val = SplitWithCustomDelimiter(val, tagDelimiters, whitelist).FirstOrDefault(); + } + + return val; + } } } From cf11a2dc1eec3cde51713df745934933102a2dd5 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Fri, 15 Nov 2024 08:02:02 +0800 Subject: [PATCH 32/66] Fix missing procamp vaapi filter (#13026) --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 21c4798af6..9399679a4f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3332,7 +3332,7 @@ namespace MediaBrowser.Controller.MediaEncoding doVaVppProcamp = true; } - args = "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32"; + args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32"; return string.Format( CultureInfo.InvariantCulture, From 8bee67f1f8dab604d745b3d077330085f7f111d4 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Fri, 15 Nov 2024 01:03:31 +0100 Subject: [PATCH 33/66] Fix playlists (#12934) --- .../ConfigurationOptions.cs | 1 - .../Playlists/PlaylistManager.cs | 40 +++++++---- .../Controllers/PlaylistsController.cs | 10 +-- Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../Migrations/Routines/FixPlaylistOwner.cs | 4 +- .../RemoveDuplicatePlaylistChildren.cs | 68 +++++++++++++++++++ .../Entities/LinkedChild.cs | 7 +- .../Extensions/ConfigurationExtensions.cs | 13 ---- .../Playlists/IPlaylistManager.cs | 3 +- 9 files changed, 107 insertions(+), 42 deletions(-) create mode 100644 Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 91791a1c82..a06f6e7fe9 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -17,7 +17,6 @@ namespace Emby.Server.Implementations { DefaultRedirectKey, "web/" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, - { PlaylistsAllowDuplicatesKey, bool.FalseString }, { BindToUnixSocketKey, bool.FalseString }, { SqliteCacheSizeKey, "20000" }, { FfmpegSkipValidationKey, bool.FalseString }, diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 47ff22c0b3..daeb7fed88 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -216,14 +216,11 @@ namespace Emby.Server.Implementations.Playlists var newItems = GetPlaylistItems(newItemIds, user, options) .Where(i => i.SupportsAddingToPlaylist); - // Filter out duplicate items, if necessary - if (!_appConfig.DoPlaylistsAllowDuplicates()) - { - var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet(); - newItems = newItems - .Where(i => !existingIds.Contains(i.Id)) - .Distinct(); - } + // Filter out duplicate items + var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet(); + newItems = newItems + .Where(i => !existingIds.Contains(i.Id)) + .Distinct(); // Create a list of the new linked children to add to the playlist var childrenToAdd = newItems @@ -269,7 +266,7 @@ namespace Emby.Server.Implementations.Playlists var idList = entryIds.ToList(); - var removals = children.Where(i => idList.Contains(i.Item1.Id)); + var removals = children.Where(i => idList.Contains(i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture))); playlist.LinkedChildren = children.Except(removals) .Select(i => i.Item1) @@ -286,26 +283,39 @@ namespace Emby.Server.Implementations.Playlists RefreshPriority.High); } - public async Task MoveItemAsync(string playlistId, string entryId, int newIndex) + public async Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId) { if (_libraryManager.GetItemById(playlistId) is not Playlist playlist) { throw new ArgumentException("No Playlist exists with the supplied Id"); } + var user = _userManager.GetUserById(callingUserId); var children = playlist.GetManageableItems().ToList(); + var accessibleChildren = children.Where(c => c.Item2.IsVisible(user)).ToArray(); - var oldIndex = children.FindIndex(i => string.Equals(entryId, i.Item1.Id, StringComparison.OrdinalIgnoreCase)); + var oldIndexAll = children.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)); + var oldIndexAccessible = accessibleChildren.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)); - if (oldIndex == newIndex) + if (oldIndexAccessible == newIndex) { return; } - var item = playlist.LinkedChildren[oldIndex]; + var newPriorItemIndex = newIndex > oldIndexAccessible ? newIndex : newIndex - 1 < 0 ? 0 : newIndex - 1; + var newPriorItemId = accessibleChildren[newPriorItemIndex].Item1.ItemId; + var newPriorItemIndexOnAllChildren = children.FindIndex(c => c.Item1.ItemId.Equals(newPriorItemId)); + var adjustedNewIndex = newPriorItemIndexOnAllChildren + 1; + + var item = playlist.LinkedChildren.FirstOrDefault(i => string.Equals(entryId, i.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)); + if (item is null) + { + _logger.LogWarning("Modified item not found in playlist. ItemId: {ItemId}, PlaylistId: {PlaylistId}", item.ItemId, playlistId); + + return; + } var newList = playlist.LinkedChildren.ToList(); - newList.Remove(item); if (newIndex >= newList.Count) @@ -314,7 +324,7 @@ namespace Emby.Server.Implementations.Playlists } else { - newList.Insert(newIndex, item); + newList.Insert(adjustedNewIndex, item); } playlist.LinkedChildren = [.. newList]; diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index e6f23b1364..1ab36ccc64 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Attributes; @@ -426,7 +427,7 @@ public class PlaylistsController : BaseJellyfinApiController return Forbid(); } - await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false); + await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex, callingUserId).ConfigureAwait(false); return NoContent(); } @@ -514,7 +515,8 @@ public class PlaylistsController : BaseJellyfinApiController return Forbid(); } - var items = playlist.GetManageableItems().ToArray(); + var user = _userManager.GetUserById(callingUserId); + var items = playlist.GetManageableItems().Where(i => i.Item2.IsVisible(user)).ToArray(); var count = items.Length; if (startIndex.HasValue) { @@ -529,11 +531,11 @@ public class PlaylistsController : BaseJellyfinApiController var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - var user = _userManager.GetUserById(callingUserId); + var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user); for (int index = 0; index < dtos.Count; index++) { - dtos[index].PlaylistItemId = items[index].Item1.Id; + dtos[index].PlaylistItemId = items[index].Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture); } var result = new QueryResult( diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 9d4441ac39..2ab130eefb 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -47,7 +47,8 @@ namespace Jellyfin.Server.Migrations typeof(Routines.AddDefaultCastReceivers), typeof(Routines.UpdateDefaultPluginRepository), typeof(Routines.FixAudioData), - typeof(Routines.MoveTrickplayFiles) + typeof(Routines.MoveTrickplayFiles), + typeof(Routines.RemoveDuplicatePlaylistChildren) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs index 3655a610d3..192c170b26 100644 --- a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs +++ b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs @@ -15,12 +15,12 @@ namespace Jellyfin.Server.Migrations.Routines; /// internal class FixPlaylistOwner : IMigrationRoutine { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IPlaylistManager _playlistManager; public FixPlaylistOwner( - ILogger logger, + ILogger logger, ILibraryManager libraryManager, IPlaylistManager playlistManager) { diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs new file mode 100644 index 0000000000..99047b2a2a --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs @@ -0,0 +1,68 @@ +using System; +using System.Linq; +using System.Threading; + +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// Remove duplicate playlist entries. +/// +internal class RemoveDuplicatePlaylistChildren : IMigrationRoutine +{ + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IPlaylistManager _playlistManager; + + public RemoveDuplicatePlaylistChildren( + ILogger logger, + ILibraryManager libraryManager, + IPlaylistManager playlistManager) + { + _logger = logger; + _libraryManager = libraryManager; + _playlistManager = playlistManager; + } + + /// + public Guid Id => Guid.Parse("{96C156A2-7A13-4B3B-A8B8-FB80C94D20C0}"); + + /// + public string Name => "RemoveDuplicatePlaylistChildren"; + + /// + public bool PerformOnNewInstall => false; + + /// + public void Perform() + { + var playlists = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Playlist] + }) + .Cast() + .ToArray(); + + if (playlists.Length > 0) + { + foreach (var playlist in playlists) + { + var linkedChildren = playlist.LinkedChildren; + if (linkedChildren.Length > 0) + { + var nullItemChildren = linkedChildren.Where(c => c.ItemId is null); + var deduplicatedChildren = linkedChildren.DistinctBy(c => c.ItemId); + var newLinkedChildren = nullItemChildren.Concat(deduplicatedChildren); + playlist.LinkedChildren = linkedChildren; + playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); + _playlistManager.SavePlaylistFile(playlist); + } + } + } + } +} diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index fd5fef3dc5..98e4f525f5 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; -using System.Text.Json.Serialization; namespace MediaBrowser.Controller.Entities { @@ -12,7 +11,6 @@ namespace MediaBrowser.Controller.Entities { public LinkedChild() { - Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } public string Path { get; set; } @@ -21,9 +19,6 @@ namespace MediaBrowser.Controller.Entities public string LibraryItemId { get; set; } - [JsonIgnore] - public string Id { get; set; } - /// /// Gets or sets the linked item id. /// @@ -31,6 +26,8 @@ namespace MediaBrowser.Controller.Entities public static LinkedChild Create(BaseItem item) { + ArgumentNullException.ThrowIfNull(item); + var child = new LinkedChild { Path = item.Path, diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index f8049cd488..e4806109a1 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -49,11 +49,6 @@ namespace MediaBrowser.Controller.Extensions /// public const string FfmpegPathKey = "ffmpeg"; - /// - /// The key for a setting that indicates whether playlists should allow duplicate entries. - /// - public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates"; - /// /// The key for a setting that indicates whether kestrel should bind to a unix socket. /// @@ -120,14 +115,6 @@ namespace MediaBrowser.Controller.Extensions public static bool GetFFmpegImgExtractPerfTradeoff(this IConfiguration configuration) => configuration.GetValue(FfmpegImgExtractPerfTradeoffKey); - /// - /// Gets a value indicating whether playlists should allow duplicate entries from the . - /// - /// The configuration to read the setting from. - /// True if playlists should allow duplicates, otherwise false. - public static bool DoPlaylistsAllowDuplicates(this IConfiguration configuration) - => configuration.GetValue(PlaylistsAllowDuplicatesKey); - /// /// Gets a value indicating whether kestrel should bind to a unix socket from the . /// diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs index 038cbd2d67..497c4a511e 100644 --- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -92,8 +92,9 @@ namespace MediaBrowser.Controller.Playlists /// The playlist identifier. /// The entry identifier. /// The new index. + /// The calling user. /// Task. - Task MoveItemAsync(string playlistId, string entryId, int newIndex); + Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId); /// /// Removed all playlists of a user. From f47d2c1f1ab668f835c3061cd498909ab6128b3b Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 15 Nov 2024 18:34:52 +0100 Subject: [PATCH 34/66] Merge pull request #12792 from jellyfin/renovate/dotnet-monorepo Update dotnet monorepo --- .config/dotnet-tools.json | 2 +- global.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a3847dcdfb..02afa3f072 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "8.0.8", + "version": "8.0.11", "commands": [ "dotnet-ef" ] diff --git a/global.json b/global.json index dbf2988d57..c9b932026e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "8.0.404", "rollForward": "latestMinor" } } From db266d75d6b064178a6e17f96bdcd2cc8385d08c Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 15 Nov 2024 18:37:24 +0100 Subject: [PATCH 35/66] Merge pull request #12986 from jellyfin/renovate/skiasharp-monorepo Update skiasharp monorepo --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 5aadeb2541..a02ae3d7e4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -17,7 +17,7 @@ - + @@ -66,9 +66,9 @@ - - - + + + From a6f04ffb7c64f4f5c4f790de3018863d7a908d46 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 15 Nov 2024 18:52:01 +0100 Subject: [PATCH 36/66] Merge pull request #13021 from jellyfin/renovate/microsoft Update Microsoft to 8.0.11 --- Directory.Packages.props | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a02ae3d7e4..393ce75da6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -24,14 +24,14 @@ - - + + - - - - - + + + + + @@ -40,8 +40,8 @@ - - + + From ea88bdf2f3a680d9ce5cfc28c5f3c561d9e3b4f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Nov 2024 10:51:06 -0700 Subject: [PATCH 37/66] Update dependency z440.atl.core to 6.7.0 (#12943) 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 393ce75da6..ea180c3a2f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -80,7 +80,7 @@ - + From 6870e3496cf88093110bf246869bd8471f2a05a2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:29:58 +0000 Subject: [PATCH 38/66] Update dependency z440.atl.core to 6.8.0 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ea180c3a2f..85404c5e06 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -80,7 +80,7 @@ - + From 293e0f5fafe6ba0c7cfc269b889cb0d4d1ada59a Mon Sep 17 00:00:00 2001 From: Akaanksh Raj <18450543+goknsh@users.noreply.github.com> Date: Sat, 16 Nov 2024 17:16:43 +0000 Subject: [PATCH 39/66] Respect cancellation token/HTTP request aborts correctly in `SymlinkFollowingPhysicalFileResultExecutor` (#13033) --- ...linkFollowingPhysicalFileResultExecutor.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs index 801026c549..901ed55be6 100644 --- a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs +++ b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs @@ -101,7 +101,7 @@ namespace Jellyfin.Server.Infrastructure count: null); } - private async Task SendFileAsync(string filePath, HttpResponse response, long offset, long? count) + private async Task SendFileAsync(string filePath, HttpResponse response, long offset, long? count, CancellationToken cancellationToken = default) { var fileInfo = GetFileInfo(filePath); if (offset < 0 || offset > fileInfo.Length) @@ -118,6 +118,9 @@ namespace Jellyfin.Server.Infrastructure // Copied from SendFileFallback.SendFileAsync const int BufferSize = 1024 * 16; + var useRequestAborted = !cancellationToken.CanBeCanceled; + var localCancel = useRequestAborted ? response.HttpContext.RequestAborted : cancellationToken; + var fileStream = new FileStream( filePath, FileMode.Open, @@ -127,10 +130,17 @@ namespace Jellyfin.Server.Infrastructure options: FileOptions.Asynchronous | FileOptions.SequentialScan); await using (fileStream.ConfigureAwait(false)) { - fileStream.Seek(offset, SeekOrigin.Begin); - await StreamCopyOperation - .CopyToAsync(fileStream, response.Body, count, BufferSize, CancellationToken.None) - .ConfigureAwait(true); + try + { + localCancel.ThrowIfCancellationRequested(); + fileStream.Seek(offset, SeekOrigin.Begin); + await StreamCopyOperation + .CopyToAsync(fileStream, response.Body, count, BufferSize, localCancel) + .ConfigureAwait(true); + } + catch (OperationCanceledException) when (useRequestAborted) + { + } } } From 1b4ab5e7777b88d6b4082dfa44b69783daed28ab Mon Sep 17 00:00:00 2001 From: JPVenson Date: Sat, 16 Nov 2024 18:39:11 +0000 Subject: [PATCH 40/66] pr review stuff --- .../MediaSegments/MediaSegmentManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index b7cf2c6655..a044fec0d9 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -139,17 +139,17 @@ public class MediaSegmentManager : IMediaSegmentManager } /// - public Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter, bool filterByProvider = true) + public async Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter, bool filterByProvider = true) { var baseItem = _libraryManager.GetItemById(itemId); if (baseItem is null) { _logger.LogError("Tried to request segments for an invalid item"); - return Task.FromResult>([]); + return []; } - return GetSegmentsAsync(baseItem, typeFilter, filterByProvider); + return await GetSegmentsAsync(baseItem, typeFilter, filterByProvider).ConfigureAwait(false); } /// @@ -185,7 +185,7 @@ public class MediaSegmentManager : IMediaSegmentManager .AsNoTracking() .AsEnumerable() .Select(Map) - .ToImmutableArray(); + .ToArray(); } private static MediaSegmentDto Map(MediaSegment segment) From be23f4eb0d94217f6e38a45c9a7343fbfd6886cd Mon Sep 17 00:00:00 2001 From: Jellyfin Release Bot Date: Sat, 16 Nov 2024 14:59:25 -0500 Subject: [PATCH 41/66] Bump version to 10.10.2 --- Emby.Naming/Emby.Naming.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- SharedVersion.cs | 4 ++-- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 4df181a699..03c52c6bf8 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -36,7 +36,7 @@ Jellyfin Contributors Jellyfin.Naming - 10.10.1 + 10.10.2 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 944e270bea..91be579892 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -18,7 +18,7 @@ Jellyfin Contributors Jellyfin.Data - 10.10.1 + 10.10.2 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 80f63c8bf6..debd14a16c 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Common - 10.10.1 + 10.10.2 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 7940e1d98b..5b1c5bff2a 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Controller - 10.10.1 + 10.10.2 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index e31731bb86..7674e46ca8 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Model - 10.10.1 + 10.10.2 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/SharedVersion.cs b/SharedVersion.cs index 150708df6b..8d836c68b8 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.10.1")] -[assembly: AssemblyFileVersion("10.10.1")] +[assembly: AssemblyVersion("10.10.2")] +[assembly: AssemblyFileVersion("10.10.2")] diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index 8f84b7f957..b9e10f680e 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -15,7 +15,7 @@ Jellyfin Contributors Jellyfin.Extensions - 10.10.1 + 10.10.2 https://github.com/jellyfin/jellyfin GPL-3.0-only From 23de7e517e3b4acdefd92e731140d0fa358d3611 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Mon, 18 Nov 2024 04:18:53 +0100 Subject: [PATCH 42/66] Exclude file system based library playlists from migration (#13059) --- .../Library/Resolvers/PlaylistResolver.cs | 2 +- .../Migrations/Routines/RemoveDuplicatePlaylistChildren.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index a03c1214d6..14798dda65 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Resolvers { if (args.IsDirectory) { - // It's a boxset if the path is a directory with [playlist] in its name + // It's a playlist if the path is a directory with [playlist] in its name var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path)); if (string.IsNullOrEmpty(filename)) { diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs index 99047b2a2a..f84bccc258 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs @@ -46,6 +46,7 @@ internal class RemoveDuplicatePlaylistChildren : IMigrationRoutine IncludeItemTypes = [BaseItemKind.Playlist] }) .Cast() + .Where(p => !p.OpenAccess || !p.OwnerUserId.Equals(Guid.Empty)) .ToArray(); if (playlists.Length > 0) From 5e45403cb12bcceade5eb2f7126d4cea5e1585b6 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Mon, 18 Nov 2024 05:58:57 -0700 Subject: [PATCH 43/66] Downgrade minimum sdk version (#13063) --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index c9b932026e..dbf2988d57 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.404", + "version": "8.0.0", "rollForward": "latestMinor" } } From b3e563385c1ae8e07caff508ae59ab2518682989 Mon Sep 17 00:00:00 2001 From: Jellyfin Release Bot Date: Mon, 18 Nov 2024 22:38:42 -0500 Subject: [PATCH 44/66] Bump version to 10.10.3 --- Emby.Naming/Emby.Naming.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- SharedVersion.cs | 4 ++-- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 03c52c6bf8..499ad41188 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -36,7 +36,7 @@ Jellyfin Contributors Jellyfin.Naming - 10.10.2 + 10.10.3 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 91be579892..fb8f4e1397 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -18,7 +18,7 @@ Jellyfin Contributors Jellyfin.Data - 10.10.2 + 10.10.3 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index debd14a16c..507f076c25 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Common - 10.10.2 + 10.10.3 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5b1c5bff2a..1c42fc0646 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Controller - 10.10.2 + 10.10.3 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 7674e46ca8..559562876d 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Model - 10.10.2 + 10.10.3 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/SharedVersion.cs b/SharedVersion.cs index 8d836c68b8..d28302a071 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.10.2")] -[assembly: AssemblyFileVersion("10.10.2")] +[assembly: AssemblyVersion("10.10.3")] +[assembly: AssemblyFileVersion("10.10.3")] diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index b9e10f680e..7f61e71fdc 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -15,7 +15,7 @@ Jellyfin Contributors Jellyfin.Extensions - 10.10.2 + 10.10.3 https://github.com/jellyfin/jellyfin GPL-3.0-only From b0105179ebd99f60b2d703fed9352e403854cf8b Mon Sep 17 00:00:00 2001 From: RealGreenDragon <14246920+RealGreenDragon@users.noreply.github.com> Date: Mon, 25 Nov 2024 08:40:20 +0100 Subject: [PATCH 45/66] Enable RemoveOldPlugins by default Backport of PR #13102 to 10.10.z branch. --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 5ad588200b..92065c964c 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -246,7 +246,7 @@ public class ServerConfiguration : BaseApplicationConfiguration /// /// Gets or sets a value indicating whether older plugins should automatically be deleted from the plugin folder. /// - public bool RemoveOldPlugins { get; set; } + public bool RemoveOldPlugins { get; set; } = true; /// /// Gets or sets a value indicating whether clients should be allowed to upload logs. From 9bc6e8a306a563e0ec2198950df2462c8669ec9b Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 26 Nov 2024 15:01:59 +0800 Subject: [PATCH 46/66] Only do DoVi remux when the client supports profiles without fallbacks In 10.10 clients that can only play the fallback layer like the Samsung TVs will report `DOVIWithHDR10` as supported video range, but the server should not do remux in DoVi as the client can only play the fallback layer. This changes the server to only do DoVi remux when the client can play DoVi videos without a fallback layer. --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 54e0527c90..a641ec2091 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1819,16 +1819,13 @@ public class DynamicHlsController : BaseJellyfinApiController if (isActualOutputVideoCodecHevc || isActualOutputVideoCodecAv1) { var requestedRange = state.GetRequestedRangeTypes(state.ActualOutputVideoCodec); - var requestHasDOVI = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase); - var requestHasDOVIWithHDR10 = requestedRange.Contains(VideoRangeType.DOVIWithHDR10.ToString(), StringComparison.OrdinalIgnoreCase); - var requestHasDOVIWithHLG = requestedRange.Contains(VideoRangeType.DOVIWithHLG.ToString(), StringComparison.OrdinalIgnoreCase); - var requestHasDOVIWithSDR = requestedRange.Contains(VideoRangeType.DOVIWithSDR.ToString(), StringComparison.OrdinalIgnoreCase); + // Clients reporting Dolby Vision capabilities with fallbacks may only support the fallback layer. + // Only enable Dolby Vision remuxing if the client explicitly declares support for profiles without fallbacks. + var clientSupportsDoVi = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase); + var videoIsDoVi = state.VideoStream.VideoRangeType is VideoRangeType.DOVI or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG or VideoRangeType.DOVIWithSDR; if (EncodingHelper.IsCopyCodec(codec) - && ((state.VideoStream.VideoRangeType == VideoRangeType.DOVI && requestHasDOVI) - || (state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10 && requestHasDOVIWithHDR10) - || (state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHLG && requestHasDOVIWithHLG) - || (state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithSDR && requestHasDOVIWithSDR))) + && (videoIsDoVi && clientSupportsDoVi)) { if (isActualOutputVideoCodecHevc) { From e7ac3e3929739b35f92ade87dfdb351e8b6a7302 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 2 Dec 2024 01:57:37 +0800 Subject: [PATCH 47/66] Fix missing ConfigureAwait (#13139) Regression from #12940 --- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index c5689550d4..46ddccff8a 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -262,7 +262,7 @@ namespace MediaBrowser.Providers.Manager try { var fileStream = AsyncFile.OpenRead(source); - await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); + await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken).ConfigureAwait(false); } finally { From 65f722f23c781d6346741589a681b8b00579fc8a Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 2 Dec 2024 08:08:28 +0800 Subject: [PATCH 48/66] Fallback to lossy audio codec for bitrate limit (#13127) --- Jellyfin.Api/Helpers/StreamingHelpers.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 3a5db2f3fb..74ba423e43 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -235,6 +235,11 @@ public static class StreamingHelpers state.VideoRequest.MaxHeight = resolution.MaxHeight; } } + + if (!EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(state.AudioStream.Codec, state.OutputAudioCodec, StringComparison.OrdinalIgnoreCase) && state.OutputAudioBitrate.HasValue) + { + state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Contains(c)).FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec); + } } var ext = string.IsNullOrWhiteSpace(state.OutputContainer) From 8e248c7c0555411d321bc7a3a4c9044ab9a302aa Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 3 Dec 2024 22:39:27 +0800 Subject: [PATCH 49/66] Enable software tone-mapping by default Transcoding HDR video without tonemapping results in an unacceptable viewing experience. Many users are not even aware of the option and therefore we should always enable the software tonemapx filter. Signed-off-by: nyanmisaka --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9399679a4f..6bd8e96c91 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -309,7 +309,6 @@ namespace MediaBrowser.Controller.MediaEncoding private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) { if (state.VideoStream is null - || !options.EnableTonemapping || GetVideoColorBitDepth(state) < 10 || !_mediaEncoder.SupportsFilter("tonemapx")) { From cd4519c15f1debc5109ab2de970b5b7f042468b0 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 7 Dec 2024 01:40:41 +0800 Subject: [PATCH 50/66] Check if the video has an audio track before fallback This would break transcoding for videos without an audio track as the codec checking would be null referencing. --- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 74ba423e43..1177879825 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -236,7 +236,7 @@ public static class StreamingHelpers } } - if (!EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(state.AudioStream.Codec, state.OutputAudioCodec, StringComparison.OrdinalIgnoreCase) && state.OutputAudioBitrate.HasValue) + if (state.AudioStream is not null && !EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(state.AudioStream.Codec, state.OutputAudioCodec, StringComparison.OrdinalIgnoreCase) && state.OutputAudioBitrate.HasValue) { state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Contains(c)).FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec); } From cf6aa126278772e12fd47d06601e97651203b748 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:48:40 +0000 Subject: [PATCH 51/66] Update dependency z440.atl.core to 6.9.0 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 85404c5e06..b9e8730201 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -80,7 +80,7 @@ - + From d49bb1d86da60fd9dc658f7554fc1aaa958f9c71 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 8 Dec 2024 10:56:05 +0800 Subject: [PATCH 52/66] Don't fall back to ffprobe results for multi-value audio tags --- MediaBrowser.Providers/MediaInfo/AudioFileProber.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 7f1fdbcb85..46fdb6eed9 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -179,7 +179,7 @@ namespace MediaBrowser.Providers.MediaInfo if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast)) { var people = new List(); - var albumArtists = string.IsNullOrEmpty(track.AlbumArtist) ? mediaInfo.AlbumArtists : track.AlbumArtist.Split(InternalValueSeparator); + var albumArtists = string.IsNullOrEmpty(track.AlbumArtist) ? [] : track.AlbumArtist.Split(InternalValueSeparator); if (libraryOptions.UseCustomTagDelimiters) { @@ -210,7 +210,7 @@ namespace MediaBrowser.Providers.MediaInfo if (performers is null || performers.Length == 0) { - performers = string.IsNullOrEmpty(track.Artist) ? mediaInfo.Artists : track.Artist.Split(InternalValueSeparator); + performers = string.IsNullOrEmpty(track.Artist) ? [] : track.Artist.Split(InternalValueSeparator); } if (libraryOptions.UseCustomTagDelimiters) @@ -314,7 +314,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!audio.LockedFields.Contains(MetadataField.Genres)) { - var genres = string.IsNullOrEmpty(track.Genre) ? mediaInfo.Genres : track.Genre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + var genres = string.IsNullOrEmpty(track.Genre) ? [] : track.Genre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); if (libraryOptions.UseCustomTagDelimiters) { From 2a96b8b34bb7edc94559972686dac568fd6253d1 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 8 Dec 2024 22:06:11 +0800 Subject: [PATCH 53/66] Properly check LAN IP in HasRemoteAccess We cannot simply use the subnet list to check if the IP is in LAN as it does not handle special cases like IPv4MappedToIPv6 and IPv6 loopback addresses. --- src/Jellyfin.Networking/Manager/NetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index 5a13cc4173..0df138fa64 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -702,7 +702,7 @@ public class NetworkManager : INetworkManager, IDisposable return false; } } - else if (!_lanSubnets.Any(x => x.Contains(remoteIP))) + else if (!IsInLocalNetwork(remoteIP)) { // Remote not enabled. So everyone should be LAN. return false; From 03ea56627120ac9f02cf6cbb1005f149cf031d9f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 8 Dec 2024 19:39:41 +0100 Subject: [PATCH 54/66] Fix possible infinite loops in incomplete MKV files https://github.com/OlegZee/NEbml/pull/14 Fixes #13122 --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 85404c5e06..831b85c449 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -51,7 +51,7 @@ - + @@ -88,4 +88,4 @@ - \ No newline at end of file + From 2c4c1d054db35c73ca74ccc063346af2a464d0e4 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 21 Dec 2024 21:54:03 +0800 Subject: [PATCH 55/66] Don't use custom params on ultrafast x265 preset Our custom parameters are slower than the ultrafast preset, but users would expect encoding to be as fast as possible when selecting ultrafast. Only apply those parameters to superfast and slower presets. --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9399679a4f..61693e33c0 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2056,11 +2056,12 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0"; } - if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase) && encodingOptions.EncoderPreset < EncoderPreset.ultrafast) { // libx265 only accept level option in -x265-params. // level option may cause libx265 to fail. // libx265 cannot adjust the given level, just throw an error. + // The following params are slower than the ultrafast preset, don't use when ultrafast is selected. param += " -x265-params:0 subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:rskip-edge-threshold=2:no-sao=1:no-strong-intra-smoothing=1:no-scenecut=1:no-open-gop=1:no-info=1"; } From 45c4bedbc6f24771c63eef567425a2a5ed0091fc Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 21 Dec 2024 22:09:56 +0800 Subject: [PATCH 56/66] Always apply necessary params --- .../MediaEncoding/EncodingHelper.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 61693e33c0..17b408c467 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2056,13 +2056,18 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0"; } - if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase) && encodingOptions.EncoderPreset < EncoderPreset.ultrafast) + if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { // libx265 only accept level option in -x265-params. // level option may cause libx265 to fail. // libx265 cannot adjust the given level, just throw an error. - // The following params are slower than the ultrafast preset, don't use when ultrafast is selected. - param += " -x265-params:0 subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:rskip-edge-threshold=2:no-sao=1:no-strong-intra-smoothing=1:no-scenecut=1:no-open-gop=1:no-info=1"; + param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1"; + + if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast) + { + // The following params are slower than the ultrafast preset, don't use when ultrafast is selected. + param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:rskip-edge-threshold=2:no-sao=1:no-strong-intra-smoothing=1"; + } } if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase) From 0ecaa98ee71637b575960cbd33da434b75ee4e6c Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 24 Dec 2024 18:24:36 +0800 Subject: [PATCH 57/66] Backport ATL update 6.11 to 10.10 This fixed long duration (> 1hr) LRC formatting --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index e497cf6fb0..eb676f7f98 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -80,7 +80,7 @@ - + From b9881b8bdf650a39cbf8f0f98d9a970266fec90a Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Tue, 31 Dec 2024 17:04:22 +0100 Subject: [PATCH 58/66] Fix EPG image caching (#13227) --- src/Jellyfin.LiveTv/Guide/GuideManager.cs | 191 ++++++++++-------- .../Listings/SchedulesDirect.cs | 31 ++- 2 files changed, 124 insertions(+), 98 deletions(-) diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs index f657422a04..05d2ae41de 100644 --- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs +++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities.Libraries; using Jellyfin.Data.Enums; using Jellyfin.Extensions; using Jellyfin.LiveTv.Configuration; @@ -39,6 +40,11 @@ public class GuideManager : IGuideManager private readonly IRecordingsManager _recordingsManager; private readonly LiveTvDtoService _tvDtoService; + /// + /// Amount of days images are pre-cached from external sources. + /// + public const int MaxCacheDays = 2; + /// /// Initializes a new instance of the class. /// @@ -204,14 +210,14 @@ public class GuideManager : IGuideManager progress.Report(15); numComplete = 0; - var programs = new List(); + var programs = new List(); var channels = new List(); var guideDays = GetGuideDays(); - _logger.LogInformation("Refreshing guide with {0} days of guide data", guideDays); + _logger.LogInformation("Refreshing guide with {Days} days of guide data", guideDays); - var maxCacheDate = DateTime.UtcNow.AddDays(2); + var maxCacheDate = DateTime.UtcNow.AddDays(MaxCacheDays); foreach (var currentChannel in list) { cancellationToken.ThrowIfCancellationRequested(); @@ -237,22 +243,23 @@ public class GuideManager : IGuideManager DtoOptions = new DtoOptions(true) }).Cast().ToDictionary(i => i.Id); - var newPrograms = new List(); - var updatedPrograms = new List(); + var newPrograms = new List(); + var updatedPrograms = new List(); foreach (var program in channelPrograms) { var (programItem, isNew, isUpdated) = GetProgram(program, existingPrograms, currentChannel); + var id = programItem.Id; if (isNew) { - newPrograms.Add(programItem); + newPrograms.Add(id); } else if (isUpdated) { - updatedPrograms.Add(programItem); + updatedPrograms.Add(id); } - programs.Add(programItem.Id); + programs.Add(programItem); isMovie |= program.IsMovie; isSeries |= program.IsSeries; @@ -261,24 +268,30 @@ public class GuideManager : IGuideManager isKids |= program.IsKids; } - _logger.LogDebug("Channel {0} has {1} new programs and {2} updated programs", currentChannel.Name, newPrograms.Count, updatedPrograms.Count); + _logger.LogDebug( + "Channel {Name} has {NewCount} new programs and {UpdatedCount} updated programs", + currentChannel.Name, + newPrograms.Count, + updatedPrograms.Count); if (newPrograms.Count > 0) { - _libraryManager.CreateItems(newPrograms, null, cancellationToken); - await PrecacheImages(newPrograms, maxCacheDate).ConfigureAwait(false); + var newProgramDtos = programs.Where(b => newPrograms.Contains(b.Id)).ToList(); + _libraryManager.CreateItems(newProgramDtos, null, cancellationToken); } if (updatedPrograms.Count > 0) { + var updatedProgramDtos = programs.Where(b => updatedPrograms.Contains(b.Id)).ToList(); await _libraryManager.UpdateItemsAsync( - updatedPrograms, + updatedProgramDtos, currentChannel, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); - await PrecacheImages(updatedPrograms, maxCacheDate).ConfigureAwait(false); } + await PreCacheImages(programs, maxCacheDate).ConfigureAwait(false); + currentChannel.IsMovie = isMovie; currentChannel.IsNews = isNews; currentChannel.IsSports = isSports; @@ -313,7 +326,8 @@ public class GuideManager : IGuideManager } progress.Report(100); - return new Tuple, List>(channels, programs); + var programIds = programs.Select(p => p.Id).ToList(); + return new Tuple, List>(channels, programIds); } private void CleanDatabase(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress progress, CancellationToken cancellationToken) @@ -618,77 +632,17 @@ public class GuideManager : IGuideManager 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); - } - } + forceUpdate = forceUpdate || UpdateImages(item, info); - if (!item.HasImage(ImageType.Thumb)) + if (isNew) { - if (!string.IsNullOrWhiteSpace(info.ThumbImageUrl)) - { - item.SetImage( - new ItemImageInfo - { - Path = info.ThumbImageUrl, - Type = ImageType.Thumb - }, - 0); - } - } + item.OnMetadataChanged(); - 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); - } + return (item, isNew, false); } var isUpdated = false; - if (isNew) - { - } - else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag)) + if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag)) { isUpdated = true; } @@ -703,7 +657,7 @@ public class GuideManager : IGuideManager } } - if (isNew || isUpdated) + if (isUpdated) { item.OnMetadataChanged(); } @@ -711,7 +665,80 @@ public class GuideManager : IGuideManager return (item, isNew, isUpdated); } - private async Task PrecacheImages(IReadOnlyList programs, DateTime maxCacheDate) + private static bool UpdateImages(BaseItem item, ProgramInfo info) + { + var updated = false; + + // Primary + updated |= UpdateImage(ImageType.Primary, item, info); + + // Thumbnail + updated |= UpdateImage(ImageType.Thumb, item, info); + + // Logo + updated |= UpdateImage(ImageType.Logo, item, info); + + // Backdrop + return updated || UpdateImage(ImageType.Backdrop, item, info); + } + + private static bool UpdateImage(ImageType imageType, BaseItem item, ProgramInfo info) + { + var image = item.GetImages(imageType).FirstOrDefault(); + var currentImagePath = image?.Path; + var newImagePath = imageType switch + { + ImageType.Primary => info.ImagePath, + _ => string.Empty + }; + var newImageUrl = imageType switch + { + ImageType.Backdrop => info.BackdropImageUrl, + ImageType.Logo => info.LogoImageUrl, + ImageType.Primary => info.ImageUrl, + ImageType.Thumb => info.ThumbImageUrl, + _ => string.Empty + }; + + var differentImage = newImageUrl?.Equals(currentImagePath, StringComparison.OrdinalIgnoreCase) == false + || newImagePath?.Equals(currentImagePath, StringComparison.OrdinalIgnoreCase) == false; + if (!differentImage) + { + return false; + } + + if (!string.IsNullOrWhiteSpace(newImagePath)) + { + item.SetImage( + new ItemImageInfo + { + Path = newImagePath, + Type = imageType + }, + 0); + + return true; + } + + if (!string.IsNullOrWhiteSpace(newImageUrl)) + { + item.SetImage( + new ItemImageInfo + { + Path = newImageUrl, + Type = imageType + }, + 0); + + return true; + } + + item.RemoveImage(image); + + return false; + } + + private async Task PreCacheImages(IReadOnlyList programs, DateTime maxCacheDate) { await Parallel.ForEachAsync( programs @@ -741,7 +768,7 @@ public class GuideManager : IGuideManager } catch (Exception ex) { - _logger.LogWarning(ex, "Unable to precache {Url}", imageInfo.Path); + _logger.LogWarning(ex, "Unable to pre-cache {Url}", imageInfo.Path); } } } diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs index c7a57859e8..d6f15906ef 100644 --- a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs +++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs @@ -19,6 +19,7 @@ using System.Threading.Tasks; using AsyncKeyedLock; using Jellyfin.Extensions; using Jellyfin.Extensions.Json; +using Jellyfin.LiveTv.Guide; using Jellyfin.LiveTv.Listings.SchedulesDirectDtos; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; @@ -38,7 +39,7 @@ namespace Jellyfin.LiveTv.Listings private readonly IHttpClientFactory _httpClientFactory; private readonly AsyncNonKeyedLocker _tokenLock = new(1); - private readonly ConcurrentDictionary _tokens = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _tokens = new(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private DateTime _lastErrorResponse; private bool _disposed = false; @@ -86,7 +87,7 @@ namespace Jellyfin.LiveTv.Listings { _logger.LogWarning("SchedulesDirect token is empty, returning empty program list"); - return Enumerable.Empty(); + return []; } var dates = GetScheduleRequestDates(startDateUtc, endDateUtc); @@ -94,7 +95,7 @@ namespace Jellyfin.LiveTv.Listings _logger.LogInformation("Channel Station ID is: {ChannelID}", channelId); var requestList = new List() { - new RequestScheduleForChannelDto() + new() { StationId = channelId, Date = dates @@ -109,7 +110,7 @@ namespace Jellyfin.LiveTv.Listings var dailySchedules = await Request>(options, true, info, cancellationToken).ConfigureAwait(false); if (dailySchedules is null) { - return Array.Empty(); + return []; } _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); @@ -120,17 +121,17 @@ namespace Jellyfin.LiveTv.Listings var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions); - var programDetails = await Request>(programRequestOptions, true, info, cancellationToken) - .ConfigureAwait(false); + var programDetails = await Request>(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); if (programDetails is null) { - return Array.Empty(); + return []; } var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y); var programIdsWithImages = programDetails - .Where(p => p.HasImageArtwork).Select(p => p.ProgramId) + .Where(p => p.HasImageArtwork) + .Select(p => p.ProgramId) .ToList(); var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); @@ -138,17 +139,15 @@ namespace Jellyfin.LiveTv.Listings var programsInfo = new List(); foreach (ProgramDto schedule in dailySchedules.SelectMany(d => d.Programs)) { - // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID + - // " which corresponds to channel " + channelNumber + " and program id " + - // schedule.ProgramId + " which says it has images? " + - // programDict[schedule.ProgramId].hasImageArtwork); - if (string.IsNullOrEmpty(schedule.ProgramId)) { continue; } - if (images is not null) + // Only add images which will be pre-cached until we can implement dynamic token fetching + var endDate = schedule.AirDateTime?.AddSeconds(schedule.Duration); + var willBeCached = endDate.HasValue && endDate.Value < DateTime.UtcNow.AddDays(GuideManager.MaxCacheDays); + if (willBeCached && images is not null) { var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]); if (imageIndex > -1) @@ -456,7 +455,7 @@ namespace Jellyfin.LiveTv.Listings if (programIds.Count == 0) { - return Array.Empty(); + return []; } StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13)); @@ -483,7 +482,7 @@ namespace Jellyfin.LiveTv.Listings { _logger.LogError(ex, "Error getting image info from schedules direct"); - return Array.Empty(); + return []; } } From f0e9b2fb96b35dab4c6881e5059ef23f6c3c86c7 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Tue, 31 Dec 2024 17:06:45 +0100 Subject: [PATCH 59/66] Fix NFO ID parsing (#13167) --- .../Parsers/MovieNfoParser.cs | 15 ++++++--------- .../Parsers/SeriesNfoParser.cs | 15 ++++++++++----- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index 2d65188b63..d51cf6dce8 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -50,23 +50,20 @@ namespace MediaBrowser.XbmcMetadata.Parsers { case "id": { - // get ids from attributes + // Get ids from attributes + item.TrySetProviderId(MetadataProvider.Tmdb, reader.GetAttribute("TMDB")); + item.TrySetProviderId(MetadataProvider.Tvdb, reader.GetAttribute("TVDB")); string? imdbId = reader.GetAttribute("IMDB"); - string? tmdbId = reader.GetAttribute("TMDB"); - // read id from content + // Read id from content + // Content can be arbitrary according to Kodi wiki, so only parse if we are sure it matches a provider-specific schema var contentId = reader.ReadElementContentAsString(); - if (contentId.Contains("tt", StringComparison.Ordinal) && string.IsNullOrEmpty(imdbId)) + if (string.IsNullOrEmpty(imdbId) && contentId.StartsWith("tt", StringComparison.Ordinal)) { imdbId = contentId; } - else if (string.IsNullOrEmpty(tmdbId)) - { - tmdbId = contentId; - } item.TrySetProviderId(MetadataProvider.Imdb, imdbId); - item.TrySetProviderId(MetadataProvider.Tmdb, tmdbId); break; } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index 59abef919e..b0944515bf 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -1,3 +1,4 @@ +using System; using System.Globalization; using System.Xml; using Emby.Naming.TV; @@ -48,16 +49,20 @@ namespace MediaBrowser.XbmcMetadata.Parsers { case "id": { - item.TrySetProviderId(MetadataProvider.Imdb, reader.GetAttribute("IMDB")); + // Get ids from attributes item.TrySetProviderId(MetadataProvider.Tmdb, reader.GetAttribute("TMDB")); + item.TrySetProviderId(MetadataProvider.Tvdb, reader.GetAttribute("TVDB")); + string? imdbId = reader.GetAttribute("IMDB"); - string? tvdbId = reader.GetAttribute("TVDB"); - if (string.IsNullOrWhiteSpace(tvdbId)) + // Read id from content + // Content can be arbitrary according to Kodi wiki, so only parse if we are sure it matches a provider-specific schema + var contentId = reader.ReadElementContentAsString(); + if (string.IsNullOrEmpty(imdbId) && contentId.StartsWith("tt", StringComparison.Ordinal)) { - tvdbId = reader.ReadElementContentAsString(); + imdbId = contentId; } - item.TrySetProviderId(MetadataProvider.Tvdb, tvdbId); + item.TrySetProviderId(MetadataProvider.Imdb, imdbId); break; } From 4e28f4fe03467f35285a021d7fbab27c83c0cc41 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Tue, 31 Dec 2024 17:09:42 +0100 Subject: [PATCH 60/66] Fix missing episode removal (#13218) --- .../TV/SeriesMetadataService.cs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index f4aede463e..284415dce6 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -140,38 +140,39 @@ namespace MediaBrowser.Providers.TV private void RemoveObsoleteEpisodes(Series series) { - var episodes = series.GetEpisodes(null, new DtoOptions(), true).OfType().ToList(); - var numberOfEpisodes = episodes.Count; - // TODO: O(n^2), but can it be done faster without overcomplicating it? - for (var i = 0; i < numberOfEpisodes; i++) + var episodesBySeason = series.GetEpisodes(null, new DtoOptions(), true) + .OfType() + .GroupBy(e => e.ParentIndexNumber) + .ToList(); + + foreach (var seasonEpisodes in episodesBySeason) { - var currentEpisode = episodes[i]; - // The outer loop only examines virtual episodes - if (!currentEpisode.IsVirtualItem) + List nonPhysicalEpisodes = []; + List physicalEpisodes = []; + foreach (var episode in seasonEpisodes) { - continue; + if (episode.IsVirtualItem || episode.IsMissingEpisode) + { + nonPhysicalEpisodes.Add(episode); + continue; + } + + physicalEpisodes.Add(episode); } - // Virtual episodes without an episode number are practically orphaned and should be deleted - if (!currentEpisode.IndexNumber.HasValue) + // Only consider non-physical episodes + foreach (var episode in nonPhysicalEpisodes) { - DeleteEpisode(currentEpisode); - continue; - } + // Episodes without an episode number are practically orphaned and should be deleted + // Episodes with a physical equivalent should be deleted (they are no longer missing) + var shouldKeep = episode.IndexNumber.HasValue && !physicalEpisodes.Any(e => e.ContainsEpisodeNumber(episode.IndexNumber.Value)); - for (var j = i + 1; j < numberOfEpisodes; j++) - { - var comparisonEpisode = episodes[j]; - // The inner loop is only for "physical" episodes - if (comparisonEpisode.IsVirtualItem - || currentEpisode.ParentIndexNumber != comparisonEpisode.ParentIndexNumber - || !comparisonEpisode.ContainsEpisodeNumber(currentEpisode.IndexNumber.Value)) + if (shouldKeep) { continue; } - DeleteEpisode(currentEpisode); - break; + DeleteEpisode(episode); } } } From cea0c9594220778658c30acf640108c26191706e Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Tue, 31 Dec 2024 17:10:25 +0100 Subject: [PATCH 61/66] Fix DTS in HLS (#13288) --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9dc8698a02..2b425133b6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -7069,7 +7069,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // DTS and TrueHD are not supported by HLS // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used - shiftAudioCodecs.Add("dca"); + shiftAudioCodecs.Add("dts"); shiftAudioCodecs.Add("truehd"); } else diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 767e012029..d82391a51b 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Model.Dlna private readonly ITranscoderSupport _transcoderSupport; private static readonly string[] _supportedHlsVideoCodecs = ["h264", "hevc", "vp9", "av1"]; private static readonly string[] _supportedHlsAudioCodecsTs = ["aac", "ac3", "eac3", "mp3"]; - private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd"]; + private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dts", "truehd"]; /// /// Initializes a new instance of the class. From 8aa41d59041c792571530c514dd6d21ba22a1881 Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 1 Jan 2025 00:15:05 +0800 Subject: [PATCH 62/66] Transcode to audio codec satisfied other conditions when copy check failed. (#13209) --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 46 +++++++++++++++++-- MediaBrowser.Model/Dlna/TranscodingProfile.cs | 29 ++++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index d82391a51b..2cabb2dd3d 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -862,18 +862,37 @@ namespace MediaBrowser.Model.Dlna if (options.AllowAudioStreamCopy) { - if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec)) + // For Audio stream, we prefer the audio codec that can be directly copied, then the codec that can otherwise satisfies + // the transcoding conditions, then the one does not satisfy the transcoding conditions. + // For example: A client can support both aac and flac, but flac only supports 2 channels while aac supports 6. + // When the source audio is 6 channel flac, we should transcode to 6 channel aac, instead of down-mix to 2 channel flac. + var transcodingAudioCodecs = ContainerHelper.Split(transcodingProfile.AudioCodec); + + foreach (var transcodingAudioCodec in transcodingAudioCodecs) { var appliedVideoConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.VideoAudio && - i.ContainsAnyCodec(audioCodec, container) && + i.ContainsAnyCodec(transcodingAudioCodec, container) && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false))) .Select(i => i.Conditions.All(condition => ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false))); // An empty appliedVideoConditions means that the codec has no conditions for the current audio stream var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied); - rank.Audio = conditionsSatisfied ? 1 : 2; + + var rankAudio = 3; + + if (conditionsSatisfied) + { + rankAudio = string.Equals(transcodingAudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) ? 1 : 2; + } + + rank.Audio = Math.Min(rank.Audio, rankAudio); + + if (rank.Audio == 1) + { + break; + } } } @@ -963,9 +982,26 @@ namespace MediaBrowser.Model.Dlna var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerHelper.ContainsContainer(audioCodecs, false, stream.Codec)).FirstOrDefault(); - var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec.Channels.Value <= (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue) ? audioStreamWithSupportedCodec : null; + var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && audioStreamWithSupportedCodec.Channels > (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue); - var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && directAudioStream is null; + var directAudioStreamSatisfied = audioStreamWithSupportedCodec is not null && !channelsExceedsLimit + && options.Profile.CodecProfiles + .Where(i => i.Type == CodecType.VideoAudio + && i.ContainsAnyCodec(audioStreamWithSupportedCodec.Codec, container) + && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioStreamWithSupportedCodec.Channels, audioStreamWithSupportedCodec.BitRate, audioStreamWithSupportedCodec.SampleRate, audioStreamWithSupportedCodec.BitDepth, audioStreamWithSupportedCodec.Profile, false))) + .Select(i => i.Conditions.All(condition => + { + var satisfied = ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioStreamWithSupportedCodec.Channels, audioStreamWithSupportedCodec.BitRate, audioStreamWithSupportedCodec.SampleRate, audioStreamWithSupportedCodec.BitDepth, audioStreamWithSupportedCodec.Profile, false); + if (!satisfied) + { + playlistItem.TranscodeReasons |= GetTranscodeReasonForFailedCondition(condition); + } + + return satisfied; + })) + .All(satisfied => satisfied); + + var directAudioStream = directAudioStreamSatisfied ? audioStreamWithSupportedCodec : null; if (channelsExceedsLimit && playlistItem.TargetAudioStream is not null) { diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 5a9fa22ae4..5797d42506 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel; using System.Xml.Serialization; using Jellyfin.Data.Enums; @@ -6,6 +7,7 @@ namespace MediaBrowser.Model.Dlna; /// /// A class for transcoding profile information. +/// Note for client developers: Conditions defined in has higher priority and can override values defined here. /// public class TranscodingProfile { @@ -17,6 +19,33 @@ public class TranscodingProfile Conditions = []; } + /// + /// Initializes a new instance of the class copying the values from another instance. + /// + /// Another instance of to be copied. + public TranscodingProfile(TranscodingProfile other) + { + ArgumentNullException.ThrowIfNull(other); + + Container = other.Container; + Type = other.Type; + VideoCodec = other.VideoCodec; + AudioCodec = other.AudioCodec; + Protocol = other.Protocol; + EstimateContentLength = other.EstimateContentLength; + EnableMpegtsM2TsMode = other.EnableMpegtsM2TsMode; + TranscodeSeekInfo = other.TranscodeSeekInfo; + CopyTimestamps = other.CopyTimestamps; + Context = other.Context; + EnableSubtitlesInManifest = other.EnableSubtitlesInManifest; + MaxAudioChannels = other.MaxAudioChannels; + MinSegments = other.MinSegments; + SegmentLength = other.SegmentLength; + BreakOnNonKeyFrames = other.BreakOnNonKeyFrames; + Conditions = other.Conditions; + EnableAudioVbrEncoding = other.EnableAudioVbrEncoding; + } + /// /// Gets or sets the container. /// From 80940c0c57bc180d88b57da5b797fef949f85200 Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 1 Jan 2025 00:15:39 +0800 Subject: [PATCH 63/66] Don't generate trickplay for backdrops (#13183) --- .../Trickplay/TrickplayManager.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs index af57bc134d..b9965085fd 100644 --- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs +++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs @@ -194,6 +194,14 @@ public class TrickplayManager : ITrickplayManager return; } + // We support video backdrops, but we should not generate trickplay images for them + var parentDirectory = Directory.GetParent(mediaPath); + if (parentDirectory is not null && string.Equals(parentDirectory.Name, "backdrops", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Ignoring backdrop media found at {Path} for item {ItemID}", mediaPath, video.Id); + return; + } + // The width has to be even, otherwise a lot of filters will not be able to sample it var actualWidth = 2 * (width / 2); From 5c6317f68d6e255189ceb64e49500afd046e3a50 Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 3 Jan 2025 07:47:51 +0800 Subject: [PATCH 64/66] Use nv15 as intermediate format for 2-pass rkrga scaling (#13313) --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2b425133b6..86f35c4ea4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -5695,7 +5695,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(doScaling) && !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f)) { - var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={outFormat}:afbc=1"; + // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P010 format. + // Use NV15 instead of P010 to avoid the issue. + // SDR inputs are using BGRA formats already which is not affected. + var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? "nv15" : outFormat; + var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_divisible_by=4:afbc=1"; mainFilters.Add(hwScaleFilterFirstPass); } From cc9c0004126d59e58d4e70b39e62265603c77a1c Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 10 Jan 2025 15:24:10 +0800 Subject: [PATCH 65/66] Never treat matroska as webm for audio playback This would break browsers like Firefox where the matroska file cannot be played as audio file. --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 33 ++++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 2cabb2dd3d..d1c2fbddfa 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -2249,7 +2249,7 @@ namespace MediaBrowser.Model.Dlna } } - private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + private static bool IsAudioContainerSupported(DirectPlayProfile profile, MediaSourceInfo item) { // Check container type if (!profile.SupportsContainer(item.Container)) @@ -2257,6 +2257,20 @@ namespace MediaBrowser.Model.Dlna return false; } + // Never direct play audio in matroska when the device only declare support for webm. + // The first check is not enough because mkv is assumed can be webm. + // See https://github.com/jellyfin/jellyfin/issues/13344 + return !ContainerHelper.ContainsContainer("mkv", item.Container) + || profile.SupportsContainer("mkv"); + } + + private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + { + if (!IsAudioContainerSupported(profile, item)) + { + return false; + } + // Check audio codec string? audioCodec = audioStream?.Codec; if (!profile.SupportsAudioCodec(audioCodec)) @@ -2271,19 +2285,16 @@ namespace MediaBrowser.Model.Dlna { // Check container type, this should NOT be supported // If the container is supported, the file should be directly played - if (!profile.SupportsContainer(item.Container)) + if (IsAudioContainerSupported(profile, item)) { - // Check audio codec, we cannot use the SupportsAudioCodec here - // Because that one assumes empty container supports all codec, which is just useless - string? audioCodec = audioStream?.Codec; - if (string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) || - string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return true; - } + return false; } - return false; + // Check audio codec, we cannot use the SupportsAudioCodec here + // Because that one assumes empty container supports all codec, which is just useless + string? audioCodec = audioStream?.Codec; + return string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) + || string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase); } private int GetRank(ref TranscodeReason a, TranscodeReason[] rankings) From cf78aefbb7bf2eb5be9c79efdce240ff2ec78d71 Mon Sep 17 00:00:00 2001 From: Jellyfin Release Bot Date: Tue, 21 Jan 2025 21:20:10 -0500 Subject: [PATCH 66/66] Bump version to 10.10.4 --- Emby.Naming/Emby.Naming.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- SharedVersion.cs | 4 ++-- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 499ad41188..6d9b718904 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -36,7 +36,7 @@ Jellyfin Contributors Jellyfin.Naming - 10.10.3 + 10.10.4 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index fb8f4e1397..135dae4885 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -18,7 +18,7 @@ Jellyfin Contributors Jellyfin.Data - 10.10.3 + 10.10.4 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 507f076c25..fb2afd09c2 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Common - 10.10.3 + 10.10.4 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 1c42fc0646..742175c947 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Controller - 10.10.3 + 10.10.4 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 559562876d..71387d52cd 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Model - 10.10.3 + 10.10.4 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/SharedVersion.cs b/SharedVersion.cs index d28302a071..c27f7bae7d 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.10.3")] -[assembly: AssemblyFileVersion("10.10.3")] +[assembly: AssemblyVersion("10.10.4")] +[assembly: AssemblyFileVersion("10.10.4")] diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index 7f61e71fdc..0ac8d57e94 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -15,7 +15,7 @@ Jellyfin Contributors Jellyfin.Extensions - 10.10.3 + 10.10.4 https://github.com/jellyfin/jellyfin GPL-3.0-only