aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2026-05-15 10:19:28 +0200
committerGitHub <noreply@github.com>2026-05-15 10:19:28 +0200
commit31889c0215c694cb8779c0f680ea64c1a094bed9 (patch)
tree4ca17bc80b168515ec78043018f103b77e7ae654 /Emby.Server.Implementations/Library
parent8ec3b5c7ac989a1f62a1764e36b5a24ffa8f5a41 (diff)
parent4f81f29a90fac6036206c5ade4ae4c8cf35e5353 (diff)
Merge pull request #16828 from Shadowghost/episode-multiple-versions
Implement multiple versions for episodes.
Diffstat (limited to 'Emby.Server.Implementations/Library')
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs45
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs25
2 files changed, 61 insertions, 9 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 11f1496086..f2480679d9 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -13,6 +13,7 @@ using System.Threading.Tasks;
using BitFaster.Caching.Lru;
using Emby.Naming.Common;
using Emby.Naming.TV;
+using Emby.Naming.Video;
using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists;
@@ -787,6 +788,42 @@ namespace Emby.Server.Implementations.Library
CollectionType? collectionType = null)
=> ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent, collectionType);
+ private void SetAdditionalPartsFromStack(Video altVideo, string path)
+ {
+ if (altVideo.AdditionalParts is { Length: > 0 })
+ {
+ return;
+ }
+
+ var directory = Path.GetDirectoryName(path);
+ if (string.IsNullOrEmpty(directory))
+ {
+ return;
+ }
+
+ IEnumerable<FileSystemMetadata> siblings;
+ try
+ {
+ siblings = _fileSystem.GetFiles(directory);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Failed to enumerate siblings to detect stack for {Path}", path);
+ return;
+ }
+
+ var stacks = StackResolver.Resolve(siblings, _namingOptions);
+ foreach (var stack in stacks)
+ {
+ if (stack.Files.Count > 1
+ && string.Equals(stack.Files[0], path, StringComparison.OrdinalIgnoreCase))
+ {
+ altVideo.AdditionalParts = stack.Files.Skip(1).ToArray();
+ return;
+ }
+ }
+ }
+
/// <inheritdoc />
public Video? ResolveAlternateVersion(string path, Type expectedVideoType, Folder? parent, CollectionType? collectionType)
{
@@ -2307,6 +2344,10 @@ namespace Emby.Server.Implementations.Library
{
altVideo.OwnerId = video.Id;
altVideo.SetPrimaryVersionId(video.Id);
+ // ResolveAlternateVersion only sees the alternate's primary file.
+ // If the alternate is itself a stack (e.g. 1080p part1 + part2),
+ // detect its parts from sibling files so its AdditionalParts persist.
+ SetAdditionalPartsFromStack(altVideo, path);
allItems.Add(altVideo);
}
}
@@ -2510,6 +2551,10 @@ namespace Emby.Server.Implementations.Library
{
altVideo.OwnerId = video.Id;
altVideo.SetPrimaryVersionId(video.Id);
+ // ResolveAlternateVersion only sees the alternate's primary file.
+ // If the alternate is itself a stack (e.g. 1080p part1 + part2),
+ // detect its parts from sibling files so its AdditionalParts persist.
+ SetAdditionalPartsFromStack(altVideo, path);
allItems.Add(altVideo);
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 98e8f5350b..68b66ab7f5 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -28,15 +28,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
public partial class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
private readonly IImageProcessor _imageProcessor;
+ private readonly VideoListResolver _videoListResolver;
- private static readonly CollectionType[] _validCollectionTypes = new[]
- {
+ private static readonly CollectionType[] _validCollectionTypes =
+ [
CollectionType.movies,
CollectionType.homevideos,
CollectionType.musicvideos,
CollectionType.tvshows,
CollectionType.photos
- };
+ ];
/// <summary>
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
@@ -45,10 +46,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <param name="logger">The logger.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="directoryService">The directory service.</param>
- public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService)
+ /// <param name="videoListResolver">The video list resolver.</param>
+ public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService, VideoListResolver videoListResolver)
: base(logger, namingOptions, directoryService)
{
_imageProcessor = imageProcessor;
+ _videoListResolver = videoListResolver;
}
/// <summary>
@@ -228,7 +231,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (collectionType == CollectionType.tvshows)
{
- return ResolveVideos<Episode>(parent, files, false, collectionType, true);
+ return ResolveVideos<Episode>(parent, files, true, collectionType, true);
}
return null;
@@ -274,7 +277,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
.Where(f => f is not null)
.ToList();
- var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName, parent.ContainingFolderPath);
+ var resolverResult = _videoListResolver.Resolve(videoInfos, supportMultiEditions, parseName, parent.ContainingFolderPath, collectionType);
var result = new MultiItemResolverResult
{
@@ -302,7 +305,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
ProductionYear = video.Year,
Name = parseName ? video.Name : firstVideo.Name,
AdditionalParts = additionalParts,
- LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
+ LocalAlternateVersions = video.AlternateVersions.Select(av => av.Files[0].Path).ToArray()
};
SetVideoType(videoItem, firstVideo);
@@ -331,9 +334,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
for (var j = 0; j < current.AlternateVersions.Count; j++)
{
- if (ContainsFile(current.AlternateVersions[j], file))
+ var alternate = current.AlternateVersions[j];
+ for (var k = 0; k < alternate.Files.Count; k++)
{
- return true;
+ if (ContainsFile(alternate.Files[k], file))
+ {
+ return true;
+ }
}
}
}