Moved TV into the main project and added Series properties to DTOBaseItem

This commit is contained in:
LukePulverenti Luke Pulverenti luke pulverenti
2012-09-08 15:05:18 -04:00
parent 2884df296c
commit 8b39ed2f63
31 changed files with 126 additions and 341 deletions

View File

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Weather;
using MediaBrowser.Model.Authentication;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Entities.TV;
using MediaBrowser.Model.Progress;
using System;
using System.Collections.Generic;
@@ -111,12 +112,6 @@ namespace MediaBrowser.Controller
// Sort the providers by priority
MetadataProviders = MetadataProvidersEnumerable.OrderBy(e => e.Priority).ToArray();
// Initialize the metadata providers
Parallel.ForEach(MetadataProviders, provider =>
{
provider.Init();
});
}
/// <summary>
@@ -126,17 +121,26 @@ namespace MediaBrowser.Controller
/// </summary>
void ItemController_PreBeginResolvePath(object sender, PreBeginResolveEventArgs e)
{
// Ignore hidden files and folders
if (e.IsHidden || e.IsSystemFile)
{
// Ignore hidden files and folders
e.Cancel = true;
}
// Ignore any folders named "trailers"
else if (Path.GetFileName(e.Path).Equals("trailers", StringComparison.OrdinalIgnoreCase))
{
// Ignore any folders named "trailers"
e.Cancel = true;
}
// Don't try and resolve files within the season metadata folder
else if (Path.GetFileName(e.Path).Equals("metadata", StringComparison.OrdinalIgnoreCase) && e.IsDirectory)
{
if (e.Parent is Season || e.Parent is Series)
{
e.Cancel = true;
}
}
}
/// <summary>
@@ -383,26 +387,5 @@ namespace MediaBrowser.Controller
}
}
}
protected override void DisposeComposableParts()
{
base.DisposeComposableParts();
DisposeProviders();
}
/// <summary>
/// Disposes all providers
/// </summary>
private void DisposeProviders()
{
if (MetadataProviders != null)
{
foreach (var provider in MetadataProviders)
{
provider.Dispose();
}
}
}
}
}

View File

