diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-02-08 17:22:52 +0100 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-02-08 17:22:52 +0100 |
| commit | 71594b4a9a1fa91fbb03e6e8f79090465619bd9c (patch) | |
| tree | bec80f2a36a1fd3840df150ee5064af015316163 /Emby.Server.Implementations | |
| parent | bb6c3b4eecee46a0a6222ffe17657cabc7da97f4 (diff) | |
Fix multiple version resolution
Diffstat (limited to 'Emby.Server.Implementations')
| -rw-r--r-- | Emby.Server.Implementations/Library/LibraryManager.cs | 79 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Localization/Core/en-US.json | 2 |
2 files changed, 64 insertions, 17 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6bb5f87d03..df259c303f 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -704,6 +704,57 @@ namespace Emby.Server.Implementations.Library public BaseItem? ResolvePath(FileSystemMetadata fileInfo, Folder? parent = null, IDirectoryService? directoryService = null, CollectionType? collectionType = null) => ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent, collectionType); + /// <inheritdoc /> + public Video? ResolveAlternateVersion(string path, Type expectedVideoType, Folder? parent, CollectionType? collectionType) + { + // Clean up any existing item saved with wrong type (e.g. Video instead of Movie). + // This happens when items were previously resolved without proper type context + // in mixed-content libraries where collectionType is null. + var expectedId = GetNewItemId(path, expectedVideoType); + if (expectedVideoType != typeof(Video)) + { + var wrongTypeId = GetNewItemId(path, typeof(Video)); + if (!wrongTypeId.Equals(expectedId) && GetItemById(wrongTypeId) is Video wrongTypeItem) + { + _logger.LogInformation( + "Removing alternate version with wrong type {WrongType}, expected {ExpectedType}: {Path}", + wrongTypeItem.GetType().Name, + expectedVideoType.Name, + path); + DeleteItem(wrongTypeItem, new DeleteOptions { DeleteFileLocation = false }); + } + } + + var resolved = ResolvePath( + _fileSystem.GetFileSystemInfo(path), + parent, + collectionType: collectionType) as Video; + + if (resolved is null) + { + return null; + } + + // Ensure the alternate version has the same concrete type as the primary video. + // ResolvePath may return a generic Video for files in mixed-content libraries + // where collectionType is null, even though the primary is a Movie/Episode/etc. + if (resolved.GetType() != expectedVideoType) + { + if (Activator.CreateInstance(expectedVideoType) is Video correctVideo) + { + correctVideo.Path = resolved.Path; + correctVideo.Name = resolved.Name; + correctVideo.VideoType = resolved.VideoType; + correctVideo.ProductionYear = resolved.ProductionYear; + correctVideo.ExtraType = resolved.ExtraType; + resolved = correctVideo; + } + } + + resolved.Id = expectedId; + return resolved; + } + private BaseItem? ResolvePath( FileSystemMetadata fileInfo, IDirectoryService directoryService, @@ -2125,14 +2176,8 @@ namespace Emby.Server.Implementations.Library if (GetItemById(altId) is null && !allItems.Any(i => i.Id.Equals(altId))) { // Alternate version doesn't exist, resolve and create it - // Pass parent and collectionType so the resolver creates the correct type - // (e.g. Movie instead of generic Video) - var altVideo = ResolvePath( - _fileSystem.GetFileSystemInfo(path), - new DirectoryService(_fileSystem), - null, - parentFolder, - parentCollectionType) as Video; + // ensuring it has the same type as the primary video + var altVideo = ResolveAlternateVersion(path, videoType, parentFolder, parentCollectionType); if (altVideo is not null) { altVideo.OwnerId = video.Id; @@ -2333,14 +2378,8 @@ namespace Emby.Server.Implementations.Library if (GetItemById(altId) is null && !allItems.Any(i => i.Id.Equals(altId))) { // Alternate version doesn't exist, resolve and create it - // Pass parent and collectionType so the resolver creates the correct type - // (e.g. Movie instead of generic Video) - var altVideo = ResolvePath( - _fileSystem.GetFileSystemInfo(path), - new DirectoryService(_fileSystem), - null, - parentFolder, - parentCollectionType) as Video; + // ensuring it has the same type as the primary video + var altVideo = ResolveAlternateVersion(path, videoType, parentFolder, parentCollectionType); if (altVideo is not null) { altVideo.OwnerId = video.Id; @@ -2353,6 +2392,14 @@ namespace Emby.Server.Implementations.Library _itemRepository.SaveItems(allItems, cancellationToken); + foreach (var item in allItems) + { + if (!items.Contains(item)) + { + RegisterItem(item); + } + } + if (parent is Folder folder) { folder.Children = null; diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index c40448151c..45b1cbb6a0 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -130,7 +130,7 @@ "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.", "TaskKeyframeExtractor": "Keyframe Extractor", "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.", -"TaskExtractMediaSegments": "Media Segment Scan", + "TaskExtractMediaSegments": "Media Segment Scan", "TaskExtractMediaSegmentsDescription": "Extracts or obtains media segments from MediaSegment enabled plugins.", "TaskMoveTrickplayImages": "Migrate Trickplay Image Location", "TaskMoveTrickplayImagesDescription": "Moves existing trickplay files according to the library settings.", |
