< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Library.Resolvers.ExtraResolver
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
Line coverage
100%
Covered lines: 37
Uncovered lines: 0
Coverable lines: 37
Total lines: 106
Line coverage: 100%
Branch coverage
82%
Covered branches: 23
Total branches: 28
Branch coverage: 82.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/6/2026 - 12:15:23 AM Line coverage: 100% (35/35) Branch coverage: 76.9% (20/26) Total lines: 1048/3/2026 - 12:16:46 AM Line coverage: 100% (37/37) Branch coverage: 82.1% (23/28) Total lines: 106 5/6/2026 - 12:15:23 AM Line coverage: 100% (35/35) Branch coverage: 76.9% (20/26) Total lines: 1048/3/2026 - 12:16:46 AM Line coverage: 100% (37/37) Branch coverage: 82.1% (23/28) Total lines: 106

Coverage delta

Coverage delta 6 -6

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Resolve(...)100%11100%
GetResolversForExtraType(...)100%44100%
TryGetExtraTypeForOwner(...)85%2020100%
TrimFilenameDelimiters(...)50%22100%
StartsWith(...)50%22100%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs

#LineLine coverage
 1using System;
 2using System.Diagnostics.CodeAnalysis;
 3using System.IO;
 4using Emby.Naming.Common;
 5using Emby.Naming.Video;
 6using MediaBrowser.Controller.Entities;
 7using MediaBrowser.Controller.Library;
 8using MediaBrowser.Controller.Providers;
 9using MediaBrowser.Controller.Resolvers;
 10using MediaBrowser.Model.Entities;
 11using Microsoft.Extensions.Logging;
 12using static Emby.Naming.Video.ExtraRuleResolver;
 13
 14namespace Emby.Server.Implementations.Library.Resolvers
 15{
 16    /// <summary>
 17    /// Resolves a Path into a Video or Video subclass.
 18    /// </summary>
 19    internal class ExtraResolver : BaseVideoResolver<Video>
 20    {
 21        private readonly NamingOptions _namingOptions;
 22        private readonly IItemResolver[] _trailerResolvers;
 23        private readonly IItemResolver[] _videoResolvers;
 24
 25        /// <summary>
 26        /// Initializes a new instance of the <see cref="ExtraResolver"/> class.
 27        /// </summary>
 28        /// <param name="logger">The logger.</param>
 29        /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
 30        /// <param name="directoryService">The directory service.</param>
 31        public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions, IDirectoryService directoryServ
 3832            : base(logger, namingOptions, directoryService)
 33        {
 3834            _namingOptions = namingOptions;
 3835            _trailerResolvers = [new GenericVideoResolver<Trailer>(logger, namingOptions, directoryService, parseName: t
 3836            _videoResolvers = [this];
 3837        }
 38
 39        protected override Video Resolve(ItemResolveArgs args)
 40        {
 1241            return ResolveVideo<Video>(args, true);
 42        }
 43
 44        /// <summary>
 45        /// Gets the resolvers for the extra type.
 46        /// </summary>
 47        /// <param name="extraType">The extra type.</param>
 48        /// <returns>The resolvers for the extra type.</returns>
 3649        public IItemResolver[]? GetResolversForExtraType(ExtraType extraType) => extraType switch
 3650        {
 2251            ExtraType.Trailer => _trailerResolvers,
 3652            // For audio we'll have to rely on the AudioResolver, which is a "built-in"
 253            ExtraType.ThemeSong => null,
 1254            _ => _videoResolvers
 3655        };
 56
 57        public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out Extra
 58        {
 6059            var extraResult = GetExtraInfo(path, _namingOptions, libraryRoot);
 6060            if (extraResult.ExtraType is null || extraResult.Rule is null)
 61            {
 2262                extraType = null;
 2263                extraRule = null;
 2264                return false;
 65            }
 66
 3867            var cleanDateTimeResult = CleanDateTimeParser.Clean(Path.GetFileNameWithoutExtension(path), _namingOptions.C
 3868            var name = cleanDateTimeResult.Name;
 3869            var year = cleanDateTimeResult.Year;
 70
 3871            var parentDir = ownerVideoFileInfo.IsDirectory ? ownerVideoFileInfo.Path : Path.GetDirectoryName(ownerVideoF
 72
 3873            var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(ownerVideoFileInfo.FileNameWithoutExtension, _n
 3874            var trimmedVideoInfoName = TrimFilenameDelimiters(ownerVideoFileInfo.Name, _namingOptions.VideoFlagDelimiter
 3875            var trimmedExtraFileName = TrimFilenameDelimiters(name, _namingOptions.VideoFlagDelimiters);
 76
 77            // first check filenames
 3878            bool isValid = StartsWith(trimmedExtraFileName, trimmedFileNameWithoutExtension)
 3879                           || (StartsWith(trimmedExtraFileName, trimmedVideoInfoName) && year == ownerVideoFileInfo.Year
 80
 3881            if (!isValid)
 82            {
 83                // When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
 2184                var currentParentDir = extraResult.Rule?.RuleType == ExtraRuleType.DirectoryName
 2185                    ? Path.GetDirectoryName(Path.GetDirectoryName(path.AsSpan()))
 2186                    : Path.GetDirectoryName(path.AsSpan());
 87
 2188                isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringCo
 89            }
 90
 3891            extraType = extraResult.ExtraType;
 3892            extraRule = extraResult.Rule;
 3893            return isValid;
 94        }
 95
 96        private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDe
 97        {
 11498            return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
 99        }
 100
 101        private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName)
 102        {
 71103            return !baseName.IsEmpty && fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase);
 104        }
 105    }
 106}