aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers')
-rw-r--r--MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs43
-rw-r--r--MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs59
-rw-r--r--MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Games/GameMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Games/GameSystemMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs53
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs8
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj13
-rw-r--r--MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs104
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs22
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs113
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs103
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs587
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs12
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs276
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbProvider.cs5
-rw-r--r--MediaBrowser.Providers/Movies/MovieProviderFromXml.cs2
-rw-r--r--MediaBrowser.Providers/Movies/MovieXmlParser.cs20
-rw-r--r--MediaBrowser.Providers/Movies/MovieXmlProvider.cs78
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs5
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoMetadataService.cs53
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs60
-rw-r--r--MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs1
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbProvider.cs7
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs28
-rw-r--r--MediaBrowser.Providers/ProviderUtils.cs20
-rw-r--r--MediaBrowser.Providers/Studios/StudioMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs163
-rw-r--r--MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs99
-rw-r--r--MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs59
-rw-r--r--MediaBrowser.Providers/TV/EpisodeMetadataService.cs137
-rw-r--r--MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs103
-rw-r--r--MediaBrowser.Providers/TV/EpisodeXmlParser.cs26
-rw-r--r--MediaBrowser.Providers/TV/EpisodeXmlProvider.cs62
-rw-r--r--MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs34
-rw-r--r--MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs225
-rw-r--r--MediaBrowser.Providers/Users/UserMetadataService.cs2
40 files changed, 1606 insertions, 990 deletions
diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs
new file mode 100644
index 000000000..23f370974
--- /dev/null
+++ b/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs
@@ -0,0 +1,43 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.AdultVideos
+{
+ class AdultVideoMetadataService : MetadataService<AdultVideo, ItemId>
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public AdultVideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+ : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ /// <summary>
+ /// Merges the specified source.
+ /// </summary>
+ /// <param name="source">The source.</param>
+ /// <param name="target">The target.</param>
+ /// <param name="lockedFields">The locked fields.</param>
+ /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+ /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+ protected override void MergeData(AdultVideo source, AdultVideo target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ {
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+ }
+
+ protected override Task SaveItem(AdultVideo item, ItemUpdateType reason, CancellationToken cancellationToken)
+ {
+ return _libraryManager.UpdateItem(item, reason, cancellationToken);
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs
new file mode 100644
index 000000000..3b6439b4b
--- /dev/null
+++ b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs
@@ -0,0 +1,59 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Movies;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.AdultVideos
+{
+ class AdultVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider<AdultVideo>
+ {
+ private readonly ILogger _logger;
+
+ public AdultVideoXmlProvider(IFileSystem fileSystem, ILogger logger)
+ : base(fileSystem)
+ {
+ _logger = logger;
+ }
+
+ public async Task<MetadataResult<AdultVideo>> GetMetadata(string path, CancellationToken cancellationToken)
+ {
+ path = GetXmlFile(path).FullName;
+
+ var result = new MetadataResult<AdultVideo>();
+
+ await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ result.Item = new AdultVideo();
+
+ new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+ result.HasMetadata = true;
+ }
+ catch (FileNotFoundException)
+ {
+ result.HasMetadata = false;
+ }
+ finally
+ {
+ XmlParsingResourcePool.Release();
+ }
+
+ return result;
+ }
+
+ public string Name
+ {
+ get { return "Media Browser Xml"; }
+ }
+
+ protected override FileInfo GetXmlFile(string path)
+ {
+ return MovieXmlProvider.GetXmlFileInfo(path, FileSystem);
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs
index 68602b159..2e0e21a46 100644
--- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs
+++ b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs
@@ -1,10 +1,8 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
diff --git a/MediaBrowser.Providers/Games/GameMetadataService.cs b/MediaBrowser.Providers/Games/GameMetadataService.cs
index afa123bf7..8ca34eb05 100644
--- a/MediaBrowser.Providers/Games/GameMetadataService.cs
+++ b/MediaBrowser.Providers/Games/GameMetadataService.cs
@@ -1,10 +1,8 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
diff --git a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs
index 9e5532a27..6dd1b1bbc 100644
--- a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs
+++ b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs
@@ -1,10 +1,8 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
diff --git a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs
index b0c916a61..01adb3ac3 100644
--- a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs
+++ b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs
@@ -1,10 +1,8 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
index 02d5c1a79..037fedd79 100644
--- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
+++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
@@ -1,10 +1,8 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index da82dcb3f..c817180c5 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
@@ -230,13 +231,23 @@ namespace MediaBrowser.Providers.Manager
protected virtual TIdType GetId(TItemType item)
{
- return new TIdType
+ var id = new TIdType
{
MetadataCountryCode = item.GetPreferredMetadataCountryCode(),
MetadataLanguage = item.GetPreferredMetadataLanguage(),
Name = item.Name,
ProviderIds = item.ProviderIds
};
+
+ var baseItem = item as BaseItem;
+
+ if (baseItem != null)
+ {
+ id.IndexNumber = baseItem.IndexNumber;
+ id.ParentIndexNumber = baseItem.ParentIndexNumber;
+ }
+
+ return id;
}
public bool CanRefresh(IHasMetadata item)
@@ -253,6 +264,7 @@ namespace MediaBrowser.Providers.Manager
};
var temp = CreateNew();
+ temp.Path = item.Path;
// If replacing all metadata, run internet providers first
if (options.ReplaceAllMetadata)
@@ -313,29 +325,32 @@ namespace MediaBrowser.Providers.Manager
foreach (var provider in providers.OfType<ICustomMetadataProvider<TItemType>>())
{
- Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
-
- try
- {
- await provider.FetchAsync(item, cancellationToken).ConfigureAwait(false);
-
- refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception ex)
- {
- refreshResult.Status = ProviderRefreshStatus.CompletedWithErrors;
- refreshResult.ErrorMessage = ex.Message;
- Logger.ErrorException("Error in {0}", ex, provider.Name);
- }
+ await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false);
}
return refreshResult;
}
+ private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, RefreshResult refreshResult, CancellationToken cancellationToken)
+ {
+ Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
+
+ try
+ {
+ refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, cancellationToken).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ refreshResult.Status = ProviderRefreshStatus.CompletedWithErrors;
+ refreshResult.ErrorMessage = ex.Message;
+ Logger.ErrorException("Error in {0}", ex, provider.Name);
+ }
+ }
+
protected virtual TItemType CreateNew()
{
return new TItemType();
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 6b0ea7ed4..dbfc97d51 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -246,11 +246,7 @@ namespace MediaBrowser.Providers.Manager
cancellationToken.ThrowIfCancellationRequested();
- // Don't clog up the log with these providers
- if (!(provider is IDynamicInfoProvider))
- {
- _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--");
- }
+ _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--");
try
{
@@ -637,7 +633,7 @@ namespace MediaBrowser.Providers.Manager
}));
// Fetchers
- list.AddRange(providers.Where(i => !(i is ILocalMetadataProvider)).Select(i => new MetadataPlugin
+ list.AddRange(providers.Where(i => (i is IRemoteMetadataProvider)).Select(i => new MetadataPlugin
{
Name = i.Name,
Type = MetadataPluginType.MetadataFetcher
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 0ac26330a..ad8465009 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -64,6 +64,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
+ <Compile Include="AdultVideos\AdultVideoMetadataService.cs" />
<Compile Include="All\LocalImageProvider.cs" />
<Compile Include="Books\BookMetadataService.cs" />
<Compile Include="BoxSets\BoxSetMetadataService.cs" />
@@ -89,7 +90,11 @@
<Compile Include="Games\GameSystemXmlProvider.cs" />
<Compile Include="ImageFromMediaLocationProvider.cs" />
<Compile Include="ImagesByNameProvider.cs" />
+ <Compile Include="MediaInfo\FFProbeHelpers.cs" />
+ <Compile Include="MediaInfo\FFProbeProvider.cs" />
+ <Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
<Compile Include="Movies\MovieDbSearch.cs" />
+ <Compile Include="Movies\MovieXmlProvider.cs" />
<Compile Include="MusicGenres\MusicGenreImageProvider.cs" />
<Compile Include="GameGenres\GameGenreImageProvider.cs" />
<Compile Include="Genres\GenreImageProvider.cs" />
@@ -107,6 +112,8 @@
<Compile Include="Music\ArtistMetadataService.cs" />
<Compile Include="Music\LastfmArtistProvider.cs" />
<Compile Include="Music\MusicBrainzArtistProvider.cs" />
+ <Compile Include="Music\MusicVideoMetadataService.cs" />
+ <Compile Include="Music\MusicVideoXmlProvider.cs" />
<Compile Include="Omdb\OmdbProvider.cs" />
<Compile Include="Omdb\OmdbSeriesProvider.cs" />
<Compile Include="People\MovieDbPersonImageProvider.cs" />
@@ -150,9 +157,9 @@
<Compile Include="Savers\XmlSaverHelpers.cs" />
<Compile Include="Studios\StudiosImageProvider.cs" />
<Compile Include="Studios\StudioMetadataService.cs" />
- <Compile Include="TV\EpisodeImageFromMediaLocationProvider.cs" />
- <Compile Include="TV\EpisodeIndexNumberProvider.cs" />
- <Compile Include="TV\EpisodeProviderFromXml.cs" />
+ <Compile Include="TV\EpisodeLocalImageProvider.cs" />
+ <Compile Include="TV\EpisodeMetadataService.cs" />
+ <Compile Include="TV\EpisodeXmlProvider.cs" />
<Compile Include="TV\EpisodeXmlParser.cs" />
<Compile Include="TV\FanArtTvUpdatesPrescanTask.cs" />
<Compile Include="TV\FanartSeasonProvider.cs" />
diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
index e897eb1eb..ad4630dcf 100644
--- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
@@ -141,109 +141,5 @@ namespace MediaBrowser.Providers.MediaInfo
{
}
-
- /// <summary>
- /// Normalizes the FF probe result.
- /// </summary>
- /// <param name="result">The result.</param>
- protected void NormalizeFFProbeResult(InternalMediaInfoResult result)
- {
- if (result.format != null && result.format.tags != null)
- {
- result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags);
- }
-
- if (result.streams != null)
- {
- // Convert all dictionaries to case insensitive
- foreach (var stream in result.streams)
- {
- if (stream.tags != null)
- {
- stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags);
- }
-
- if (stream.disposition != null)
- {
- stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition);
- }
- }
- }
- }
-
- /// <summary>
- /// Gets a string from an FFProbeResult tags dictionary
- /// </summary>
- /// <param name="tags">The tags.</param>
- /// <param name="key">The key.</param>
- /// <returns>System.String.</returns>
- protected string GetDictionaryValue(Dictionary<string, string> tags, string key)
- {
- if (tags == null)
- {
- return null;
- }
-
- string val;
-
- tags.TryGetValue(key, out val);
- return val;
- }
-
- /// <summary>
- /// Gets an int from an FFProbeResult tags dictionary
- /// </summary>
- /// <param name="tags">The tags.</param>
- /// <param name="key">The key.</param>
- /// <returns>System.Nullable{System.Int32}.</returns>
- protected int? GetDictionaryNumericValue(Dictionary<string, string> tags, string key)
- {
- var val = GetDictionaryValue(tags, key);
-
- if (!string.IsNullOrEmpty(val))
- {
- int i;
-
- if (int.TryParse(val, out i))
- {
- return i;
- }
- }
-
- return null;
- }
-
- /// <summary>
- /// Gets a DateTime from an FFProbeResult tags dictionary
- /// </summary>
- /// <param name="tags">The tags.</param>
- /// <param name="key">The key.</param>
- /// <returns>System.Nullable{DateTime}.</returns>
- protected DateTime? GetDictionaryDateTime(Dictionary<string, string> tags, string key)
- {
- var val = GetDictionaryValue(tags, key);
-
- if (!string.IsNullOrEmpty(val))
- {
- DateTime i;
-
- if (DateTime.TryParse(val, out i))
- {
- return i.ToUniversalTime();
- }
- }
-
- return null;
- }
-
- /// <summary>
- /// Converts a dictionary to case insensitive
- /// </summary>
- /// <param name="dict">The dict.</param>
- /// <returns>Dictionary{System.StringSystem.String}.</returns>
- private Dictionary<string, string> ConvertDictionaryToCaseInSensitive(Dictionary<string, string> dict)
- {
- return new Dictionary<string, string>(dict, StringComparer.OrdinalIgnoreCase);
- }
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
index d27b65e2a..bae719eea 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
@@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested();
- NormalizeFFProbeResult(result);
+ FFProbeHelpers.NormalizeFFProbeResult(result);
cancellationToken.ThrowIfCancellationRequested();
@@ -102,7 +102,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="tags">The tags.</param>
private void FetchDataFromTags(Audio audio, Dictionary<string, string> tags)
{
- var title = GetDictionaryValue(tags, "title");
+ var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
// Only set Name if title was found in the dictionary
if (!string.IsNullOrEmpty(title))
@@ -114,7 +114,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
audio.People.Clear();
- var composer = GetDictionaryValue(tags, "composer");
+ var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
if (!string.IsNullOrWhiteSpace(composer))
{
@@ -125,9 +125,9 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- audio.Album = GetDictionaryValue(tags, "album");
+ audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
- var artist = GetDictionaryValue(tags, "artist");
+ var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
if (string.IsNullOrWhiteSpace(artist))
{
@@ -142,7 +142,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
// Several different forms of albumartist
- audio.AlbumArtist = GetDictionaryValue(tags, "albumartist") ?? GetDictionaryValue(tags, "album artist") ?? GetDictionaryValue(tags, "album_artist");
+ audio.AlbumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist") ?? FFProbeHelpers.GetDictionaryValue(tags, "album artist") ?? FFProbeHelpers.GetDictionaryValue(tags, "album_artist");
// Track number
audio.IndexNumber = GetDictionaryDiscValue(tags, "track");
@@ -150,10 +150,10 @@ namespace MediaBrowser.Providers.MediaInfo
// Disc number
audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
- audio.ProductionYear = GetDictionaryNumericValue(tags, "date");
+ audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
- audio.PremiereDate = GetDictionaryDateTime(tags, "retaildate") ?? GetDictionaryDateTime(tags, "retail date") ?? GetDictionaryDateTime(tags, "retail_date");
+ audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date");
// If we don't have a ProductionYear try and get it from PremiereDate
if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
@@ -219,7 +219,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="tagName">Name of the tag.</param>
private void FetchStudios(Audio audio, Dictionary<string, string> tags, string tagName)
{
- var val = GetDictionaryValue(tags, tagName);
+ var val = FFProbeHelpers.GetDictionaryValue(tags, tagName);
if (!string.IsNullOrEmpty(val))
{
@@ -240,7 +240,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="tags">The tags.</param>
private void FetchGenres(Audio audio, Dictionary<string, string> tags)
{
- var val = GetDictionaryValue(tags, "genre");
+ var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
if (!string.IsNullOrEmpty(val))
{
@@ -261,7 +261,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <returns>System.Nullable{System.Int32}.</returns>
private int? GetDictionaryDiscValue(Dictionary<string, string> tags, string tagName)
{
- var disc = GetDictionaryValue(tags, tagName);
+ var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName);
if (!string.IsNullOrEmpty(disc))
{
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs b/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs
new file mode 100644
index 000000000..5a4b2beb2
--- /dev/null
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs
@@ -0,0 +1,113 @@
+using MediaBrowser.Controller.MediaInfo;
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Providers.MediaInfo
+{
+ public static class FFProbeHelpers
+ {
+ /// <summary>
+ /// Normalizes the FF probe result.
+ /// </summary>
+ /// <param name="result">The result.</param>
+ public static void NormalizeFFProbeResult(InternalMediaInfoResult result)
+ {
+ if (result.format != null && result.format.tags != null)
+ {
+ result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags);
+ }
+
+ if (result.streams != null)
+ {
+ // Convert all dictionaries to case insensitive
+ foreach (var stream in result.streams)
+ {
+ if (stream.tags != null)
+ {
+ stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags);
+ }
+
+ if (stream.disposition != null)
+ {
+ stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets a string from an FFProbeResult tags dictionary
+ /// </summary>
+ /// <param name="tags">The tags.</param>
+ /// <param name="key">The key.</param>
+ /// <returns>System.String.</returns>
+ public static string GetDictionaryValue(Dictionary<string, string> tags, string key)
+ {
+ if (tags == null)
+ {
+ return null;
+ }
+
+ string val;
+
+ tags.TryGetValue(key, out val);
+ return val;
+ }
+
+ /// <summary>
+ /// Gets an int from an FFProbeResult tags dictionary
+ /// </summary>
+ /// <param name="tags">The tags.</param>
+ /// <param name="key">The key.</param>
+ /// <returns>System.Nullable{System.Int32}.</returns>
+ public static int? GetDictionaryNumericValue(Dictionary<string, string> tags, string key)
+ {
+ var val = GetDictionaryValue(tags, key);
+
+ if (!string.IsNullOrEmpty(val))
+ {
+ int i;
+
+ if (int.TryParse(val, out i))
+ {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets a DateTime from an FFProbeResult tags dictionary
+ /// </summary>
+ /// <param name="tags">The tags.</param>
+ /// <param name="key">The key.</param>
+ /// <returns>System.Nullable{DateTime}.</returns>
+ public static DateTime? GetDictionaryDateTime(Dictionary<string, string> tags, string key)
+ {
+ var val = GetDictionaryValue(tags, key);
+
+ if (!string.IsNullOrEmpty(val))
+ {
+ DateTime i;
+
+ if (DateTime.TryParse(val, out i))
+ {
+ return i.ToUniversalTime();
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Converts a dictionary to case insensitive
+ /// </summary>
+ /// <param name="dict">The dict.</param>
+ /// <returns>Dictionary{System.StringSystem.String}.</returns>
+ private static Dictionary<string, string> ConvertDictionaryToCaseInSensitive(Dictionary<string, string> dict)
+ {
+ return new Dictionary<string, string>(dict, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
new file mode 100644
index 000000000..1f3723653
--- /dev/null
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -0,0 +1,103 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.MediaInfo;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.MediaInfo
+{
+ public class FFProbeProvider : ICustomMetadataProvider<Episode>,
+ ICustomMetadataProvider<MusicVideo>,
+ ICustomMetadataProvider<Movie>,
+ ICustomMetadataProvider<AdultVideo>,
+ ICustomMetadataProvider<LiveTvVideoRecording>,
+ IHasChangeMonitor
+ {
+ private readonly ILogger _logger;
+ private readonly IIsoManager _isoManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IItemRepository _itemRepo;
+ private readonly IBlurayExaminer _blurayExaminer;
+ private readonly ILocalizationManager _localization;
+
+ public string Name
+ {
+ get { return "ffprobe"; }
+ }
+
+ public Task<ItemUpdateType> FetchAsync(Episode item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public Task<ItemUpdateType> FetchAsync(MusicVideo item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public Task<ItemUpdateType> FetchAsync(Movie item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public Task<ItemUpdateType> FetchAsync(AdultVideo item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public Task<ItemUpdateType> FetchAsync(LiveTvVideoRecording item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization)
+ {
+ _logger = logger;
+ _isoManager = isoManager;
+ _mediaEncoder = mediaEncoder;
+ _itemRepo = itemRepo;
+ _blurayExaminer = blurayExaminer;
+ _localization = localization;
+ }
+
+ private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.Unspecified);
+ public Task<ItemUpdateType> FetchVideoInfo<T>(T item, CancellationToken cancellationToken)
+ where T : Video
+ {
+ if (item.LocationType != LocationType.FileSystem)
+ {
+ return _cachedTask;
+ }
+
+ if (item.VideoType == VideoType.Iso && !_isoManager.CanMount(item.Path))
+ {
+ return _cachedTask;
+ }
+
+ if (item.VideoType == VideoType.HdDvd)
+ {
+ return _cachedTask;
+ }
+
+ var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization);
+
+ return prober.ProbeVideo(item, cancellationToken);
+ }
+
+ public bool HasChanged(IHasMetadata item, DateTime date)
+ {
+ return item.DateModified > date;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
new file mode 100644
index 000000000..8adb75839
--- /dev/null
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -0,0 +1,587 @@
+using DvdLib.Ifo;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.MediaInfo;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.MediaInfo
+{
+ public class FFProbeVideoInfo
+ {
+ private readonly ILogger _logger;
+ private readonly IIsoManager _isoManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IItemRepository _itemRepo;
+ private readonly IBlurayExaminer _blurayExaminer;
+ private readonly ILocalizationManager _localization;
+
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization)
+ {
+ _logger = logger;
+ _isoManager = isoManager;
+ _mediaEncoder = mediaEncoder;
+ _itemRepo = itemRepo;
+ _blurayExaminer = blurayExaminer;
+ _localization = localization;
+ }
+
+ public async Task<ItemUpdateType> ProbeVideo<T>(T item, CancellationToken cancellationToken)
+ where T : Video
+ {
+ var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ OnPreFetch(item, isoMount);
+
+ // If we didn't find any satisfying the min length, just take them all
+ if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd))
+ {
+ if (item.PlayableStreamFileNames.Count == 0)
+ {
+ _logger.Error("No playable vobs found in dvd structure, skipping ffprobe.");
+ return ItemUpdateType.MetadataImport;
+ }
+ }
+
+ var result = await GetMediaInfo(item, isoMount, cancellationToken).ConfigureAwait(false);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ FFProbeHelpers.NormalizeFFProbeResult(result);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ await Fetch(item, cancellationToken, result, isoMount).ConfigureAwait(false);
+
+ }
+ finally
+ {
+ if (isoMount != null)
+ {
+ isoMount.Dispose();
+ }
+ }
+
+ return ItemUpdateType.MetadataImport;
+ }
+
+ private async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var type = InputType.File;
+ var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath };
+
+ var video = item as Video;
+
+ if (video != null)
+ {
+ inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
+ }
+
+ return await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false);
+ }
+
+ protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount)
+ {
+ if (data.format != null)
+ {
+ // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
+ var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
+
+ if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
+ {
+ video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
+ }
+ }
+
+ var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;
+
+ var chapters = data.Chapters ?? new List<ChapterInfo>();
+
+ if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
+ {
+ var inputPath = isoMount != null ? isoMount.MountedPath : video.Path;
+ FetchBdInfo(video, chapters, mediaStreams, inputPath, cancellationToken);
+ }
+
+ AddExternalSubtitles(video, mediaStreams);
+
+ FetchWtvInfo(video, data);
+
+ video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);
+
+ if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
+ {
+ AddDummyChapters(video, chapters);
+ }
+
+ var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
+
+ video.VideoBitRate = videoStream == null ? null : videoStream.BitRate;
+ video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;
+
+ video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
+
+ await FFMpegManager.Instance.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
+
+ await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);
+
+ await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
+ }
+
+ private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, string inputPath, CancellationToken cancellationToken)
+ {
+ var video = (Video)item;
+
+ var result = GetBDInfo(inputPath);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ int? currentHeight = null;
+ int? currentWidth = null;
+ int? currentBitRate = null;
+
+ var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
+
+ // Grab the values that ffprobe recorded
+ if (videoStream != null)
+ {
+ currentBitRate = videoStream.BitRate;
+ currentWidth = videoStream.Width;
+ currentHeight = videoStream.Height;
+ }
+
+ // Fill video properties from the BDInfo result
+ Fetch(video, mediaStreams, result, chapters);
+
+ videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
+
+ // Use the ffprobe values if these are empty
+ if (videoStream != null)
+ {
+ videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate;
+ videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width;
+ videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height;
+ }
+ }
+
+ private bool IsEmpty(int? num)
+ {
+ return !num.HasValue || num.Value == 0;
+ }
+
+ /// <param name="chapters">The chapters.</param>
+ private void Fetch(Video video, List<MediaStream> mediaStreams, BlurayDiscInfo stream, List<ChapterInfo> chapters)
+ {
+ // Check all input for null/empty/zero
+
+ mediaStreams.Clear();
+ mediaStreams.AddRange(stream.MediaStreams);
+
+ video.MainFeaturePlaylistName = stream.PlaylistName;
+
+ if (stream.RunTimeTicks.HasValue && stream.RunTimeTicks.Value > 0)
+ {
+ video.RunTimeTicks = stream.RunTimeTicks;
+ }
+
+ video.PlayableStreamFileNames = stream.Files.ToList();
+
+ if (stream.Chapters != null)
+ {
+ chapters.Clear();
+
+ chapters.AddRange(stream.Chapters.Select(c => new ChapterInfo
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(c).Ticks
+
+ }));
+ }
+ }
+
+ /// <summary>
+ /// Gets information about the longest playlist on a bdrom
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns>VideoStream.</returns>
+ private BlurayDiscInfo GetBDInfo(string path)
+ {
+ return _blurayExaminer.GetDiscInfo(path);
+ }
+
+ private void FetchWtvInfo(Video video, InternalMediaInfoResult data)
+ {
+ if (data.format == null || data.format.tags == null)
+ {
+ return;
+ }
+
+ if (video.Genres.Count == 0)
+ {
+ if (!video.LockedFields.Contains(MetadataFields.Genres))
+ {
+ var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
+
+ if (!string.IsNullOrEmpty(genres))
+ {
+ video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .Select(i => i.Trim())
+ .ToList();
+ }
+ }
+ }
+
+ if (string.IsNullOrEmpty(video.Overview))
+ {
+ if (!video.LockedFields.Contains(MetadataFields.Overview))
+ {
+ var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
+
+ if (!string.IsNullOrWhiteSpace(overview))
+ {
+ video.Overview = overview;
+ }
+ }
+ }
+
+ if (string.IsNullOrEmpty(video.OfficialRating))
+ {
+ var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
+
+ if (!string.IsNullOrWhiteSpace(officialRating))
+ {
+ if (!video.LockedFields.Contains(MetadataFields.OfficialRating))
+ {
+ video.OfficialRating = officialRating;
+ }
+ }
+ }
+
+ if (video.People.Count == 0)
+ {
+ if (!video.LockedFields.Contains(MetadataFields.Cast))
+ {
+ var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");
+
+ if (!string.IsNullOrEmpty(people))
+ {
+ video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor })
+ .ToList();
+ }
+ }
+ }
+
+ if (!video.ProductionYear.HasValue)
+ {
+ var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
+
+ if (!string.IsNullOrWhiteSpace(year))
+ {
+ int val;
+
+ if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val))
+ {
+ video.ProductionYear = val;
+ }
+ }
+ }
+ }
+
+ private IEnumerable<string> SubtitleExtensions
+ {
+ get
+ {
+ return new[] { ".srt", ".ssa", ".ass" };
+ }
+ }
+
+ /// <summary>
+ /// Adds the external subtitles.
+ /// </summary>
+ /// <param name="video">The video.</param>
+ /// <param name="currentStreams">The current streams.</param>
+ private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams)
+ {
+ var useParent = !video.ResolveArgs.IsDirectory;
+
+ if (useParent && video.Parent == null)
+ {
+ return;
+ }
+
+ var fileSystemChildren = useParent
+ ? video.Parent.ResolveArgs.FileSystemChildren
+ : video.ResolveArgs.FileSystemChildren;
+
+ var startIndex = currentStreams.Count;
+ var streams = new List<MediaStream>();
+
+ var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
+
+ foreach (var file in fileSystemChildren
+ .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase)))
+ {
+ var fullName = file.FullName;
+
+ var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
+
+ // If the subtitle file matches the video file name
+ if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+ {
+ streams.Add(new MediaStream
+ {
+ Index = startIndex++,
+ Type = MediaStreamType.Subtitle,
+ IsExternal = true,
+ Path = fullName,
+ Codec = Path.GetExtension(fullName).ToLower().TrimStart('.')
+ });
+ }
+ else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
+ {
+ // Support xbmc naming conventions - 300.spanish.srt
+ var language = fileNameWithoutExtension.Split('.').LastOrDefault();
+
+ // Try to translate to three character code
+ // Be flexible and check against both the full and three character versions
+ var culture = _localization.GetCultures()
+ .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
+
+ if (culture != null)
+ {
+ language = culture.ThreeLetterISOLanguageName;
+ }
+
+ streams.Add(new MediaStream
+ {
+ Index = startIndex++,
+ Type = MediaStreamType.Subtitle,
+ IsExternal = true,
+ Path = fullName,
+ Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'),
+ Language = language
+ });
+ }
+ }
+
+ currentStreams.AddRange(streams);
+ }
+
+ /// <summary>
+ /// The dummy chapter duration
+ /// </summary>
+ private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
+
+ /// <summary>
+ /// Adds the dummy chapters.
+ /// </summary>
+ /// <param name="video">The video.</param>
+ /// <param name="chapters">The chapters.</param>
+ private void AddDummyChapters(Video video, List<ChapterInfo> chapters)
+ {
+ var runtime = video.RunTimeTicks ?? 0;
+
+ if (runtime < 0)
+ {
+ throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime));
+ }
+
+ if (runtime < _dummyChapterDuration)
+ {
+ return;
+ }
+
+ long currentChapterTicks = 0;
+ var index = 1;
+
+ // Limit to 100 chapters just in case there's some incorrect metadata here
+ while (currentChapterTicks < runtime && index < 100)
+ {
+ chapters.Add(new ChapterInfo
+ {
+ Name = "Chapter " + index,
+ StartPositionTicks = currentChapterTicks
+ });
+
+ index++;
+ currentChapterTicks += _dummyChapterDuration;
+ }
+ }
+
+ /// <summary>
+ /// Called when [pre fetch].
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="mount">The mount.</param>
+ private void OnPreFetch(Video item, IIsoMount mount)
+ {
+ if (item.VideoType == VideoType.Iso)
+ {
+ item.IsoType = DetermineIsoType(mount);
+ }
+
+ if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd))
+ {
+ FetchFromDvdLib(item, mount);
+ }
+ }
+
+ private void FetchFromDvdLib(Video item, IIsoMount mount)
+ {
+ var path = mount == null ? item.Path : mount.MountedPath;
+ var dvd = new Dvd(path);
+
+ item.RunTimeTicks = dvd.Titles.Select(GetRuntime).Max();
+
+ var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
+
+ uint? titleNumber = null;
+
+ if (primaryTitle != null)
+ {
+ titleNumber = primaryTitle.TitleNumber;
+ }
+
+ item.PlayableStreamFileNames = GetPrimaryPlaylistVobFiles(item, mount, titleNumber)
+ .Select(Path.GetFileName)
+ .ToList();
+ }
+
+ private long GetRuntime(Title title)
+ {
+ return title.ProgramChains
+ .Select(i => (TimeSpan)i.PlaybackTime)
+ .Select(i => i.Ticks)
+ .Sum();
+ }
+
+ /// <summary>
+ /// Mounts the iso if needed.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>IsoMount.</returns>
+ protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
+ {
+ if (item.VideoType == VideoType.Iso)
+ {
+ return _isoManager.Mount(item.Path, cancellationToken);
+ }
+
+ return Task.FromResult<IIsoMount>(null);
+ }
+
+ /// <summary>
+ /// Determines the type of the iso.
+ /// </summary>
+ /// <param name="isoMount">The iso mount.</param>
+ /// <returns>System.Nullable{IsoType}.</returns>
+ private IsoType? DetermineIsoType(IIsoMount isoMount)
+ {
+ var folders = Directory.EnumerateDirectories(isoMount.MountedPath).Select(Path.GetFileName).ToList();
+
+ if (folders.Contains("video_ts", StringComparer.OrdinalIgnoreCase))
+ {
+ return IsoType.Dvd;
+ }
+ if (folders.Contains("bdmv", StringComparer.OrdinalIgnoreCase))
+ {
+ return IsoType.BluRay;
+ }
+
+ return null;
+ }
+
+ private IEnumerable<string> GetPrimaryPlaylistVobFiles(Video video, IIsoMount isoMount, uint? titleNumber)
+ {
+ // min size 300 mb
+ const long minPlayableSize = 314572800;
+
+ var root = isoMount != null ? isoMount.MountedPath : video.Path;
+
+ // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size
+ // Once we reach a file that is at least the minimum, return all subsequent ones
+ var allVobs = Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories)
+ .Where(file => string.Equals(Path.GetExtension(file), ".vob", StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ // If we didn't find any satisfying the min length, just take them all
+ if (allVobs.Count == 0)
+ {
+ _logger.Error("No vobs found in dvd structure.");
+ return new List<string>();
+ }
+
+ if (titleNumber.HasValue)
+ {
+ var prefix = string.Format("VTS_0{0}_", titleNumber.Value.ToString(_usCulture));
+ var vobs = allVobs.Where(i => Path.GetFileName(i).StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
+
+ if (vobs.Count > 0)
+ {
+ return vobs;
+ }
+
+ _logger.Debug("Could not determine vob file list for {0} using DvdLib. Will scan using file sizes.", video.Path);
+ }
+
+ var files = allVobs
+ .SkipWhile(f => new FileInfo(f).Length < minPlayableSize)
+ .ToList();
+
+ // If we didn't find any satisfying the min length, just take them all
+ if (files.Count == 0)
+ {
+ _logger.Warn("Vob size filter resulted in zero matches. Taking all vobs.");
+ files = allVobs;
+ }
+
+ // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file
+ if (files.Count > 0)
+ {
+ var parts = Path.GetFileNameWithoutExtension(files[0]).Split('_');
+
+ if (parts.Length == 3)
+ {
+ var title = parts[1];
+
+ files = files.TakeWhile(f =>
+ {
+ var fileParts = Path.GetFileNameWithoutExtension(f).Split('_');
+
+ return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase);
+
+ }).ToList();
+
+ // If this resulted in not getting any vobs, just take them all
+ if (files.Count == 0)
+ {
+ _logger.Warn("Vob filename filter resulted in zero matches. Taking all vobs.");
+ files = allVobs;
+ }
+ }
+ }
+
+ return files;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
index afbea7f8b..8d69e6ba2 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
@@ -192,7 +192,7 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested();
- NormalizeFFProbeResult(result);
+ FFProbeHelpers.NormalizeFFProbeResult(result);
cancellationToken.ThrowIfCancellationRequested();
@@ -401,7 +401,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
if (!video.LockedFields.Contains(MetadataFields.Genres))
{
- var genres = GetDictionaryValue(data.format.tags, "genre");
+ var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
if (!string.IsNullOrEmpty(genres))
{
@@ -417,7 +417,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
if (!video.LockedFields.Contains(MetadataFields.Overview))
{
- var overview = GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
+ var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
if (!string.IsNullOrWhiteSpace(overview))
{
@@ -428,7 +428,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (force || string.IsNullOrEmpty(video.OfficialRating))
{
- var officialRating = GetDictionaryValue(data.format.tags, "WM/ParentalRating");
+ var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
if (!string.IsNullOrWhiteSpace(officialRating))
{
@@ -443,7 +443,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
if (!video.LockedFields.Contains(MetadataFields.Cast))
{
- var people = GetDictionaryValue(data.format.tags, "WM/MediaCredits");
+ var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");
if (!string.IsNullOrEmpty(people))
{
@@ -457,7 +457,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (force || !video.ProductionYear.HasValue)
{
- var year = GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
+ var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
if (!string.IsNullOrWhiteSpace(year))
{
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 21d3ae3e8..70b849e1c 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -1,90 +1,28 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
using System;
-using System.Collections.Concurrent;
-using System.IO;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.MediaInfo
{
- class VideoImageProvider : BaseMetadataProvider
+ public class VideoImageProvider : IDynamicImageProvider
{
- /// <summary>
- /// The _locks
- /// </summary>
- private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
-
- /// <summary>
- /// The _media encoder
- /// </summary>
- private readonly IMediaEncoder _mediaEncoder;
private readonly IIsoManager _isoManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IServerConfigurationManager _config;
- public VideoImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IIsoManager isoManager)
- : base(logManager, configurationManager)
+ public VideoImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config)
{
- _mediaEncoder = mediaEncoder;
_isoManager = isoManager;
- }
-
- /// <summary>
- /// Gets a value indicating whether [refresh on version change].
- /// </summary>
- /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
- protected override bool RefreshOnVersionChange
- {
- get
- {
- return true;
- }
- }
-
- /// <summary>
- /// Gets the provider version.
- /// </summary>
- /// <value>The provider version.</value>
- protected override string ProviderVersion
- {
- get
- {
- return "1";
- }
- }
-
- /// <summary>
- /// Supportses the specified item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- public override bool Supports(BaseItem item)
- {
- return item.LocationType == LocationType.FileSystem && item is Video;
- }
-
- /// <summary>
- /// Needses the refresh internal.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="providerInfo">The provider info.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
- {
- var video = (Video)item;
-
- if (!QualifiesForExtraction(video))
- {
- return false;
- }
-
- return base.NeedsRefreshInternal(item, providerInfo);
+ _mediaEncoder = mediaEncoder;
+ _config = config;
}
/// <summary>
@@ -94,16 +32,6 @@ namespace MediaBrowser.Providers.MediaInfo
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
private bool QualifiesForExtraction(Video item)
{
- if (!ConfigurationManager.Configuration.EnableVideoImageExtraction)
- {
- return false;
- }
-
- if (!string.IsNullOrEmpty(item.PrimaryImagePath))
- {
- return false;
- }
-
// No support for this
if (item.VideoType == VideoType.HdDvd)
{
@@ -126,137 +54,61 @@ namespace MediaBrowser.Providers.MediaInfo
}
/// <summary>
- /// Override this to return the date that should be compared to the last refresh date
- /// to determine if this provider should be re-fetched.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>DateTime.</returns>
- protected override DateTime CompareDate(BaseItem item)
- {
- return item.DateModified;
- }
-
- /// <summary>
- /// Gets the priority.
+ /// The null mount task result
/// </summary>
- /// <value>The priority.</value>
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.Last; }
- }
-
- public override ItemUpdateType ItemUpdateType
- {
- get
- {
- return ItemUpdateType.ImageUpdate;
- }
- }
+ protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
/// <summary>
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
+ /// Mounts the iso if needed.
/// </summary>
/// <param name="item">The item.</param>
- /// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.Boolean}.</returns>
- public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ /// <returns>Task{IIsoMount}.</returns>
+ protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
{
- item.ValidateImages();
-
- var video = (Video)item;
-
- // Double check this here in case force was used
- if (QualifiesForExtraction(video))
+ if (item.VideoType == VideoType.Iso)
{
- try
- {
- await ExtractImage(video, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- // Swallow this so that we don't keep on trying over and over again
-
- Logger.ErrorException("Error extracting image for {0}", ex, item.Name);
- }
+ return _isoManager.Mount(item.Path, cancellationToken);
}
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
- return true;
+ return NullMountTaskResult;
}
- /// <summary>
- /// Extracts the image.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task ExtractImage(Video item, CancellationToken cancellationToken)
+ public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- var path = GetVideoImagePath(item);
-
- if (!File.Exists(path))
- {
- var semaphore = GetLock(path);
-
- // Acquire a lock
- await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- // Check again
- if (!File.Exists(path))
- {
- try
- {
- var parentPath = Path.GetDirectoryName(path);
-
- Directory.CreateDirectory(parentPath);
-
- await ExtractImageInternal(item, path, cancellationToken).ConfigureAwait(false);
- }
- finally
- {
- semaphore.Release();
- }
- }
- else
- {
- semaphore.Release();
- }
- }
+ return new List<ImageType> { ImageType.Primary };
+ }
- // Image is already in the cache
- item.SetImagePath(ImageType.Primary, path);
+ public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
+ {
+ return GetVideoImage((Video)item, cancellationToken);
}
- /// <summary>
- /// Extracts the image.
- /// </summary>
- /// <param name="video">The video.</param>
- /// <param name="path">The path.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task ExtractImageInternal(Video video, string path, CancellationToken cancellationToken)
+ public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken)
{
- var isoMount = await MountIsoIfNeeded(video, cancellationToken).ConfigureAwait(false);
+ var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
try
{
// If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
// Always use 10 seconds for dvd because our duration could be out of whack
- var imageOffset = video.VideoType != VideoType.Dvd && video.RunTimeTicks.HasValue &&
- video.RunTimeTicks.Value > 0
- ? TimeSpan.FromTicks(Convert.ToInt64(video.RunTimeTicks.Value * .1))
+ var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue &&
+ item.RunTimeTicks.Value > 0
+ ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1))
: TimeSpan.FromSeconds(10);
InputType type;
- var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, item.LocationType == LocationType.Remote, item.VideoType, item.IsoType, isoMount, item.PlayableStreamFileNames, out type);
- await _mediaEncoder.ExtractImage(inputPath, type, false, video.Video3DFormat, imageOffset, path, cancellationToken).ConfigureAwait(false);
+ var stream = await _mediaEncoder.ExtractImage(inputPath, type, false, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
- video.SetImagePath(ImageType.Primary, path);
+ return new DynamicImageResponse
+ {
+ Format = ImageFormat.Jpg,
+ HasImage = true,
+ Stream = stream
+ };
}
finally
{
@@ -267,63 +119,19 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- /// <summary>
- /// The null mount task result
- /// </summary>
- protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
-
- /// <summary>
- /// Mounts the iso if needed.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IIsoMount}.</returns>
- protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
- {
- if (item.VideoType == VideoType.Iso)
- {
- return _isoManager.Mount(item.Path, cancellationToken);
- }
-
- return NullMountTaskResult;
- }
-
- /// <summary>
- /// Gets the lock.
- /// </summary>
- /// <param name="filename">The filename.</param>
- /// <returns>SemaphoreSlim.</returns>
- private SemaphoreSlim GetLock(string filename)
+ public string Name
{
- return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
+ get { return "Embedded Image"; }
}
- /// <summary>
- /// Gets the video images data path.
- /// </summary>
- /// <value>The video images data path.</value>
- public string VideoImagesPath
+ public bool Supports(IHasImages item)
{
- get
+ if (!_config.Configuration.EnableVideoImageExtraction)
{
- return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-video-images");
+ return false;
}
- }
- /// <summary>
- /// Gets the audio image path.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>System.String.</returns>
- private string GetVideoImagePath(Video item)
- {
- var filename = item.Path + "_" + item.DateModified.Ticks + "_primary";
-
- filename = filename.GetMD5() + ".jpg";
-
- var prefix = filename.Substring(0, 1);
-
- return Path.Combine(VideoImagesPath, prefix, filename);
+ return item.LocationType == LocationType.FileSystem && item is Video;
}
}
}
diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
index 87e6be1f7..3adc682fc 100644
--- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
@@ -371,6 +371,11 @@ namespace MediaBrowser.Providers.Movies
{
var path = GetDataFilePath(item);
+ if (string.IsNullOrEmpty(path))
+ {
+ return _cachedTask;
+ }
+
var fileInfo = _fileSystem.GetFileSystemInfo(path);
if (fileInfo.Exists)
diff --git a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
index 67b62548e..7ae6dffc7 100644
--- a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
+++ b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
@@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.Movies
try
{
- await new MovieXmlParser(Logger, _itemRepo).FetchAsync(video, path, cancellationToken).ConfigureAwait(false);
+ new MovieXmlParser(Logger).FetchAsync(video, path, cancellationToken);
}
finally
{
diff --git a/MediaBrowser.Providers/Movies/MovieXmlParser.cs b/MediaBrowser.Providers/Movies/MovieXmlParser.cs
index 61b73360c..64038e853 100644
--- a/MediaBrowser.Providers/Movies/MovieXmlParser.cs
+++ b/MediaBrowser.Providers/Movies/MovieXmlParser.cs
@@ -1,10 +1,8 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.Threading;
-using System.Threading.Tasks;
using System.Xml;
namespace MediaBrowser.Providers.Movies
@@ -14,28 +12,14 @@ namespace MediaBrowser.Providers.Movies
/// </summary>
public class MovieXmlParser : BaseItemXmlParser<Video>
{
- private readonly IItemRepository _itemRepo;
-
- private Task _chaptersTask = null;
-
- public MovieXmlParser(ILogger logger, IItemRepository itemRepo)
+ public MovieXmlParser(ILogger logger)
: base(logger)
{
- _itemRepo = itemRepo;
}
- public async Task FetchAsync(Video item, string metadataFile, CancellationToken cancellationToken)
+ public void FetchAsync(Video item, string metadataFile, CancellationToken cancellationToken)
{
- _chaptersTask = null;
-
Fetch(item, metadataFile, cancellationToken);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (_chaptersTask != null)
- {
- await _chaptersTask.ConfigureAwait(false);
- }
}
/// <summary>
diff --git a/MediaBrowser.Providers/Movies/MovieXmlProvider.cs b/MediaBrowser.Providers/Movies/MovieXmlProvider.cs
new file mode 100644
index 000000000..82250f2b5
--- /dev/null
+++ b/MediaBrowser.Providers/Movies/MovieXmlProvider.cs
@@ -0,0 +1,78 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Movies
+{
+ public class MovieXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Movie>
+ {
+ private readonly ILogger _logger;
+
+ public MovieXmlProvider(IFileSystem fileSystem, ILogger logger)
+ : base(fileSystem)
+ {
+ _logger = logger;
+ }
+
+ public async Task<MetadataResult<Movie>> GetMetadata(string path, CancellationToken cancellationToken)
+ {
+ path = GetXmlFile(path).FullName;
+
+ var result = new MetadataResult<Movie>();
+
+ await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ result.Item = new Movie();
+
+ new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+ result.HasMetadata = true;
+ }
+ catch (FileNotFoundException)
+ {
+ result.HasMetadata = false;
+ }
+ finally
+ {
+ XmlParsingResourcePool.Release();
+ }
+
+ return result;
+ }
+
+ public string Name
+ {
+ get { return "Media Browser Xml"; }
+ }
+
+ protected override FileInfo GetXmlFile(string path)
+ {
+ return GetXmlFileInfo(path, FileSystem);
+ }
+
+ public static FileInfo GetXmlFileInfo(string path, IFileSystem _fileSystem)
+ {
+ var fileInfo = _fileSystem.GetFileSystemInfo(path);
+
+ var directoryInfo = fileInfo as DirectoryInfo;
+
+ if (directoryInfo == null)
+ {
+ directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
+ }
+
+ var directoryPath = directoryInfo.FullName;
+
+ var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml");
+
+ var file = new FileInfo(specificFile);
+
+ return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml"));
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index 024e44cad..a0e2cc81b 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -112,7 +112,7 @@ namespace MediaBrowser.Providers.Music
if (artist != null)
{
- id.ArtistMusicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz);
+ id.ArtistProviderIds = artist.ProviderIds;
id.AlbumArtist = id.AlbumArtist ?? artist.Name;
}
diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
index 3f7c0ce96..f5cf49f74 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
@@ -37,7 +37,10 @@ namespace MediaBrowser.Providers.Music
if (string.IsNullOrEmpty(releaseId))
{
- var releaseResult = await GetReleaseResult(albumId.ArtistMusicBrainzId, albumId.AlbumArtist, albumId.Name, cancellationToken).ConfigureAwait(false);
+ string artistMusicBrainzId;
+ albumId.ArtistProviderIds.TryGetValue(MetadataProviders.Musicbrainz.ToString(), out artistMusicBrainzId);
+
+ var releaseResult = await GetReleaseResult(artistMusicBrainzId, albumId.AlbumArtist, albumId.Name, cancellationToken).ConfigureAwait(false);
result.Item = new MusicAlbum();
diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
new file mode 100644
index 000000000..6dd16819b
--- /dev/null
+++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
@@ -0,0 +1,53 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Music
+{
+ class MusicVideoMetadataService : MetadataService<MusicVideo, ItemId>
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public MusicVideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+ : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ /// <summary>
+ /// Merges the specified source.
+ /// </summary>
+ /// <param name="source">The source.</param>
+ /// <param name="target">The target.</param>
+ /// <param name="lockedFields">The locked fields.</param>
+ /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+ /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+ protected override void MergeData(MusicVideo source, MusicVideo target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ {
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+
+ if (replaceData || string.IsNullOrEmpty(target.Album))
+ {
+ target.Album = source.Album;
+ }
+
+ if (replaceData || string.IsNullOrEmpty(target.Artist))
+ {
+ target.Artist = source.Artist;
+ }
+ }
+
+ protected override Task SaveItem(MusicVideo item, ItemUpdateType reason, CancellationToken cancellationToken)
+ {
+ return _libraryManager.UpdateItem(item, reason, cancellationToken);
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs b/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs
new file mode 100644
index 000000000..dcfe0d89e
--- /dev/null
+++ b/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs
@@ -0,0 +1,60 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Movies;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Music
+{
+ class MusicVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicVideo>
+ {
+ private readonly ILogger _logger;
+
+ public MusicVideoXmlProvider(IFileSystem fileSystem, ILogger logger)
+ : base(fileSystem)
+ {
+ _logger = logger;
+ }
+
+ public async Task<MetadataResult<MusicVideo>> GetMetadata(string path, CancellationToken cancellationToken)
+ {
+ path = GetXmlFile(path).FullName;
+
+ var result = new MetadataResult<MusicVideo>();
+
+ await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ var item = new MusicVideo();
+
+ new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken);
+ result.HasMetadata = true;
+ result.Item = item;
+ }
+ catch (FileNotFoundException)
+ {
+ result.HasMetadata = false;
+ }
+ finally
+ {
+ XmlParsingResourcePool.Release();
+ }
+
+ return result;
+ }
+
+ public string Name
+ {
+ get { return "Media Browser Xml"; }
+ }
+
+ protected override FileInfo GetXmlFile(string path)
+ {
+ return MovieXmlProvider.GetXmlFileInfo(path, FileSystem);
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
index c0a62fba2..f64f30a27 100644
--- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
+++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
@@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
index 70dea5db4..bc1be2dd6 100644
--- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Serialization;
using System;
@@ -26,13 +27,13 @@ namespace MediaBrowser.Providers.Omdb
_httpClient = httpClient;
}
- public async Task Fetch(BaseItem item, CancellationToken cancellationToken)
+ public async Task<ItemUpdateType> Fetch(BaseItem item, CancellationToken cancellationToken)
{
var imdbId = item.GetProviderId(MetadataProviders.Imdb);
if (string.IsNullOrEmpty(imdbId))
{
- return;
+ return ItemUpdateType.Unspecified;
}
var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
@@ -97,6 +98,8 @@ namespace MediaBrowser.Providers.Omdb
ParseAdditionalMetadata(item, result);
}
+
+ return ItemUpdateType.MetadataDownload;
}
private void ParseAdditionalMetadata(BaseItem item, RootObject result)
diff --git a/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs b/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs
index 3659868c7..4ce2ad5e1 100644
--- a/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs
@@ -1,5 +1,8 @@
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Serialization;
using System.Threading;
@@ -7,7 +10,8 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.Omdb
{
- public class OmdbSeriesProvider : ICustomMetadataProvider<Series>
+ public class OmdbSeriesProvider : ICustomMetadataProvider<Series>,
+ ICustomMetadataProvider<Movie>, ICustomMetadataProvider<Trailer>
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
@@ -18,14 +22,30 @@ namespace MediaBrowser.Providers.Omdb
_httpClient = httpClient;
}
- public Task FetchAsync(Series item, CancellationToken cancellationToken)
+ public string Name
+ {
+ get { return "OMDb"; }
+ }
+
+ public Task<ItemUpdateType> FetchAsync(Series item, CancellationToken cancellationToken)
{
return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken);
}
- public string Name
+ public Task<ItemUpdateType> FetchAsync(Movie item, CancellationToken cancellationToken)
{
- get { return "OMDb"; }
+ return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken);
+ }
+
+ private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.Unspecified);
+ public Task<ItemUpdateType> FetchAsync(Trailer item, CancellationToken cancellationToken)
+ {
+ if (item.IsLocalTrailer)
+ {
+ return _cachedTask;
+ }
+
+ return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/ProviderUtils.cs b/MediaBrowser.Providers/ProviderUtils.cs
index 543643474..61fe19a61 100644
--- a/MediaBrowser.Providers/ProviderUtils.cs
+++ b/MediaBrowser.Providers/ProviderUtils.cs
@@ -158,6 +158,7 @@ namespace MediaBrowser.Providers
}
MergeAlbumArtist(source, target, lockedFields, replaceData);
+ MergeBudget(source, target, lockedFields, replaceData);
if (mergeMetadataSettings)
{
@@ -198,5 +199,24 @@ namespace MediaBrowser.Providers
}
}
}
+
+ private static void MergeBudget(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
+ {
+ var sourceHasBudget = source as IHasBudget;
+ var targetHasBudget = target as IHasBudget;
+
+ if (sourceHasBudget != null && targetHasBudget != null)
+ {
+ if (replaceData || !targetHasBudget.Budget.HasValue)
+ {
+ targetHasBudget.Budget = sourceHasBudget.Budget;
+ }
+
+ if (replaceData || !targetHasBudget.Revenue.HasValue)
+ {
+ targetHasBudget.Revenue = sourceHasBudget.Revenue;
+ }
+ }
+ }
}
}
diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
index a42a882af..8d5184fc2 100644
--- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs
+++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
@@ -1,10 +1,8 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
diff --git a/MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs
deleted file mode 100644
index f2aaa0f06..000000000
--- a/MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs
+++ /dev/null
@@ -1,163 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.TV
-{
- /// <summary>
- /// Class EpisodeImageFromMediaLocationProvider
- /// </summary>
- public class EpisodeImageFromMediaLocationProvider : BaseMetadataProvider
- {
- public EpisodeImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
- : base(logManager, configurationManager)
- {
- }
-
- public override ItemUpdateType ItemUpdateType
- {
- get
- {
- return ItemUpdateType.ImageUpdate;
- }
- }
-
- /// <summary>
- /// Supportses the specified item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- public override bool Supports(BaseItem item)
- {
- return item is Episode && item.LocationType == LocationType.FileSystem;
- }
-
- /// <summary>
- /// Gets the priority.
- /// </summary>
- /// <value>The priority.</value>
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.First; }
- }
-
- /// <summary>
- /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
- /// </summary>
- /// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
- protected override bool RefreshOnFileSystemStampChange
- {
- get
- {
- return true;
- }
- }
-
- /// <summary>
- /// Gets the filestamp extensions.
- /// </summary>
- /// <value>The filestamp extensions.</value>
- protected override string[] FilestampExtensions
- {
- get
- {
- return BaseItem.SupportedImageExtensions;
- }
- }
-
- /// <summary>
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="force">if set to <c>true</c> [force].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.Boolean}.</returns>
- public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var episode = (Episode)item;
-
- var episodeFileName = Path.GetFileName(episode.Path);
-
- var parent = item.ResolveArgs.Parent;
-
- ValidateImage(episode);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- SetPrimaryImagePath(episode, parent, item.MetaLocation, episodeFileName);
-
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
- return TrueTaskResult;
- }
-
- /// <summary>
- /// Validates the primary image path still exists
- /// </summary>
- /// <param name="episode">The episode.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- private void ValidateImage(Episode episode)
- {
- var path = episode.PrimaryImagePath;
-
- if (string.IsNullOrEmpty(path))
- {
- return;
- }
-
- if (!File.Exists(path))
- {
- episode.SetImagePath(ImageType.Primary, null);
- }
- }
-
- /// <summary>
- /// Sets the primary image path.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="parent">The parent.</param>
- /// <param name="metadataFolder">The metadata folder.</param>
- /// <param name="episodeFileName">Name of the episode file.</param>
- private void SetPrimaryImagePath(Episode item, Folder parent, string metadataFolder, string episodeFileName)
- {
- foreach (var extension in BaseItem.SupportedImageExtensions)
- {
- var path = Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, extension));
-
- var file = parent.ResolveArgs.GetMetaFileByPath(path);
-
- if (file != null)
- {
- item.SetImagePath(ImageType.Primary, file.FullName);
- return;
- }
- }
-
- var seasonFolder = Path.GetDirectoryName(item.Path);
-
- foreach (var extension in BaseItem.SupportedImageExtensions)
- {
- var imageFilename = Path.GetFileNameWithoutExtension(episodeFileName) + "-thumb" + extension;
-
- var path = Path.Combine(seasonFolder, imageFilename);
-
- var file = parent.ResolveArgs.GetMetaFileByPath(path);
-
- if (file != null)
- {
- item.SetImagePath(ImageType.Primary, file.FullName);
- return;
- }
- }
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs
deleted file mode 100644
index 3e7597e0d..000000000
--- a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.TV
-{
- /// <summary>
- /// Making this a provider because of how slow it is
- /// It only ever needs to run once
- /// </summary>
- public class EpisodeIndexNumberProvider : BaseMetadataProvider
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
- /// </summary>
- /// <param name="logManager">The log manager.</param>
- /// <param name="configurationManager">The configuration manager.</param>
- public EpisodeIndexNumberProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
- : base(logManager, configurationManager)
- {
- }
-
- protected override bool RefreshOnVersionChange
- {
- get
- {
- return true;
- }
- }
-
- protected override string ProviderVersion
- {
- get
- {
- return "2";
- }
- }
-
- /// <summary>
- /// Supportses the specified item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- public override bool Supports(BaseItem item)
- {
- if (item is Episode)
- {
- var locationType = item.LocationType;
- return locationType != LocationType.Virtual && locationType != LocationType.Remote;
- }
- return false;
- }
-
- /// <summary>
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="force">if set to <c>true</c> [force].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.Boolean}.</returns>
- public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
- {
- var episode = (Episode)item;
-
- episode.IndexNumber = TVUtils.GetEpisodeNumberFromFile(item.Path, item.Parent is Season);
- episode.IndexNumberEnd = TVUtils.GetEndingEpisodeNumberFromFile(item.Path);
-
- if (!episode.ParentIndexNumber.HasValue)
- {
- var season = episode.Parent as Season;
-
- if (season != null)
- {
- episode.ParentIndexNumber = season.IndexNumber;
- }
- }
-
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-
- return TrueTaskResult;
- }
-
- /// <summary>
- /// Gets the priority.
- /// </summary>
- /// <value>The priority.</value>
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.First; }
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs b/MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs
new file mode 100644
index 000000000..96e8f3158
--- /dev/null
+++ b/MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs
@@ -0,0 +1,59 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Providers.TV
+{
+ public class EpisodeLocalImageProvider : IImageFileProvider
+ {
+ public string Name
+ {
+ get { return "Local Images"; }
+ }
+
+ public bool Supports(IHasImages item)
+ {
+ return item is Episode && item.LocationType == LocationType.FileSystem;
+ }
+
+ public List<LocalImageInfo> GetImages(IHasImages item)
+ {
+ var parentPath = Path.GetDirectoryName(item.Path);
+
+ var nameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path);
+ var thumbName = nameWithoutExtension + "-thumb";
+
+ return Directory.EnumerateFiles(parentPath, "*", SearchOption.AllDirectories)
+ .Where(i =>
+ {
+ if (BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i) ?? string.Empty))
+ {
+ var currentNameWithoutExtension = Path.GetFileNameWithoutExtension(i);
+
+ if (string.Equals(nameWithoutExtension, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ if (string.Equals(thumbName, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ })
+ .Select(i => new LocalImageInfo
+ {
+ Path = i,
+ Type = ImageType.Primary
+ })
+ .ToList();
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
new file mode 100644
index 000000000..2b6edaf08
--- /dev/null
+++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
@@ -0,0 +1,137 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.TV
+{
+ public class EpisodeMetadataService : MetadataService<Episode, EpisodeId>
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public EpisodeMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+ : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ /// <summary>
+ /// Merges the specified source.
+ /// </summary>
+ /// <param name="source">The source.</param>
+ /// <param name="target">The target.</param>
+ /// <param name="lockedFields">The locked fields.</param>
+ /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+ /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+ protected override void MergeData(Episode source, Episode target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ {
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+
+ if (replaceData || !target.AirsBeforeSeasonNumber.HasValue)
+ {
+ target.AirsBeforeSeasonNumber = source.AirsBeforeSeasonNumber;
+ }
+
+ if (replaceData || !target.AirsAfterSeasonNumber.HasValue)
+ {
+ target.AirsAfterSeasonNumber = source.AirsAfterSeasonNumber;
+ }
+
+ if (replaceData || !target.AirsBeforeEpisodeNumber.HasValue)
+ {
+ target.AirsBeforeEpisodeNumber = source.AirsBeforeEpisodeNumber;
+ }
+
+ if (replaceData || !target.DvdSeasonNumber.HasValue)
+ {
+ target.DvdSeasonNumber = source.DvdSeasonNumber;
+ }
+
+ if (replaceData || !target.DvdEpisodeNumber.HasValue)
+ {
+ target.DvdEpisodeNumber = source.DvdEpisodeNumber;
+ }
+
+ if (replaceData || !target.AbsoluteEpisodeNumber.HasValue)
+ {
+ target.AbsoluteEpisodeNumber = source.AbsoluteEpisodeNumber;
+ }
+
+ if (replaceData || !target.IndexNumberEnd.HasValue)
+ {
+ target.IndexNumberEnd = source.IndexNumberEnd;
+ }
+ }
+
+ protected override Task SaveItem(Episode item, ItemUpdateType reason, CancellationToken cancellationToken)
+ {
+ return _libraryManager.UpdateItem(item, reason, cancellationToken);
+ }
+
+ protected override EpisodeId GetId(Episode item)
+ {
+ var id = base.GetId(item);
+
+ var series = item.Series;
+
+ if (series != null)
+ {
+ id.SeriesProviderIds = series.ProviderIds;
+ }
+
+ id.IndexNumberEnd = item.IndexNumberEnd;
+
+ return id;
+ }
+
+ protected override ItemUpdateType BeforeMetadataRefresh(Episode item)
+ {
+ var updateType = base.BeforeMetadataRefresh(item);
+
+ var locationType = item.LocationType;
+ if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
+ {
+ var currentIndexNumber = item.IndexNumber;
+ var currentIndexNumberEnd = item.IndexNumberEnd;
+ var currentParentIndexNumber = item.ParentIndexNumber;
+
+ item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(item.Path, item.Parent is Season);
+ item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(item.Path);
+
+ if (!item.ParentIndexNumber.HasValue)
+ {
+ var season = item.Season;
+
+ if (season != null)
+ {
+ item.ParentIndexNumber = season.IndexNumber;
+ }
+ }
+
+ if ((currentIndexNumber ?? -1) != (item.IndexNumber ?? -1))
+ {
+ updateType = updateType | ItemUpdateType.MetadataImport;
+ }
+
+ if ((currentIndexNumberEnd ?? -1) != (item.IndexNumberEnd ?? -1))
+ {
+ updateType = updateType | ItemUpdateType.MetadataImport;
+ }
+
+ if ((currentParentIndexNumber ?? -1) != (item.ParentIndexNumber ?? -1))
+ {
+ updateType = updateType | ItemUpdateType.MetadataImport;
+ }
+ }
+
+ return updateType;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs b/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs
deleted file mode 100644
index 38921c008..000000000
--- a/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.TV
-{
- /// <summary>
- /// Class EpisodeProviderFromXml
- /// </summary>
- public class EpisodeProviderFromXml : BaseMetadataProvider
- {
- private readonly IItemRepository _itemRepo;
- private readonly IFileSystem _fileSystem;
-
- public EpisodeProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IItemRepository itemRepo, IFileSystem fileSystem)
- : base(logManager, configurationManager)
- {
- _itemRepo = itemRepo;
- _fileSystem = fileSystem;
- }
-
- /// <summary>
- /// Supportses the specified item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- public override bool Supports(BaseItem item)
- {
- return item is Episode && item.LocationType == LocationType.FileSystem;
- }
-
- /// <summary>
- /// Gets the priority.
- /// </summary>
- /// <value>The priority.</value>
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.First; }
- }
-
- /// <summary>
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="force">if set to <c>true</c> [force].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.Boolean}.</returns>
- public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var metadataFile = Path.Combine(item.MetaLocation, Path.ChangeExtension(Path.GetFileName(item.Path), ".xml"));
-
- var file = item.ResolveArgs.Parent.ResolveArgs.GetMetaFileByPath(metadataFile);
-
- if (file != null)
- {
- await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- await new EpisodeXmlParser(Logger, _itemRepo).FetchAsync((Episode)item, metadataFile, cancellationToken).ConfigureAwait(false);
- }
- finally
- {
- XmlParsingResourcePool.Release();
- }
- }
-
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
- return true;
- }
-
- /// <summary>
- /// Needses the refresh based on compare date.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="providerInfo">The provider info.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
- {
- var metadataFile = Path.Combine(item.MetaLocation, Path.ChangeExtension(Path.GetFileName(item.Path), ".xml"));
-
- var file = item.ResolveArgs.Parent.ResolveArgs.GetMetaFileByPath(metadataFile);
-
- if (file == null)
- {
- return false;
- }
-
- return _fileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved;
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/EpisodeXmlParser.cs b/MediaBrowser.Providers/TV/EpisodeXmlParser.cs
index d0bf7bcde..b35c18e09 100644
--- a/MediaBrowser.Providers/TV/EpisodeXmlParser.cs
+++ b/MediaBrowser.Providers/TV/EpisodeXmlParser.cs
@@ -1,13 +1,11 @@
-using System;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
+using System;
using System.Globalization;
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
using System.Xml;
namespace MediaBrowser.Providers.TV
@@ -17,28 +15,14 @@ namespace MediaBrowser.Providers.TV
/// </summary>
public class EpisodeXmlParser : BaseItemXmlParser<Episode>
{
- private readonly IItemRepository _itemRepo;
-
- private Task _chaptersTask = null;
-
- public EpisodeXmlParser(ILogger logger, IItemRepository itemRepo)
+ public EpisodeXmlParser(ILogger logger)
: base(logger)
{
- _itemRepo = itemRepo;
}
- public async Task FetchAsync(Episode item, string metadataFile, CancellationToken cancellationToken)
+ public void FetchAsync(Episode item, string metadataFile, CancellationToken cancellationToken)
{
- _chaptersTask = null;
-
- Fetch(item, metadataFile, cancellationToken);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (_chaptersTask != null)
- {
- await _chaptersTask.ConfigureAwait(false);
- }
+ Fetch(item, metadataFile, cancellationToken);
}
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
diff --git a/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs b/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs
new file mode 100644
index 000000000..b8d88f5e6
--- /dev/null
+++ b/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs
@@ -0,0 +1,62 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.TV
+{
+ public class EpisodeXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Episode>
+ {
+ private readonly ILogger _logger;
+
+ public EpisodeXmlProvider(IFileSystem fileSystem, ILogger logger)
+ : base(fileSystem)
+ {
+ _logger = logger;
+ }
+
+ public async Task<MetadataResult<Episode>> GetMetadata(string path, CancellationToken cancellationToken)
+ {
+ path = GetXmlFile(path).FullName;
+
+ var result = new MetadataResult<Episode>();
+
+ await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ result.Item = new Episode();
+
+ new EpisodeXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+ result.HasMetadata = true;
+ }
+ catch (FileNotFoundException)
+ {
+ result.HasMetadata = false;
+ }
+ finally
+ {
+ XmlParsingResourcePool.Release();
+ }
+
+ return result;
+ }
+
+ public string Name
+ {
+ get { return "Media Browser Xml"; }
+ }
+
+ protected override FileInfo GetXmlFile(string path)
+ {
+ var metadataPath = Path.GetDirectoryName(path);
+ metadataPath = Path.Combine(metadataPath, "metadata");
+ var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(path), ".xml"));
+
+ return new FileInfo(metadataFile);
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs
index 85353bad5..6f988a2f6 100644
--- a/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -17,16 +19,18 @@ using System.Xml;
namespace MediaBrowser.Providers.TV
{
- public class TvdbEpisodeImageProvider : IRemoteImageProvider
+ public class TvdbEpisodeImageProvider : IRemoteImageProvider, IHasChangeMonitor
{
private readonly IServerConfigurationManager _config;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient;
+ private readonly IFileSystem _fileSystem;
- public TvdbEpisodeImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
+ public TvdbEpisodeImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
_config = config;
_httpClient = httpClient;
+ _fileSystem = fileSystem;
}
public string Name
@@ -65,7 +69,7 @@ namespace MediaBrowser.Providers.TV
// Process images
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
- var files = TvdbEpisodeProvider.Current.GetEpisodeXmlFiles(episode, seriesDataPath);
+ var files = TvdbEpisodeProvider.Current.GetEpisodeXmlFiles(episode.ParentIndexNumber, episode.IndexNumber, episode.IndexNumberEnd, seriesDataPath);
var result = files.Select(i => GetImageInfo(i, cancellationToken))
.Where(i => i != null);
@@ -186,5 +190,27 @@ namespace MediaBrowser.Providers.TV
ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
});
}
+
+ public bool HasChanged(IHasMetadata item, DateTime date)
+ {
+ if (!item.HasImage(ImageType.Primary))
+ {
+ var episode = (Episode)item;
+ var series = episode.Series;
+
+ var seriesId = series != null ? series.GetProviderId(MetadataProviders.Tvdb) : null;
+
+ if (!string.IsNullOrEmpty(seriesId))
+ {
+ // Process images
+ var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
+
+ var files = TvdbEpisodeProvider.Current.GetEpisodeXmlFiles(episode.ParentIndexNumber, episode.IndexNumber, episode.IndexNumberEnd, seriesDataPath);
+
+ return files.Any(i => _fileSystem.GetLastWriteTimeUtc(i) > date);
+ }
+ }
+ return false;
+ }
}
}
diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
index f5e21bf69..5523b8ab3 100644
--- a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
@@ -1,12 +1,10 @@
using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
using System.Collections.Generic;
@@ -25,161 +23,88 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Class RemoteEpisodeProvider
/// </summary>
- class TvdbEpisodeProvider : BaseMetadataProvider
+ class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode>, IHasChangeMonitor
{
- /// <summary>
- /// The _provider manager
- /// </summary>
- private readonly IProviderManager _providerManager;
-
- /// <summary>
- /// Gets the HTTP client.
- /// </summary>
- /// <value>The HTTP client.</value>
- protected IHttpClient HttpClient { get; private set; }
- private readonly IFileSystem _fileSystem;
-
internal static TvdbEpisodeProvider Current;
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerConfigurationManager _config;
- /// <summary>
- /// Initializes a new instance of the <see cref="TvdbEpisodeProvider" /> class.
- /// </summary>
- /// <param name="httpClient">The HTTP client.</param>
- /// <param name="logManager">The log manager.</param>
- /// <param name="configurationManager">The configuration manager.</param>
- /// <param name="providerManager">The provider manager.</param>
- public TvdbEpisodeProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
- : base(logManager, configurationManager)
+ public TvdbEpisodeProvider(IFileSystem fileSystem, IServerConfigurationManager config)
{
- HttpClient = httpClient;
- _providerManager = providerManager;
_fileSystem = fileSystem;
+ _config = config;
Current = this;
}
- /// <summary>
- /// Supportses the specified item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- public override bool Supports(BaseItem item)
+ public string Name
{
- return item is Episode;
+ get { return "TheTVDB"; }
}
- public override ItemUpdateType ItemUpdateType
+ public Task<MetadataResult<Episode>> GetMetadata(ItemId id, CancellationToken cancellationToken)
{
- get
- {
- return ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataDownload;
- }
- }
+ var episodeId = (EpisodeId)id;
- /// <summary>
- /// Gets the priority.
- /// </summary>
- /// <value>The priority.</value>
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.Third; }
- }
+ string seriesTvdbId;
+ episodeId.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);
- /// <summary>
- /// Gets a value indicating whether [requires internet].
- /// </summary>
- /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
- public override bool RequiresInternet
- {
- get { return true; }
- }
+ var result = new MetadataResult<Episode>();
- /// <summary>
- /// Gets a value indicating whether [refresh on version change].
- /// </summary>
- /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
- protected override bool RefreshOnVersionChange
- {
- get
+ if (!string.IsNullOrEmpty(seriesTvdbId))
{
- return true;
- }
- }
+ var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesTvdbId);
- /// <summary>
- /// Gets the provider version.
- /// </summary>
- /// <value>The provider version.</value>
- protected override string ProviderVersion
- {
- get
- {
- return "5";
- }
- }
-
- /// <summary>
- /// Needses the refresh internal.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="providerInfo">The provider info.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
- {
- var locationType = item.LocationType;
-
- // Always use tvdb updates for non-file system episodes
- if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
- {
- // Don't proceed if there's local metadata
- if (!ConfigurationManager.Configuration.EnableTvDbUpdates && HasLocalMeta(item))
+ try
{
- return false;
+ result.Item = FetchEpisodeData(episodeId, seriesDataPath, cancellationToken);
+ result.HasMetadata = result.Item != null;
+ }
+ catch (FileNotFoundException)
+ {
+ // Don't fail the provider because this will just keep on going and going.
}
}
- return base.NeedsRefreshInternal(item, providerInfo);
+ return Task.FromResult(result);
}
- protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
+ public bool HasChanged(IHasMetadata item, DateTime date)
{
var episode = (Episode)item;
+ var series = episode.Series;
- var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;
+ var seriesId = series != null ? series.GetProviderId(MetadataProviders.Tvdb) : null;
if (!string.IsNullOrEmpty(seriesId))
{
// Process images
- var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
+ var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
- var files = GetEpisodeXmlFiles(episode, seriesDataPath);
+ var files = GetEpisodeXmlFiles(episode.ParentIndexNumber, episode.IndexNumber, episode.IndexNumberEnd, seriesDataPath);
- if (files.Count > 0)
- {
- return files.Select(i => _fileSystem.GetLastWriteTimeUtc(i)).Max() > providerInfo.LastRefreshed;
- }
+ return files.Any(i => _fileSystem.GetLastWriteTimeUtc(i) > date);
}
-
+
return false;
}
/// <summary>
/// Gets the episode XML files.
/// </summary>
- /// <param name="episode">The episode.</param>
+ /// <param name="seasonNumber">The season number.</param>
+ /// <param name="episodeNumber">The episode number.</param>
+ /// <param name="endingEpisodeNumber">The ending episode number.</param>
/// <param name="seriesDataPath">The series data path.</param>
/// <returns>List{FileInfo}.</returns>
- internal List<FileInfo> GetEpisodeXmlFiles(Episode episode, string seriesDataPath)
+ internal List<FileInfo> GetEpisodeXmlFiles(int? seasonNumber, int? episodeNumber, int? endingEpisodeNumber, string seriesDataPath)
{
var files = new List<FileInfo>();
- if (episode.IndexNumber == null)
+ if (episodeNumber == null)
{
return files;
}
- var episodeNumber = episode.IndexNumber.Value;
- var seasonNumber = episode.ParentIndexNumber;
-
if (seasonNumber == null)
{
return files;
@@ -205,7 +130,7 @@ namespace MediaBrowser.Providers.TV
}
}
- var end = episode.IndexNumberEnd ?? episodeNumber;
+ var end = endingEpisodeNumber ?? episodeNumber;
episodeNumber++;
while (episodeNumber <= end)
@@ -236,72 +161,34 @@ namespace MediaBrowser.Providers.TV
}
/// <summary>
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="force">if set to <c>true</c> [force].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.Boolean}.</returns>
- public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var status = ProviderRefreshStatus.Success;
-
- var episode = (Episode)item;
-
- var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;
-
- if (!string.IsNullOrEmpty(seriesId))
- {
- var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
-
- try
- {
- status = await FetchEpisodeData(episode, seriesDataPath, cancellationToken).ConfigureAwait(false);
- }
- catch (FileNotFoundException)
- {
- // Don't fail the provider because this will just keep on going and going.
- }
- }
-
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo, status);
- return true;
- }
-
-
- /// <summary>
/// Fetches the episode data.
/// </summary>
- /// <param name="episode">The episode.</param>
+ /// <param name="id">The identifier.</param>
/// <param name="seriesDataPath">The series data path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
- private async Task<ProviderRefreshStatus> FetchEpisodeData(Episode episode, string seriesDataPath, CancellationToken cancellationToken)
+ private Episode FetchEpisodeData(EpisodeId id, string seriesDataPath, CancellationToken cancellationToken)
{
- var status = ProviderRefreshStatus.Success;
-
- if (episode.IndexNumber == null)
+ if (id.IndexNumber == null)
{
- return status;
+ return null;
}
- var episodeNumber = episode.IndexNumber.Value;
- var seasonNumber = episode.ParentIndexNumber;
+ var episodeNumber = id.IndexNumber.Value;
+ var seasonNumber = id.ParentIndexNumber;
if (seasonNumber == null)
{
- return status;
+ return null;
}
var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber));
var success = false;
var usingAbsoluteData = false;
-
+ var episode = new Episode();
try
{
- status = await FetchMainEpisodeInfo(episode, file, cancellationToken).ConfigureAwait(false);
+ FetchMainEpisodeInfo(episode, file, cancellationToken);
success = true;
}
@@ -318,11 +205,11 @@ namespace MediaBrowser.Providers.TV
{
file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber));
- status = await FetchMainEpisodeInfo(episode, file, cancellationToken).ConfigureAwait(false);
+ FetchMainEpisodeInfo(episode, file, cancellationToken);
usingAbsoluteData = true;
}
- var end = episode.IndexNumberEnd ?? episodeNumber;
+ var end = id.IndexNumberEnd ?? episodeNumber;
episodeNumber++;
while (episodeNumber <= end)
@@ -348,12 +235,12 @@ namespace MediaBrowser.Providers.TV
episodeNumber++;
}
- return status;
+ return success ? episode : null;
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private async Task<ProviderRefreshStatus> FetchMainEpisodeInfo(Episode item, string xmlFile, CancellationToken cancellationToken)
+ private void FetchMainEpisodeInfo(Episode item, string xmlFile, CancellationToken cancellationToken)
{
var status = ProviderRefreshStatus.Success;
@@ -506,7 +393,7 @@ namespace MediaBrowser.Providers.TV
item.AirsBeforeSeasonNumber = rval;
}
}
-
+
break;
}
@@ -534,7 +421,7 @@ namespace MediaBrowser.Providers.TV
{
var url = TVUtils.BannerUrl + val;
- await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
+ //await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
}
catch (HttpException)
{
@@ -661,8 +548,6 @@ namespace MediaBrowser.Providers.TV
}
}
}
-
- return status;
}
private void AddPeople(BaseItem item, string val, string personType)
@@ -802,15 +687,5 @@ namespace MediaBrowser.Providers.TV
}
}
}
-
- /// <summary>
- /// Determines whether [has local meta] [the specified episode].
- /// </summary>
- /// <param name="episode">The episode.</param>
- /// <returns><c>true</c> if [has local meta] [the specified episode]; otherwise, <c>false</c>.</returns>
- private bool HasLocalMeta(BaseItem episode)
- {
- return (episode.Parent.ResolveArgs.ContainsMetaFileByName(Path.GetFileNameWithoutExtension(episode.Path) + ".xml"));
- }
}
}
diff --git a/MediaBrowser.Providers/Users/UserMetadataService.cs b/MediaBrowser.Providers/Users/UserMetadataService.cs
index 01c27c754..9f0b77dca 100644
--- a/MediaBrowser.Providers/Users/UserMetadataService.cs
+++ b/MediaBrowser.Providers/Users/UserMetadataService.cs
@@ -1,10 +1,8 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;