< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Library.Resolvers.Movies.MovieResolver
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
Line coverage
7%
Covered lines: 18
Uncovered lines: 228
Coverable lines: 246
Total lines: 573
Line coverage: 7.3%
Branch coverage
4%
Covered branches: 7
Total branches: 162
Branch coverage: 4.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)100%11100%
get_Priority()100%11100%
ResolveMultiple(...)25%5.02460%
Resolve(...)7.14%1210.824212.82%
ResolveMultipleInternal(...)4.54%354.542211.76%
ResolveVideos(...)0%930300%
ContainsFile(...)0%110100%
ContainsFile(...)100%210%
SetInitialItemValues(...)100%210%
SetProviderIdsFromPath(...)0%110100%
FindMovie(...)0%930300%
GetMultiDiscMovie(...)0%7280%
IsInvalid(...)33.33%10.5650%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.IO;
 6using System.Linq;
 7using System.Text.RegularExpressions;
 8using Emby.Naming.Common;
 9using Emby.Naming.Video;
 10using Jellyfin.Data.Enums;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Controller.Drawing;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Entities.Movies;
 15using MediaBrowser.Controller.Entities.TV;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Controller.Providers;
 18using MediaBrowser.Controller.Resolvers;
 19using MediaBrowser.Model.Entities;
 20using MediaBrowser.Model.IO;
 21using Microsoft.Extensions.Logging;
 22
 23namespace Emby.Server.Implementations.Library.Resolvers.Movies
 24{
 25    /// <summary>
 26    /// Class MovieResolver.
 27    /// </summary>
 28    public partial class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
 29    {
 30        private readonly IImageProcessor _imageProcessor;
 31
 032        private static readonly CollectionType[] _validCollectionTypes = new[]
 033        {
 034            CollectionType.movies,
 035            CollectionType.homevideos,
 036            CollectionType.musicvideos,
 037            CollectionType.tvshows,
 038            CollectionType.photos
 039        };
 40
 41        /// <summary>
 42        /// Initializes a new instance of the <see cref="MovieResolver"/> class.
 43        /// </summary>
 44        /// <param name="imageProcessor">The image processor.</param>
 45        /// <param name="logger">The logger.</param>
 46        /// <param name="namingOptions">The naming options.</param>
 47        /// <param name="directoryService">The directory service.</param>
 48        public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions,
 2349            : base(logger, namingOptions, directoryService)
 50        {
 2351            _imageProcessor = imageProcessor;
 2352        }
 53
 54        /// <summary>
 55        /// Gets the priority.
 56        /// </summary>
 57        /// <value>The priority.</value>
 2258        public override ResolverPriority Priority => ResolverPriority.Fourth;
 59
 60        [GeneratedRegex(@"\bsample\b", RegexOptions.IgnoreCase)]
 61        private static partial Regex IsIgnoredRegex();
 62
 63        /// <inheritdoc />
 64        public MultiItemResolverResult ResolveMultiple(
 65            Folder parent,
 66            List<FileSystemMetadata> files,
 67            CollectionType? collectionType,
 68            IDirectoryService directoryService)
 69        {
 6170            var result = ResolveMultipleInternal(parent, files, collectionType);
 71
 6172            if (result is not null)
 73            {
 074                foreach (var item in result.Items)
 75                {
 076                    SetInitialItemValues((Video)item, null);
 77                }
 78            }
 79
 6180            return result;
 81        }
 82
 83        /// <summary>
 84        /// Resolves the specified args.
 85        /// </summary>
 86        /// <param name="args">The args.</param>
 87        /// <returns>Video.</returns>
 88        protected override Video Resolve(ItemResolveArgs args)
 89        {
 490            var collectionType = args.GetCollectionType();
 91
 92            // Find movies with their own folders
 493            if (args.IsDirectory)
 94            {
 095                if (IsInvalid(args.Parent, collectionType))
 96                {
 097                    return null;
 98                }
 99
 0100                Video movie = null;
 0101                var files = args.GetActualFileSystemChildren().ToList();
 102
 0103                if (collectionType == CollectionType.musicvideos)
 104                {
 0105                    movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType,
 106                }
 107
 0108                if (collectionType == CollectionType.homevideos)
 109                {
 0110                    movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, fals
 111                }
 112
 0113                if (collectionType is null)
 114                {
 115                    // Owned items will be caught by the video extra resolver
 0116                    if (args.Parent is null)
 117                    {
 0118                        return null;
 119                    }
 120
 0121                    if (args.HasParent<Series>())
 122                    {
 0123                        return null;
 124                    }
 125
 0126                    movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true
 127                }
 128
 0129                if (collectionType == CollectionType.movies)
 130                {
 0131                    movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true
 132                }
 133
 134                // ignore extras
 0135                return movie?.ExtraType is null ? movie : null;
 136            }
 137
 4138            if (args.Parent is null)
 139            {
 1140                return base.Resolve(args);
 141            }
 142
 3143            if (IsInvalid(args.Parent, collectionType))
 144            {
 3145                return null;
 146            }
 147
 0148            Video item = null;
 149
 0150            if (collectionType == CollectionType.musicvideos)
 151            {
 0152                item = ResolveVideo<MusicVideo>(args, false);
 153            }
 154
 155            // To find a movie file, the collection type must be movies or boxsets
 0156            else if (collectionType == CollectionType.movies)
 157            {
 0158                item = ResolveVideo<Movie>(args, true);
 159            }
 0160            else if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos)
 161            {
 0162                item = ResolveVideo<Video>(args, false);
 163            }
 0164            else if (collectionType is null)
 165            {
 0166                if (args.HasParent<Series>())
 167                {
 0168                    return null;
 169                }
 170
 0171                item = ResolveVideo<Video>(args, false);
 172            }
 173
 174            // Ignore extras
 0175            if (item?.ExtraType is not null)
 176            {
 0177                return null;
 178            }
 179
 0180            if (item is not null)
 181            {
 0182                item.IsInMixedFolder = true;
 183            }
 184
 0185            return item;
 186        }
 187
 188        private MultiItemResolverResult ResolveMultipleInternal(
 189            Folder parent,
 190            List<FileSystemMetadata> files,
 191            CollectionType? collectionType)
 192        {
 61193            if (IsInvalid(parent, collectionType))
 194            {
 61195                return null;
 196            }
 197
 0198            if (collectionType is CollectionType.musicvideos)
 199            {
 0200                return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false);
 201            }
 202
 0203            if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos)
 204            {
 0205                return ResolveVideos<Video>(parent, files, false, collectionType, false);
 206            }
 207
 0208            if (collectionType is null)
 209            {
 210                // Owned items should just use the plain video type
 0211                if (parent is null)
 212                {
 0213                    return ResolveVideos<Video>(parent, files, false, collectionType, false);
 214                }
 215
 0216                if (parent is Series || parent.GetParents().OfType<Series>().Any())
 217                {
 0218                    return null;
 219                }
 220
 0221                return ResolveVideos<Movie>(parent, files, false, collectionType, true);
 222            }
 223
 0224            if (collectionType == CollectionType.movies)
 225            {
 0226                return ResolveVideos<Movie>(parent, files, true, collectionType, true);
 227            }
 228
 0229            if (collectionType == CollectionType.tvshows)
 230            {
 0231                return ResolveVideos<Episode>(parent, files, false, collectionType, true);
 232            }
 233
 0234            return null;
 235        }
 236
 237        private MultiItemResolverResult ResolveVideos<T>(
 238            Folder parent,
 239            IEnumerable<FileSystemMetadata> fileSystemEntries,
 240            bool supportMultiEditions,
 241            CollectionType? collectionType,
 242            bool parseName)
 243            where T : Video, new()
 244        {
 0245            var files = new List<FileSystemMetadata>();
 0246            var leftOver = new List<FileSystemMetadata>();
 0247            var hasCollectionType = collectionType is not null;
 248
 249            // Loop through each child file/folder and see if we find a video
 0250            foreach (var child in fileSystemEntries)
 251            {
 252                // This is a hack but currently no better way to resolve a sometimes ambiguous situation
 0253                if (!hasCollectionType)
 254                {
 0255                    if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
 0256                        || string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
 257                    {
 0258                        return null;
 259                    }
 260                }
 261
 0262                if (child.IsDirectory)
 263                {
 0264                    leftOver.Add(child);
 265                }
 0266                else if (!IsIgnoredRegex().IsMatch(child.Name))
 267                {
 0268                    files.Add(child);
 269                }
 270            }
 271
 0272            var videoInfos = files
 0273                .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName))
 0274                .Where(f => f is not null)
 0275                .ToList();
 276
 0277            var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName);
 278
 0279            var result = new MultiItemResolverResult
 0280            {
 0281                ExtraFiles = leftOver
 0282            };
 283
 0284            var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true;
 285
 0286            foreach (var video in resolverResult)
 287            {
 0288                var firstVideo = video.Files[0];
 0289                var path = firstVideo.Path;
 0290                if (video.ExtraType is not null)
 291                {
 0292                    result.ExtraFiles.Add(files.Find(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnore
 0293                    continue;
 294                }
 295
 0296                var additionalParts = video.Files.Count > 1 ? video.Files.Skip(1).Select(i => i.Path).ToArray() : Array.
 297
 0298                var videoItem = new T
 0299                {
 0300                    Path = path,
 0301                    IsInMixedFolder = isInMixedFolder,
 0302                    ProductionYear = video.Year,
 0303                    Name = parseName ? video.Name : firstVideo.Name,
 0304                    AdditionalParts = additionalParts,
 0305                    LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
 0306                };
 307
 0308                SetVideoType(videoItem, firstVideo);
 0309                Set3DFormat(videoItem, firstVideo);
 310
 0311                result.Items.Add(videoItem);
 312            }
 313
 0314            result.ExtraFiles.AddRange(files.Where(i => !ContainsFile(resolverResult, i)));
 315
 0316            return result;
 0317        }
 318
 319        private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file)
 320        {
 0321            for (var i = 0; i < result.Count; i++)
 322            {
 0323                var current = result[i];
 0324                for (var j = 0; j < current.Files.Count; j++)
 325                {
 0326                    if (ContainsFile(current.Files[j], file))
 327                    {
 0328                        return true;
 329                    }
 330                }
 331
 0332                for (var j = 0; j < current.AlternateVersions.Count; j++)
 333                {
 0334                    if (ContainsFile(current.AlternateVersions[j], file))
 335                    {
 0336                        return true;
 337                    }
 338                }
 339            }
 340
 0341            return false;
 342        }
 343
 344        private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
 345        {
 0346            return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
 347        }
 348
 349        /// <summary>
 350        /// Sets the initial item values.
 351        /// </summary>
 352        /// <param name="item">The item.</param>
 353        /// <param name="args">The args.</param>
 354        protected override void SetInitialItemValues(Video item, ItemResolveArgs args)
 355        {
 0356            base.SetInitialItemValues(item, args);
 357
 0358            SetProviderIdsFromPath(item);
 0359        }
 360
 361        /// <summary>
 362        /// Sets the provider id from path.
 363        /// </summary>
 364        /// <param name="item">The item.</param>
 365        private static void SetProviderIdsFromPath(Video item)
 366        {
 0367            if (item is Movie || item is MusicVideo)
 368            {
 369                // We need to only look at the name of this actual item (not parents)
 0370                var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.Conta
 371
 0372                if (!justName.IsEmpty)
 373                {
 374                    // Check for TMDb id
 0375                    var tmdbid = justName.GetAttributeValue("tmdbid");
 0376                    item.TrySetProviderId(MetadataProvider.Tmdb, tmdbid);
 377                }
 378
 0379                if (!string.IsNullOrEmpty(item.Path))
 380                {
 381                    // Check for IMDb id - we use full media path, as we can assume that this will match in any use case
 0382                    var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid");
 0383                    item.TrySetProviderId(MetadataProvider.Imdb, imdbid);
 384                }
 385            }
 0386        }
 387
 388        /// <summary>
 389        /// Finds a movie based on a child file system entries.
 390        /// </summary>
 391        /// <returns>Movie.</returns>
 392        private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntr
 393            where T : Video, new()
 394        {
 0395            var multiDiscFolders = new List<FileSystemMetadata>();
 396
 0397            var libraryOptions = args.LibraryOptions;
 0398            var supportPhotos = collectionType == CollectionType.homevideos && libraryOptions.EnablePhotos;
 0399            var photos = new List<FileSystemMetadata>();
 400
 401            // Search for a folder rip
 0402            foreach (var child in fileSystemEntries)
 403            {
 0404                var filename = child.Name;
 405
 0406                if (child.IsDirectory)
 407                {
 0408                    if (IsDvdDirectory(child.FullName, filename, directoryService))
 409                    {
 0410                        var movie = new T
 0411                        {
 0412                            Path = path,
 0413                            VideoType = VideoType.Dvd
 0414                        };
 0415                        Set3DFormat(movie);
 0416                        return movie;
 417                    }
 418
 0419                    if (IsBluRayDirectory(filename))
 420                    {
 0421                        var movie = new T
 0422                        {
 0423                            Path = path,
 0424                            VideoType = VideoType.BluRay
 0425                        };
 0426                        Set3DFormat(movie);
 0427                        return movie;
 428                    }
 429
 0430                    multiDiscFolders.Add(child);
 431                }
 0432                else if (IsDvdFile(filename))
 433                {
 0434                    var movie = new T
 0435                    {
 0436                        Path = path,
 0437                        VideoType = VideoType.Dvd
 0438                    };
 0439                    Set3DFormat(movie);
 0440                    return movie;
 441                }
 0442                else if (supportPhotos && PhotoResolver.IsImageFile(child.FullName, _imageProcessor))
 443                {
 0444                    photos.Add(child);
 445                }
 446            }
 447
 448            // TODO: Allow GetMultiDiscMovie in here
 449            const bool SupportsMultiVersion = true;
 450
 0451            var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
 0452                new MultiItemResolverResult();
 453
 0454            var isPhotosCollection = collectionType == CollectionType.homevideos || collectionType == CollectionType.pho
 0455            if (!isPhotosCollection && result.Items.Count == 1)
 456            {
 0457                var videoPath = result.Items[0].Path;
 0458                var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name));
 459
 0460                if (!hasPhotos)
 461                {
 0462                    var movie = (T)result.Items[0];
 0463                    movie.IsInMixedFolder = false;
 0464                    movie.Name = Path.GetFileName(movie.ContainingFolderPath);
 0465                    return movie;
 466                }
 467            }
 0468            else if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
 469            {
 0470                return GetMultiDiscMovie<T>(multiDiscFolders, directoryService);
 471            }
 472
 0473            return null;
 0474        }
 475
 476        /// <summary>
 477        /// Gets the multi disc movie.
 478        /// </summary>
 479        /// <param name="multiDiscFolders">The folders.</param>
 480        /// <param name="directoryService">The directory service.</param>
 481        /// <returns>``0.</returns>
 482        private T GetMultiDiscMovie<T>(List<FileSystemMetadata> multiDiscFolders, IDirectoryService directoryService)
 483               where T : Video, new()
 484        {
 0485            var videoTypes = new List<VideoType>();
 486
 0487            var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i =>
 0488            {
 0489                var subFileEntries = directoryService.GetFileSystemEntries(i);
 0490
 0491                var subfolders = subFileEntries
 0492                    .Where(e => e.IsDirectory)
 0493                    .ToList();
 0494
 0495                if (subfolders.Any(s => IsDvdDirectory(s.FullName, s.Name, directoryService)))
 0496                {
 0497                    videoTypes.Add(VideoType.Dvd);
 0498                    return true;
 0499                }
 0500
 0501                if (subfolders.Any(s => IsBluRayDirectory(s.Name)))
 0502                {
 0503                    videoTypes.Add(VideoType.BluRay);
 0504                    return true;
 0505                }
 0506
 0507                var subFiles = subFileEntries
 0508                 .Where(e => !e.IsDirectory)
 0509                 .Select(d => d.Name);
 0510
 0511                if (subFiles.Any(IsDvdFile))
 0512                {
 0513                    videoTypes.Add(VideoType.Dvd);
 0514                    return true;
 0515                }
 0516
 0517                return false;
 0518            }).Order().ToList();
 519
 520            // If different video types were found, don't allow this
 0521            if (videoTypes.Distinct().Count() > 1)
 522            {
 0523                return null;
 524            }
 525
 0526            if (folderPaths.Count == 0)
 527            {
 0528                return null;
 529            }
 530
 0531            var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList();
 532
 0533            if (result.Count != 1)
 534            {
 0535                return null;
 536            }
 537
 0538            int additionalPartsLen = folderPaths.Count - 1;
 0539            var additionalParts = new string[additionalPartsLen];
 0540            folderPaths.CopyTo(1, additionalParts, 0, additionalPartsLen);
 541
 0542            var returnVideo = new T
 0543            {
 0544                Path = folderPaths[0],
 0545                AdditionalParts = additionalParts,
 0546                VideoType = videoTypes[0],
 0547                Name = result[0].Name
 0548            };
 549
 0550            SetIsoType(returnVideo);
 551
 0552            return returnVideo;
 553        }
 554
 555        private bool IsInvalid(Folder parent, CollectionType? collectionType)
 556        {
 64557            if (parent is not null)
 558            {
 64559                if (parent.IsRoot)
 560                {
 64561                    return true;
 562                }
 563            }
 564
 0565            if (collectionType is null)
 566            {
 0567                return false;
 568            }
 569
 0570            return !_validCollectionTypes.Contains(collectionType.Value);
 571        }
 572    }
 573}