@@ -59,8 +59,17 @@
<ItemGroup>
<Compile Include="Providers\Movies\MovieProviderFromXml.cs" />
<Compile Include="Providers\Movies\MovieSpecialFeaturesProvider.cs" />
<Compile Include="Providers\TV\EpisodeImageFromMediaLocationProvider.cs" />
<Compile Include="Providers\TV\EpisodeProviderFromXml.cs" />
<Compile Include="Providers\TV\EpisodeXmlParser.cs" />
<Compile Include="Providers\TV\SeriesProviderFromXml.cs" />
<Compile Include="Providers\TV\SeriesXmlParser.cs" />
<Compile Include="Resolvers\Movies\BoxSetResolver.cs" />
<Compile Include="Resolvers\Movies\MovieResolver.cs" />
<Compile Include="Resolvers\TV\EpisodeResolver.cs" />
<Compile Include="Resolvers\TV\SeasonResolver.cs" />
<Compile Include="Resolvers\TV\SeriesResolver.cs" />
<Compile Include="Resolvers\TV\TVUtils.cs" />
<Compile Include="ServerApplicationPaths.cs" />
<Compile Include="Library\ItemResolveEventArgs.cs" />
<Compile Include="FFMpeg\FFProbe.cs" />
@@ -82,7 +91,7 @@
<Compile Include="Resolvers\FolderResolver.cs" />
<Compile Include="Resolvers\VideoResolver.cs" />
<Compile Include="Weather\WeatherClient.cs" />
<Compile Include="Xml\BaseItemXmlParser.cs" />
<Compile Include="Providers\BaseItemXmlParser.cs" />
<Compile Include="Xml\XmlExtensions.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,17 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common.Logging;
using MediaBrowser.Controller.FFMpeg;
using MediaBrowser.Controller.FFMpeg;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Providers
{
//[Export(typeof(BaseMetadataProvider))]
[Export(typeof(BaseMetadataProvider))]
public class AudioInfoProvider : BaseMediaInfoProvider<Audio>
{
public override MetadataProviderPriority Priority
@@ -161,7 +159,7 @@ namespace MediaBrowser.Controller.Providers
{
await Task.Run(() =>
{
T myItem = item as T;
/*T myItem = item as T;
if (CanSkipFFProbe(myItem))
{
@@ -192,43 +190,12 @@ namespace MediaBrowser.Controller.Providers
}
}
Fetch(myItem, result);
Fetch(myItem, result);*/
});
}
protected abstract void Fetch(T item, FFProbeResult result);
public override void Init()
{
base.Init();
EnsureCacheSubFolders(CacheDirectory);
}
private void EnsureCacheSubFolders(string root)
{
// Do this now so that we don't have to do this on every operation, which would require us to create a lock in order to maintain thread-safety
for (int i = 0; i <= 9; i++)
{
EnsureDirectory(Path.Combine(root, i.ToString()));
}
EnsureDirectory(Path.Combine(root, "a"));
EnsureDirectory(Path.Combine(root, "b"));
EnsureDirectory(Path.Combine(root, "c"));
EnsureDirectory(Path.Combine(root, "d"));
EnsureDirectory(Path.Combine(root, "e"));
EnsureDirectory(Path.Combine(root, "f"));
}
private void EnsureDirectory(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
protected virtual bool CanSkipFFProbe(T item)
{
return false;

View File

@@ -1,10 +1,11 @@
using System;
using MediaBrowser.Controller.Xml;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Xml
namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// Provides a base class for parsing metadata xml

View File

@@ -1,26 +1,11 @@
using System;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Providers
{
public abstract class BaseMetadataProvider : IDisposable
public abstract class BaseMetadataProvider
{
/// <summary>
/// If the provider needs any startup routines, add them here
/// </summary>
public virtual void Init()
{
}
/// <summary>
/// Disposes anything created during Init
/// </summary>
public virtual void Dispose()
{
}
public abstract bool Supports(BaseEntity item);
public virtual bool RequiresInternet

View File

@@ -1,9 +1,8 @@
using System.ComponentModel.Composition;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Xml;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{

View File

@@ -1,10 +1,10 @@
using System;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{

View File

@@ -0,0 +1,67 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Entities.TV;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Providers.TV
{
[Export(typeof(BaseMetadataProvider))]
public class EpisodeImageFromMediaLocationProvider : BaseMetadataProvider
{
public override bool Supports(BaseEntity item)
{
return item is Episode;
}
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
public override Task FetchAsync(BaseEntity item, ItemResolveEventArgs args)
{
return Task.Run(() =>
{
Episode episode = item as Episode;
string metadataFolder = Path.Combine(args.Parent.Path, "metadata");
string episodeFileName = Path.GetFileName(episode.Path);
Season season = args.Parent as Season;
SetPrimaryImagePath(episode, season, metadataFolder, episodeFileName);
});
}
private void SetPrimaryImagePath(Episode item, Season season, string metadataFolder, string episodeFileName)
{
// Look for the image file in the metadata folder, and if found, set PrimaryImagePath
string[] imageFiles = new string[] {
Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, ".jpg")),
Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, ".png"))
};
string image;
if (season == null)
{
// Epsiode directly in Series folder. Gotta do this the slow way
image = imageFiles.FirstOrDefault(f => File.Exists(f));
}
else
{
image = imageFiles.FirstOrDefault(f => season.ContainsMetadataFile(f));
}
// If we found something, set PrimaryImagePath
if (!string.IsNullOrEmpty(image))
{
item.PrimaryImagePath = image;
}
}
}
}

View File

@@ -0,0 +1,59 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Entities.TV;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Providers.TV
{
[Export(typeof(BaseMetadataProvider))]
public class EpisodeProviderFromXml : BaseMetadataProvider
{
public override bool Supports(BaseEntity item)
{
return item is Episode;
}
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
public override async Task FetchAsync(BaseEntity item, ItemResolveEventArgs args)
{
await Task.Run(() => { Fetch(item, args); }).ConfigureAwait(false);
}
private void Fetch(BaseEntity item, ItemResolveEventArgs args)
{
string metadataFolder = Path.Combine(args.Parent.Path, "metadata");
string metadataFile = Path.Combine(metadataFolder, Path.ChangeExtension(Path.GetFileName(args.Path), ".xml"));
FetchMetadata(item as Episode, args.Parent as Season, metadataFile);
}
private void FetchMetadata(Episode item, Season season, string metadataFile)
{
if (season == null)
{
// Episode directly in Series folder
// Need to validate it the slow way
if (!File.Exists(metadataFile))
{
return;
}
}
else
{
if (!season.ContainsMetadataFile(metadataFile))
{
return;
}
}
new EpisodeXmlParser().Fetch(item, metadataFile);
}
}
}

View File

@@ -0,0 +1,56 @@
using MediaBrowser.Model.Entities.TV;
using System.IO;
using System.Xml;
namespace MediaBrowser.Controller.Providers.TV
{
public class EpisodeXmlParser : BaseItemXmlParser<Episode>
{
protected override void FetchDataFromXmlNode(XmlReader reader, Episode item)
{
switch (reader.Name)
{
case "filename":
{
string filename = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(filename))
{
string seasonFolder = Path.GetDirectoryName(item.Path);
item.PrimaryImagePath = Path.Combine(seasonFolder, "metadata", filename);
}
break;
}
case "SeasonNumber":
{
string number = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(number))
{
item.ParentIndexNumber = int.Parse(number);
}
break;
}
case "EpisodeNumber":
{
string number = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(number))
{
item.IndexNumber = int.Parse(number);
}
break;
}
case "EpisodeName":
item.Name = reader.ReadElementContentAsString();
break;
default:
base.FetchDataFromXmlNode(reader, item);
break;
}
}
}
}

View File

@@ -0,0 +1,36 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Entities.TV;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Providers.TV
{
[Export(typeof(BaseMetadataProvider))]
public class SeriesProviderFromXml : BaseMetadataProvider
{
public override bool Supports(BaseEntity item)
{
return item is Series;
}
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
public override async Task FetchAsync(BaseEntity item, ItemResolveEventArgs args)
{
await Task.Run(() => { Fetch(item, args); }).ConfigureAwait(false);
}
private void Fetch(BaseEntity item, ItemResolveEventArgs args)
{
if (args.ContainsFile("series.xml"))
{
new SeriesXmlParser().Fetch(item as Series, Path.Combine(args.Path, "series.xml"));
}
}
}
}

View File

@@ -0,0 +1,69 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Entities.TV;
using System;
using System.Xml;
namespace MediaBrowser.Controller.Providers.TV
{
public class SeriesXmlParser : BaseItemXmlParser<Series>
{
protected override void FetchDataFromXmlNode(XmlReader reader, Series item)
{
switch (reader.Name)
{
case "id":
string id = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(id))
{
item.SetProviderId(MetadataProviders.Tvdb, id);
}
break;
case "Airs_DayOfWeek":
{
string day = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(day))
{
if (day.Equals("Daily", StringComparison.OrdinalIgnoreCase))
{
item.AirDays = new DayOfWeek[] {
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
}
else
{
item.AirDays = new DayOfWeek[] {
(DayOfWeek)Enum.Parse(typeof(DayOfWeek), day, true)
};
}
}
break;
}
case "Airs_Time":
item.AirTime = reader.ReadElementContentAsString();
break;
case "SeriesName":
item.Name = reader.ReadElementContentAsString();
break;
case "Status":
item.Status = reader.ReadElementContentAsString();
break;
default:
base.FetchDataFromXmlNode(reader, item);
break;
}
}
}
}

