mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-03 14:28:46 +01:00
Moved TV into the main project and added Series properties to DTOBaseItem
This commit is contained in:
parent
2884df296c
commit
8b39ed2f63
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
MediaBrowser.Controller/Providers/TV/EpisodeXmlParser.cs
Normal file
56
MediaBrowser.Controller/Providers/TV/EpisodeXmlParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
MediaBrowser.Controller/Providers/TV/SeriesXmlParser.cs
Normal file
69
MediaBrowser.Controller/Providers/TV/SeriesXmlParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
21
MediaBrowser.Controller/Resolvers/TV/EpisodeResolver.cs
Normal file
21
MediaBrowser.Controller/Resolvers/TV/EpisodeResolver.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs
Normal file
35
MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
MediaBrowser.Controller/Resolvers/TV/SeriesResolver.cs
Normal file
64
MediaBrowser.Controller/Resolvers/TV/SeriesResolver.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
166
MediaBrowser.Controller/Resolvers/TV/TVUtils.cs
Normal file
166
MediaBrowser.Controller/Resolvers/TV/TVUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user