From e8761044c2303403feb18bfff4f93b9979e30068 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Sun, 17 Nov 2024 16:12:43 +0100 Subject: [PATCH 01/18] Fixed segment providers never presented to UI (#13060) --- Jellyfin.Api/Controllers/LibraryController.cs | 10 ++++++++++ .../Models/LibraryDtos/LibraryOptionsResultDto.cs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index afc93c3a8d..1b23683fb4 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -867,6 +867,16 @@ public class LibraryController : BaseJellyfinApiController .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .ToArray(); + result.MediaSegmentProviders = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MediaSegmentProvider)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = true + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + var typeOptions = new List(); foreach (var type in types) diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs index d07349bdf6..c492436689 100644 --- a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs +++ b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs @@ -28,6 +28,11 @@ public class LibraryOptionsResultDto /// public IReadOnlyList LyricFetchers { get; set; } = Array.Empty(); + /// + /// Gets or sets the list of MediaSegment Providers. + /// + public IReadOnlyList MediaSegmentProviders { get; set; } = Array.Empty(); + /// /// Gets or sets the type options. /// From 4c65e0d3976a26958b3a66c7bfb845fa77900314 Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 18 Nov 2024 00:13:01 +0900 Subject: [PATCH 02/18] make playlist creation private by default (#12853) --- MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index ec54b1afd3..98f7c6ce12 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -38,5 +38,5 @@ public class PlaylistCreationRequest /// /// Gets or sets a value indicating whether the playlist is public. /// - public bool? Public { get; set; } = true; + public bool? Public { get; set; } = false; } From d1d5ea9c80d254aba9d749f1fad5ace5b31579ab Mon Sep 17 00:00:00 2001 From: koreapyj Date: Sun, 17 Nov 2024 07:26:33 +0000 Subject: [PATCH 03/18] Translated using Weblate (Korean) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/ --- .../Localization/Core/ko.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index 13bacb2d27..efc9f61ddf 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -3,7 +3,7 @@ "AppDeviceValues": "앱: {0}, 장치: {1}", "Application": "애플리케이션", "Artists": "아티스트", - "AuthenticationSucceededWithUserName": "{0}이(가) 성공적으로 인증됨", + "AuthenticationSucceededWithUserName": "{0} 사용자가 성공적으로 인증됨", "Books": "도서", "CameraImageUploadedFrom": "{0}에서 새로운 카메라 이미지가 업로드됨", "Channels": "채널", @@ -70,7 +70,7 @@ "ScheduledTaskFailedWithName": "{0} 실패", "ScheduledTaskStartedWithName": "{0} 시작", "ServerNameNeedsToBeRestarted": "{0}를 재시작해야합니다", - "Shows": "쇼", + "Shows": "시리즈", "Songs": "노래", "StartupEmbyServerIsLoading": "Jellyfin 서버를 불러오고 있습니다. 잠시 후에 다시 시도하십시오.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", @@ -81,14 +81,14 @@ "User": "사용자", "UserCreatedWithName": "사용자 {0} 생성됨", "UserDeletedWithName": "사용자 {0} 삭제됨", - "UserDownloadingItemWithValues": "{0}이(가) {1}을 다운로드 중입니다", - "UserLockedOutWithName": "유저 {0} 은(는) 잠금처리 되었습니다", - "UserOfflineFromDevice": "{1}에서 {0}의 연결이 끊킴", - "UserOnlineFromDevice": "{0}이 {1}으로 접속", - "UserPasswordChangedWithName": "사용자 {0}의 비밀번호가 변경되었습니다", - "UserPolicyUpdatedWithName": "{0}의 사용자 정책이 업데이트되었습니다", - "UserStartedPlayingItemWithValues": "{2}에서 {0}이 {1} 재생 중", - "UserStoppedPlayingItemWithValues": "{2}에서 {0}이 {1} 재생을 마침", + "UserDownloadingItemWithValues": "{0} 사용자가 {1} 다운로드 중", + "UserLockedOutWithName": "{0} 사용자 잠김", + "UserOfflineFromDevice": "{0} 사용자의 {1}에서 연결이 끊김", + "UserOnlineFromDevice": "{0} 사용자가 {1}에서 접속함", + "UserPasswordChangedWithName": "{0} 사용자 비밀번호 변경됨", + "UserPolicyUpdatedWithName": "{0} 사용자 정책 업데이트됨", + "UserStartedPlayingItemWithValues": "{0} 사용자의 {2}에서 {1} 재생 중", + "UserStoppedPlayingItemWithValues": "{0} 사용자의 {2}에서 {1} 재생을 마침", "ValueHasBeenAddedToLibrary": "{0}가 미디어 라이브러리에 추가되었습니다", "ValueSpecialEpisodeName": "스페셜 - {0}", "VersionNumber": "버전 {0}", From 27b044493a24df6abe92c811c8e6dbdcb316b6aa Mon Sep 17 00:00:00 2001 From: JPVenson Date: Tue, 19 Nov 2024 15:43:17 -0500 Subject: [PATCH 04/18] Backport pull request #12916 from jellyfin/release-10.10.z Added query filter to disregard disabled Providers Original-merge: 38c08c4fadf9c44625c0baa49929f7e905efdf70 Merged-by: joshuaboniface Backported-by: Joshua M. Boniface --- .../Controllers/MediaSegmentsController.cs | 2 +- .../MediaSegments/MediaSegmentManager.cs | 38 +++++++++++++++++-- .../MediaSegements/IMediaSegmentManager.cs | 12 +++++- 3 files changed, 46 insertions(+), 6 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 d641f521b9..a044fec0d9 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -139,23 +139,53 @@ 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); + + if (baseItem is null) + { + _logger.LogError("Tried to request segments for an invalid item"); + return []; + } + + return await GetSegmentsAsync(baseItem, typeFilter, filterByProvider).ConfigureAwait(false); + } + + /// + 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) { query = query.Where(e => typeFilter.Contains(e.Type)); } + if (filterByProvider) + { + var libraryOptions = _libraryManager.GetLibraryOptions(item); + var providerIds = _segmentProviders + .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) + .Select(f => GetProviderId(f.Name)) + .ToArray(); + if (providerIds.Length == 0) + { + return []; + } + + query = query.Where(e => providerIds.Contains(e.SegmentProviderId)); + } + return query .OrderBy(e => e.StartTicks) .AsNoTracking() - .ToImmutableList() - .Select(Map); + .AsEnumerable() + .Select(Map) + .ToArray(); } private static MediaSegmentDto Map(MediaSegment segment) diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs index 010d7edb4f..672f27eca2 100644 --- a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs +++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs @@ -50,8 +50,18 @@ 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); + + /// + /// 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 item, IEnumerable? typeFilter, bool filterByProvider = true); /// /// Gets information about any media segments stored for the given itemId. From 6e7118eff1e6bc9c5ca70d80e5ff5e6eff7c90e5 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 19 Nov 2024 15:43:18 -0500 Subject: [PATCH 05/18] Backport pull request #12934 from jellyfin/release-10.10.z Fix playlists Original-merge: 8bee67f1f8dab604d745b3d077330085f7f111d4 Merged-by: crobibero Backported-by: Joshua M. Boniface --- .../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 4f562d67b036092891b24585527869ec8c64d7cc Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 19 Nov 2024 15:43:19 -0500 Subject: [PATCH 06/18] Backport pull request #12947 from jellyfin/release-10.10.z Add a small tolerance value to remux fps check Original-merge: 954950dc145db4edf85cc2c1e3ce068274097b71 Merged-by: crobibero Backported-by: Joshua M. Boniface --- 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 19c5c95f4e2de29ae090a992155c3d539a0ad6ec Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 19 Nov 2024 15:43:20 -0500 Subject: [PATCH 07/18] Backport pull request #12949 from jellyfin/release-10.10.z Fix json array string writer in JsonDelimitedArrayConverter Original-merge: 3089e9e40aea4bfe2b99d8b8bd5fdf1dd9d37984 Merged-by: crobibero Backported-by: Joshua M. Boniface --- .../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 cdeaf29b08..c53ef275b3 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -70,24 +70,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 882f3374eda9f80ec86d3164b4241028935a9991 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 19 Nov 2024 15:43:21 -0500 Subject: [PATCH 08/18] Backport pull request #12955 from jellyfin/release-10.10.z Fix trickplay images never being replaced Original-merge: 9c6454ec46622c32702b64cff01b859b97a9aeb4 Merged-by: joshuaboniface Backported-by: Joshua M. Boniface --- 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 ee66c745274ae0b24022ec077e39d6bb9d6d6469 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 19 Nov 2024 15:43:22 -0500 Subject: [PATCH 09/18] Backport pull request #12962 from jellyfin/release-10.10.z Always consider null char as delimiter for ID3v2 Original-merge: 97dc02b1632c3c329a181c816ff2c6dc84319732 Merged-by: crobibero Backported-by: Joshua M. Boniface --- 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 547d393af053b4dc0d9ab29c4d2da542b617e2f6 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 19 Nov 2024 15:43:23 -0500 Subject: [PATCH 10/18] Backport pull request #12964 from jellyfin/release-10.10.z Fix height of imported trickplay tiles Original-merge: 09c377fb6c50b29f7b6cf03e14ac09b4b556db38 Merged-by: joshuaboniface Backported-by: Joshua M. Boniface --- 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 87a3c5d11c9fcfa5e14598c028ac7a665906fd17 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 19 Nov 2024 15:43:24 -0500 Subject: [PATCH 11/18] Backport pull request #12973 from jellyfin/release-10.10.z Fix pixel format in HEVC RExt SDR transcoding Original-merge: aa08d3f2bf155d55f748bff1f0a0c7f071f79ae7 Merged-by: crobibero Backported-by: Joshua M. Boniface --- 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 661caa62e27c191750350c0e3397f433d5a0c7ec Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 19 Nov 2024 15:43:26 -0500 Subject: [PATCH 12/18] Backport pull request #12989 from jellyfin/release-10.10.z Fix InvariantCulture in VPP tonemap options Original-merge: 25321d7f80a3b065a8d3061a93adb78d701b7412 Merged-by: crobibero Backported-by: Joshua M. Boniface --- .../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 fbdbf77a5992235df5a1bbfff5d409f7d9b7f7e3 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 19 Nov 2024 15:43:27 -0500 Subject: [PATCH 13/18] Backport pull request #12991 from jellyfin/release-10.10.z Use invariant culture for tonemap options Original-merge: d292fde9e29609b58278e46e4edb155698b2fe1c Merged-by: crobibero Backported-by: Joshua M. Boniface --- .../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 7f296d06e694d53d40a0136cc4fa0a184abde2be Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 19 Nov 2024 15:43:28 -0500 Subject: [PATCH 14/18] Backport pull request #13003 from jellyfin/release-10.10.z Only set first MusicBrainz ID for audio tags Original-merge: e2434d38c54b90070bc4eaffa7e3c5cdd9934602 Merged-by: crobibero Backported-by: Joshua M. Boniface --- .../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 9f86f8748cc173a065b21c35641407a8cfa08bf7 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 19 Nov 2024 15:43:29 -0500 Subject: [PATCH 15/18] Backport pull request #13026 from jellyfin/release-10.10.z Fix missing procamp vaapi filter Original-merge: cf11a2dc1eec3cde51713df745934933102a2dd5 Merged-by: crobibero Backported-by: Joshua M. Boniface --- 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 7f81bbd42f0b2dbab4887f515ec6fb3c89c47745 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 19 Nov 2024 15:43:31 -0500 Subject: [PATCH 16/18] Backport pull request #13030 from jellyfin/release-10.10.z Always cleanup trickplay temp for ffmpeg failures Original-merge: 9e61a6fd729b2980832014ae42bd4f7d1f3afb69 Merged-by: crobibero Backported-by: Joshua M. Boniface --- 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 924c80a209be808366498bef46aaf1c52d52509b Mon Sep 17 00:00:00 2001 From: goknsh Date: Tue, 19 Nov 2024 15:43:32 -0500 Subject: [PATCH 17/18] Backport pull request #13033 from jellyfin/release-10.10.z Respect cancellation token/HTTP request aborts correctly in `SymlinkFollowingPhysicalFileResultExecutor` Original-merge: 293e0f5fafe6ba0c7cfc269b889cb0d4d1ada59a Merged-by: crobibero Backported-by: Joshua M. Boniface --- ...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 06c603428bceccdb793eb34cc0340e25552d6c1d Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 19 Nov 2024 15:43:33 -0500 Subject: [PATCH 18/18] Backport pull request #13059 from jellyfin/release-10.10.z Exclude file system based library playlists from migration Original-merge: 23de7e517e3b4acdefd92e731140d0fa358d3611 Merged-by: crobibero Backported-by: Joshua M. Boniface --- .../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)