View File

@@ -1,13 +1,13 @@
using System;
using MediaBrowser.Controller.FFMpeg;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using MediaBrowser.Controller.FFMpeg;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
//[Export(typeof(BaseMetadataProvider))]
[Export(typeof(BaseMetadataProvider))]
public class VideoInfoProvider : BaseMediaInfoProvider<Video>
{
public override MetadataProviderPriority Priority
@@ -163,15 +163,5 @@ namespace MediaBrowser.Controller.Providers
return true;
}
public override void Init()
{
base.Init();
// This is an optimzation. Do this now so that it doesn't have to be done upon first serialization.
ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(FFProbeResult), true);
ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(MediaStream), true);
ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(MediaFormat), true);
}
}
}

View File

@@ -0,0 +1,21 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities.TV;
using System.ComponentModel.Composition;
namespace MediaBrowser.Controller.Resolvers.TV
{
[Export(typeof(IBaseItemResolver))]
public class EpisodeResolver : BaseVideoResolver<Episode>
{
protected override Episode Resolve(ItemResolveEventArgs args)
{
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
if (args.Parent is Season || args.Parent is Series)
{
return base.Resolve(args);
}
return null;
}
}
}

View File

@@ -0,0 +1,35 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities.TV;
using System.ComponentModel.Composition;
using System.IO;
namespace MediaBrowser.Controller.Resolvers.TV
{
[Export(typeof(IBaseItemResolver))]
public class SeasonResolver : BaseFolderResolver<Season>
{
protected override Season Resolve(ItemResolveEventArgs args)
{
if (args.Parent is Series && args.IsDirectory)
{
Season season = new Season();
season.IndexNumber = TVUtils.GetSeasonNumberFromPath(args.Path);
// Gather these now so that the episode provider classes can utilize them instead of having to make their own file system calls
if (args.ContainsFolder("metadata"))
{
season.MetadataFiles = Directory.GetFiles(Path.Combine(args.Path, "metadata"), "*", SearchOption.TopDirectoryOnly);
}
else
{
season.MetadataFiles = new string[] { };
}
return season;
}
return null;
}
}
}

View File

