diff options
Diffstat (limited to 'MediaBrowser.Controller')
18 files changed, 623 insertions, 116 deletions
diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 0e4f7e5fd8..f493d33002 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -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();
- }
- }
- }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 66da56480f..1ebc384d4f 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -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>
diff --git a/MediaBrowser.Controller/Providers/AudioInfoProvider.cs b/MediaBrowser.Controller/Providers/AudioInfoProvider.cs index 3b55cd4925..5c2f8dea2e 100644 --- a/MediaBrowser.Controller/Providers/AudioInfoProvider.cs +++ b/MediaBrowser.Controller/Providers/AudioInfoProvider.cs @@ -1,17 +1,15 @@ -using System;
+using MediaBrowser.Controller.FFMpeg;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
+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.Library;
-using MediaBrowser.Model.Entities;
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;
diff --git a/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 612ba4c7cb..96bc47c8d2 100644 --- a/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -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
diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index 45cd598837..3e3ec59bd9 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -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
diff --git a/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs b/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs index cdf02e90bc..59ef9e5dfe 100644 --- a/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs @@ -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
{
diff --git a/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs index b0b01404d5..ce433bfec1 100644 --- a/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs @@ -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
{
diff --git a/MediaBrowser.Controller/Providers/LocalTrailerProvider.cs b/MediaBrowser.Controller/Providers/LocalTrailerProvider.cs index ac258ee61e..0359a10b20 100644 --- a/MediaBrowser.Controller/Providers/LocalTrailerProvider.cs +++ b/MediaBrowser.Controller/Providers/LocalTrailerProvider.cs @@ -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
{
diff --git a/MediaBrowser.Controller/Providers/TV/EpisodeImageFromMediaLocationProvider.cs b/MediaBrowser.Controller/Providers/TV/EpisodeImageFromMediaLocationProvider.cs new file mode 100644 index 0000000000..10a7045192 --- /dev/null +++ b/MediaBrowser.Controller/Providers/TV/EpisodeImageFromMediaLocationProvider.cs @@ -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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs b/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs new file mode 100644 index 0000000000..fc52646dfd --- /dev/null +++ b/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs @@ -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);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/TV/EpisodeXmlParser.cs b/MediaBrowser.Controller/Providers/TV/EpisodeXmlParser.cs new file mode 100644 index 0000000000..06db12c970 --- /dev/null +++ b/MediaBrowser.Controller/Providers/TV/EpisodeXmlParser.cs @@ -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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs b/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs new file mode 100644 index 0000000000..aa1dc8aaa8 --- /dev/null +++ b/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs @@ -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"));
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/TV/SeriesXmlParser.cs b/MediaBrowser.Controller/Providers/TV/SeriesXmlParser.cs new file mode 100644 index 0000000000..8ef0ee853e --- /dev/null +++ b/MediaBrowser.Controller/Providers/TV/SeriesXmlParser.cs @@ -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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/VideoInfoProvider.cs b/MediaBrowser.Controller/Providers/VideoInfoProvider.cs index 5cd60eac08..e749165f8d 100644 --- a/MediaBrowser.Controller/Providers/VideoInfoProvider.cs +++ b/MediaBrowser.Controller/Providers/VideoInfoProvider.cs @@ -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);
- }
}
}
diff --git a/MediaBrowser.Controller/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Controller/Resolvers/TV/EpisodeResolver.cs new file mode 100644 index 0000000000..6d1261dfb0 --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/TV/EpisodeResolver.cs @@ -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;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs new file mode 100644 index 0000000000..0bb880b787 --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs @@ -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;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Controller/Resolvers/TV/SeriesResolver.cs new file mode 100644 index 0000000000..dd82b14484 --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/TV/SeriesResolver.cs @@ -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);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Resolvers/TV/TVUtils.cs b/MediaBrowser.Controller/Resolvers/TV/TVUtils.cs new file mode 100644 index 0000000000..ebfcda6028 --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/TV/TVUtils.cs @@ -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;
+ }
+ }
+}
|
