aboutsummaryrefslogtreecommitdiff
path: root/Emby.Naming/TV/EpisodePathParser.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Naming/TV/EpisodePathParser.cs')
-rw-r--r--Emby.Naming/TV/EpisodePathParser.cs223
1 files changed, 223 insertions, 0 deletions
diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs
new file mode 100644
index 0000000000..7f8a6a70ec
--- /dev/null
+++ b/Emby.Naming/TV/EpisodePathParser.cs
@@ -0,0 +1,223 @@
+using Emby.Naming.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace Emby.Naming.TV
+{
+ public class EpisodePathParser
+ {
+ private readonly NamingOptions _options;
+
+ public EpisodePathParser(NamingOptions options)
+ {
+ _options = options;
+ }
+
+ public EpisodePathParserResult Parse(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
+ {
+ // Added to be able to use regex patterns which require a file extension.
+ // There were no failed tests without this block, but to be safe, we can keep it until
+ // the regex which require file extensions are modified so that they don't need them.
+ if (IsDirectory)
+ path += ".mp4";
+
+ EpisodePathParserResult result = null;
+
+ foreach (var expression in _options.EpisodeExpressions)
+ {
+ if (supportsAbsoluteNumbers.HasValue)
+ {
+ if (expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
+ {
+ continue;
+ }
+ }
+ if (isNamed.HasValue)
+ {
+ if (expression.IsNamed != isNamed.Value)
+ {
+ continue;
+ }
+ }
+ if (isOptimistic.HasValue)
+ {
+ if (expression.IsOptimistic != isOptimistic.Value)
+ {
+ continue;
+ }
+ }
+
+ var currentResult = Parse(path, expression);
+ if (currentResult.Success)
+ {
+ result = currentResult;
+ break;
+ }
+ }
+
+ if (result != null && fillExtendedInfo)
+ {
+ FillAdditional(path, result);
+
+ if (!string.IsNullOrEmpty(result.SeriesName))
+ {
+ result.SeriesName = result.SeriesName
+ .Trim()
+ .Trim(new[] { '_', '.', '-' })
+ .Trim();
+ }
+ }
+
+ return result ?? new EpisodePathParserResult();
+ }
+
+ private EpisodePathParserResult Parse(string name, EpisodeExpression expression)
+ {
+ var result = new EpisodePathParserResult();
+
+ // This is a hack to handle wmc naming
+ if (expression.IsByDate)
+ {
+ name = name.Replace('_', '-');
+ }
+
+ var match = expression.Regex.Match(name);
+
+ // (Full)(Season)(Episode)(Extension)
+ if (match.Success && match.Groups.Count >= 3)
+ {
+ if (expression.IsByDate)
+ {
+ DateTime date;
+ if (expression.DateTimeFormats.Length > 0)
+ {
+ if (DateTime.TryParseExact(match.Groups[0].Value,
+ expression.DateTimeFormats,
+ CultureInfo.InvariantCulture,
+ DateTimeStyles.None,
+ out date))
+ {
+ result.Year = date.Year;
+ result.Month = date.Month;
+ result.Day = date.Day;
+ result.Success = true;
+ }
+ }
+ else
+ {
+ if (DateTime.TryParse(match.Groups[0].Value, out date))
+ {
+ result.Year = date.Year;
+ result.Month = date.Month;
+ result.Day = date.Day;
+ result.Success = true;
+ }
+ }
+
+ // TODO: Only consider success if date successfully parsed?
+ result.Success = true;
+ }
+ else if (expression.IsNamed)
+ {
+ int num;
+ if (int.TryParse(match.Groups["seasonnumber"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ {
+ result.SeasonNumber = num;
+ }
+
+ if (int.TryParse(match.Groups["epnumber"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ {
+ result.EpisodeNumber = num;
+ }
+
+ Group endingNumberGroup = match.Groups["endingepnumber"];
+ if (endingNumberGroup.Success)
+ {
+ // Will only set EndingEpsiodeNumber if the captured number is not followed by additional numbers
+ // or a 'p' or 'i' as what you would get with a pixel resolution specification.
+ // It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
+ int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
+ if (nextIndex >= name.Length || "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
+ {
+ if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ {
+ result.EndingEpsiodeNumber = num;
+ }
+ }
+ }
+
+ result.SeriesName = match.Groups["seriesname"].Value;
+ result.Success = result.EpisodeNumber.HasValue;
+ }
+ else
+ {
+ int num;
+ if (int.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ {
+ result.SeasonNumber = num;
+ }
+ if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ {
+ result.EpisodeNumber = num;
+ }
+
+ result.Success = result.EpisodeNumber.HasValue;
+ }
+
+ // Invalidate match when the season is 200 through 1927 or above 2500
+ // because it is an error unless the TV show is intentionally using false season numbers.
+ // It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080.
+ if (result.SeasonNumber >= 200 && result.SeasonNumber < 1928 || result.SeasonNumber > 2500)
+ result.Success = false;
+
+ result.IsByDate = expression.IsByDate;
+ }
+
+ return result;
+ }
+
+ private void FillAdditional(string path, EpisodePathParserResult info)
+ {
+ var expressions = _options.MultipleEpisodeExpressions.ToList();
+
+ if (string.IsNullOrEmpty(info.SeriesName))
+ {
+ expressions.InsertRange(0, _options.EpisodeExpressions.Where(i => i.IsNamed));
+ }
+
+ FillAdditional(path, info, expressions);
+ }
+
+ private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable<EpisodeExpression> expressions)
+ {
+ var results = expressions
+ .Where(i => i.IsNamed)
+ .Select(i => Parse(path, i))
+ .Where(i => i.Success);
+
+ foreach (var result in results)
+ {
+ if (string.IsNullOrEmpty(info.SeriesName))
+ {
+ info.SeriesName = result.SeriesName;
+ }
+
+ if (!info.EndingEpsiodeNumber.HasValue && info.EpisodeNumber.HasValue)
+ {
+ info.EndingEpsiodeNumber = result.EndingEpsiodeNumber;
+ }
+
+ if (!string.IsNullOrEmpty(info.SeriesName))
+ {
+ if (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+}