123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- using MediaBrowser.Controller.Entities;
- using MediaBrowser.Controller.Resolvers;
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.IO;
- using System.Linq;
- using System.Text.RegularExpressions;
- namespace MediaBrowser.Controller.Library
- {
- /// <summary>
- /// Class TVUtils
- /// </summary>
- public static class TVUtils
- {
- /// <summary>
- /// The TVDB API key
- /// </summary>
- public static readonly string TvdbApiKey = "B89CE93890E9419B";
- /// <summary>
- /// The banner URL
- /// </summary>
- public static readonly string BannerUrl = "http://www.thetvdb.com/banners/";
- /// <summary>
- /// A season folder must contain one of these somewhere in the name
- /// </summary>
- private static readonly string[] SeasonFolderNames = new[]
- {
- "season",
- "sæson",
- "temporada",
- "saison",
- "staffel"
- };
- /// <summary>
- /// Used to detect paths that represent episodes, need to make sure they don't also
- /// match movie titles like "2001 A Space..."
- /// Currently we limit the numbers here to 2 digits to try and avoid this
- /// </summary>
- private static readonly Regex[] EpisodeExpressions = new[]
- {
- new Regex(
- @".*\\[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\[s|S](?<seasonnumber>\d{1,2})[x,X]?[e|E](?<epnumber>\d{1,3})[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\(?<seriesname>((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3}))[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\(?<seriesname>[^\\]*)[s|S](?<seasonnumber>\d{1,2})[x|X|\.]?[e|E](?<epnumber>\d{1,3})[^\\]*$",
- RegexOptions.Compiled)
- };
- private static readonly Regex[] MultipleEpisodeExpressions = new[]
- {
- new Regex(
- @".*\\[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})([ |-]{1,3}\d{1,2}[e|E|x|X](?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})([ |-]{1,3}\d{1,2}[x|X][e|E](?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})([ |-]{0,3}[x|X|e|E](?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})([ |-]{1,3}[x|E]?[e|E]?(?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\(?<seriesname>((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3}))([ |-]{1,3}\d{1,2}[x|X|e|E](?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\(?<seriesname>((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3}))([ |-]{1,3}\d{1,2}[x|X][e|E](?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\(?<seriesname>((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3}))([ |-]{0,3}[x|X|e|E](?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\(?<seriesname>((?![s|S]?\d{1,2}[x|X]\d{1,3})[^\\])*)?([s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3}))([ |-]{1,3}[x|X]?[e|E]?(?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\(?<seriesname>[^\\]*)[s|S](?<seasonnumber>\d{1,2})[x|X|\.]?[e|E](?<epnumber>\d{1,3})([ |-]{0,3}[x|X|e|E](?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*\\(?<seriesname>[^\\]*)[s|S](?<seasonnumber>\d{1,2})[x|X|\.]?[e|E](?<epnumber>\d{1,3})([ |-]{1,3}[x|X]?[e|E]?(?<endingepnumber>\d{1,3}))+[^\\]*$",
- RegexOptions.Compiled)
- };
- /// <summary>
- /// To avoid the following matching movies they are only valid when contained in a folder which has been matched as a being season
- /// </summary>
- private static readonly Regex[] EpisodeExpressionsInASeasonFolder = new[]
- {
- new Regex(
- @".*\\(?<epnumber>\d{1,2})\s?-\s?[^\\]*$",
- RegexOptions.Compiled),
- // 01 - blah.avi, 01-blah.avi
- new Regex(
- @".*\\(?<epnumber>\d{1,2})[^\d\\]*[^\\]*$",
- RegexOptions.Compiled),
- // 01.avi, 01.blah.avi "01 - 22 blah.avi"
- new Regex(
- @".*\\(?<seasonnumber>\d)(?<epnumber>\d{1,2})[^\d\\]+[^\\]*$",
- RegexOptions.Compiled),
- // 01.avi, 01.blah.avi
- new Regex(
- @".*\\\D*\d+(?<epnumber>\d{2})",
- RegexOptions.Compiled)
- // hell0 - 101 - hello.avi
- };
- /// <summary>
- /// Gets the season number from path.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.Nullable{System.Int32}.</returns>
- public static int? GetSeasonNumberFromPath(string path)
- {
- var filename = Path.GetFileName(path);
- // Look for one of the season folder names
- foreach (var name in SeasonFolderNames)
- {
- var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- return GetSeasonNumberFromPathSubstring(path.Substring(index + name.Length));
- }
- }
- return null;
- }
- /// <summary>
- /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel")
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.Nullable{System.Int32}.</returns>
- private static int? GetSeasonNumberFromPathSubstring(string path)
- {
- int numericStart = -1;
- int length = 0;
- // Find out where the numbers start, and then keep going until they end
- for (var i = 0; i < path.Length; i++)
- {
- if (char.IsNumber(path, i))
- {
- if (numericStart == -1)
- {
- numericStart = i;
- }
- length++;
- }
- else if (numericStart != -1)
- {
- break;
- }
- }
- if (numericStart == -1)
- {
- return null;
- }
- return int.Parse(path.Substring(numericStart, length));
- }
- /// <summary>
- /// Determines whether [is season folder] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
- private static bool IsSeasonFolder(string path)
- {
- // It's a season folder if it's named as such and does not contain any audio files, apart from theme.mp3
- return GetSeasonNumberFromPath(path) != null && !new DirectoryInfo(path).EnumerateFiles().Any(i => EntityResolutionHelper.IsAudioFile(i.FullName) && !string.Equals(Path.GetFileNameWithoutExtension(i.Name), BaseItem.ThemeSongFilename));
- }
- /// <summary>
- /// Determines whether [is series folder] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="fileSystemChildren">The file system children.</param>
- /// <returns><c>true</c> if [is series folder] [the specified path]; otherwise, <c>false</c>.</returns>
- public static bool IsSeriesFolder(string path, IEnumerable<FileSystemInfo> fileSystemChildren)
- {
- // A folder with more than 3 non-season folders in will not becounted as a series
- var nonSeriesFolders = 0;
- foreach (var child in fileSystemChildren)
- {
- var attributes = child.Attributes;
- if (attributes.HasFlag(FileAttributes.Hidden) || attributes.HasFlag(FileAttributes.System))
- {
- continue;
- }
- if (attributes.HasFlag(FileAttributes.Directory))
- {
- if (IsSeasonFolder(child.FullName))
- {
- return true;
- }
- nonSeriesFolders++;
- if (nonSeriesFolders >= 3)
- {
- return false;
- }
- }
- else
- {
- if (EntityResolutionHelper.IsVideoFile(child.FullName) && GetEpisodeNumberFromFile(child.FullName, false).HasValue)
- {
- return true;
- }
- }
- }
- return false;
- }
- /// <summary>
- /// Episodes the number from file.
- /// </summary>
- /// <param name="fullPath">The full path.</param>
- /// <param name="isInSeason">if set to <c>true</c> [is in season].</param>
- /// <returns>System.String.</returns>
- public static int? GetEpisodeNumberFromFile(string fullPath, bool isInSeason)
- {
- string fl = fullPath.ToLower();
- foreach (var r in EpisodeExpressions)
- {
- Match m = r.Match(fl);
- if (m.Success)
- return ParseEpisodeNumber(m.Groups["epnumber"].Value);
- }
- if (isInSeason)
- {
- var match = EpisodeExpressionsInASeasonFolder.Select(r => r.Match(fl))
- .FirstOrDefault(m => m.Success);
- if (match != null)
- {
- return ParseEpisodeNumber(match.Value);
- }
- }
- return null;
- }
- public static int? GetEndingEpisodeNumberFromFile(string fullPath)
- {
- var fl = fullPath.ToLower();
- foreach (var r in MultipleEpisodeExpressions)
- {
- var m = r.Match(fl);
- if (m.Success && !string.IsNullOrEmpty(m.Groups["endingepnumber"].Value))
- return ParseEpisodeNumber(m.Groups["endingepnumber"].Value);
- }
- return null;
- }
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- private static int? ParseEpisodeNumber(string val)
- {
- int num;
- if (!string.IsNullOrEmpty(val) && int.TryParse(val, NumberStyles.Integer, UsCulture, out num))
- {
- return num;
- }
- return null;
- }
- /// <summary>
- /// Seasons the number from episode file.
- /// </summary>
- /// <param name="fullPath">The full path.</param>
- /// <returns>System.String.</returns>
- public static int? GetSeasonNumberFromEpisodeFile(string fullPath)
- {
- string fl = fullPath.ToLower();
- foreach (var r in EpisodeExpressions)
- {
- Match m = r.Match(fl);
- if (m.Success)
- {
- Group g = m.Groups["seasonnumber"];
- if (g != null)
- {
- var val = g.Value;
- if (!string.IsNullOrWhiteSpace(val))
- {
- int num;
- if (int.TryParse(val, NumberStyles.Integer, UsCulture, out num))
- {
- return num;
- }
- }
- }
- return null;
- }
- }
- return null;
- }
- /// <summary>
- /// Gets the air days.
- /// </summary>
- /// <param name="day">The day.</param>
- /// <returns>List{DayOfWeek}.</returns>
- public static List<DayOfWeek> GetAirDays(string day)
- {
- if (!string.IsNullOrWhiteSpace(day))
- {
- if (day.Equals("Daily", StringComparison.OrdinalIgnoreCase))
- {
- return new List<DayOfWeek>
- {
- DayOfWeek.Sunday,
- DayOfWeek.Monday,
- DayOfWeek.Tuesday,
- DayOfWeek.Wednesday,
- DayOfWeek.Thursday,
- DayOfWeek.Friday,
- DayOfWeek.Saturday
- };
- }
- DayOfWeek value;
- if (Enum.TryParse(day, true, out value))
- {
- return new List<DayOfWeek>
- {
- value
- };
- }
- return new List<DayOfWeek>();
- }
- return null;
- }
- }
- }
|