@@ -0,0 +1,64 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Entities.TV;
using System;
using System.ComponentModel.Composition;
using System.IO;
namespace MediaBrowser.Controller.Resolvers.TV
{
[Export(typeof(IBaseItemResolver))]
public class SeriesResolver : BaseFolderResolver<Series>
{
protected override Series Resolve(ItemResolveEventArgs args)
{
if (args.IsDirectory)
{
// Optimization to avoid running all these tests against VF's
if (args.Parent != null && args.Parent.IsRoot)
{
return null;
}
// Optimization to avoid running these tests against Seasons
if (args.Parent is Series)
{
return null;
}
// It's a Series if any of the following conditions are met:
// series.xml exists
// [tvdbid= is present in the path
// TVUtils.IsSeriesFolder returns true
if (args.ContainsFile("series.xml") || Path.GetFileName(args.Path).IndexOf("[tvdbid=", StringComparison.OrdinalIgnoreCase) != -1 || TVUtils.IsSeriesFolder(args.Path, args.FileSystemChildren))
{
return new Series();
}
}
return null;
}
protected override void SetInitialItemValues(Series item, ItemResolveEventArgs args)
{
base.SetInitialItemValues(item, args);
SetProviderIdFromPath(item);
}
private void SetProviderIdFromPath(Series item)
{
string srch = "[tvdbid=";
int index = item.Path.IndexOf(srch, System.StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
string id = item.Path.Substring(index + srch.Length);
id = id.Substring(0, id.IndexOf(']'));
item.SetProviderId(MetadataProviders.Tvdb, id);
}
}
}
}

View File

@@ -0,0 +1,166 @@
using MediaBrowser.Controller.IO;
using System;
using System.Text.RegularExpressions;
namespace MediaBrowser.Controller.Resolvers.TV
{
public static class TVUtils
{
/// <summary>
/// A season folder must contain one of these somewhere in the name
/// </summary>
private static string[] SeasonFolderNames = new string[] {
"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>
/// <remarks>
/// The order here is important, if the order is changed some of the later
/// ones might incorrectly match things that higher ones would have caught.
/// The most restrictive expressions should appear first
/// </remarks>
private static readonly Regex[] episodeExpressions = new Regex[] {
new Regex(@".*\\[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})[^\\]*$", RegexOptions.Compiled), // 01x02 blah.avi S01x01 balh.avi
new Regex(@".*\\[s|S](?<seasonnumber>\d{1,2})x?[e|E](?<epnumber>\d{1,3})[^\\]*$", RegexOptions.Compiled), // S01E02 blah.avi, S01xE01 blah.avi
new Regex(@".*\\(?<seriesname>[^\\]*)[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})[^\\]*$", RegexOptions.Compiled), // 01x02 blah.avi S01x01 balh.avi
new Regex(@".*\\(?<seriesname>[^\\]*)[s|S](?<seasonnumber>\d{1,2})[x|X|\.]?[e|E](?<epnumber>\d{1,3})[^\\]*$", RegexOptions.Compiled) // S01E02 blah.avi, S01xE01 blah.avi
};
/// <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 Regex[] {
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
};
public static int? GetSeasonNumberFromPath(string path)
{
// Look for one of the season folder names
foreach (string name in SeasonFolderNames)
{
int index = path.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>
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 (int 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));
}
public static bool IsSeasonFolder(string path)
{
return GetSeasonNumberFromPath(path) != null;
}
public static bool IsSeriesFolder(string path, WIN32_FIND_DATA[] fileSystemChildren)
{
// A folder with more than 3 non-season folders in will not becounted as a series
int nonSeriesFolders = 0;
for (int i = 0; i < fileSystemChildren.Length; i++)
{
var child = fileSystemChildren[i];
if (child.IsHidden || child.IsSystemFile)
{
continue;
}
if (child.IsDirectory)
{
if (IsSeasonFolder(child.Path))
{
return true;
}
else
{
nonSeriesFolders++;
if (nonSeriesFolders >= 3)
{
return false;
}
}
}
else
{
if (!string.IsNullOrEmpty(EpisodeNumberFromFile(child.Path, false)))
{
return true;
}
}
}
return false;
}
public static string EpisodeNumberFromFile(string fullPath, bool isInSeason)
{
string fl = fullPath.ToLower();
foreach (Regex r in episodeExpressions)
{
Match m = r.Match(fl);
if (m.Success)
return m.Groups["epnumber"].Value;
}
if (isInSeason)
{
foreach (Regex r in episodeExpressionsInASeasonFolder)
{
Match m = r.Match(fl);
if (m.Success)
return m.Groups["epnumber"].Value;
}
}
return null;
}
}
}