Methods/Properties

.cctor()
.ctor(MediaBrowser.Controller.Drawing.IImageProcessor,Microsoft.Extensions.Logging.ILogger`1<Emby.Server.Implementations.Library.Resolvers.Movies.MovieResolver>,Emby.Naming.Common.NamingOptions,MediaBrowser.Controller.Providers.IDirectoryService)
get_Priority()
ResolveMultiple(MediaBrowser.Controller.Entities.Folder,System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.Nullable`1<Jellyfin.Data.Enums.CollectionType>,MediaBrowser.Controller.Providers.IDirectoryService)
Resolve(MediaBrowser.Controller.Library.ItemResolveArgs)
ResolveMultipleInternal(MediaBrowser.Controller.Entities.Folder,System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.Nullable`1<Jellyfin.Data.Enums.CollectionType>)
ResolveVideos(MediaBrowser.Controller.Entities.Folder,System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.Boolean,System.Nullable`1<Jellyfin.Data.Enums.CollectionType>,System.Boolean)
ContainsFile(System.Collections.Generic.IReadOnlyList`1<Emby.Naming.Video.VideoInfo>,MediaBrowser.Model.IO.FileSystemMetadata)
ContainsFile(Emby.Naming.Video.VideoFileInfo,MediaBrowser.Model.IO.FileSystemMetadata)
SetInitialItemValues(MediaBrowser.Controller.Entities.Video,MediaBrowser.Controller.Library.ItemResolveArgs)
SetProviderIdsFromPath(MediaBrowser.Controller.Entities.Video)
FindMovie(MediaBrowser.Controller.Library.ItemResolveArgs,System.String,MediaBrowser.Controller.Entities.Folder,System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,MediaBrowser.Controller.Providers.IDirectoryService,System.Nullable`1<Jellyfin.Data.Enums.CollectionType>,System.Boolean)
GetMultiDiscMovie(System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,MediaBrowser.Controller.Providers.IDirectoryService)
IsInvalid(MediaBrowser.Controller.Entities.Folder,System.Nullable`1<Jellyfin.Data.Enums.CollectionType>)