< Summary - Jellyfin

Information
Class: Emby.Naming.TV.EpisodePathParser
Assembly: Emby.Naming
File(s): /srv/git/jellyfin/Emby.Naming/TV/EpisodePathParser.cs
Line coverage
100%
Covered lines: 85
Uncovered lines: 0
Coverable lines: 85
Total lines: 242
Line coverage: 100%
Branch coverage
100%
Covered branches: 80
Total branches: 80
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
.ctor(...)100%11100%
Parse(...)100%2424100%
Parse(...)100%3838100%
FillAdditional(...)100%22100%
FillAdditional(...)100%1616100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.Linq;
 5using Emby.Naming.Common;
 6
 7namespace Emby.Naming.TV
 8{
 9    /// <summary>
 10    /// Used to parse information about episode from path.
 11    /// </summary>
 12    public class EpisodePathParser
 13    {
 14        private readonly NamingOptions _options;
 15
 16        /// <summary>
 17        /// Initializes a new instance of the <see cref="EpisodePathParser"/> class.
 18        /// </summary>
 19        /// <param name="options"><see cref="NamingOptions"/> object containing EpisodeExpressions and MultipleEpisodeEx
 20        public EpisodePathParser(NamingOptions options)
 21        {
 23322            _options = options;
 23323        }
 24
 25        /// <summary>
 26        /// Parses information about episode from path.
 27        /// </summary>
 28        /// <param name="path">Path.</param>
 29        /// <param name="isDirectory">Is path for a directory or file.</param>
 30        /// <param name="isNamed">Do we want to use IsNamed expressions.</param>
 31        /// <param name="isOptimistic">Do we want to use Optimistic expressions.</param>
 32        /// <param name="supportsAbsoluteNumbers">Do we want to use expressions supporting absolute episode numbers.</pa
 33        /// <param name="fillExtendedInfo">Should we attempt to retrieve extended information.</param>
 34        /// <returns>Returns <see cref="EpisodePathParserResult"/> object.</returns>
 35        public EpisodePathParserResult Parse(
 36            string path,
 37            bool isDirectory,
 38            bool? isNamed = null,
 39            bool? isOptimistic = null,
 40            bool? supportsAbsoluteNumbers = null,
 41            bool fillExtendedInfo = true)
 42        {
 43            // Added to be able to use regex patterns which require a file extension.
 44            // There were no failed tests without this block, but to be safe, we can keep it until
 45            // the regex which require file extensions are modified so that they don't need them.
 23346            if (isDirectory)
 47            {
 848                path += ".mp4";
 49            }
 50
 23351            EpisodePathParserResult? result = null;
 52
 413753            foreach (var expression in _options.EpisodeExpressions)
 54            {
 195155                if (supportsAbsoluteNumbers.HasValue
 195156                    && expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
 57                {
 58                    continue;
 59                }
 60
 195061                if (isNamed.HasValue && expression.IsNamed != isNamed.Value)
 62                {
 63                    continue;
 64                }
 65
 194466                if (isOptimistic.HasValue && expression.IsOptimistic != isOptimistic.Value)
 67                {
 68                    continue;
 69                }
 70
 193571                var currentResult = Parse(path, expression);
 193572                if (currentResult.Success)
 73                {
 23174                    result = currentResult;
 23175                    break;
 76                }
 77            }
 78
 23379            if (result is not null && fillExtendedInfo)
 80            {
 23181                FillAdditional(path, result);
 82
 23183                if (!string.IsNullOrEmpty(result.SeriesName))
 84                {
 13285                    result.SeriesName = result.SeriesName
 13286                        .Trim()
 13287                        .Trim('_', '.', '-')
 13288                        .Trim();
 89                }
 90            }
 91
 23392            return result ?? new EpisodePathParserResult();
 93        }
 94
 95        private static EpisodePathParserResult Parse(string name, EpisodeExpression expression)
 96        {
 660197            var result = new EpisodePathParserResult();
 98
 99            // This is a hack to handle wmc naming
 6601100            if (expression.IsByDate)
 101            {
 336102                name = name.Replace('_', '-');
 103            }
 104
 6601105            var match = expression.Regex.Match(name);
 106
 107            // (Full)(Season)(Episode)(Extension)
 6601108            if (match.Success && match.Groups.Count >= 3)
 109            {
 710110                if (expression.IsByDate)
 111                {
 112                    DateTime date;
 6113                    if (expression.DateTimeFormats.Length > 0)
 114                    {
 5115                        if (DateTime.TryParseExact(
 5116                            match.Groups[0].ValueSpan,
 5117                            expression.DateTimeFormats,
 5118                            CultureInfo.InvariantCulture,
 5119                            DateTimeStyles.None,
 5120                            out date))
 121                        {
 5122                            result.Year = date.Year;
 5123                            result.Month = date.Month;
 5124                            result.Day = date.Day;
 5125                            result.Success = true;
 126                        }
 127                    }
 1128                    else if (DateTime.TryParse(match.Groups[0].ValueSpan, out date))
 129                    {
 1130                        result.Year = date.Year;
 1131                        result.Month = date.Month;
 1132                        result.Day = date.Day;
 1133                        result.Success = true;
 134                    }
 135
 136                    // TODO: Only consider success if date successfully parsed?
 6137                    result.Success = true;
 138                }
 704139                else if (expression.IsNamed)
 140                {
 611141                    if (int.TryParse(match.Groups["seasonnumber"].ValueSpan, NumberStyles.Integer, CultureInfo.Invariant
 142                    {
 485143                        result.SeasonNumber = num;
 144                    }
 145
 611146                    if (int.TryParse(match.Groups["epnumber"].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCult
 147                    {
 582148                        result.EpisodeNumber = num;
 149                    }
 150
 611151                    var endingNumberGroup = match.Groups["endingepnumber"];
 611152                    if (endingNumberGroup.Success)
 153                    {
 154                        // Will only set EndingEpisodeNumber if the captured number is not followed by additional number
 155                        // or a 'p' or 'i' as what you would get with a pixel resolution specification.
 156                        // It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode fr
 130157                        int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
 130158                        if (nextIndex >= name.Length
 130159                            || !"0123456789iIpP".Contains(name[nextIndex], StringComparison.Ordinal))
 160                        {
 127161                            if (int.TryParse(endingNumberGroup.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCul
 162                            {
 127163                                result.EndingEpisodeNumber = num;
 164                            }
 165                        }
 166                    }
 167
 611168                    result.SeriesName = match.Groups["seriesname"].Value;
 611169                    result.Success = result.EpisodeNumber.HasValue;
 170                }
 171                else
 172                {
 93173                    if (int.TryParse(match.Groups[1].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out 
 174                    {
 91175                        result.SeasonNumber = num;
 176                    }
 177
 93178                    if (int.TryParse(match.Groups[2].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out 
 179                    {
 93180                        result.EpisodeNumber = num;
 181                    }
 182
 93183                    result.Success = result.EpisodeNumber.HasValue;
 184                }
 185
 186                // Invalidate match when the season is 200 through 1927 or above 2500
 187                // because it is an error unless the TV show is intentionally using false season numbers.
 188                // It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 e
 710189                if ((result.SeasonNumber >= 200 && result.SeasonNumber < 1928)
 710190                    || result.SeasonNumber > 2500)
 191                {
 1192                    result.Success = false;
 193                }
 194
 710195                result.IsByDate = expression.IsByDate;
 196            }
 197
 6601198            return result;
 199        }
 200
 201        private void FillAdditional(string path, EpisodePathParserResult info)
 202        {
 231203            var expressions = _options.MultipleEpisodeExpressions.Where(i => i.IsNamed).ToList();
 204
 231205            if (string.IsNullOrEmpty(info.SeriesName))
 206            {
 153207                expressions.InsertRange(0, _options.EpisodeExpressions.Where(i => i.IsNamed));
 208            }
 209
 231210            FillAdditional(path, info, expressions);
 231211        }
 212
 213        private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable<EpisodeExpression> expression
 214        {
 9747215            foreach (var i in expressions)
 216            {
 4666217                var result = Parse(path, i);
 218
 4666219                if (!result.Success)
 220                {
 221                    continue;
 222                }
 223
 449224                if (string.IsNullOrEmpty(info.SeriesName))
 225                {
 369226                    info.SeriesName = result.SeriesName;
 227                }
 228
 449229                if (!info.EndingEpisodeNumber.HasValue && info.EpisodeNumber.HasValue)
 230                {
 392231                    info.EndingEpisodeNumber = result.EndingEpisodeNumber;
 232                }
 233
 449234                if (!string.IsNullOrEmpty(info.SeriesName)
 449235                    && (!info.EpisodeNumber.HasValue || info.EndingEpisodeNumber.HasValue))
 236                {
 42237                    break;
 238                }
 239            }
 231240        }
 241    }
 242}