< Summary - Jellyfin

Information
Class: Emby.Naming.TV.SeasonPathParser
Assembly: Emby.Naming
File(s): /srv/git/jellyfin/Emby.Naming/TV/SeasonPathParser.cs
Line coverage
100%
Covered lines: 68
Uncovered lines: 0
Coverable lines: 68
Total lines: 191
Line coverage: 100%
Branch coverage
100%
Covered branches: 46
Total branches: 46
Branch coverage: 100%
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%11100%
Parse(...)100%22100%
GetSeasonNumberFromPath(...)100%2222100%
TryGetSeasonNumberFromPart(...)100%66100%
GetSeasonNumberFromPathSubstring(...)100%1616100%

File(s)

/srv/git/jellyfin/Emby.Naming/TV/SeasonPathParser.cs

#LineLine coverage
 1using System;
 2using System.Globalization;
 3using System.IO;
 4
 5namespace Emby.Naming.TV
 6{
 7    /// <summary>
 8    /// Class to parse season paths.
 9    /// </summary>
 10    public static class SeasonPathParser
 11    {
 12        /// <summary>
 13        /// A season folder must contain one of these somewhere in the name.
 14        /// </summary>
 115        private static readonly string[] _seasonFolderNames =
 116        {
 117            "season",
 118            "sæson",
 119            "temporada",
 120            "saison",
 121            "staffel",
 122            "series",
 123            "сезон",
 124            "stagione"
 125        };
 26
 127        private static readonly char[] _splitChars = ['.', '_', ' ', '-'];
 28
 29        /// <summary>
 30        /// Attempts to parse season number from path.
 31        /// </summary>
 32        /// <param name="path">Path to season.</param>
 33        /// <param name="supportSpecialAliases">Support special aliases when parsing.</param>
 34        /// <param name="supportNumericSeasonFolders">Support numeric season folders when parsing.</param>
 35        /// <returns>Returns <see cref="SeasonPathParserResult"/> object.</returns>
 36        public static SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFol
 37        {
 2038            var result = new SeasonPathParserResult();
 39
 2040            var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeas
 41
 2042            result.SeasonNumber = seasonNumber;
 43
 2044            if (result.SeasonNumber.HasValue)
 45            {
 1746                result.Success = true;
 1747                result.IsSeasonFolder = isSeasonFolder;
 48            }
 49
 2050            return result;
 51        }
 52
 53        /// <summary>
 54        /// Gets the season number from path.
 55        /// </summary>
 56        /// <param name="path">The path.</param>
 57        /// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
 58        /// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
 59        /// <returns>System.Nullable{System.Int32}.</returns>
 60        private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
 61            string path,
 62            bool supportSpecialAliases,
 63            bool supportNumericSeasonFolders)
 64        {
 2065            string filename = Path.GetFileName(path);
 66
 2067            if (supportSpecialAliases)
 68            {
 2069                if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
 70                {
 171                    return (0, true);
 72                }
 73
 1974                if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
 75                {
 176                    return (0, true);
 77                }
 78            }
 79
 1880            if (supportNumericSeasonFolders)
 81            {
 1882                if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
 83                {
 184                    return (val, true);
 85                }
 86            }
 87
 1788            if (TryGetSeasonNumberFromPart(filename, out int seasonNumber))
 89            {
 390                return (seasonNumber, true);
 91            }
 92
 93            // Look for one of the season folder names
 13294            foreach (var name in _seasonFolderNames)
 95            {
 5796                if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
 97                {
 1098                    var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIg
 1099                    if (result.SeasonNumber.HasValue)
 100                    {
 9101                        return result;
 102                    }
 103
 104                    break;
 105                }
 106            }
 107
 5108            var parts = filename.Split(_splitChars, StringSplitOptions.RemoveEmptyEntries);
 52109            foreach (var part in parts)
 110            {
 22111                if (TryGetSeasonNumberFromPart(part, out seasonNumber))
 112                {
 2113                    return (seasonNumber, true);
 114                }
 115            }
 116
 3117            return (null, true);
 118        }
 119
 120        private static bool TryGetSeasonNumberFromPart(ReadOnlySpan<char> part, out int seasonNumber)
 121        {
 39122            seasonNumber = 0;
 39123            if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
 124            {
 20125                return false;
 126            }
 127
 19128            if (int.TryParse(part.Slice(1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
 129            {
 5130                seasonNumber = value;
 5131                return true;
 132            }
 133
 14134            return false;
 135        }
 136
 137        /// <summary>
 138        /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "St
 139        /// </summary>
 140        /// <param name="path">The path.</param>
 141        /// <returns>System.Nullable{System.Int32}.</returns>
 142        private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path
 143        {
 10144            var numericStart = -1;
 10145            var length = 0;
 146
 10147            var hasOpenParenthesis = false;
 10148            var isSeasonFolder = true;
 149
 150            // Find out where the numbers start, and then keep going until they end
 86151            for (var i = 0; i < path.Length; i++)
 152            {
 37153                if (char.IsNumber(path[i]))
 154                {
 14155                    if (!hasOpenParenthesis)
 156                    {
 13157                        if (numericStart == -1)
 158                        {
 9159                            numericStart = i;
 160                        }
 161
 13162                        length++;
 163                    }
 164                }
 23165                else if (numericStart != -1)
 166                {
 167                    // There's other stuff after the season number, e.g. episode number
 4168                    isSeasonFolder = false;
 4169                    break;
 170                }
 171
 33172                var currentChar = path[i];
 33173                if (currentChar == '(')
 174                {
 1175                    hasOpenParenthesis = true;
 176                }
 32177                else if (currentChar == ')')
 178                {
 1179                    hasOpenParenthesis = false;
 180                }
 181            }
 182
 10183            if (numericStart == -1)
 184            {
 1185                return (null, isSeasonFolder);
 186            }
 187
 9188            return (int.Parse(path.Slice(numericStart, length), provider: CultureInfo.InvariantCulture), isSeasonFolder)
 189        }
 190    }
 191}