diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs
index a0055f4e6b..ca56e40af8 100644
--- a/MediaBrowser.Api/ItemRefreshService.cs
+++ b/MediaBrowser.Api/ItemRefreshService.cs
@@ -11,13 +11,16 @@ using System.Threading.Tasks;
namespace MediaBrowser.Api
{
- [Route("/Items/{Id}/Refresh", "POST")]
- [Api(Description = "Refreshes metadata for an item")]
- public class RefreshItem : IReturnVoid
+ public class BaseRefreshRequest : IReturnVoid
{
[ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
public bool Forced { get; set; }
+ }
+ [Route("/Items/{Id}/Refresh", "POST")]
+ [Api(Description = "Refreshes metadata for an item")]
+ public class RefreshItem : BaseRefreshRequest
+ {
[ApiMember(Name = "Recursive", Description = "Indicates if the refresh should occur recursively.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
public bool Recursive { get; set; }
@@ -27,66 +30,48 @@ namespace MediaBrowser.Api
[Route("/Artists/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for an artist")]
- public class RefreshArtist : IReturnVoid
+ public class RefreshArtist : BaseRefreshRequest
{
- [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
- public bool Forced { get; set; }
-
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/Genres/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a genre")]
- public class RefreshGenre : IReturnVoid
+ public class RefreshGenre : BaseRefreshRequest
{
- [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
- public bool Forced { get; set; }
-
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/MusicGenres/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a music genre")]
- public class RefreshMusicGenre : IReturnVoid
+ public class RefreshMusicGenre : BaseRefreshRequest
{
- [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
- public bool Forced { get; set; }
-
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/GameGenres/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a game genre")]
- public class RefreshGameGenre : IReturnVoid
+ public class RefreshGameGenre : BaseRefreshRequest
{
- [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
- public bool Forced { get; set; }
-
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/Persons/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a person")]
- public class RefreshPerson : IReturnVoid
+ public class RefreshPerson : BaseRefreshRequest
{
- [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
- public bool Forced { get; set; }
-
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/Studios/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a studio")]
- public class RefreshStudio : IReturnVoid
+ public class RefreshStudio : BaseRefreshRequest
{
- [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
- public bool Forced { get; set; }
-
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
@@ -132,11 +117,7 @@ namespace MediaBrowser.Api
try
{
- await item.RefreshMetadata(new MetadataRefreshOptions
- {
- ReplaceAllMetadata = request.Forced,
-
- }, CancellationToken.None).ConfigureAwait(false);
+ await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -157,11 +138,7 @@ namespace MediaBrowser.Api
try
{
- await item.RefreshMetadata(new MetadataRefreshOptions
- {
- ReplaceAllMetadata = request.Forced,
-
- }, CancellationToken.None).ConfigureAwait(false);
+ await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -182,11 +159,7 @@ namespace MediaBrowser.Api
try
{
- await item.RefreshMetadata(new MetadataRefreshOptions
- {
- ReplaceAllMetadata = request.Forced,
-
- }, CancellationToken.None).ConfigureAwait(false);
+ await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -207,11 +180,7 @@ namespace MediaBrowser.Api
try
{
- await item.RefreshMetadata(new MetadataRefreshOptions
- {
- ReplaceAllMetadata = request.Forced,
-
- }, CancellationToken.None).ConfigureAwait(false);
+ await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -232,11 +201,7 @@ namespace MediaBrowser.Api
try
{
- await item.RefreshMetadata(new MetadataRefreshOptions
- {
- ReplaceAllMetadata = request.Forced,
-
- }, CancellationToken.None).ConfigureAwait(false);
+ await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -257,11 +222,7 @@ namespace MediaBrowser.Api
try
{
- await item.RefreshMetadata(new MetadataRefreshOptions
- {
- ReplaceAllMetadata = request.Forced,
-
- }, CancellationToken.None).ConfigureAwait(false);
+ await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -291,11 +252,7 @@ namespace MediaBrowser.Api
try
{
- await item.RefreshMetadata(new MetadataRefreshOptions
- {
- ReplaceAllMetadata = request.Forced,
-
- }, CancellationToken.None).ConfigureAwait(false);
+ await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
if (item.IsFolder)
{
@@ -328,13 +285,11 @@ namespace MediaBrowser.Api
/// Task.
private async Task RefreshCollectionFolderChildren(RefreshItem request, CollectionFolder collectionFolder)
{
+ var options = GetRefreshOptions(request);
+
foreach (var child in collectionFolder.Children.ToList())
{
- await child.RefreshMetadata(new MetadataRefreshOptions
- {
- ReplaceAllMetadata = request.Forced,
-
- }, CancellationToken.None).ConfigureAwait(false);
+ await child.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
if (child.IsFolder)
{
@@ -344,5 +299,15 @@ namespace MediaBrowser.Api
}
}
}
+
+ private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request)
+ {
+ return new MetadataRefreshOptions
+ {
+ MetadataRefreshMode = request.Forced ? MetadataRefreshMode.FullRefresh : MetadataRefreshMode.EnsureMetadata,
+ ImageRefreshMode = request.Forced ? ImageRefreshMode.FullRefresh : ImageRefreshMode.Default,
+ ReplaceAllMetadata = request.Forced
+ };
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 61bf14543f..3bdfc3c444 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -1,8 +1,8 @@
-using System;
+using MediaBrowser.Model.Configuration;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
-using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Entities.TV
{
@@ -57,6 +57,12 @@ namespace MediaBrowser.Controller.Entities.TV
///
/// The absolute episode number.
public int? AbsoluteEpisodeNumber { get; set; }
+
+ ///
+ /// This is the ending episode number for double episodes.
+ ///
+ /// The index number.
+ public int? IndexNumberEnd { get; set; }
///
/// We want to group into series not show individually in an index
@@ -89,7 +95,7 @@ namespace MediaBrowser.Controller.Entities.TV
return value;
}
- var season = Parent as Season;
+ var season = Season;
return season != null ? season.IndexNumber : null;
}
@@ -140,10 +146,6 @@ namespace MediaBrowser.Controller.Entities.TV
get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; }
}
- ///
- /// The _series
- ///
- private Series _series;
///
/// This Episode's Series Instance
///
@@ -151,14 +153,14 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public Series Series
{
- get { return _series ?? (_series = FindParent()); }
+ get { return FindParent(); }
}
- ///
- /// This is the ending episode number for double episodes.
- ///
- /// The index number.
- public int? IndexNumberEnd { get; set; }
+ [IgnoreDataMember]
+ public Season Season
+ {
+ get { return FindParent(); }
+ }
///
/// Creates the name of the sort.
@@ -217,7 +219,7 @@ namespace MediaBrowser.Controller.Entities.TV
get
{
// First see if the parent is a Season
- var season = Parent as Season;
+ var season = Season;
if (season != null)
{
@@ -229,7 +231,7 @@ namespace MediaBrowser.Controller.Entities.TV
// Parent is a Series
if (seasonNumber.HasValue)
{
- var series = Parent as Series;
+ var series = Series;
if (series != null)
{
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 7bc8154239..8a11cc9a0b 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -144,7 +144,6 @@
-
diff --git a/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs b/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs
index 86b508012d..e77cd14d17 100644
--- a/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs
@@ -1,7 +1,8 @@
-using System;
+using MediaBrowser.Model.Entities;
+using System;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.MediaInfo
{
@@ -35,6 +36,18 @@ namespace MediaBrowser.Controller.MediaInfo
/// Task.
Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, CancellationToken cancellationToken);
+ ///
+ /// Extracts the image.
+ ///
+ /// The input files.
+ /// The type.
+ /// if set to true [is audio].
+ /// The threed format.
+ /// The offset.
+ /// The cancellation token.
+ /// Task{Stream}.
+ Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
+
///
/// Extracts the text subtitle.
///
diff --git a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
index a53222b5a1..de75c62e9b 100644
--- a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
@@ -1,4 +1,5 @@
-using System.Threading;
+using MediaBrowser.Controller.Library;
+using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Providers
@@ -10,6 +11,6 @@ namespace MediaBrowser.Controller.Providers
public interface ICustomMetadataProvider : IMetadataProvider, ICustomMetadataProvider
where TItemType : IHasMetadata
{
- Task FetchAsync(TItemType item, CancellationToken cancellationToken);
+ Task FetchAsync(TItemType item, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs b/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs
deleted file mode 100644
index 0f5dea5f6e..0000000000
--- a/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-
-namespace MediaBrowser.Controller.Providers
-{
- ///
- /// Marker interface for a provider that always runs
- ///
- public interface IDynamicInfoProvider
- {
- }
-}
diff --git a/MediaBrowser.Controller/Providers/IHasMetadata.cs b/MediaBrowser.Controller/Providers/IHasMetadata.cs
index 1e2a76b792..3fba73a28b 100644
--- a/MediaBrowser.Controller/Providers/IHasMetadata.cs
+++ b/MediaBrowser.Controller/Providers/IHasMetadata.cs
@@ -16,6 +16,12 @@ namespace MediaBrowser.Controller.Providers
/// System.String.
string GetPreferredMetadataCountryCode();
+ ///
+ /// Gets the date modified.
+ ///
+ /// The date modified.
+ DateTime DateModified { get; }
+
///
/// Gets the locked fields.
///
diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
index 5c3ebd9acf..ed7cdc8b2b 100644
--- a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
+++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Providers
public ImageType Type { get; set; }
}
- public interface IDynamicImageProvider : ILocalImageProvider
+ public interface IDynamicImageProvider : IImageProvider
{
///
/// Gets the supported images.
diff --git a/MediaBrowser.Controller/Providers/ItemId.cs b/MediaBrowser.Controller/Providers/ItemId.cs
index 3dbaa78fa3..9be6b783cb 100644
--- a/MediaBrowser.Controller/Providers/ItemId.cs
+++ b/MediaBrowser.Controller/Providers/ItemId.cs
@@ -31,6 +31,8 @@ namespace MediaBrowser.Controller.Providers
///
/// The year.
public int? Year { get; set; }
+ public int? IndexNumber { get; set; }
+ public int? ParentIndexNumber { get; set; }
public ItemId()
{
@@ -45,11 +47,17 @@ namespace MediaBrowser.Controller.Providers
///
/// The album artist.
public string AlbumArtist { get; set; }
+
///
- /// Gets or sets the artist music brainz identifier.
+ /// Gets or sets the artist provider ids.
///
- /// The artist music brainz identifier.
- public string ArtistMusicBrainzId { get; set; }
+ /// The artist provider ids.
+ public Dictionary ArtistProviderIds { get; set; }
+
+ public AlbumId()
+ {
+ ArtistProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
}
public class GameId : ItemId
@@ -69,4 +77,16 @@ namespace MediaBrowser.Controller.Providers
/// The path.
public string Path { get; set; }
}
+
+ public class EpisodeId : ItemId
+ {
+ public Dictionary SeriesProviderIds { get; set; }
+
+ public int? IndexNumberEnd { get; set; }
+
+ public EpisodeId()
+ {
+ SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+ }
}
diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs
new file mode 100644
index 0000000000..23f370974c
--- /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
+ {
+ 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;
+ }
+
+ ///
+ /// Merges the specified source.
+ ///
+ /// The source.
+ /// The target.
+ /// The locked fields.
+ /// if set to true [replace data].
+ /// if set to true [merge metadata settings].
+ protected override void MergeData(AdultVideo source, AdultVideo target, List 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 0000000000..3b6439b4b4
--- /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
+ {
+ private readonly ILogger _logger;
+
+ public AdultVideoXmlProvider(IFileSystem fileSystem, ILogger logger)
+ : base(fileSystem)
+ {
+ _logger = logger;
+ }
+
+ public async Task> GetMetadata(string path, CancellationToken cancellationToken)
+ {
+ path = GetXmlFile(path).FullName;
+
+ var result = new MetadataResult();
+
+ 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 68602b1592..2e0e21a463 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 afa123bf7e..8ca34eb05a 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 9e5532a27e..6dd1b1bbc1 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 b0c916a613..01adb3ac34 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 02d5c1a796..037fedd794 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 da82dcb3fb..c817180c51 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>())
{
- 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 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 6b0ea7ed47..dbfc97d517 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 0ac26330a6..ad8465009d 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -64,6 +64,7 @@
+
@@ -89,7 +90,11 @@
+
+
+
+
@@ -107,6 +112,8 @@
+
+
@@ -150,9 +157,9 @@
-
-
-
+
+
+
diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
index e897eb1eb1..ad4630dcf6 100644
--- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
@@ -141,109 +141,5 @@ namespace MediaBrowser.Providers.MediaInfo
{
}
-
- ///
- /// Normalizes the FF probe result.
- ///
- /// The result.
- 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);
- }
- }
- }
- }
-
- ///
- /// Gets a string from an FFProbeResult tags dictionary
- ///
- /// The tags.
- /// The key.
- /// System.String.
- protected string GetDictionaryValue(Dictionary tags, string key)
- {
- if (tags == null)
- {
- return null;
- }
-
- string val;
-
- tags.TryGetValue(key, out val);
- return val;
- }
-
- ///
- /// Gets an int from an FFProbeResult tags dictionary
- ///
- /// The tags.
- /// The key.
- /// System.Nullable{System.Int32}.
- protected int? GetDictionaryNumericValue(Dictionary tags, string key)
- {
- var val = GetDictionaryValue(tags, key);
-
- if (!string.IsNullOrEmpty(val))
- {
- int i;
-
- if (int.TryParse(val, out i))
- {
- return i;
- }
- }
-
- return null;
- }
-
- ///
- /// Gets a DateTime from an FFProbeResult tags dictionary
- ///
- /// The tags.
- /// The key.
- /// System.Nullable{DateTime}.
- protected DateTime? GetDictionaryDateTime(Dictionary 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;
- }
-
- ///
- /// Converts a dictionary to case insensitive
- ///
- /// The dict.
- /// Dictionary{System.StringSystem.String}.
- private Dictionary ConvertDictionaryToCaseInSensitive(Dictionary dict)
- {
- return new Dictionary(dict, StringComparer.OrdinalIgnoreCase);
- }
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
index d27b65e2aa..bae719eea0 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
/// The tags.
private void FetchDataFromTags(Audio audio, Dictionary 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
/// Name of the tag.
private void FetchStudios(Audio audio, Dictionary 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
/// The tags.
private void FetchGenres(Audio audio, Dictionary tags)
{
- var val = GetDictionaryValue(tags, "genre");
+ var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
if (!string.IsNullOrEmpty(val))
{
@@ -261,7 +261,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// System.Nullable{System.Int32}.
private int? GetDictionaryDiscValue(Dictionary 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 0000000000..5a4b2beb26
--- /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
+ {
+ ///
+ /// Normalizes the FF probe result.
+ ///
+ /// The result.
+ 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);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets a string from an FFProbeResult tags dictionary
+ ///
+ /// The tags.
+ /// The key.
+ /// System.String.
+ public static string GetDictionaryValue(Dictionary tags, string key)
+ {
+ if (tags == null)
+ {
+ return null;
+ }
+
+ string val;
+
+ tags.TryGetValue(key, out val);
+ return val;
+ }
+
+ ///
+ /// Gets an int from an FFProbeResult tags dictionary
+ ///
+ /// The tags.
+ /// The key.
+ /// System.Nullable{System.Int32}.
+ public static int? GetDictionaryNumericValue(Dictionary tags, string key)
+ {
+ var val = GetDictionaryValue(tags, key);
+
+ if (!string.IsNullOrEmpty(val))
+ {
+ int i;
+
+ if (int.TryParse(val, out i))
+ {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets a DateTime from an FFProbeResult tags dictionary
+ ///
+ /// The tags.
+ /// The key.
+ /// System.Nullable{DateTime}.
+ public static DateTime? GetDictionaryDateTime(Dictionary 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;
+ }
+
+ ///
+ /// Converts a dictionary to case insensitive
+ ///
+ /// The dict.
+ /// Dictionary{System.StringSystem.String}.
+ private static Dictionary ConvertDictionaryToCaseInSensitive(Dictionary dict)
+ {
+ return new Dictionary(dict, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
new file mode 100644
index 0000000000..1f3723653e
--- /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,
+ ICustomMetadataProvider,
+ ICustomMetadataProvider,
+ ICustomMetadataProvider,
+ ICustomMetadataProvider,
+ 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 FetchAsync(Episode item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public Task FetchAsync(MusicVideo item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public Task FetchAsync(Movie item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public Task FetchAsync(AdultVideo item, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, cancellationToken);
+ }
+
+ public Task 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 _cachedTask = Task.FromResult(ItemUpdateType.Unspecified);
+ public Task FetchVideoInfo(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 0000000000..8adb75839d
--- /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 ProbeVideo(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 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();
+
+ 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 chapters, List 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;
+ }
+
+ /// The chapters.
+ private void Fetch(Video video, List mediaStreams, BlurayDiscInfo stream, List 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
+
+ }));
+ }
+ }
+
+ ///
+ /// Gets information about the longest playlist on a bdrom
+ ///
+ /// The path.
+ /// VideoStream.
+ 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 SubtitleExtensions
+ {
+ get
+ {
+ return new[] { ".srt", ".ssa", ".ass" };
+ }
+ }
+
+ ///
+ /// Adds the external subtitles.
+ ///
+ /// The video.
+ /// The current streams.
+ private void AddExternalSubtitles(Video video, List 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();
+
+ 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);
+ }
+
+ ///
+ /// The dummy chapter duration
+ ///
+ private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
+
+ ///
+ /// Adds the dummy chapters.
+ ///
+ /// The video.
+ /// The chapters.
+ private void AddDummyChapters(Video video, List 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;
+ }
+ }
+
+ ///
+ /// Called when [pre fetch].
+ ///
+ /// The item.
+ /// The mount.
+ 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();
+ }
+
+ ///
+ /// Mounts the iso if needed.
+ ///
+ /// The item.
+ /// The cancellation token.
+ /// IsoMount.
+ protected Task MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
+ {
+ if (item.VideoType == VideoType.Iso)
+ {
+ return _isoManager.Mount(item.Path, cancellationToken);
+ }
+
+ return Task.FromResult(null);
+ }
+
+ ///
+ /// Determines the type of the iso.
+ ///
+ /// The iso mount.
+ /// System.Nullable{IsoType}.
+ 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 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();
+ }
+
+ 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 afbea7f8bf..8d69e6ba23 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 21d3ae3e8e..70b849e1c3 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
{
- ///
- /// The _locks
- ///
- private readonly ConcurrentDictionary _locks = new ConcurrentDictionary();
-
- ///
- /// The _media encoder
- ///
- 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;
- }
-
- ///
- /// Gets a value indicating whether [refresh on version change].
- ///
- /// true if [refresh on version change]; otherwise, false.
- protected override bool RefreshOnVersionChange
- {
- get
- {
- return true;
- }
- }
-
- ///
- /// Gets the provider version.
- ///
- /// The provider version.
- protected override string ProviderVersion
- {
- get
- {
- return "1";
- }
- }
-
- ///
- /// Supportses the specified item.
- ///
- /// The item.
- /// true if XXXX, false otherwise
- public override bool Supports(BaseItem item)
- {
- return item.LocationType == LocationType.FileSystem && item is Video;
- }
-
- ///
- /// Needses the refresh internal.
- ///
- /// The item.
- /// The provider info.
- /// true if XXXX, false otherwise
- 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;
}
///
@@ -94,16 +32,6 @@ namespace MediaBrowser.Providers.MediaInfo
/// true if XXXX, false otherwise
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
}
///
- /// Override this to return the date that should be compared to the last refresh date
- /// to determine if this provider should be re-fetched.
- ///
- /// The item.
- /// DateTime.
- protected override DateTime CompareDate(BaseItem item)
- {
- return item.DateModified;
- }
-
- ///
- /// Gets the priority.
+ /// The null mount task result
///
- /// The priority.
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.Last; }
- }
-
- public override ItemUpdateType ItemUpdateType
- {
- get
- {
- return ItemUpdateType.ImageUpdate;
- }
- }
+ protected readonly Task NullMountTaskResult = Task.FromResult(null);
///
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
+ /// Mounts the iso if needed.
///
/// The item.
- /// if set to true [force].
/// The cancellation token.
- /// Task{System.Boolean}.
- public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ /// Task{IIsoMount}.
+ protected Task 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;
}
- ///
- /// Extracts the image.
- ///
- /// The item.
- /// The cancellation token.
- /// Task.
- private async Task ExtractImage(Video item, CancellationToken cancellationToken)
+ public IEnumerable 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.Primary };
+ }
- // Image is already in the cache
- item.SetImagePath(ImageType.Primary, path);
+ public Task GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
+ {
+ return GetVideoImage((Video)item, cancellationToken);
}
- ///
- /// Extracts the image.
- ///
- /// The video.
- /// The path.
- /// The cancellation token.
- /// Task.
- private async Task ExtractImageInternal(Video video, string path, CancellationToken cancellationToken)
+ public async Task 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
}
}
- ///
- /// The null mount task result
- ///
- protected readonly Task NullMountTaskResult = Task.FromResult(null);
-
- ///
- /// Mounts the iso if needed.
- ///
- /// The item.
- /// The cancellation token.
- /// Task{IIsoMount}.
- protected Task MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
- {
- if (item.VideoType == VideoType.Iso)
- {
- return _isoManager.Mount(item.Path, cancellationToken);
- }
-
- return NullMountTaskResult;
- }
-
- ///
- /// Gets the lock.
- ///
- /// The filename.
- /// SemaphoreSlim.
- private SemaphoreSlim GetLock(string filename)
+ public string Name
{
- return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
+ get { return "Embedded Image"; }
}
- ///
- /// Gets the video images data path.
- ///
- /// The video images data path.
- public string VideoImagesPath
+ public bool Supports(IHasImages item)
{
- get
+ if (!_config.Configuration.EnableVideoImageExtraction)
{
- return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-video-images");
+ return false;
}
- }
- ///
- /// Gets the audio image path.
- ///
- /// The item.
- /// System.String.
- 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 87e6be1f7c..3adc682fca 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 67b62548e6..7ae6dffc79 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 61b73360cc..64038e8537 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
///
public class MovieXmlParser : BaseItemXmlParser