mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
converted movie providers to new system
This commit is contained in:
parent
64eb8c82a3
commit
821a3d29a2
@ -75,15 +75,7 @@ namespace MediaBrowser.Api.Library
|
||||
|
||||
if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
|
||||
{
|
||||
try
|
||||
{
|
||||
return c.PhysicalLocations;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, c.Path);
|
||||
}
|
||||
|
||||
return c.PhysicalLocations;
|
||||
}
|
||||
|
||||
return new List<string>();
|
||||
|
@ -331,21 +331,8 @@ namespace MediaBrowser.Api
|
||||
{
|
||||
if (item.Parent is AggregateFolder)
|
||||
{
|
||||
return user.RootFolder.GetChildren(user, true).FirstOrDefault(i =>
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
return i.LocationType == LocationType.FileSystem &&
|
||||
i.PhysicalLocations.Contains(item.Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, i.Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.LocationType == LocationType.FileSystem &&
|
||||
i.PhysicalLocations.Contains(item.Path));
|
||||
}
|
||||
|
||||
return item;
|
||||
|
@ -3,6 +3,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
public class AdultVideo : Video, IHasPreferredMetadataLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the preferred metadata language.
|
||||
/// </summary>
|
||||
/// <value>The preferred metadata language.</value>
|
||||
public string PreferredMetadataLanguage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
@ -11,6 +15,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
public class AggregateFolder : Folder
|
||||
{
|
||||
public AggregateFolder()
|
||||
{
|
||||
PhysicalLocationsList = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We don't support manual shortcuts
|
||||
/// </summary>
|
||||
@ -36,6 +45,60 @@ namespace MediaBrowser.Controller.Entities
|
||||
get { return _virtualChildren; }
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override IEnumerable<string> PhysicalLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return PhysicalLocationsList;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> PhysicalLocationsList { get; set; }
|
||||
|
||||
protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
|
||||
{
|
||||
return CreateResolveArgs().FileSystemChildren;
|
||||
}
|
||||
|
||||
private ItemResolveArgs CreateResolveArgs()
|
||||
{
|
||||
var path = ContainingFolderPath;
|
||||
|
||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
|
||||
{
|
||||
FileInfo = new DirectoryInfo(path),
|
||||
Path = path,
|
||||
Parent = Parent
|
||||
};
|
||||
|
||||
// Gather child folder and files
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
var isPhysicalRoot = args.IsPhysicalRoot;
|
||||
|
||||
// When resolving the root, we need it's grandchildren (children of user views)
|
||||
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
|
||||
|
||||
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
|
||||
|
||||
// Need to remove subpaths that may have been resolved from shortcuts
|
||||
// Example: if \\server\movies exists, then strip out \\server\movies\action
|
||||
if (isPhysicalRoot)
|
||||
{
|
||||
var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
|
||||
|
||||
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
|
||||
}
|
||||
|
||||
args.FileSystemDictionary = fileSystemDictionary;
|
||||
}
|
||||
|
||||
PhysicalLocationsList = args.PhysicalLocations.ToList();
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the virtual child.
|
||||
/// </summary>
|
||||
|
@ -1,12 +1,10 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Localization;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
@ -102,6 +100,35 @@ namespace MediaBrowser.Controller.Entities
|
||||
[IgnoreDataMember]
|
||||
protected internal bool IsOffline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the folder containing the item.
|
||||
/// If the item is a folder, it returns the folder itself
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
public virtual string ContainingFolderPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsFolder)
|
||||
{
|
||||
return Path;
|
||||
}
|
||||
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public bool IsOwnedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
// Local trailer, special feature, theme video, etc.
|
||||
// An item that belongs to another item but is not part of the Parent-Child tree
|
||||
return !IsFolder && Parent == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the location.
|
||||
/// </summary>
|
||||
@ -189,19 +216,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The locked fields.</value>
|
||||
public List<MetadataFields> LockedFields { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be overridden to return the proper folder where metadata lives
|
||||
/// </summary>
|
||||
/// <value>The meta location.</value>
|
||||
[IgnoreDataMember]
|
||||
public virtual string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the media.
|
||||
/// </summary>
|
||||
@ -215,160 +229,19 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _resolve args
|
||||
/// </summary>
|
||||
private ItemResolveArgs _resolveArgs;
|
||||
/// <summary>
|
||||
/// We attach these to the item so that we only ever have to hit the file system once
|
||||
/// (this includes the children of the containing folder)
|
||||
/// </summary>
|
||||
/// <value>The resolve args.</value>
|
||||
[IgnoreDataMember]
|
||||
public ItemResolveArgs ResolveArgs
|
||||
public virtual IEnumerable<string> PhysicalLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_resolveArgs == null)
|
||||
var locationType = LocationType;
|
||||
|
||||
if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
|
||||
{
|
||||
try
|
||||
{
|
||||
_resolveArgs = CreateResolveArgs();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error creating resolve args for {0}", ex, Path);
|
||||
|
||||
IsOffline = true;
|
||||
|
||||
throw;
|
||||
}
|
||||
return new string[] { };
|
||||
}
|
||||
|
||||
return _resolveArgs;
|
||||
}
|
||||
set
|
||||
{
|
||||
_resolveArgs = value;
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<string> PhysicalLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResolveArgs.PhysicalLocations;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the resolve args.
|
||||
/// </summary>
|
||||
/// <param name="pathInfo">The path info.</param>
|
||||
public void ResetResolveArgs(FileSystemInfo pathInfo)
|
||||
{
|
||||
ResetResolveArgs(CreateResolveArgs(pathInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the resolve args.
|
||||
/// </summary>
|
||||
public void ResetResolveArgs()
|
||||
{
|
||||
_resolveArgs = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the resolve args.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
public void ResetResolveArgs(ItemResolveArgs args)
|
||||
{
|
||||
_resolveArgs = args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates ResolveArgs on demand
|
||||
/// </summary>
|
||||
/// <param name="pathInfo">The path info.</param>
|
||||
/// <returns>ItemResolveArgs.</returns>
|
||||
/// <exception cref="System.IO.IOException">Unable to retrieve file system info for + path</exception>
|
||||
protected internal virtual ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
|
||||
{
|
||||
var path = Path;
|
||||
|
||||
var locationType = LocationType;
|
||||
|
||||
if (locationType == LocationType.Remote ||
|
||||
locationType == LocationType.Virtual)
|
||||
{
|
||||
return new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager);
|
||||
}
|
||||
|
||||
var isDirectory = false;
|
||||
|
||||
if (UseParentPathToCreateResolveArgs)
|
||||
{
|
||||
path = System.IO.Path.GetDirectoryName(path);
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
pathInfo = pathInfo ?? (isDirectory ? new DirectoryInfo(path) : FileSystem.GetFileSystemInfo(path));
|
||||
|
||||
if (pathInfo == null || !pathInfo.Exists)
|
||||
{
|
||||
throw new IOException("Unable to retrieve file system info for " + path);
|
||||
}
|
||||
|
||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
|
||||
{
|
||||
FileInfo = pathInfo,
|
||||
Path = path,
|
||||
Parent = Parent
|
||||
};
|
||||
|
||||
// Gather child folder and files
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
var isPhysicalRoot = args.IsPhysicalRoot;
|
||||
|
||||
// When resolving the root, we need it's grandchildren (children of user views)
|
||||
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
|
||||
|
||||
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
|
||||
|
||||
// Need to remove subpaths that may have been resolved from shortcuts
|
||||
// Example: if \\server\movies exists, then strip out \\server\movies\action
|
||||
if (isPhysicalRoot)
|
||||
{
|
||||
var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
|
||||
|
||||
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
|
||||
}
|
||||
|
||||
args.FileSystemDictionary = fileSystemDictionary;
|
||||
}
|
||||
|
||||
//update our dates
|
||||
EntityResolutionHelper.EnsureDates(FileSystem, this, args, false);
|
||||
|
||||
IsOffline = false;
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some subclasses will stop resolving at a directory and point their Path to a file within. This will help ensure the on-demand resolve args are identical to the
|
||||
/// original ones.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
|
||||
[IgnoreDataMember]
|
||||
protected virtual bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
return new[] { Path };
|
||||
}
|
||||
}
|
||||
|
||||
@ -583,122 +456,93 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// Loads local trailers from the file system
|
||||
/// </summary>
|
||||
/// <returns>List{Video}.</returns>
|
||||
private IEnumerable<Trailer> LoadLocalTrailers()
|
||||
private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
ItemResolveArgs resolveArgs;
|
||||
return new List<Trailer>();
|
||||
//ItemResolveArgs resolveArgs;
|
||||
|
||||
try
|
||||
{
|
||||
resolveArgs = ResolveArgs;
|
||||
//try
|
||||
//{
|
||||
// resolveArgs = ResolveArgs;
|
||||
|
||||
if (!resolveArgs.IsDirectory)
|
||||
{
|
||||
return new List<Trailer>();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return new List<Trailer>();
|
||||
}
|
||||
// if (!resolveArgs.IsDirectory)
|
||||
// {
|
||||
// return new List<Trailer>();
|
||||
// }
|
||||
//}
|
||||
//catch (IOException ex)
|
||||
//{
|
||||
// Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
// return new List<Trailer>();
|
||||
//}
|
||||
|
||||
var files = new List<FileSystemInfo>();
|
||||
//var files = new List<FileSystemInfo>();
|
||||
|
||||
var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
|
||||
//var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
|
||||
|
||||
// Path doesn't exist. No biggie
|
||||
if (folder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading trailers for {0}", ex, Name);
|
||||
}
|
||||
}
|
||||
//// Path doesn't exist. No biggie
|
||||
//if (folder != null)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
|
||||
// }
|
||||
// catch (IOException ex)
|
||||
// {
|
||||
// Logger.ErrorException("Error loading trailers for {0}", ex, Name);
|
||||
// }
|
||||
//}
|
||||
|
||||
// Support xbmc trailers (-trailer suffix on video file names)
|
||||
files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
|
||||
{
|
||||
if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
|
||||
}
|
||||
//// Support xbmc trailers (-trailer suffix on video file names)
|
||||
//files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
|
||||
// {
|
||||
// if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (IOException ex)
|
||||
// {
|
||||
// Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
|
||||
// }
|
||||
|
||||
return false;
|
||||
}));
|
||||
// return false;
|
||||
//}));
|
||||
|
||||
return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
|
||||
//return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
|
||||
//{
|
||||
// // Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
// var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(video.ResolveArgs);
|
||||
video = dbItem;
|
||||
}
|
||||
// if (dbItem != null)
|
||||
// {
|
||||
// video = dbItem;
|
||||
// }
|
||||
|
||||
return video;
|
||||
// return video;
|
||||
|
||||
}).ToList();
|
||||
//}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the theme songs.
|
||||
/// </summary>
|
||||
/// <returns>List{Audio.Audio}.</returns>
|
||||
private IEnumerable<Audio.Audio> LoadThemeSongs()
|
||||
private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
ItemResolveArgs resolveArgs;
|
||||
|
||||
try
|
||||
{
|
||||
resolveArgs = ResolveArgs;
|
||||
|
||||
if (!resolveArgs.IsDirectory)
|
||||
{
|
||||
return new List<Audio.Audio>();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return new List<Audio.Audio>();
|
||||
}
|
||||
|
||||
var files = new List<FileSystemInfo>();
|
||||
|
||||
var folder = resolveArgs.GetFileSystemEntryByName(ThemeSongsFolderName);
|
||||
|
||||
// Path doesn't exist. No biggie
|
||||
if (folder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading theme songs for {0}", ex, Name);
|
||||
}
|
||||
}
|
||||
var files = fileSystemChildren.OfType<DirectoryInfo>()
|
||||
.Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
|
||||
.ToList();
|
||||
|
||||
// Support plex/xbmc convention
|
||||
files.AddRange(resolveArgs.FileSystemChildren
|
||||
.Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsAudioFile(i.Name))
|
||||
files.AddRange(fileSystemChildren.OfType<FileInfo>()
|
||||
.Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
|
||||
);
|
||||
|
||||
return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio =>
|
||||
@ -708,7 +552,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(audio.ResolveArgs);
|
||||
audio = dbItem;
|
||||
}
|
||||
|
||||
@ -720,44 +563,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// Loads the video backdrops.
|
||||
/// </summary>
|
||||
/// <returns>List{Video}.</returns>
|
||||
private IEnumerable<Video> LoadThemeVideos()
|
||||
private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
ItemResolveArgs resolveArgs;
|
||||
|
||||
try
|
||||
{
|
||||
resolveArgs = ResolveArgs;
|
||||
|
||||
if (!resolveArgs.IsDirectory)
|
||||
{
|
||||
return new List<Video>();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return new List<Video>();
|
||||
}
|
||||
|
||||
var folder = resolveArgs.GetFileSystemEntryByName(ThemeVideosFolderName);
|
||||
|
||||
// Path doesn't exist. No biggie
|
||||
if (folder == null)
|
||||
{
|
||||
return new List<Video>();
|
||||
}
|
||||
|
||||
IEnumerable<FileSystemInfo> files;
|
||||
|
||||
try
|
||||
{
|
||||
files = new DirectoryInfo(folder.FullName).EnumerateFiles();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading video backdrops for {0}", ex, Name);
|
||||
return new List<Video>();
|
||||
}
|
||||
var files = fileSystemChildren.OfType<DirectoryInfo>()
|
||||
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
|
||||
|
||||
return LibraryManager.ResolvePaths<Video>(files, null).Select(item =>
|
||||
{
|
||||
@ -766,7 +576,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(item.ResolveArgs);
|
||||
item = dbItem;
|
||||
}
|
||||
|
||||
@ -774,9 +583,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public Task<bool> RefreshMetadata(CancellationToken cancellationToken, bool resetResolveArgs = true)
|
||||
public Task RefreshMetadata(CancellationToken cancellationToken)
|
||||
{
|
||||
return RefreshMetadata(new MetadataRefreshOptions { ResetResolveArgs = resetResolveArgs }, cancellationToken);
|
||||
return RefreshMetadata(new MetadataRefreshOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -785,35 +594,24 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>true if a provider reports we changed</returns>
|
||||
public async Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
public async Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
if (options.ResetResolveArgs)
|
||||
var locationType = LocationType;
|
||||
|
||||
if (IsFolder || Parent != null)
|
||||
{
|
||||
// Reload this
|
||||
ResetResolveArgs();
|
||||
var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ?
|
||||
GetFileSystemChildren().ToList() :
|
||||
new List<FileSystemInfo>();
|
||||
|
||||
await BeforeRefreshMetadata(options, files, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await BeforeRefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly Task _cachedTask = Task.FromResult(true);
|
||||
protected virtual Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
return _cachedTask;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public virtual async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
{
|
||||
// Refresh for the item
|
||||
var itemRefreshTask = ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var themeSongsChanged = false;
|
||||
|
||||
var themeVideosChanged = false;
|
||||
@ -825,102 +623,83 @@ namespace MediaBrowser.Controller.Entities
|
||||
var hasThemeMedia = this as IHasThemeMedia;
|
||||
if (hasThemeMedia != null)
|
||||
{
|
||||
themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
if (!IsInMixedFolder)
|
||||
{
|
||||
themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
var hasTrailers = this as IHasTrailers;
|
||||
if (hasTrailers != null)
|
||||
{
|
||||
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get the result from the item task
|
||||
var updateReason = await itemRefreshTask.ConfigureAwait(false);
|
||||
|
||||
var changed = updateReason.HasValue;
|
||||
|
||||
if (changed || forceSave || themeSongsChanged || themeVideosChanged || localTrailersChanged)
|
||||
|
||||
if (themeSongsChanged || themeVideosChanged || localTrailersChanged)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await LibraryManager.UpdateItem(this, updateReason ?? ItemUpdateType.Unspecified, cancellationToken).ConfigureAwait(false);
|
||||
options.ForceSave = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren()
|
||||
{
|
||||
var newItems = LoadLocalTrailers().ToList();
|
||||
var path = ContainingFolderPath;
|
||||
|
||||
return new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newItems = LoadLocalTrailers(fileSystemChildren).ToList();
|
||||
var newItemIds = newItems.Select(i => i.Id).ToList();
|
||||
|
||||
var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds);
|
||||
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh,
|
||||
ResetResolveArgs = false
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
item.LocalTrailerIds = newItemIds;
|
||||
|
||||
return itemsChanged || results.Contains(true);
|
||||
return itemsChanged;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newThemeVideos = LoadThemeVideos().ToList();
|
||||
var newThemeVideos = LoadThemeVideos(fileSystemChildren).ToList();
|
||||
var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList();
|
||||
|
||||
var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
|
||||
|
||||
var tasks = newThemeVideos.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh,
|
||||
ResetResolveArgs = false
|
||||
var tasks = newThemeVideos.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
item.ThemeVideoIds = newThemeVideoIds;
|
||||
|
||||
return themeVideosChanged || results.Contains(true);
|
||||
return themeVideosChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the theme songs.
|
||||
/// </summary>
|
||||
private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newThemeSongs = LoadThemeSongs().ToList();
|
||||
var newThemeSongs = LoadThemeSongs(fileSystemChildren).ToList();
|
||||
var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList();
|
||||
|
||||
var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds);
|
||||
|
||||
var tasks = newThemeSongs.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh,
|
||||
ResetResolveArgs = false
|
||||
var tasks = newThemeSongs.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
item.ThemeSongIds = newThemeSongIds;
|
||||
|
||||
return themeSongsChanged || results.Contains(true);
|
||||
return themeSongsChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1655,27 +1434,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
throw new ArgumentNullException("imagePath");
|
||||
}
|
||||
|
||||
var locationType = LocationType;
|
||||
|
||||
if (locationType == LocationType.Remote ||
|
||||
locationType == LocationType.Virtual)
|
||||
{
|
||||
return FileSystem.GetLastWriteTimeUtc(imagePath);
|
||||
}
|
||||
|
||||
var metaFileEntry = ResolveArgs.GetMetaFileByPath(imagePath);
|
||||
|
||||
// If we didn't the metafile entry, check the Season
|
||||
if (metaFileEntry == null)
|
||||
{
|
||||
if (Parent != null)
|
||||
{
|
||||
metaFileEntry = Parent.ResolveArgs.GetMetaFileByPath(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// See if we can avoid a file system lookup by looking for the file in ResolveArgs
|
||||
return metaFileEntry == null ? FileSystem.GetLastWriteTimeUtc(imagePath) : FileSystem.GetLastWriteTimeUtc(metaFileEntry);
|
||||
return FileSystem.GetLastWriteTimeUtc(imagePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -29,25 +29,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The preferred metadata country code.</value>
|
||||
public string PreferredMetadataCountryCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
return !IsInMixedFolder;
|
||||
}
|
||||
}
|
||||
|
||||
public Book()
|
||||
{
|
||||
Tags = new List<string>();
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -14,6 +16,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
public class CollectionFolder : Folder, ICollectionFolder
|
||||
{
|
||||
public CollectionFolder()
|
||||
{
|
||||
PhysicalLocationsList = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is virtual folder.
|
||||
/// </summary>
|
||||
@ -42,6 +49,60 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override IEnumerable<string> PhysicalLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return PhysicalLocationsList;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> PhysicalLocationsList { get; set; }
|
||||
|
||||
protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
|
||||
{
|
||||
return CreateResolveArgs().FileSystemChildren;
|
||||
}
|
||||
|
||||
private ItemResolveArgs CreateResolveArgs()
|
||||
{
|
||||
var path = ContainingFolderPath;
|
||||
|
||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
|
||||
{
|
||||
FileInfo = new DirectoryInfo(path),
|
||||
Path = path,
|
||||
Parent = Parent
|
||||
};
|
||||
|
||||
// Gather child folder and files
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
var isPhysicalRoot = args.IsPhysicalRoot;
|
||||
|
||||
// When resolving the root, we need it's grandchildren (children of user views)
|
||||
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
|
||||
|
||||
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
|
||||
|
||||
// Need to remove subpaths that may have been resolved from shortcuts
|
||||
// Example: if \\server\movies exists, then strip out \\server\movies\action
|
||||
if (isPhysicalRoot)
|
||||
{
|
||||
var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
|
||||
|
||||
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
|
||||
}
|
||||
|
||||
args.FileSystemDictionary = fileSystemDictionary;
|
||||
}
|
||||
|
||||
PhysicalLocationsList = args.PhysicalLocations.ToList();
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
// Cache this since it will be used a lot
|
||||
/// <summary>
|
||||
/// The null task result
|
||||
@ -59,13 +120,14 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <returns>Task.</returns>
|
||||
protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
|
||||
{
|
||||
CreateResolveArgs();
|
||||
ResetDynamicChildren();
|
||||
|
||||
return NullTaskResult;
|
||||
}
|
||||
|
||||
private List<LinkedChild> _linkedChildren;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Our children are actually just references to the ones in the physical root...
|
||||
/// </summary>
|
||||
|
@ -1,6 +1,7 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Localization;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public List<Guid> ThemeSongIds { get; set; }
|
||||
public List<Guid> ThemeVideoIds { get; set; }
|
||||
|
||||
|
||||
public Folder()
|
||||
{
|
||||
LinkedChildren = new List<LinkedChild>();
|
||||
@ -379,7 +380,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
nonCachedChildren = new BaseItem[] {};
|
||||
nonCachedChildren = new BaseItem[] { };
|
||||
|
||||
Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
|
||||
}
|
||||
@ -402,8 +403,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (currentChildren.TryGetValue(child.Id, out currentChild))
|
||||
{
|
||||
currentChild.ResetResolveArgs(child.ResolveArgs);
|
||||
|
||||
//existing item - check if it has changed
|
||||
if (currentChild.HasChanged(child))
|
||||
{
|
||||
@ -411,7 +410,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
if (currentChildLocationType != LocationType.Remote &&
|
||||
currentChildLocationType != LocationType.Virtual)
|
||||
{
|
||||
EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
|
||||
currentChild.DateModified = child.DateModified;
|
||||
}
|
||||
|
||||
currentChild.IsInMixedFolder = child.IsInMixedFolder;
|
||||
@ -539,8 +538,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
await child.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = currentTuple.Item2,
|
||||
ReplaceAllMetadata = forceRefreshMetadata,
|
||||
ResetResolveArgs = false
|
||||
ReplaceAllMetadata = forceRefreshMetadata
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@ -581,16 +579,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
});
|
||||
|
||||
await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
// Some folder providers are unable to refresh until children have been refreshed.
|
||||
await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -661,14 +649,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||
protected virtual IEnumerable<BaseItem> GetNonCachedChildren()
|
||||
{
|
||||
var resolveArgs = ResolveArgs;
|
||||
|
||||
if (resolveArgs == null || resolveArgs.FileSystemDictionary == null)
|
||||
{
|
||||
Logger.Error("ResolveArgs null for {0}", Path);
|
||||
}
|
||||
|
||||
return LibraryManager.ResolvePaths<BaseItem>(resolveArgs.FileSystemChildren, this);
|
||||
return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(), this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -914,43 +895,29 @@ namespace MediaBrowser.Controller.Entities
|
||||
return item;
|
||||
}
|
||||
|
||||
protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
if (SupportsShortcutChildren && LocationType == LocationType.FileSystem)
|
||||
{
|
||||
RefreshLinkedChildren();
|
||||
if (RefreshLinkedChildren(fileSystemChildren))
|
||||
{
|
||||
options.ForceSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.BeforeRefreshMetadata(options, cancellationToken);
|
||||
return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the linked children.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
private bool RefreshLinkedChildren()
|
||||
private bool RefreshLinkedChildren(IEnumerable<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
ItemResolveArgs resolveArgs;
|
||||
|
||||
try
|
||||
{
|
||||
resolveArgs = ResolveArgs;
|
||||
|
||||
if (!resolveArgs.IsDirectory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
|
||||
var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
|
||||
|
||||
var newShortcutLinks = resolveArgs.FileSystemChildren
|
||||
var newShortcutLinks = fileSystemChildren
|
||||
.Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName))
|
||||
.Select(i =>
|
||||
{
|
||||
@ -1058,7 +1025,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return this;
|
||||
}
|
||||
|
||||
if (locationType != LocationType.Virtual && ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
if (locationType != LocationType.Virtual && PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
@ -79,17 +79,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The game system.</value>
|
||||
public string GameSystem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is multi part.
|
||||
/// </summary>
|
||||
@ -101,17 +90,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
public List<string> MultiPartGameFiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
return !IsInMixedFolder;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetUserDataKey()
|
||||
{
|
||||
var id = this.GetProviderId(MetadataProviders.Gamesdb);
|
||||
|
@ -99,6 +99,12 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
/// <value>The backdrop image paths.</value>
|
||||
List<string> BackdropImagePaths { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is owned item.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
|
||||
bool IsOwnedItem { get; }
|
||||
}
|
||||
|
||||
public static class HasImagesExtensions
|
||||
|
@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
||||
public List<Guid> ThemeSongIds { get; set; }
|
||||
public List<Guid> ThemeVideoIds { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the preferred metadata country code.
|
||||
/// </summary>
|
||||
@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
public string PreferredMetadataCountryCode { get; set; }
|
||||
|
||||
public string PreferredMetadataLanguage { get; set; }
|
||||
|
||||
|
||||
public Movie()
|
||||
{
|
||||
SpecialFeatureIds = new List<Guid>();
|
||||
@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
||||
public List<Guid> LocalTrailerIds { get; set; }
|
||||
public List<string> Keywords { get; set; }
|
||||
|
||||
|
||||
public List<MediaUrl> RemoteTrailers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -103,88 +103,48 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the base implementation to refresh metadata for special features
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="forceSave">if set to <c>true</c> [is new item].</param>
|
||||
/// <param name="forceRefresh">if set to <c>true</c> [force].</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
// Kick off a task to refresh the main item
|
||||
var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
|
||||
var specialFeaturesChanged = false;
|
||||
await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Must have a parent to have special features
|
||||
// In other words, it must be part of the Parent/Child tree
|
||||
if (LocationType == LocationType.FileSystem && Parent != null && !IsInMixedFolder)
|
||||
{
|
||||
specialFeaturesChanged = await RefreshSpecialFeatures(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
}
|
||||
var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return specialFeaturesChanged || result;
|
||||
if (specialFeaturesChanged)
|
||||
{
|
||||
options.ForceSave = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshSpecialFeatures(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true)
|
||||
private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newItems = LoadSpecialFeatures().ToList();
|
||||
var newItems = LoadSpecialFeatures(fileSystemChildren).ToList();
|
||||
var newItemIds = newItems.Select(i => i.Id).ToList();
|
||||
|
||||
var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds);
|
||||
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh,
|
||||
ResetResolveArgs = false
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
SpecialFeatureIds = newItemIds;
|
||||
|
||||
return itemsChanged || results.Contains(true);
|
||||
return itemsChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the special features.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Video}.</returns>
|
||||
private IEnumerable<Video> LoadSpecialFeatures()
|
||||
private IEnumerable<Video> LoadSpecialFeatures(IEnumerable<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
FileSystemInfo folder;
|
||||
|
||||
try
|
||||
{
|
||||
folder = ResolveArgs.GetFileSystemEntryByName("extras") ??
|
||||
ResolveArgs.GetFileSystemEntryByName("specials");
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return new List<Video>();
|
||||
}
|
||||
|
||||
// Path doesn't exist. No biggie
|
||||
if (folder == null)
|
||||
{
|
||||
return new List<Video>();
|
||||
}
|
||||
|
||||
IEnumerable<FileSystemInfo> files;
|
||||
|
||||
try
|
||||
{
|
||||
files = new DirectoryInfo(folder.FullName).EnumerateFiles();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading special features for {0}", ex, Name);
|
||||
return new List<Video>();
|
||||
}
|
||||
var files = fileSystemChildren.OfType<DirectoryInfo>()
|
||||
.Where(i => string.Equals(i.Name, "extras", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "specials", StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
|
||||
|
||||
return LibraryManager.ResolvePaths<Video>(files, null).Select(video =>
|
||||
{
|
||||
@ -193,7 +153,6 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(video.ResolveArgs);
|
||||
video = dbItem;
|
||||
}
|
||||
|
||||
|
@ -11,28 +11,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
/// </summary>
|
||||
public class Episode : Video
|
||||
{
|
||||
/// <summary>
|
||||
/// Episodes have a special Metadata folder
|
||||
/// </summary>
|
||||
/// <value>The meta location.</value>
|
||||
[IgnoreDataMember]
|
||||
public override string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return System.IO.Path.Combine(Parent.Path, "metadata");
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the season in which it aired.
|
||||
/// </summary>
|
||||
|
@ -131,34 +131,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add files from the metadata folder to ResolveArgs
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
public static void AddMetadataFiles(ItemResolveArgs args)
|
||||
{
|
||||
var folder = args.GetFileSystemEntryByName("metadata");
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
args.AddMetadataFiles(new DirectoryInfo(folder.FullName).EnumerateFiles());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates ResolveArgs on demand
|
||||
/// </summary>
|
||||
/// <param name="pathInfo">The path info.</param>
|
||||
/// <returns>ItemResolveArgs.</returns>
|
||||
protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
|
||||
{
|
||||
var args = base.CreateResolveArgs(pathInfo);
|
||||
|
||||
AddMetadataFiles(args);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the name of the sort.
|
||||
/// </summary>
|
||||
|
@ -111,20 +111,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates ResolveArgs on demand
|
||||
/// </summary>
|
||||
/// <param name="pathInfo">The path info.</param>
|
||||
/// <returns>ItemResolveArgs.</returns>
|
||||
protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
|
||||
{
|
||||
var args = base.CreateResolveArgs(pathInfo);
|
||||
|
||||
Season.AddMetadataFiles(args);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public bool ContainsEpisodesWithoutSeasonFolders
|
||||
{
|
||||
|
@ -89,33 +89,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should be overridden to return the proper folder where metadata lives
|
||||
/// </summary>
|
||||
/// <value>The meta location.</value>
|
||||
[IgnoreDataMember]
|
||||
public override string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsLocalTrailer)
|
||||
{
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
|
||||
return base.MetaLocation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Needed because the resolver stops at the trailer folder and we find the video inside.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get { return !IsLocalTrailer; }
|
||||
}
|
||||
|
||||
public override string GetUserDataKey()
|
||||
{
|
||||
var key = this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tvcom);
|
||||
|
@ -84,33 +84,23 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The aspect ratio.</value>
|
||||
public string AspectRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be overridden to return the proper folder where metadata lives
|
||||
/// </summary>
|
||||
/// <value>The meta location.</value>
|
||||
[IgnoreDataMember]
|
||||
public override string MetaLocation
|
||||
public override string ContainingFolderPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Needed because the resolver stops at the movie folder and we find the video inside.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsInMixedFolder)
|
||||
if (IsMultiPart)
|
||||
{
|
||||
return false;
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
|
||||
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart;
|
||||
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd ||
|
||||
VideoType == VideoType.HdDvd)
|
||||
{
|
||||
return Path;
|
||||
}
|
||||
|
||||
return base.ContainingFolderPath;
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,106 +149,73 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the base implementation to refresh metadata for local trailers
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="forceSave">if set to <c>true</c> [is new item].</param>
|
||||
/// <param name="forceRefresh">if set to <c>true</c> [force].</param>
|
||||
/// <returns>true if a provider reports we changed</returns>
|
||||
public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
// Kick off a task to refresh the main item
|
||||
var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
|
||||
var additionalPartsChanged = false;
|
||||
await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Must have a parent to have additional parts
|
||||
// In other words, it must be part of the Parent/Child tree
|
||||
// The additional parts won't have additional parts themselves
|
||||
if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null)
|
||||
{
|
||||
try
|
||||
var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (additionalPartsChanged)
|
||||
{
|
||||
additionalPartsChanged = await RefreshAdditionalParts(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading additional parts for {0}.", ex, Name);
|
||||
options.ForceSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
return additionalPartsChanged || result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the additional parts.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="fileSystemChildren">The file system children.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="forceSave">if set to <c>true</c> [force save].</param>
|
||||
/// <param name="forceRefresh">if set to <c>true</c> [force refresh].</param>
|
||||
/// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
private async Task<bool> RefreshAdditionalParts(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true)
|
||||
private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newItems = LoadAdditionalParts().ToList();
|
||||
var newItems = LoadAdditionalParts(fileSystemChildren).ToList();
|
||||
|
||||
var newItemIds = newItems.Select(i => i.Id).ToList();
|
||||
|
||||
var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds);
|
||||
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
AdditionalPartIds = newItemIds;
|
||||
|
||||
return itemsChanged || results.Contains(true);
|
||||
return itemsChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the additional parts.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Video}.</returns>
|
||||
private IEnumerable<Video> LoadAdditionalParts()
|
||||
private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
IEnumerable<FileSystemInfo> files;
|
||||
|
||||
var path = Path;
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ApplicationException(string.Format("Item {0} has a null path.", Name ?? Id.ToString()));
|
||||
}
|
||||
|
||||
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
|
||||
{
|
||||
var parentPath = System.IO.Path.GetDirectoryName(path);
|
||||
|
||||
if (string.IsNullOrEmpty(parentPath))
|
||||
files = fileSystemChildren.Where(i =>
|
||||
{
|
||||
throw new IOException("Unable to get parent path info from " + path);
|
||||
}
|
||||
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
|
||||
}
|
||||
|
||||
files = new DirectoryInfo(parentPath)
|
||||
.EnumerateDirectories()
|
||||
.Where(i => !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFile(i.Name));
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var resolveArgs = ResolveArgs;
|
||||
|
||||
if (resolveArgs == null)
|
||||
{
|
||||
throw new IOException("ResolveArgs are null for " + path);
|
||||
}
|
||||
|
||||
files = resolveArgs.FileSystemChildren.Where(i =>
|
||||
files = fileSystemChildren.Where(i =>
|
||||
{
|
||||
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
@ -276,7 +233,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(video.ResolveArgs);
|
||||
video = dbItem;
|
||||
}
|
||||
|
||||
|
@ -195,77 +195,6 @@ namespace MediaBrowser.Controller.Library
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store these to reduce disk access in Resolvers
|
||||
/// </summary>
|
||||
/// <value>The metadata file dictionary.</value>
|
||||
private Dictionary<string, FileSystemInfo> MetadataFileDictionary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the metadata files.
|
||||
/// </summary>
|
||||
/// <value>The metadata files.</value>
|
||||
public IEnumerable<FileSystemInfo> MetadataFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MetadataFileDictionary != null)
|
||||
{
|
||||
return MetadataFileDictionary.Values;
|
||||
}
|
||||
|
||||
return new FileSystemInfo[] { };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the metadata file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <exception cref="System.IO.FileNotFoundException"></exception>
|
||||
public void AddMetadataFile(string path)
|
||||
{
|
||||
var file = new FileInfo(path);
|
||||
|
||||
if (!file.Exists)
|
||||
{
|
||||
throw new FileNotFoundException(path);
|
||||
}
|
||||
|
||||
AddMetadataFile(file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the metadata file.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo">The file info.</param>
|
||||
public void AddMetadataFile(FileSystemInfo fileInfo)
|
||||
{
|
||||
AddMetadataFiles(new[] { fileInfo });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the metadata files.
|
||||
/// </summary>
|
||||
/// <param name="files">The files.</param>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public void AddMetadataFiles(IEnumerable<FileSystemInfo> files)
|
||||
{
|
||||
if (files == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (MetadataFileDictionary == null)
|
||||
{
|
||||
MetadataFileDictionary = new Dictionary<string, FileSystemInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
foreach (var file in files)
|
||||
{
|
||||
MetadataFileDictionary[file.Name] = file;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the file system entry by.
|
||||
/// </summary>
|
||||
@ -320,16 +249,6 @@ namespace MediaBrowser.Controller.Library
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (MetadataFileDictionary != null)
|
||||
{
|
||||
FileSystemInfo entry;
|
||||
|
||||
if (MetadataFileDictionary.TryGetValue(System.IO.Path.GetFileName(path), out entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return GetFileSystemEntryByPath(path);
|
||||
}
|
||||
@ -346,16 +265,6 @@ namespace MediaBrowser.Controller.Library
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (MetadataFileDictionary != null)
|
||||
{
|
||||
FileSystemInfo entry;
|
||||
|
||||
if (MetadataFileDictionary.TryGetValue(name, out entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return GetFileSystemEntryByName(name);
|
||||
}
|
||||
|
@ -19,6 +19,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
|
||||
bool IsParentalAllowed(User user);
|
||||
|
||||
Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
|
||||
Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class BaseItemXmlParser<T>
|
||||
where T : BaseItem, new()
|
||||
where T : BaseItem
|
||||
{
|
||||
/// <summary>
|
||||
/// The logger
|
||||
@ -422,11 +422,7 @@ namespace MediaBrowser.Controller.Providers
|
||||
int runtime;
|
||||
if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out runtime))
|
||||
{
|
||||
// For audio and video don't replace ffmpeg data
|
||||
if (!(item is Video || item is Audio))
|
||||
{
|
||||
item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
|
||||
}
|
||||
item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -2,13 +2,8 @@
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -146,19 +141,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
providerInfo.LastRefreshed = value;
|
||||
providerInfo.LastRefreshStatus = status;
|
||||
providerInfo.ProviderVersion = providerVersion;
|
||||
|
||||
// Save the file system stamp for future comparisons
|
||||
if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
try
|
||||
{
|
||||
providerInfo.FileStamp = GetCurrentFileSystemStamp(item);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting file stamp for {0}", ex, item.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -233,12 +215,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem &&
|
||||
HasFileSystemStampChanged(item, providerInfo))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RefreshOnVersionChange && !String.Equals(ProviderVersion, providerInfo.ProviderVersion))
|
||||
{
|
||||
return true;
|
||||
@ -263,17 +239,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
return CompareDate(item) > providerInfo.LastRefreshed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the item's file system stamp has changed from the last time the provider refreshed
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="providerInfo">The provider info.</param>
|
||||
/// <returns><c>true</c> if [has file system stamp changed] [the specified item]; otherwise, <c>false</c>.</returns>
|
||||
protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo)
|
||||
{
|
||||
return GetCurrentFileSystemStamp(item) != providerInfo.FileStamp;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
@ -301,159 +266,5 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public abstract MetadataProviderPriority Priority { get; }
|
||||
|
||||
/// <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 virtual bool RefreshOnFileSystemStampChange
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string[] FilestampExtensions
|
||||
{
|
||||
get { return new string[] { }; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the parent's file system stamp should be used for comparison
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
protected virtual bool UseParentFileSystemStamp(BaseItem item)
|
||||
{
|
||||
// True when the current item is just a file
|
||||
return !item.ResolveArgs.IsDirectory;
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
|
||||
{
|
||||
if (UseParentFileSystemStamp(item) && item.Parent != null)
|
||||
{
|
||||
return new[] { item.Parent };
|
||||
}
|
||||
|
||||
return new[] { item };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item's current file system stamp
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>Guid.</returns>
|
||||
private Guid GetCurrentFileSystemStamp(BaseItem item)
|
||||
{
|
||||
return GetFileSystemStamp(GetItemsForFileStampComparison(item));
|
||||
}
|
||||
|
||||
private Dictionary<string, string> _fileStampExtensionsDictionary;
|
||||
|
||||
private Dictionary<string, string> FileStampExtensionsDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
return _fileStampExtensionsDictionary ??
|
||||
(_fileStampExtensionsDictionary =
|
||||
FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file system stamp.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <returns>Guid.</returns>
|
||||
protected virtual Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var extensions = FileStampExtensionsDictionary;
|
||||
var numExtensions = FilestampExtensions.Length;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
// If there's no path or the item is a file, there's nothing to do
|
||||
if (item.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
var resolveArgs = item.ResolveArgs;
|
||||
|
||||
if (resolveArgs.IsDirectory)
|
||||
{
|
||||
// Record the name of each file
|
||||
// Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
|
||||
AddFiles(sb, resolveArgs.FileSystemChildren, extensions, numExtensions);
|
||||
AddFiles(sb, resolveArgs.MetadataFiles, extensions, numExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stamp = sb.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(stamp))
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return stamp.GetMD5();
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> FoldersToMonitor = new[] { "extrafanart", "extrathumbs" }
|
||||
.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
protected Guid GetFileSystemStamp(IEnumerable<FileSystemInfo> files)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var extensions = FileStampExtensionsDictionary;
|
||||
var numExtensions = FilestampExtensions.Length;
|
||||
|
||||
// Record the name of each file
|
||||
// Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
|
||||
AddFiles(sb, files, extensions, numExtensions);
|
||||
|
||||
return sb.ToString().GetMD5();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the files.
|
||||
/// </summary>
|
||||
/// <param name="sb">The sb.</param>
|
||||
/// <param name="files">The files.</param>
|
||||
/// <param name="extensions">The extensions.</param>
|
||||
/// <param name="numExtensions">The num extensions.</param>
|
||||
private void AddFiles(StringBuilder sb, IEnumerable<FileSystemInfo> files, Dictionary<string, string> extensions, int numExtensions)
|
||||
{
|
||||
foreach (var file in files
|
||||
.OrderBy(f => f.Name))
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((file.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
if (FoldersToMonitor.ContainsKey(file.Name))
|
||||
{
|
||||
sb.Append(file.Name);
|
||||
|
||||
var children = ((DirectoryInfo)file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList();
|
||||
AddFiles(sb, children, extensions, numExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
// It's a file
|
||||
else if (numExtensions == 0 || extensions.ContainsKey(file.Extension))
|
||||
{
|
||||
sb.Append(file.Name);
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error accessing file attributes for {0}", ex, file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,5 +39,11 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns>
|
||||
bool IsSaveLocalMetadataEnabled();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is in mixed folder.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
|
||||
bool IsInMixedFolder { get; }
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
{
|
||||
public interface ILocalMetadataProvider : IMetadataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether [has local metadata] [the specified item].
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if [has local metadata] [the specified item]; otherwise, <c>false</c>.</returns>
|
||||
bool HasLocalMetadata(IHasMetadata item);
|
||||
}
|
||||
|
||||
public interface ILocalMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ILocalMetadataProvider
|
||||
@ -19,9 +13,16 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// <summary>
|
||||
/// Gets the metadata.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="info">The information.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{MetadataResult{`0}}.</returns>
|
||||
Task<MetadataResult<TItemType>> GetMetadata(string path, CancellationToken cancellationToken);
|
||||
Task<MetadataResult<TItemType>> GetMetadata(ItemInfo info, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public class ItemInfo
|
||||
{
|
||||
public string Path { get; set; }
|
||||
|
||||
public bool IsInMixedFolder { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -24,15 +24,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// <returns>Task.</returns>
|
||||
Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the metadata providers.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="force">if set to <c>true</c> [force].</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the image.
|
||||
/// </summary>
|
||||
@ -60,12 +51,11 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// <summary>
|
||||
/// Adds the metadata providers.
|
||||
/// </summary>
|
||||
/// <param name="providers">The providers.</param>
|
||||
/// <param name="imageProviders">The image providers.</param>
|
||||
/// <param name="metadataServices">The metadata services.</param>
|
||||
/// <param name="metadataProviders">The metadata providers.</param>
|
||||
/// <param name="savers">The savers.</param>
|
||||
void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
|
||||
void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
|
||||
IEnumerable<IMetadataSaver> savers);
|
||||
|
||||
/// <summary>
|
||||
|
@ -16,17 +16,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public bool ForceSave { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TODO: deprecate. Keeping this for now, for api compatibility
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public bool ResetResolveArgs { get; set; }
|
||||
|
||||
public MetadataRefreshOptions()
|
||||
{
|
||||
ResetResolveArgs = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class ImageRefreshOptions
|
||||
|
@ -152,12 +152,6 @@ namespace MediaBrowser.Model.Dto
|
||||
/// <value>The vote count.</value>
|
||||
public int? VoteCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original run time ticks.
|
||||
/// </summary>
|
||||
/// <value>The original run time ticks.</value>
|
||||
public long? OriginalRunTimeTicks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cumulative run time ticks.
|
||||
/// </summary>
|
||||
|
@ -71,11 +71,6 @@ namespace MediaBrowser.Model.Querying
|
||||
/// </summary>
|
||||
Settings,
|
||||
|
||||
/// <summary>
|
||||
/// The original run time ticks
|
||||
/// </summary>
|
||||
OriginalRunTimeTicks,
|
||||
|
||||
/// <summary>
|
||||
/// The item overview
|
||||
/// </summary>
|
||||
|
@ -5,11 +5,10 @@ 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>
|
||||
class AdultVideoXmlProvider : BaseXmlProvider<AdultVideo>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -19,41 +18,14 @@ namespace MediaBrowser.Providers.AdultVideos
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<AdultVideo>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(AdultVideo item, 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;
|
||||
new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return MovieXmlProvider.GetXmlFileInfo(path, FileSystem);
|
||||
return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.All
|
||||
if (locationType == LocationType.FileSystem)
|
||||
{
|
||||
// Episode has it's own provider
|
||||
if (item is Episode || item is Audio)
|
||||
if (item.IsOwnedItem || item is Episode || item is Audio)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -3,32 +3,81 @@ using MediaBrowser.Controller.Providers;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers
|
||||
{
|
||||
public abstract class BaseXmlProvider: IHasChangeMonitor
|
||||
public abstract class BaseXmlProvider<T> : ILocalMetadataProvider<T>, IHasChangeMonitor
|
||||
where T : IHasMetadata, new()
|
||||
{
|
||||
protected static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4);
|
||||
|
||||
protected IFileSystem FileSystem;
|
||||
|
||||
public async Task<MetadataResult<T>> GetMetadata(ItemInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<T>();
|
||||
|
||||
var file = GetXmlFile(info);
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var path = file.FullName;
|
||||
|
||||
await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
result.Item = new T();
|
||||
|
||||
Fetch(result.Item, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlProviderUtils.XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract void Fetch(T item, string path, CancellationToken cancellationToken);
|
||||
|
||||
protected BaseXmlProvider(IFileSystem fileSystem)
|
||||
{
|
||||
FileSystem = fileSystem;
|
||||
}
|
||||
|
||||
protected abstract FileInfo GetXmlFile(string path);
|
||||
protected abstract FileInfo GetXmlFile(ItemInfo info);
|
||||
|
||||
public bool HasChanged(IHasMetadata item, DateTime date)
|
||||
{
|
||||
var file = GetXmlFile(item.Path);
|
||||
var file = GetXmlFile(new ItemInfo { IsInMixedFolder = item.IsInMixedFolder, Path = item.Path });
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return FileSystem.GetLastWriteTimeUtc(file) > date;
|
||||
}
|
||||
|
||||
public bool HasLocalMetadata(IHasMetadata item)
|
||||
public string Name
|
||||
{
|
||||
return GetXmlFile(item.Path).Exists;
|
||||
get
|
||||
{
|
||||
return "Media Browser Xml";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class XmlProviderUtils
|
||||
{
|
||||
internal static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4);
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.BoxSets
|
||||
{
|
||||
/// <summary>
|
||||
/// Class BoxSetXmlProvider.
|
||||
/// </summary>
|
||||
public class BoxSetXmlProvider : BaseXmlProvider, ILocalMetadataProvider<BoxSet>
|
||||
public class BoxSetXmlProvider : BaseXmlProvider<BoxSet>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.BoxSets
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<BoxSet>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(BoxSet item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<BoxSet>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var item = new BoxSet();
|
||||
|
||||
new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = item;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return new FileInfo(Path.Combine(path, "collection.xml"));
|
||||
return new FileInfo(Path.Combine(info.Path, "collection.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Providers
|
||||
{
|
||||
public class CollectionFolderImageProvider : ImageFromMediaLocationProvider
|
||||
{
|
||||
public CollectionFolderImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
|
||||
: base(logManager, configurationManager, fileSystem)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Supports(BaseItem item)
|
||||
{
|
||||
return item is CollectionFolder && item.LocationType == LocationType.FileSystem;
|
||||
}
|
||||
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get { return MetadataProviderPriority.Second; }
|
||||
}
|
||||
|
||||
protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
|
||||
{
|
||||
return item.PhysicalLocations
|
||||
.Select(i => GetImageFromLocation(i, filenameWithoutExtension))
|
||||
.FirstOrDefault(i => i != null);
|
||||
}
|
||||
|
||||
protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
|
||||
{
|
||||
var files = items.SelectMany(i => i.PhysicalLocations)
|
||||
.Select(i => new DirectoryInfo(i))
|
||||
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
|
||||
.Where(i =>
|
||||
{
|
||||
var ext = i.Extension;
|
||||
|
||||
return !string.IsNullOrEmpty(ext) &&
|
||||
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return GetFileSystemStamp(files);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides metadata for Folders and all subclasses by parsing folder.xml
|
||||
/// </summary>
|
||||
public class FolderProviderFromXml : BaseMetadataProvider
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public FolderProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
|
||||
: base(logManager, configurationManager)
|
||||
{
|
||||
_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.IsFolder && item.LocationType == LocationType.FileSystem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get { return MetadataProviderPriority.First; }
|
||||
}
|
||||
|
||||
private const string XmlFileName = "folder.xml";
|
||||
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
|
||||
{
|
||||
var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
|
||||
|
||||
if (xml == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
|
||||
}
|
||||
|
||||
/// <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="providerInfo">The provider information.</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 = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
|
||||
|
||||
if (metadataFile != null)
|
||||
{
|
||||
var path = metadataFile.FullName;
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
new BaseItemXmlParser<Folder>(Logger).Fetch((Folder)item, path, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
}
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
52
MediaBrowser.Providers/Folders/FolderMetadataService.cs
Normal file
52
MediaBrowser.Providers/Folders/FolderMetadataService.cs
Normal file
@ -0,0 +1,52 @@
|
||||
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.Folders
|
||||
{
|
||||
public class FolderMetadataService : MetadataService<Folder, ItemId>
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public FolderMetadataService(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(Folder source, Folder target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
protected override Task SaveItem(Folder item, ItemUpdateType reason, CancellationToken cancellationToken)
|
||||
{
|
||||
return _libraryManager.UpdateItem(item, reason, cancellationToken);
|
||||
}
|
||||
|
||||
public override int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
// Make sure the type-specific services get picked first
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
MediaBrowser.Providers/Folders/FolderXmlProvider.cs
Normal file
33
MediaBrowser.Providers/Folders/FolderXmlProvider.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace MediaBrowser.Providers.Folders
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides metadata for Folders and all subclasses by parsing folder.xml
|
||||
/// </summary>
|
||||
public class FolderXmlProvider : BaseXmlProvider<Folder>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public FolderXmlProvider(IFileSystem fileSystem, ILogger logger)
|
||||
: base(fileSystem)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override void Fetch(Folder item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
new BaseItemXmlParser<Folder>(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
return new FileInfo(Path.Combine(info.Path, "folder.xml"));
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers
|
||||
namespace MediaBrowser.Providers.Folders
|
||||
{
|
||||
public class UserRootFolderNameProvider : BaseMetadataProvider
|
||||
{
|
@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Games
|
||||
{
|
||||
public class GameSystemXmlProvider : BaseXmlProvider, ILocalMetadataProvider<GameSystem>
|
||||
public class GameSystemXmlProvider : BaseXmlProvider<GameSystem>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Games
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<GameSystem>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(GameSystem item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<GameSystem>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var item = new GameSystem();
|
||||
|
||||
new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = item;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return new FileInfo(Path.Combine(path, "gamesystem.xml"));
|
||||
return new FileInfo(Path.Combine(info.Path, "gamesystem.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Games
|
||||
{
|
||||
public class GameXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Game>
|
||||
public class GameXmlProvider : BaseXmlProvider<Game>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -18,57 +17,29 @@ namespace MediaBrowser.Providers.Games
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Game>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(Game item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<Game>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var item = new Game();
|
||||
|
||||
new GameXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = item;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new GameXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
var fileInfo = FileSystem.GetFileSystemInfo(path);
|
||||
var fileInfo = FileSystem.GetFileSystemInfo(info.Path);
|
||||
|
||||
var directoryInfo = fileInfo as DirectoryInfo;
|
||||
|
||||
if (directoryInfo == null)
|
||||
{
|
||||
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
|
||||
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path));
|
||||
}
|
||||
|
||||
var directoryPath = directoryInfo.FullName;
|
||||
|
||||
var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml");
|
||||
var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml");
|
||||
|
||||
var file = new FileInfo(specificFile);
|
||||
|
||||
return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml"));
|
||||
return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,637 +0,0 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
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.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides images for all types by looking for standard images - folder, backdrop, logo, etc.
|
||||
/// </summary>
|
||||
public class ImageFromMediaLocationProvider : BaseMetadataProvider
|
||||
{
|
||||
protected readonly IFileSystem FileSystem;
|
||||
|
||||
public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
|
||||
: base(logManager, configurationManager)
|
||||
{
|
||||
FileSystem = fileSystem;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (item.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
if (item.ResolveArgs.IsDirectory)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return item.IsInMixedFolder && item.Parent != null && !(item is Episode);
|
||||
}
|
||||
if (item.LocationType == LocationType.Virtual)
|
||||
{
|
||||
var season = item as Season;
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
var series = season.Series;
|
||||
|
||||
if (series != null && series.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
|
||||
{
|
||||
var season = item as Season;
|
||||
if (season != null)
|
||||
{
|
||||
var list = new List<BaseItem>();
|
||||
|
||||
if (season.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
list.Add(season);
|
||||
}
|
||||
|
||||
var series = season.Series;
|
||||
if (series != null && series.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
list.Add(series);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
return base.GetItemsForFileStampComparison(item);
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
// Make sure current image paths still exist
|
||||
item.ValidateImages();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var args = GetResolveArgsContainingImages(item);
|
||||
|
||||
PopulateBaseItemImages(item, args);
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return TrueTaskResult;
|
||||
}
|
||||
|
||||
private ItemResolveArgs GetResolveArgsContainingImages(BaseItem item)
|
||||
{
|
||||
if (item.LocationType != LocationType.FileSystem)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item.IsInMixedFolder)
|
||||
{
|
||||
if (item.Parent == null)
|
||||
{
|
||||
return item.ResolveArgs;
|
||||
}
|
||||
return item.Parent.ResolveArgs;
|
||||
}
|
||||
|
||||
return item.ResolveArgs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <param name="filenameWithoutExtension">The filename without extension.</param>
|
||||
/// <returns>FileSystemInfo.</returns>
|
||||
protected virtual FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.MetaLocation))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return BaseItem.SupportedImageExtensions
|
||||
.Select(i => args.GetMetaFileByPath(GetFullImagePath(item, args, filenameWithoutExtension, i)))
|
||||
.FirstOrDefault(i => i != null);
|
||||
}
|
||||
|
||||
protected virtual string GetFullImagePath(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension, string extension)
|
||||
{
|
||||
var path = item.MetaLocation;
|
||||
|
||||
if (item.IsInMixedFolder)
|
||||
{
|
||||
var pathFilenameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path);
|
||||
|
||||
// If the image filename and path file name match, just look for an image using the same full path as the item
|
||||
if (string.Equals(pathFilenameWithoutExtension, filenameWithoutExtension))
|
||||
{
|
||||
return Path.ChangeExtension(item.Path, extension);
|
||||
}
|
||||
|
||||
return Path.Combine(path, pathFilenameWithoutExtension + "-" + filenameWithoutExtension + extension);
|
||||
}
|
||||
|
||||
return Path.Combine(path, filenameWithoutExtension + extension);
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// Fills in image paths based on files win the folder
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
private void PopulateBaseItemImages(BaseItem item, ItemResolveArgs args)
|
||||
{
|
||||
PopulatePrimaryImage(item, args);
|
||||
|
||||
// Logo Image
|
||||
var image = GetImage(item, args, "logo");
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.Logo, image.FullName);
|
||||
}
|
||||
|
||||
// Clearart
|
||||
image = GetImage(item, args, "clearart");
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.Art, image.FullName);
|
||||
}
|
||||
|
||||
// Disc
|
||||
image = GetImage(item, args, "disc") ??
|
||||
GetImage(item, args, "cdart");
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.Disc, image.FullName);
|
||||
}
|
||||
|
||||
// Box Image
|
||||
image = GetImage(item, args, "box");
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.Box, image.FullName);
|
||||
}
|
||||
|
||||
// BoxRear Image
|
||||
image = GetImage(item, args, "boxrear");
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.BoxRear, image.FullName);
|
||||
}
|
||||
|
||||
// Thumbnail Image
|
||||
image = GetImage(item, args, "menu");
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.Menu, image.FullName);
|
||||
}
|
||||
|
||||
PopulateBanner(item, args);
|
||||
PopulateThumb(item, args);
|
||||
|
||||
// Backdrop Image
|
||||
PopulateBackdrops(item, args);
|
||||
PopulateScreenshots(item, args);
|
||||
}
|
||||
|
||||
private void PopulatePrimaryImage(BaseItem item, ItemResolveArgs args)
|
||||
{
|
||||
// Primary Image
|
||||
var image = GetImage(item, args, "folder") ??
|
||||
GetImage(item, args, "poster") ??
|
||||
GetImage(item, args, "cover") ??
|
||||
GetImage(item, args, "default");
|
||||
|
||||
// Support plex/xbmc convention
|
||||
if (image == null && item is Series)
|
||||
{
|
||||
image = GetImage(item, args, "show");
|
||||
}
|
||||
|
||||
// Support plex/xbmc convention
|
||||
if (image == null)
|
||||
{
|
||||
// Supprt xbmc conventions
|
||||
var season = item as Season;
|
||||
if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
image = GetSeasonImageFromSeriesFolder(season, "-poster");
|
||||
}
|
||||
}
|
||||
|
||||
// Support plex/xbmc convention
|
||||
if (image == null && (item is Movie || item is MusicVideo || item is AdultVideo))
|
||||
{
|
||||
image = GetImage(item, args, "movie");
|
||||
}
|
||||
|
||||
// Look for a file with the same name as the item
|
||||
if (image == null && !string.IsNullOrEmpty(item.Path))
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(item.Path);
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
image = GetImage(item, args, name) ??
|
||||
GetImage(item, args, name + "-poster");
|
||||
}
|
||||
}
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.Primary, image.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the banner.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
private void PopulateBanner(BaseItem item, ItemResolveArgs args)
|
||||
{
|
||||
// Banner Image
|
||||
var image = GetImage(item, args, "banner");
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
// Supprt xbmc conventions
|
||||
var season = item as Season;
|
||||
if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
image = GetSeasonImageFromSeriesFolder(season, "-banner");
|
||||
}
|
||||
}
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.Banner, image.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the thumb.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
private void PopulateThumb(BaseItem item, ItemResolveArgs args)
|
||||
{
|
||||
// Thumbnail Image
|
||||
var image = GetImage(item, args, "thumb") ??
|
||||
GetImage(item, args, "landscape");
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
// Supprt xbmc conventions
|
||||
var season = item as Season;
|
||||
if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
image = GetSeasonImageFromSeriesFolder(season, "-landscape");
|
||||
}
|
||||
}
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
item.SetImagePath(ImageType.Thumb, image.FullName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the backdrops.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
private void PopulateBackdrops(BaseItem item, ItemResolveArgs args)
|
||||
{
|
||||
var backdropFiles = new List<string>();
|
||||
|
||||
PopulateBackdrops(item, args, backdropFiles, "backdrop", "backdrop");
|
||||
|
||||
// Support {name}-fanart.ext
|
||||
if (!string.IsNullOrEmpty(item.Path))
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(item.Path);
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
var image = GetImage(item, args, name + "-fanart");
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
backdropFiles.Add(image.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Support plex/xbmc conventions
|
||||
PopulateBackdrops(item, args, backdropFiles, "fanart", "fanart-");
|
||||
PopulateBackdrops(item, args, backdropFiles, "background", "background-");
|
||||
PopulateBackdrops(item, args, backdropFiles, "art", "art-");
|
||||
|
||||
var season = item as Season;
|
||||
if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
var image = GetSeasonImageFromSeriesFolder(season, "-fanart");
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
backdropFiles.Add(image.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
PopulateBackdropsFromExtraFanart(args, backdropFiles);
|
||||
}
|
||||
|
||||
if (backdropFiles.Count > 0)
|
||||
{
|
||||
item.BackdropImagePaths = backdropFiles;
|
||||
}
|
||||
}
|
||||
|
||||
private FileSystemInfo GetSeasonImageFromSeriesFolder(Season season, string imageSuffix)
|
||||
{
|
||||
var series = season.Series;
|
||||
var seriesFolderArgs = series.ResolveArgs;
|
||||
|
||||
var seasonNumber = season.IndexNumber;
|
||||
|
||||
string filename = null;
|
||||
FileSystemInfo image;
|
||||
|
||||
if (seasonNumber.HasValue)
|
||||
{
|
||||
var seasonMarker = seasonNumber.Value == 0
|
||||
? "-specials"
|
||||
: seasonNumber.Value.ToString("00", _usCulture);
|
||||
|
||||
// Get this one directly from the file system since we have to go up a level
|
||||
filename = "season" + seasonMarker + imageSuffix;
|
||||
|
||||
image = GetImage(series, seriesFolderArgs, filename);
|
||||
|
||||
if (image != null && image.Exists)
|
||||
{
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
var previousFilename = filename;
|
||||
|
||||
// Try using the season name
|
||||
filename = season.Name.ToLower().Replace(" ", string.Empty) + imageSuffix;
|
||||
|
||||
if (!string.Equals(previousFilename, filename))
|
||||
{
|
||||
image = GetImage(series, seriesFolderArgs, filename);
|
||||
|
||||
if (image != null && image.Exists)
|
||||
{
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the backdrops from extra fanart.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <param name="backdrops">The backdrops.</param>
|
||||
private void PopulateBackdropsFromExtraFanart(ItemResolveArgs args, List<string> backdrops)
|
||||
{
|
||||
if (!args.IsDirectory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.ContainsFileSystemEntryByName("extrafanart"))
|
||||
{
|
||||
var path = Path.Combine(args.Path, "extrafanart");
|
||||
|
||||
var imageFiles = Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly)
|
||||
.Where(i =>
|
||||
{
|
||||
var extension = Path.GetExtension(i);
|
||||
|
||||
if (string.IsNullOrEmpty(extension))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return BaseItem.SupportedImageExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
backdrops.AddRange(imageFiles);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the backdrops.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <param name="backdropFiles">The backdrop files.</param>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="numberedSuffix">The numbered suffix.</param>
|
||||
private void PopulateBackdrops(BaseItem item, ItemResolveArgs args, List<string> backdropFiles, string filename, string numberedSuffix)
|
||||
{
|
||||
var image = GetImage(item, args, filename);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
backdropFiles.Add(image.FullName);
|
||||
}
|
||||
|
||||
var unfound = 0;
|
||||
for (var i = 1; i <= 20; i++)
|
||||
{
|
||||
// Backdrop Image
|
||||
image = GetImage(item, args, numberedSuffix + i);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
backdropFiles.Add(image.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
unfound++;
|
||||
|
||||
if (unfound >= 3)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the screenshots.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
private void PopulateScreenshots(BaseItem item, ItemResolveArgs args)
|
||||
{
|
||||
// Screenshot Image
|
||||
var image = GetImage(item, args, "screenshot");
|
||||
|
||||
var screenshotFiles = new List<string>();
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
screenshotFiles.Add(image.FullName);
|
||||
}
|
||||
|
||||
var unfound = 0;
|
||||
for (var i = 1; i <= 20; i++)
|
||||
{
|
||||
// Screenshot Image
|
||||
image = GetImage(item, args, "screenshot" + i);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
screenshotFiles.Add(image.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
unfound++;
|
||||
|
||||
if (unfound >= 3)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (screenshotFiles.Count > 0)
|
||||
{
|
||||
var hasScreenshots = item as IHasScreenshots;
|
||||
if (hasScreenshots != null)
|
||||
{
|
||||
hasScreenshots.ScreenshotImagePaths = screenshotFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected FileSystemInfo GetImageFromLocation(string path, string filenameWithoutExtension)
|
||||
{
|
||||
try
|
||||
{
|
||||
var files = new DirectoryInfo(path)
|
||||
.EnumerateFiles()
|
||||
.Where(i =>
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(i.FullName);
|
||||
|
||||
if (!string.Equals(fileName, filenameWithoutExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ext = i.Extension;
|
||||
|
||||
return !string.IsNullOrEmpty(ext) &&
|
||||
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return BaseItem.SupportedImageExtensions
|
||||
.Select(ext => files.FirstOrDefault(i => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase)))
|
||||
.FirstOrDefault(file => file != null);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Providers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides images for generic types by looking for standard images in the IBN
|
||||
/// </summary>
|
||||
public class ImagesByNameProvider : ImageFromMediaLocationProvider
|
||||
{
|
||||
public ImagesByNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
|
||||
: base(logManager, configurationManager, 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)
|
||||
{
|
||||
// Only run for these generic types since we are expensive in file i/o
|
||||
return item is ICollectionFolder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get
|
||||
{
|
||||
return MetadataProviderPriority.Last;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected string GetLocation(BaseItem item)
|
||||
{
|
||||
var name = FileSystem.GetValidFilename(item.Name);
|
||||
|
||||
return Path.Combine(ConfigurationManager.ApplicationPaths.GeneralPath, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <param name="filenameWithoutExtension">The filename without extension.</param>
|
||||
/// <returns>FileSystemInfo.</returns>
|
||||
protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
|
||||
{
|
||||
var location = GetLocation(item);
|
||||
|
||||
return GetImageFromLocation(location, filenameWithoutExtension);
|
||||
}
|
||||
|
||||
protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
|
||||
{
|
||||
var location = GetLocation(items.First());
|
||||
|
||||
try
|
||||
{
|
||||
var files = new DirectoryInfo(location)
|
||||
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
|
||||
.Where(i =>
|
||||
{
|
||||
var ext = i.Extension;
|
||||
|
||||
return !string.IsNullOrEmpty(ext) &&
|
||||
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return GetFileSystemStamp(files);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
// User doesn't have the folder. No need to log or blow up
|
||||
|
||||
return Guid.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.LiveTv
|
||||
{
|
||||
public class ChannelXmlProvider : BaseXmlProvider, ILocalMetadataProvider<LiveTvChannel>
|
||||
public class ChannelXmlProvider : BaseXmlProvider<LiveTvChannel>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.LiveTv
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<LiveTvChannel>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(LiveTvChannel item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<LiveTvChannel>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var item = new LiveTvChannel();
|
||||
|
||||
new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = item;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return new FileInfo(Path.Combine(path, "channel.xml"));
|
||||
return new FileInfo(Path.Combine(info.Path, "channel.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -379,14 +379,19 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
if (saveLocally)
|
||||
{
|
||||
if (item.IsInMixedFolder && !(item is Episode))
|
||||
if (item is Episode)
|
||||
{
|
||||
path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
|
||||
}
|
||||
|
||||
else if (item.IsInMixedFolder)
|
||||
{
|
||||
path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
path = Path.Combine(item.MetaLocation, filename + extension);
|
||||
path = Path.Combine(item.ContainingFolderPath, filename + extension);
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,7 +473,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
return new[]
|
||||
{
|
||||
Path.Combine(item.MetaLocation, "fanart" + extension)
|
||||
Path.Combine(item.ContainingFolderPath, "fanart" + extension)
|
||||
};
|
||||
}
|
||||
|
||||
@ -483,8 +488,8 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
return new[]
|
||||
{
|
||||
Path.Combine(item.MetaLocation, "extrafanart", extraFanartFilename + extension),
|
||||
Path.Combine(item.MetaLocation, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)
|
||||
Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension),
|
||||
Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)
|
||||
};
|
||||
}
|
||||
|
||||
@ -519,10 +524,10 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
if (item is MusicAlbum || item is MusicArtist)
|
||||
{
|
||||
return new[] { Path.Combine(item.MetaLocation, "folder" + extension) };
|
||||
return new[] { Path.Combine(item.ContainingFolderPath, "folder" + extension) };
|
||||
}
|
||||
|
||||
return new[] { Path.Combine(item.MetaLocation, "poster" + extension) };
|
||||
return new[] { Path.Combine(item.ContainingFolderPath, "poster" + extension) };
|
||||
}
|
||||
|
||||
if (type == ImageType.Banner)
|
||||
@ -561,7 +566,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) };
|
||||
}
|
||||
|
||||
return new[] { Path.Combine(item.MetaLocation, "landscape" + extension) };
|
||||
return new[] { Path.Combine(item.ContainingFolderPath, "landscape" + extension) };
|
||||
}
|
||||
|
||||
// All other paths are the same
|
||||
|
@ -93,8 +93,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <returns>Task.</returns>
|
||||
private async Task RefreshFromProvider(IHasImages item, IDynamicImageProvider provider, MetadataOptions savedOptions, RefreshResult result, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
|
||||
|
||||
try
|
||||
{
|
||||
var images = provider.GetSupportedImages(item);
|
||||
@ -103,6 +101,8 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
if (!item.HasImage(imageType) && savedOptions.IsEnabled(imageType))
|
||||
{
|
||||
_logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
|
||||
|
||||
var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.HasImage)
|
||||
|
@ -88,12 +88,16 @@ namespace MediaBrowser.Providers.Manager
|
||||
// Next run metadata providers
|
||||
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
||||
{
|
||||
updateType = updateType | BeforeMetadataRefresh(itemOfType);
|
||||
|
||||
var providers = GetProviders(item, refreshResult.DateLastMetadataRefresh.HasValue, refreshOptions).ToList();
|
||||
|
||||
if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue)
|
||||
{
|
||||
updateType = updateType | BeforeMetadataRefresh(itemOfType);
|
||||
}
|
||||
|
||||
if (providers.Count > 0)
|
||||
{
|
||||
|
||||
var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
updateType = updateType | result.UpdateType;
|
||||
@ -145,7 +149,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
var type = item.GetType().Name;
|
||||
return ServerConfigurationManager.Configuration.MetadataOptions
|
||||
.FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ??
|
||||
.FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ??
|
||||
_defaultOptions;
|
||||
}
|
||||
|
||||
@ -278,9 +282,11 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
|
||||
|
||||
var itemInfo = new ItemInfo { Path = item.Path, IsInMixedFolder = item.IsInMixedFolder };
|
||||
|
||||
try
|
||||
{
|
||||
var localItem = await provider.GetMetadata(item.Path, cancellationToken).ConfigureAwait(false);
|
||||
var localItem = await provider.GetMetadata(itemInfo, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (localItem.HasMetadata)
|
||||
{
|
||||
@ -327,7 +333,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
return refreshResult;
|
||||
}
|
||||
|
||||
|
@ -48,12 +48,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <value>The configuration manager.</value>
|
||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of currently registered metadata prvoiders
|
||||
/// </summary>
|
||||
/// <value>The metadata providers enumerable.</value>
|
||||
private BaseMetadataProvider[] MetadataProviders { get; set; }
|
||||
|
||||
private IImageProvider[] ImageProviders { get; set; }
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
@ -86,15 +80,12 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <summary>
|
||||
/// Adds the metadata providers.
|
||||
/// </summary>
|
||||
/// <param name="providers">The providers.</param>
|
||||
/// <param name="imageProviders">The image providers.</param>
|
||||
/// <param name="metadataServices">The metadata services.</param>
|
||||
/// <param name="metadataProviders">The metadata providers.</param>
|
||||
/// <param name="metadataSavers">The metadata savers.</param>
|
||||
public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers)
|
||||
public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers)
|
||||
{
|
||||
MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
|
||||
|
||||
ImageProviders = imageProviders.ToArray();
|
||||
|
||||
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
|
||||
@ -111,174 +102,8 @@ namespace MediaBrowser.Providers.Manager
|
||||
return service.RefreshMetadata(item, options, cancellationToken);
|
||||
}
|
||||
|
||||
return ((BaseItem)item).RefreshMetadataDirect(cancellationToken, options.ForceSave, options.ReplaceAllMetadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="force">if set to <c>true</c> [force].</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
public async Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
ItemUpdateType? result = null;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders;
|
||||
|
||||
var providerHistories = item.DateLastSaved == default(DateTime) ?
|
||||
new List<BaseProviderInfo>() :
|
||||
_providerRepo.GetProviderHistory(item.Id).ToList();
|
||||
|
||||
// Run the normal providers sequentially in order of priority
|
||||
foreach (var provider in MetadataProviders)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!ProviderSupportsItem(provider, item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if internet providers are currently disabled
|
||||
if (provider.RequiresInternet && !enableInternetProviders)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running
|
||||
// This is the case for the fan art provider which depends on the movie and tv providers having run before them
|
||||
if (provider.RequiresInternet && item.DontFetchMeta && provider.EnforceDontFetchMetadata)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var providerInfo = providerHistories.FirstOrDefault(i => i.ProviderId == provider.Id);
|
||||
|
||||
if (providerInfo == null)
|
||||
{
|
||||
providerInfo = new BaseProviderInfo
|
||||
{
|
||||
ProviderId = provider.Id
|
||||
};
|
||||
providerHistories.Add(providerInfo);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!force && !provider.NeedsRefresh(item, providerInfo))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("Error determining NeedsRefresh for {0}", ex, item.Path);
|
||||
}
|
||||
|
||||
var updateType = await FetchAsync(provider, item, providerInfo, force, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (updateType.HasValue)
|
||||
{
|
||||
if (result.HasValue)
|
||||
{
|
||||
result = result.Value | updateType.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = updateType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.HasValue || force)
|
||||
{
|
||||
await _providerRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Providers the supports item.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
private bool ProviderSupportsItem(BaseMetadataProvider provider, BaseItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
return provider.Supports(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("{0} failed in Supports for type {1}", ex, provider.GetType().Name, item.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="providerInfo">The provider information.</param>
|
||||
/// <param name="force">if set to <c>true</c> [force].</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||
private async Task<ItemUpdateType?> FetchAsync(BaseMetadataProvider provider, BaseItem item, BaseProviderInfo providerInfo, bool force, CancellationToken cancellationToken)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--");
|
||||
|
||||
try
|
||||
{
|
||||
var changed = await provider.FetchAsync(item, force, providerInfo, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
return provider.ItemUpdateType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
_logger.Debug("{0} canceled for {1}", provider.GetType().Name, item.Name);
|
||||
|
||||
// If the outer cancellation token is the one that caused the cancellation, throw it
|
||||
if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("{0} failed refreshing {1} {2}", ex, provider.GetType().Name, item.Name, item.Path ?? string.Empty);
|
||||
|
||||
provider.SetLastRefreshed(item, DateTime.UtcNow, providerInfo, ProviderRefreshStatus.Failure);
|
||||
|
||||
return ItemUpdateType.Unspecified;
|
||||
}
|
||||
_logger.Error("Unable to find a metadata service for item of type " + item.GetType().Name);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -328,9 +153,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is ever used for something other than metadata we can add a file type param
|
||||
item.ResolveArgs.AddMetadataFile(path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -517,6 +339,15 @@ namespace MediaBrowser.Providers.Manager
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this restriction is ever lifted, movie xml providers will have to be updated to prevent owned items like trailers from reading those files
|
||||
if (item.IsOwnedItem)
|
||||
{
|
||||
if (provider is ILocalMetadataProvider || provider is IRemoteMetadataProvider)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -581,6 +412,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
list.Add(GetPluginSummary<AdultVideo>());
|
||||
list.Add(GetPluginSummary<MusicVideo>());
|
||||
list.Add(GetPluginSummary<Video>());
|
||||
|
||||
list.Add(GetPluginSummary<LiveTvChannel>());
|
||||
list.Add(GetPluginSummary<LiveTvProgram>());
|
||||
@ -678,6 +510,8 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
foreach (var saver in _savers.Where(i => i.IsEnabledFor(item, updateType)))
|
||||
{
|
||||
_logger.Debug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
|
||||
|
||||
var fileSaver = saver as IMetadataFileSaver;
|
||||
|
||||
if (fileSaver != null)
|
||||
|
@ -65,11 +65,13 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AdultVideos\AdultVideoMetadataService.cs" />
|
||||
<Compile Include="AdultVideos\AdultVideoXmlProvider.cs" />
|
||||
<Compile Include="All\LocalImageProvider.cs" />
|
||||
<Compile Include="Books\BookMetadataService.cs" />
|
||||
<Compile Include="BoxSets\BoxSetMetadataService.cs" />
|
||||
<Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" />
|
||||
<Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
|
||||
<Compile Include="Folders\FolderMetadataService.cs" />
|
||||
<Compile Include="GameGenres\GameGenreMetadataService.cs" />
|
||||
<Compile Include="Games\GameMetadataService.cs" />
|
||||
<Compile Include="Games\GameSystemMetadataService.cs" />
|
||||
@ -83,33 +85,34 @@
|
||||
<Compile Include="Manager\ProviderManager.cs" />
|
||||
<Compile Include="Manager\MetadataService.cs" />
|
||||
<Compile Include="BaseXmlProvider.cs" />
|
||||
<Compile Include="CollectionFolderImageProvider.cs" />
|
||||
<Compile Include="FolderProviderFromXml.cs" />
|
||||
<Compile Include="Folders\FolderXmlProvider.cs" />
|
||||
<Compile Include="Games\GameXmlParser.cs" />
|
||||
<Compile Include="Games\GameXmlProvider.cs" />
|
||||
<Compile Include="Games\GameSystemXmlProvider.cs" />
|
||||
<Compile Include="ImageFromMediaLocationProvider.cs" />
|
||||
<Compile Include="ImagesByNameProvider.cs" />
|
||||
<Compile Include="MediaInfo\FFProbeAudioInfo.cs" />
|
||||
<Compile Include="MediaInfo\FFProbeHelpers.cs" />
|
||||
<Compile Include="MediaInfo\FFProbeProvider.cs" />
|
||||
<Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
|
||||
<Compile Include="Movies\TrailerMetadataService.cs" />
|
||||
<Compile Include="Movies\GenericMovieDbInfo.cs" />
|
||||
<Compile Include="Movies\MovieDbSearch.cs" />
|
||||
<Compile Include="Movies\MovieMetadataService.cs" />
|
||||
<Compile Include="Movies\MovieXmlProvider.cs" />
|
||||
<Compile Include="Movies\TmdbSettings.cs" />
|
||||
<Compile Include="Movies\TrailerXmlProvider.cs" />
|
||||
<Compile Include="MusicGenres\MusicGenreImageProvider.cs" />
|
||||
<Compile Include="GameGenres\GameGenreImageProvider.cs" />
|
||||
<Compile Include="Genres\GenreImageProvider.cs" />
|
||||
<Compile Include="ImagesByName\ImageUtils.cs" />
|
||||
<Compile Include="MediaInfo\AudioImageProvider.cs" />
|
||||
<Compile Include="MediaInfo\BaseFFProbeProvider.cs" />
|
||||
<Compile Include="MediaInfo\FFProbeAudioInfoProvider.cs" />
|
||||
<Compile Include="MediaInfo\FFProbeVideoInfoProvider.cs" />
|
||||
<Compile Include="MediaInfo\VideoImageProvider.cs" />
|
||||
<Compile Include="BoxSets\BoxSetXmlProvider.cs" />
|
||||
<Compile Include="Movies\ManualMovieDbImageProvider.cs" />
|
||||
<Compile Include="Movies\ManualFanartMovieImageProvider.cs" />
|
||||
<Compile Include="Movies\MovieDbImageProvider.cs" />
|
||||
<Compile Include="Movies\FanartMovieImageProvider.cs" />
|
||||
<Compile Include="MusicGenres\MusicGenreMetadataService.cs" />
|
||||
<Compile Include="Music\AlbumMetadataService.cs" />
|
||||
<Compile Include="Music\ArtistMetadataService.cs" />
|
||||
<Compile Include="Music\AudioMetadataService.cs" />
|
||||
<Compile Include="Music\LastfmArtistProvider.cs" />
|
||||
<Compile Include="Music\MusicBrainzArtistProvider.cs" />
|
||||
<Compile Include="Music\MusicVideoMetadataService.cs" />
|
||||
@ -119,12 +122,8 @@
|
||||
<Compile Include="People\MovieDbPersonImageProvider.cs" />
|
||||
<Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
|
||||
<Compile Include="Movies\MovieXmlParser.cs" />
|
||||
<Compile Include="Movies\FanArtMovieProvider.cs" />
|
||||
<Compile Include="Movies\FanArtMovieUpdatesPrescanTask.cs" />
|
||||
<Compile Include="Movies\MovieDbImagesProvider.cs" />
|
||||
<Compile Include="Movies\MovieDbProvider.cs" />
|
||||
<Compile Include="Movies\MovieProviderFromXml.cs" />
|
||||
<Compile Include="Movies\OpenMovieDatabaseProvider.cs" />
|
||||
<Compile Include="Music\AlbumXmlProvider.cs" />
|
||||
<Compile Include="Music\ArtistXmlProvider.cs" />
|
||||
<Compile Include="Music\FanArtUpdatesPrescanTask.cs" />
|
||||
@ -177,9 +176,10 @@
|
||||
<Compile Include="TV\SeriesXmlProvider.cs" />
|
||||
<Compile Include="TV\SeriesXmlParser.cs" />
|
||||
<Compile Include="TV\TvdbPrescanTask.cs" />
|
||||
<Compile Include="UserRootFolderNameProvider.cs" />
|
||||
<Compile Include="Folders\UserRootFolderNameProvider.cs" />
|
||||
<Compile Include="Users\UserMetadataService.cs" />
|
||||
<Compile Include="VirtualItemImageValidator.cs" />
|
||||
<Compile Include="Videos\VideoMetadataService.cs" />
|
||||
<Compile Include="Years\YearMetadataService.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||
|
@ -1,16 +1,13 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaInfo;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.IO;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -19,207 +16,83 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
/// <summary>
|
||||
/// Uses ffmpeg to create video images
|
||||
/// </summary>
|
||||
public class AudioImageProvider : BaseMetadataProvider
|
||||
public class AudioImageProvider : IDynamicImageProvider, IHasChangeMonitor
|
||||
{
|
||||
/// <summary>
|
||||
/// The _locks
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
/// <summary>
|
||||
/// The _media encoder
|
||||
/// </summary>
|
||||
private readonly IIsoManager _isoManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
/// <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>
|
||||
/// <param name="mediaEncoder">The media encoder.</param>
|
||||
public AudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder)
|
||||
: base(logManager, configurationManager)
|
||||
public AudioImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config)
|
||||
{
|
||||
_isoManager = isoManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether [refresh on version change].
|
||||
/// The null mount task result
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
|
||||
protected override bool RefreshOnVersionChange
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the provider version.
|
||||
/// </summary>
|
||||
/// <value>The provider version.</value>
|
||||
protected override string ProviderVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supportses the specified item.
|
||||
/// Mounts the iso if needed.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
public override bool Supports(BaseItem item)
|
||||
/// <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;
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
|
||||
{
|
||||
return new List<ImageType> { ImageType.Primary };
|
||||
}
|
||||
|
||||
public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
|
||||
{
|
||||
var audio = (Audio)item;
|
||||
|
||||
// Can't extract if we didn't find a video stream in the file
|
||||
if (!audio.HasEmbeddedImage)
|
||||
{
|
||||
return Task.FromResult(new DynamicImageResponse { HasImage = false });
|
||||
}
|
||||
|
||||
return GetVideoImage((Audio)item, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<DynamicImageResponse> GetVideoImage(Audio item, CancellationToken cancellationToken)
|
||||
{
|
||||
var stream = await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new DynamicImageResponse
|
||||
{
|
||||
Format = ImageFormat.Jpg,
|
||||
HasImage = true,
|
||||
Stream = stream
|
||||
};
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Embedded Image"; }
|
||||
}
|
||||
|
||||
public bool Supports(IHasImages item)
|
||||
{
|
||||
return item.LocationType == LocationType.FileSystem && item is Audio;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
public bool HasChanged(IHasMetadata item, DateTime date)
|
||||
{
|
||||
return item.DateModified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get { return MetadataProviderPriority.Last; }
|
||||
}
|
||||
|
||||
public override ItemUpdateType ItemUpdateType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
item.ValidateImages();
|
||||
|
||||
var audio = (Audio)item;
|
||||
|
||||
if (string.IsNullOrEmpty(audio.PrimaryImagePath) && audio.HasEmbeddedImage)
|
||||
{
|
||||
try
|
||||
{
|
||||
await CreateImagesForSong(audio, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException("Error extracting image for {0}", ex, item.Name);
|
||||
}
|
||||
}
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the images for song.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">Can't extract an image unless the audio file has an embedded image.</exception>
|
||||
private async Task CreateImagesForSong(Audio item, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var path = GetAudioImagePath(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 _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, path, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// Image is already in the cache
|
||||
item.SetImagePath(ImageType.Primary, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the audio image path.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private string GetAudioImagePath(Audio item)
|
||||
{
|
||||
var album = item.Parent as MusicAlbum;
|
||||
|
||||
var filename = item.Album ?? string.Empty;
|
||||
filename += item.Artists.FirstOrDefault() ?? string.Empty;
|
||||
filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
|
||||
|
||||
filename = filename.GetMD5() + ".jpg";
|
||||
|
||||
var prefix = filename.Substring(0, 1);
|
||||
|
||||
return Path.Combine(AudioImagesPath, prefix, filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the audio images data path.
|
||||
/// </summary>
|
||||
/// <value>The audio images data path.</value>
|
||||
public string AudioImagesPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-audio-images");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lock.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <returns>SemaphoreSlim.</returns>
|
||||
private SemaphoreSlim GetLock(string filename)
|
||||
{
|
||||
return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
|
||||
return item.DateModified > date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,145 +0,0 @@
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.MediaInfo;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for extracting media information through ffprobe
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class BaseFFProbeProvider<T> : BaseMetadataProvider
|
||||
where T : BaseItem, IHasMediaStreams
|
||||
{
|
||||
protected BaseFFProbeProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer)
|
||||
: base(logManager, configurationManager)
|
||||
{
|
||||
JsonSerializer = jsonSerializer;
|
||||
MediaEncoder = mediaEncoder;
|
||||
}
|
||||
|
||||
protected readonly IMediaEncoder MediaEncoder;
|
||||
protected readonly IJsonSerializer JsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get { return MetadataProviderPriority.First; }
|
||||
}
|
||||
|
||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <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 T;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// The null mount task result
|
||||
/// </summary>
|
||||
protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the provider version.
|
||||
/// </summary>
|
||||
/// <value>The provider version.</value>
|
||||
protected override string ProviderVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return "ffmpeg20131209";
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 media info.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="isoMount">The iso mount.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{MediaInfoResult}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">inputPath
|
||||
/// or
|
||||
/// cache</exception>
|
||||
protected 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, item is Audio, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mounts the iso if needed.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>IsoMount.</returns>
|
||||
protected virtual Task<IIsoMount> MountIsoIfNeeded(T item, CancellationToken cancellationToken)
|
||||
{
|
||||
return NullMountTaskResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when [pre fetch].
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="mount">The mount.</param>
|
||||
protected virtual void OnPreFetch(T item, IIsoMount mount)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +1,36 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaInfo;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts audio information using ffprobe
|
||||
/// </summary>
|
||||
public class FFProbeAudioInfoProvider : BaseFFProbeProvider<Audio>
|
||||
class FFProbeAudioInfo
|
||||
{
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer, IItemRepository itemRepo)
|
||||
: base(logManager, configurationManager, mediaEncoder, jsonSerializer)
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public FFProbeAudioInfo(IMediaEncoder mediaEncoder, IItemRepository itemRepo)
|
||||
{
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepo = itemRepo;
|
||||
}
|
||||
|
||||
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
|
||||
public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken)
|
||||
where T : Audio
|
||||
{
|
||||
var myItem = (Audio)item;
|
||||
|
||||
OnPreFetch(myItem, null);
|
||||
|
||||
var result = await GetMediaInfo(item, null, cancellationToken).ConfigureAwait(false);
|
||||
var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@ -43,11 +38,19 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await Fetch(myItem, cancellationToken, result).ConfigureAwait(false);
|
||||
await Fetch(item, cancellationToken, result).ConfigureAwait(false);
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return ItemUpdateType.MetadataImport;
|
||||
}
|
||||
|
||||
return true;
|
||||
private async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
const InputType type = InputType.File;
|
||||
var inputPath = new[] { item.Path };
|
||||
|
||||
return await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -82,7 +85,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
// If we got something, parse it
|
||||
if (!string.IsNullOrEmpty(duration))
|
||||
{
|
||||
audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, UsCulture)).Ticks;
|
||||
audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -277,6 +280,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -18,10 +19,14 @@ using System.Threading.Tasks;
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
public class FFProbeProvider : ICustomMetadataProvider<Episode>,
|
||||
ICustomMetadataProvider<MusicVideo>,
|
||||
ICustomMetadataProvider<Movie>,
|
||||
ICustomMetadataProvider<AdultVideo>,
|
||||
ICustomMetadataProvider<LiveTvVideoRecording>,
|
||||
ICustomMetadataProvider<MusicVideo>,
|
||||
ICustomMetadataProvider<Movie>,
|
||||
ICustomMetadataProvider<AdultVideo>,
|
||||
ICustomMetadataProvider<LiveTvVideoRecording>,
|
||||
ICustomMetadataProvider<LiveTvAudioRecording>,
|
||||
ICustomMetadataProvider<Trailer>,
|
||||
ICustomMetadataProvider<Video>,
|
||||
ICustomMetadataProvider<Audio>,
|
||||
IHasChangeMonitor
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
@ -30,7 +35,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly IBlurayExaminer _blurayExaminer;
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "ffprobe"; }
|
||||
@ -61,6 +66,26 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
return FetchVideoInfo(item, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ItemUpdateType> FetchAsync(Trailer item, CancellationToken cancellationToken)
|
||||
{
|
||||
return FetchVideoInfo(item, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ItemUpdateType> FetchAsync(Video item, CancellationToken cancellationToken)
|
||||
{
|
||||
return FetchVideoInfo(item, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ItemUpdateType> FetchAsync(Audio item, CancellationToken cancellationToken)
|
||||
{
|
||||
return FetchAudioInfo(item, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ItemUpdateType> FetchAsync(LiveTvAudioRecording item, CancellationToken cancellationToken)
|
||||
{
|
||||
return FetchAudioInfo(item, cancellationToken);
|
||||
}
|
||||
|
||||
public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization)
|
||||
{
|
||||
_logger = logger;
|
||||
@ -95,6 +120,19 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
return prober.ProbeVideo(item, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken)
|
||||
where T : Audio
|
||||
{
|
||||
if (item.LocationType != LocationType.FileSystem)
|
||||
{
|
||||
return _cachedTask;
|
||||
}
|
||||
|
||||
var prober = new FFProbeAudioInfo(_mediaEncoder, _itemRepo);
|
||||
|
||||
return prober.Probe(item, cancellationToken);
|
||||
}
|
||||
|
||||
public bool HasChanged(IHasMetadata item, DateTime date)
|
||||
{
|
||||
return item.DateModified > date;
|
||||
|
@ -321,69 +321,69 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
/// <param name="currentStreams">The current streams.</param>
|
||||
private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams)
|
||||
{
|
||||
var useParent = !video.ResolveArgs.IsDirectory;
|
||||
//var useParent = !video.ResolveArgs.IsDirectory;
|
||||
|
||||
if (useParent && video.Parent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//if (useParent && video.Parent == null)
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
|
||||
var fileSystemChildren = useParent
|
||||
? video.Parent.ResolveArgs.FileSystemChildren
|
||||
: video.ResolveArgs.FileSystemChildren;
|
||||
//var fileSystemChildren = useParent
|
||||
// ? video.Parent.ResolveArgs.FileSystemChildren
|
||||
// : video.ResolveArgs.FileSystemChildren;
|
||||
|
||||
var startIndex = currentStreams.Count;
|
||||
var streams = new List<MediaStream>();
|
||||
//var startIndex = currentStreams.Count;
|
||||
//var streams = new List<MediaStream>();
|
||||
|
||||
var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
|
||||
//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;
|
||||
//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);
|
||||
// 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();
|
||||
// // 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));
|
||||
// // 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;
|
||||
}
|
||||
// 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
|
||||
});
|
||||
}
|
||||
}
|
||||
// streams.Add(new MediaStream
|
||||
// {
|
||||
// Index = startIndex++,
|
||||
// Type = MediaStreamType.Subtitle,
|
||||
// IsExternal = true,
|
||||
// Path = fullName,
|
||||
// Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'),
|
||||
// Language = language
|
||||
// });
|
||||
// }
|
||||
//}
|
||||
|
||||
currentStreams.AddRange(streams);
|
||||
//currentStreams.AddRange(streams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,686 +0,0 @@
|
||||
using DvdLib.Ifo;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
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 MediaBrowser.Model.Serialization;
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts video information using ffprobe
|
||||
/// </summary>
|
||||
public class FFProbeVideoInfoProvider : BaseFFProbeProvider<Video>
|
||||
{
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
public FFProbeVideoInfoProvider(IIsoManager isoManager, IBlurayExaminer blurayExaminer, IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, ILocalizationManager localization, IItemRepository itemRepo)
|
||||
: base(logManager, configurationManager, mediaEncoder, jsonSerializer)
|
||||
{
|
||||
if (isoManager == null)
|
||||
{
|
||||
throw new ArgumentNullException("isoManager");
|
||||
}
|
||||
if (blurayExaminer == null)
|
||||
{
|
||||
throw new ArgumentNullException("blurayExaminer");
|
||||
}
|
||||
|
||||
_blurayExaminer = blurayExaminer;
|
||||
_localization = localization;
|
||||
_itemRepo = itemRepo;
|
||||
_isoManager = isoManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bluray examiner.
|
||||
/// </summary>
|
||||
/// <value>The bluray examiner.</value>
|
||||
private readonly IBlurayExaminer _blurayExaminer;
|
||||
|
||||
/// <summary>
|
||||
/// The _iso manager
|
||||
/// </summary>
|
||||
private readonly IIsoManager _isoManager;
|
||||
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
/// <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
|
||||
{
|
||||
// Need this in case external subtitle files change
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filestamp extensions.
|
||||
/// </summary>
|
||||
/// <value>The filestamp extensions.</value>
|
||||
protected override string[] FilestampExtensions
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[] { ".srt", ".ssa", ".ass" };
|
||||
}
|
||||
}
|
||||
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get
|
||||
{
|
||||
return MetadataProviderPriority.Second;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supports video files and dvd structures
|
||||
/// </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.LocationType != LocationType.FileSystem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var video = item as Video;
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
if (video.VideoType == VideoType.Iso)
|
||||
{
|
||||
return _isoManager.CanMount(item.Path);
|
||||
}
|
||||
|
||||
return video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Dvd || video.VideoType == VideoType.BluRay;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when [pre fetch].
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="mount">The mount.</param>
|
||||
protected override 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);
|
||||
}
|
||||
|
||||
base.OnPreFetch(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();
|
||||
}
|
||||
|
||||
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var video = (Video)item;
|
||||
|
||||
var isoMount = await MountIsoIfNeeded(video, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
OnPreFetch(video, isoMount);
|
||||
|
||||
// If we didn't find any satisfying the min length, just take them all
|
||||
if (video.VideoType == VideoType.Dvd || (video.IsoType.HasValue && video.IsoType == IsoType.Dvd))
|
||||
{
|
||||
if (video.PlayableStreamFileNames.Count == 0)
|
||||
{
|
||||
Logger.Error("No playable vobs found in dvd structure, skipping ffprobe.");
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var result = await GetMediaInfo(item, isoMount, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
FFProbeHelpers.NormalizeFFProbeResult(result);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await Fetch(video, force, providerInfo, cancellationToken, result, isoMount).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (isoMount != null)
|
||||
{
|
||||
isoMount.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mounts the iso if needed.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>IsoMount.</returns>
|
||||
protected override Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
|
||||
{
|
||||
if (item.VideoType == VideoType.Iso)
|
||||
{
|
||||
return _isoManager.Mount(item.Path, cancellationToken);
|
||||
}
|
||||
|
||||
return base.MountIsoIfNeeded(item, cancellationToken);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the specified video.
|
||||
/// </summary>
|
||||
/// <param name="video">The video.</param>
|
||||
/// <param name="force">if set to <c>true</c> [force].</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="isoMount">The iso mount.</param>
|
||||
/// <returns>Task.</returns>
|
||||
protected async Task Fetch(Video video, bool force, BaseProviderInfo providerInfo, 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, force, 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);
|
||||
|
||||
var videoFileChanged = CompareDate(video) > providerInfo.LastRefreshed;
|
||||
|
||||
await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Only save chapters if forcing, if the video changed, or if there are not already any saved ones
|
||||
if (force || videoFileChanged || _itemRepo.GetChapter(video.Id, 0) == null)
|
||||
{
|
||||
await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the WTV info.
|
||||
/// </summary>
|
||||
/// <param name="video">The video.</param>
|
||||
/// <param name="force">if set to <c>true</c> [force].</param>
|
||||
/// <param name="data">The data.</param>
|
||||
private void FetchWtvInfo(Video video, bool force, InternalMediaInfoResult data)
|
||||
{
|
||||
if (data.format == null || data.format.tags == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (force || 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 (force || 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 (force || 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 (force || 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 (force || !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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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) && FilestampExtensions.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>
|
||||
/// Fetches the bd info.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="chapters">The chapters.</param>
|
||||
/// <param name="mediaStreams">The media streams.</param>
|
||||
/// <param name="inputPath">The input path.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified num is empty.
|
||||
/// </summary>
|
||||
/// <param name="num">The num.</param>
|
||||
/// <returns><c>true</c> if the specified num is empty; otherwise, <c>false</c>.</returns>
|
||||
private bool IsEmpty(int? num)
|
||||
{
|
||||
return !num.HasValue || num.Value == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills video properties from the VideoStream of the largest playlist
|
||||
/// </summary>
|
||||
/// <param name="video">The video.</param>
|
||||
/// <param name="mediaStreams">The media streams.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
public class VideoImageProvider : IDynamicImageProvider
|
||||
public class VideoImageProvider : IDynamicImageProvider, IHasChangeMonitor
|
||||
{
|
||||
private readonly IIsoManager _isoManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
@ -25,34 +25,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Qualifieses for extraction.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
private bool QualifiesForExtraction(Video item)
|
||||
{
|
||||
// No support for this
|
||||
if (item.VideoType == VideoType.HdDvd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't extract from iso's if we weren't unable to determine iso type
|
||||
if (item.VideoType == VideoType.Iso && !item.IsoType.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't extract if we didn't find a video stream in the file
|
||||
if (!item.DefaultVideoStreamIndex.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The null mount task result
|
||||
/// </summary>
|
||||
@ -81,7 +53,27 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetVideoImage((Video)item, cancellationToken);
|
||||
var video = (Video)item;
|
||||
|
||||
// No support for this
|
||||
if (video.VideoType == VideoType.HdDvd)
|
||||
{
|
||||
return Task.FromResult(new DynamicImageResponse { HasImage = false });
|
||||
}
|
||||
|
||||
// Can't extract from iso's if we weren't unable to determine iso type
|
||||
if (video.VideoType == VideoType.Iso && !video.IsoType.HasValue)
|
||||
{
|
||||
return Task.FromResult(new DynamicImageResponse { HasImage = false });
|
||||
}
|
||||
|
||||
// Can't extract if we didn't find a video stream in the file
|
||||
if (!video.DefaultVideoStreamIndex.HasValue)
|
||||
{
|
||||
return Task.FromResult(new DynamicImageResponse { HasImage = false });
|
||||
}
|
||||
|
||||
return GetVideoImage(video, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken)
|
||||
@ -133,5 +125,10 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
return item.LocationType == LocationType.FileSystem && item is Video;
|
||||
}
|
||||
|
||||
public bool HasChanged(IHasMetadata item, DateTime date)
|
||||
{
|
||||
return item.DateModified > date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,356 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Net;
|
||||
using System.Net;
|
||||
using MediaBrowser.Providers.Music;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
/// <summary>
|
||||
/// Class FanArtMovieProvider
|
||||
/// </summary>
|
||||
class FanartMovieProvider : BaseMetadataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the HTTP client.
|
||||
/// </summary>
|
||||
/// <value>The HTTP client.</value>
|
||||
protected IHttpClient HttpClient { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The _provider manager
|
||||
/// </summary>
|
||||
private readonly IProviderManager _providerManager;
|
||||
|
||||
internal static FanartMovieProvider Current { get; private set; }
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FanartMovieProvider" /> 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>
|
||||
/// <exception cref="System.ArgumentNullException">httpClient</exception>
|
||||
public FanartMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
|
||||
: base(logManager, configurationManager)
|
||||
{
|
||||
if (httpClient == null)
|
||||
{
|
||||
throw new ArgumentNullException("httpClient");
|
||||
}
|
||||
HttpClient = httpClient;
|
||||
_providerManager = providerManager;
|
||||
_fileSystem = fileSystem;
|
||||
Current = this;
|
||||
}
|
||||
|
||||
public override ItemUpdateType ItemUpdateType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 "13";
|
||||
}
|
||||
}
|
||||
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get
|
||||
{
|
||||
return MetadataProviderPriority.Fifth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The fan art base URL
|
||||
/// </summary>
|
||||
protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/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)
|
||||
{
|
||||
var trailer = item as Trailer;
|
||||
|
||||
if (trailer != null)
|
||||
{
|
||||
return !trailer.IsLocalTrailer;
|
||||
}
|
||||
|
||||
return item is Movie || item is MusicVideo;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.NeedsRefreshInternal(item, providerInfo);
|
||||
}
|
||||
|
||||
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
|
||||
{
|
||||
var id = item.GetProviderId(MetadataProviders.Tmdb);
|
||||
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
// Process images
|
||||
var xmlPath = GetFanartXmlPath(id);
|
||||
|
||||
var fileInfo = new FileInfo(xmlPath);
|
||||
|
||||
return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
|
||||
}
|
||||
|
||||
return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the movie data path.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="tmdbId">The TMDB id.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId)
|
||||
{
|
||||
var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId);
|
||||
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the movie data path.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
internal static string GetMoviesDataPath(IApplicationPaths appPaths)
|
||||
{
|
||||
var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies");
|
||||
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
/// <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 movieId = item.GetProviderId(MetadataProviders.Tmdb);
|
||||
|
||||
if (!string.IsNullOrEmpty(movieId))
|
||||
{
|
||||
var xmlPath = GetFanartXmlPath(movieId);
|
||||
|
||||
// Only download the xml if it doesn't already exist. The prescan task will take care of getting updates
|
||||
if (!File.Exists(xmlPath))
|
||||
{
|
||||
await DownloadMovieXml(movieId, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartMovieImageProvider.ProviderName).ConfigureAwait(false);
|
||||
|
||||
await FetchImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetFanartXmlPath(string tmdbId)
|
||||
{
|
||||
var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId);
|
||||
return Path.Combine(movieDataPath, "fanart.xml");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the movie XML.
|
||||
/// </summary>
|
||||
/// <param name="tmdbId">The TMDB id.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId);
|
||||
|
||||
var xmlPath = GetFanartXmlPath(tmdbId);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
|
||||
|
||||
using (var response = await HttpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
ResourcePool = FanartArtistProvider.FanArtResourcePool,
|
||||
CancellationToken = cancellationToken
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
||||
{
|
||||
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Task _cachedTask = Task.FromResult(true);
|
||||
internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken)
|
||||
{
|
||||
var path = GetFanartXmlPath(tmdbId);
|
||||
|
||||
var fileInfo = _fileSystem.GetFileSystemInfo(path);
|
||||
|
||||
if (fileInfo.Exists)
|
||||
{
|
||||
if (ConfigurationManager.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
|
||||
{
|
||||
return _cachedTask;
|
||||
}
|
||||
}
|
||||
|
||||
return DownloadMovieXml(tmdbId, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task FetchImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
|
||||
|
||||
if (options.IsEnabled(ImageType.Primary) && !item.HasImage(ImageType.Primary))
|
||||
{
|
||||
await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (options.IsEnabled(ImageType.Logo) && !item.HasImage(ImageType.Logo))
|
||||
{
|
||||
await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (options.IsEnabled(ImageType.Art) && !item.HasImage(ImageType.Art))
|
||||
{
|
||||
await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (options.IsEnabled(ImageType.Disc) && !item.HasImage(ImageType.Disc))
|
||||
{
|
||||
await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
|
||||
{
|
||||
await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (options.IsEnabled(ImageType.Thumb) && !item.HasImage(ImageType.Thumb))
|
||||
{
|
||||
await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var backdropLimit = options.GetLimit(ImageType.Backdrop);
|
||||
|
||||
if (backdropLimit > 0 &&
|
||||
item.BackdropImagePaths.Count < backdropLimit)
|
||||
{
|
||||
foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
|
||||
{
|
||||
await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (item.BackdropImagePaths.Count >= backdropLimit) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var image in images.Where(i => i.Type == type))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
// Sometimes fanart has bad url's in their xml
|
||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.Movies
|
||||
return;
|
||||
}
|
||||
|
||||
var path = FanartMovieProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
|
||||
var path = FanartMovieImageProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
|
||||
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
_logger.Info("Updating movie " + id);
|
||||
|
||||
await FanartMovieProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false);
|
||||
await FanartMovieImageProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@ -7,6 +8,7 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Providers.Music;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@ -16,22 +18,27 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using MediaBrowser.Providers.Music;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
public class ManualFanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
|
||||
public class FanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public ManualFanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
|
||||
private const string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/1";
|
||||
|
||||
internal static FanartMovieImageProvider Current;
|
||||
|
||||
public FanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
|
||||
{
|
||||
_config = config;
|
||||
_httpClient = httpClient;
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
Current = this;
|
||||
}
|
||||
|
||||
public string Name
|
||||
@ -86,9 +93,9 @@ namespace MediaBrowser.Providers.Movies
|
||||
|
||||
if (!string.IsNullOrEmpty(movieId))
|
||||
{
|
||||
await FanartMovieProvider.Current.EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false);
|
||||
await EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(movieId);
|
||||
var xmlPath = GetFanartXmlPath(movieId);
|
||||
|
||||
try
|
||||
{
|
||||
@ -344,7 +351,7 @@ namespace MediaBrowser.Providers.Movies
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
// Process images
|
||||
var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(id);
|
||||
var xmlPath = GetFanartXmlPath(id);
|
||||
|
||||
var fileInfo = new FileInfo(xmlPath);
|
||||
|
||||
@ -353,5 +360,85 @@ namespace MediaBrowser.Providers.Movies
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the movie data path.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="tmdbId">The TMDB id.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId)
|
||||
{
|
||||
var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId);
|
||||
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the movie data path.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
internal static string GetMoviesDataPath(IApplicationPaths appPaths)
|
||||
{
|
||||
var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies");
|
||||
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
public string GetFanartXmlPath(string tmdbId)
|
||||
{
|
||||
var movieDataPath = GetMovieDataPath(_config.ApplicationPaths, tmdbId);
|
||||
return Path.Combine(movieDataPath, "fanart.xml");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the movie XML.
|
||||
/// </summary>
|
||||
/// <param name="tmdbId">The TMDB id.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId);
|
||||
|
||||
var xmlPath = GetFanartXmlPath(tmdbId);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
|
||||
|
||||
using (var response = await _httpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
ResourcePool = FanartArtistProvider.FanArtResourcePool,
|
||||
CancellationToken = cancellationToken
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
||||
{
|
||||
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Task _cachedTask = Task.FromResult(true);
|
||||
internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken)
|
||||
{
|
||||
var path = GetFanartXmlPath(tmdbId);
|
||||
|
||||
var fileInfo = _fileSystem.GetFileSystemInfo(path);
|
||||
|
||||
if (fileInfo.Exists)
|
||||
{
|
||||
if (_config.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
|
||||
{
|
||||
return _cachedTask;
|
||||
}
|
||||
}
|
||||
|
||||
return DownloadMovieXml(tmdbId, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
245
MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
Normal file
245
MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
Normal file
@ -0,0 +1,245 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
public class GenericMovieDbInfo<T>
|
||||
where T : Video, new()
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public GenericMovieDbInfo(ILogger logger, IJsonSerializer jsonSerializer)
|
||||
{
|
||||
_logger = logger;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<T>> GetMetadata(ItemId itemId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<T>();
|
||||
|
||||
var tmdbId = itemId.GetProviderId(MetadataProviders.Tmdb);
|
||||
var imdbId = itemId.GetProviderId(MetadataProviders.Imdb);
|
||||
|
||||
// Don't search for music video id's because it is very easy to misidentify.
|
||||
if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId) && typeof(T) != typeof(MusicVideo))
|
||||
{
|
||||
tmdbId = await new MovieDbSearch(_logger, _jsonSerializer)
|
||||
.FindMovieId(itemId, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tmdbId) || !string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
result.Item = await FetchMovieData(tmdbId, imdbId, itemId.MetadataLanguage, itemId.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
result.HasMetadata = result.Item != null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the movie data.
|
||||
/// </summary>
|
||||
/// <param name="tmdbId">The TMDB identifier.</param>
|
||||
/// <param name="imdbId">The imdb identifier.</param>
|
||||
/// <param name="language">The language.</param>
|
||||
/// <param name="preferredCountryCode">The preferred country code.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{`0}.</returns>
|
||||
private async Task<T> FetchMovieData(string tmdbId, string imdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
|
||||
{
|
||||
string dataFilePath = null;
|
||||
MovieDbProvider.CompleteMovieData movieInfo = null;
|
||||
|
||||
// Id could be ImdbId or TmdbId
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
movieInfo = await MovieDbProvider.Current.FetchMainResult(imdbId, language, cancellationToken).ConfigureAwait(false);
|
||||
if (movieInfo == null) return null;
|
||||
|
||||
tmdbId = movieInfo.id.ToString(_usCulture);
|
||||
|
||||
dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
|
||||
_jsonSerializer.SerializeToFile(movieInfo, dataFilePath);
|
||||
}
|
||||
|
||||
await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
dataFilePath = dataFilePath ?? MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
|
||||
movieInfo = movieInfo ?? _jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(dataFilePath);
|
||||
|
||||
var item = new T();
|
||||
|
||||
ProcessMainInfo(item, preferredCountryCode, movieInfo);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the main info.
|
||||
/// </summary>
|
||||
/// <param name="movie">The movie.</param>
|
||||
/// <param name="preferredCountryCode">The preferred country code.</param>
|
||||
/// <param name="movieData">The movie data.</param>
|
||||
private void ProcessMainInfo(T movie, string preferredCountryCode, MovieDbProvider.CompleteMovieData movieData)
|
||||
{
|
||||
movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name;
|
||||
|
||||
// Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException.
|
||||
movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null;
|
||||
movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null;
|
||||
|
||||
movie.HomePageUrl = movieData.homepage;
|
||||
|
||||
var hasBudget = movie as IHasBudget;
|
||||
if (hasBudget != null)
|
||||
{
|
||||
hasBudget.Budget = movieData.budget;
|
||||
hasBudget.Revenue = movieData.revenue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(movieData.tagline))
|
||||
{
|
||||
var hasTagline = movie as IHasTaglines;
|
||||
if (hasTagline != null)
|
||||
{
|
||||
hasTagline.Taglines.Clear();
|
||||
hasTagline.AddTagline(movieData.tagline);
|
||||
}
|
||||
}
|
||||
|
||||
movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture));
|
||||
movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id);
|
||||
|
||||
if (movieData.belongs_to_collection != null)
|
||||
{
|
||||
movie.SetProviderId(MetadataProviders.TmdbCollection,
|
||||
movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var movieItem = movie as Movie;
|
||||
|
||||
if (movieItem != null)
|
||||
{
|
||||
movieItem.TmdbCollectionName = movieData.belongs_to_collection.name;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry
|
||||
}
|
||||
|
||||
float rating;
|
||||
string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
|
||||
{
|
||||
movie.CommunityRating = rating;
|
||||
}
|
||||
|
||||
movie.VoteCount = movieData.vote_count;
|
||||
|
||||
//release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match
|
||||
if (movieData.releases != null && movieData.releases.countries != null)
|
||||
{
|
||||
var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new MovieDbProvider.Country();
|
||||
var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new MovieDbProvider.Country();
|
||||
var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new MovieDbProvider.Country();
|
||||
|
||||
var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
|
||||
movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
|
||||
? ratingPrefix + ourRelease.certification
|
||||
: !string.IsNullOrEmpty(usRelease.certification)
|
||||
? usRelease.certification
|
||||
: !string.IsNullOrEmpty(minimunRelease.certification)
|
||||
? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification
|
||||
: null;
|
||||
}
|
||||
|
||||
if (movieData.release_date.Year != 1)
|
||||
{
|
||||
//no specific country release info at all
|
||||
movie.PremiereDate = movieData.release_date.ToUniversalTime();
|
||||
movie.ProductionYear = movieData.release_date.Year;
|
||||
}
|
||||
|
||||
//studios
|
||||
if (movieData.production_companies != null)
|
||||
{
|
||||
movie.Studios.Clear();
|
||||
|
||||
foreach (var studio in movieData.production_companies.Select(c => c.name))
|
||||
{
|
||||
movie.AddStudio(studio);
|
||||
}
|
||||
}
|
||||
|
||||
// genres
|
||||
// Movies get this from imdb
|
||||
var genres = movieData.genres ?? new List<MovieDbProvider.GenreItem>();
|
||||
|
||||
foreach (var genre in genres.Select(g => g.name))
|
||||
{
|
||||
movie.AddGenre(genre);
|
||||
}
|
||||
|
||||
//Actors, Directors, Writers - all in People
|
||||
//actors come from cast
|
||||
if (movieData.casts != null && movieData.casts.cast != null)
|
||||
{
|
||||
foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
|
||||
}
|
||||
|
||||
//and the rest from crew
|
||||
if (movieData.casts != null && movieData.casts.crew != null)
|
||||
{
|
||||
foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
|
||||
}
|
||||
|
||||
if (movieData.keywords != null && movieData.keywords.keywords != null)
|
||||
{
|
||||
var hasTags = movie as IHasKeywords;
|
||||
if (hasTags != null)
|
||||
{
|
||||
hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (movieData.trailers != null && movieData.trailers.youtube != null &&
|
||||
movieData.trailers.youtube.Count > 0)
|
||||
{
|
||||
var hasTrailers = movie as IHasTrailers;
|
||||
if (hasTrailers != null)
|
||||
{
|
||||
hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
|
||||
{
|
||||
Url = string.Format("http://www.youtube.com/watch?v={0}", i.source),
|
||||
IsDirectLink = false,
|
||||
Name = i.name,
|
||||
VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -15,12 +15,12 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
class ManualMovieDbImageProvider : IRemoteImageProvider, IHasOrder
|
||||
class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
public ManualMovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
|
||||
public MovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClient = httpClient;
|
||||
@ -168,9 +168,17 @@ namespace MediaBrowser.Providers.Movies
|
||||
private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await MovieDbProvider.Current.EnsureMovieInfo(item, cancellationToken).ConfigureAwait(false);
|
||||
var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var path = MovieDbProvider.Current.GetDataFilePath(item);
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var path = MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
@ -1,247 +0,0 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
/// <summary>
|
||||
/// Class MovieDbImagesProvider
|
||||
/// </summary>
|
||||
public class MovieDbImagesProvider : BaseMetadataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The _provider manager
|
||||
/// </summary>
|
||||
private readonly IProviderManager _providerManager;
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MovieDbImagesProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="providerManager">The provider manager.</param>
|
||||
public MovieDbImagesProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
|
||||
: base(logManager, configurationManager)
|
||||
{
|
||||
_providerManager = providerManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get { return MetadataProviderPriority.Fourth; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supports 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)
|
||||
{
|
||||
var trailer = item as Trailer;
|
||||
|
||||
if (trailer != null)
|
||||
{
|
||||
return !trailer.IsLocalTrailer;
|
||||
}
|
||||
|
||||
// Don't support local trailers
|
||||
return item is Movie || item is MusicVideo;
|
||||
}
|
||||
|
||||
public override ItemUpdateType ItemUpdateType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 "3";
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
|
||||
|
||||
// Don't refresh if we already have both poster and backdrop and we're not refreshing images
|
||||
if (item.HasImage(ImageType.Primary) &&
|
||||
item.BackdropImagePaths.Count >= options.GetLimit(ImageType.Backdrop) &&
|
||||
!item.LockedFields.Contains(MetadataFields.Images))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.NeedsRefreshInternal(item, providerInfo);
|
||||
}
|
||||
|
||||
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
|
||||
{
|
||||
var path = MovieDbProvider.Current.GetDataFilePath(item);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
|
||||
if (fileInfo.Exists)
|
||||
{
|
||||
return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
|
||||
}
|
||||
}
|
||||
|
||||
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 async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbImageProvider.ProviderName).ConfigureAwait(false);
|
||||
await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the images.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="images">The images.</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task ProcessImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var eligiblePosters = images
|
||||
.Where(i => i.Type == ImageType.Primary)
|
||||
.ToList();
|
||||
|
||||
// poster
|
||||
if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
|
||||
{
|
||||
var poster = eligiblePosters[0];
|
||||
|
||||
var url = poster.Url;
|
||||
|
||||
var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
CancellationToken = cancellationToken
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Primary, null, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
|
||||
|
||||
var eligibleBackdrops = images
|
||||
.Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= options.GetMinWidth(ImageType.Backdrop))
|
||||
.ToList();
|
||||
|
||||
var backdropLimit = options.GetLimit(ImageType.Backdrop);
|
||||
|
||||
// backdrops - only download if earlier providers didn't find any (fanart)
|
||||
if (eligibleBackdrops.Count > 0 &&
|
||||
options.IsEnabled(ImageType.Backdrop) &&
|
||||
item.BackdropImagePaths.Count < backdropLimit &&
|
||||
!item.LockedFields.Contains(MetadataFields.Backdrops))
|
||||
{
|
||||
for (var i = 0; i < eligibleBackdrops.Count; i++)
|
||||
{
|
||||
var url = eligibleBackdrops[i].Url;
|
||||
|
||||
var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
CancellationToken = cancellationToken
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, null, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (item.BackdropImagePaths.Count >= backdropLimit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,16 +5,11 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Providers.Savers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -23,50 +18,46 @@ namespace MediaBrowser.Providers.Movies
|
||||
/// <summary>
|
||||
/// Class MovieDbProvider
|
||||
/// </summary>
|
||||
public class MovieDbProvider : BaseMetadataProvider, IDisposable
|
||||
public class MovieDbProvider : IRemoteMetadataProvider<Movie>, IDisposable
|
||||
{
|
||||
protected static CultureInfo EnUs = new CultureInfo("en-US");
|
||||
|
||||
protected readonly IProviderManager ProviderManager;
|
||||
|
||||
/// <summary>
|
||||
/// The movie db
|
||||
/// </summary>
|
||||
internal readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1);
|
||||
|
||||
internal static MovieDbProvider Current { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the json serializer.
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
protected IJsonSerializer JsonSerializer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP client.
|
||||
/// </summary>
|
||||
/// <value>The HTTP client.</value>
|
||||
protected IHttpClient HttpClient { get; private set; }
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MovieDbProvider" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logManager">The log manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="httpClient">The HTTP client.</param>
|
||||
/// <param name="providerManager">The provider manager.</param>
|
||||
public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager, IFileSystem fileSystem)
|
||||
: base(logManager, configurationManager)
|
||||
public MovieDbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger)
|
||||
{
|
||||
JsonSerializer = jsonSerializer;
|
||||
HttpClient = httpClient;
|
||||
ProviderManager = providerManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClient = httpClient;
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_logger = logger;
|
||||
Current = this;
|
||||
}
|
||||
|
||||
public Task<MetadataResult<Movie>> GetMetadata(ItemId id, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetItemMetadata<Movie>(id, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<MetadataResult<T>> GetItemMetadata<T>(ItemId id, CancellationToken cancellationToken)
|
||||
where T : Video, new ()
|
||||
{
|
||||
var movieDb = new GenericMovieDbInfo<T>(_logger, _jsonSerializer);
|
||||
|
||||
return movieDb.GetMetadata(id, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "TheMovieDb"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
@ -79,61 +70,6 @@ namespace MediaBrowser.Providers.Movies
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get { return MetadataProviderPriority.Third; }
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var trailer = item as Trailer;
|
||||
|
||||
if (trailer != null)
|
||||
{
|
||||
return !trailer.IsLocalTrailer;
|
||||
}
|
||||
|
||||
// Don't support local trailers
|
||||
return item is Movie || item is MusicVideo;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool RefreshOnVersionChange
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string ProviderVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return "3";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _TMDB settings task
|
||||
/// </summary>
|
||||
@ -170,7 +106,7 @@ namespace MediaBrowser.Providers.Movies
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
_tmdbSettings = JsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
|
||||
_tmdbSettings = _jsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
|
||||
|
||||
return _tmdbSettings;
|
||||
}
|
||||
@ -187,30 +123,6 @@ namespace MediaBrowser.Providers.Movies
|
||||
internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669";
|
||||
internal static string AcceptHeader = "application/json,image/*";
|
||||
|
||||
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.NeedsRefreshInternal(item, providerInfo);
|
||||
}
|
||||
|
||||
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
|
||||
{
|
||||
var path = GetDataFilePath(item);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
|
||||
return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
|
||||
}
|
||||
|
||||
return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the movie data path.
|
||||
/// </summary>
|
||||
@ -231,121 +143,6 @@ namespace MediaBrowser.Providers.Movies
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
/// <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 id = item.GetProviderId(MetadataProviders.Tmdb);
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
id = item.GetProviderId(MetadataProviders.Imdb);
|
||||
}
|
||||
|
||||
// Don't search for music video id's because it is very easy to misidentify.
|
||||
if (string.IsNullOrEmpty(id) && !(item is MusicVideo))
|
||||
{
|
||||
id = await new MovieDbSearch(Logger, JsonSerializer)
|
||||
.FindMovieId(GetId(item), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await FetchMovieData(item, id, force, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ItemId GetId(IHasMetadata item)
|
||||
{
|
||||
return new ItemId
|
||||
{
|
||||
MetadataCountryCode = item.GetPreferredMetadataCountryCode(),
|
||||
MetadataLanguage = item.GetPreferredMetadataLanguage(),
|
||||
Name = item.Name,
|
||||
ProviderIds = item.ProviderIds
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [has alt meta] [the specified item].
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if [has alt meta] [the specified item]; otherwise, <c>false</c>.</returns>
|
||||
internal static bool HasAltMeta(BaseItem item)
|
||||
{
|
||||
var path = MovieXmlSaver.GetMovieSavePath((Video)item);
|
||||
|
||||
if (item.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
// If mixed with multiple movies in one folder, resolve args won't have the file system children
|
||||
return item.ResolveArgs.ContainsMetaFileByName(Path.GetFileName(path)) || File.Exists(path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the movie data.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <param name="isForcedRefresh">if set to <c>true</c> [is forced refresh].</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task FetchMovieData(BaseItem item, string id, bool isForcedRefresh, CancellationToken cancellationToken)
|
||||
{
|
||||
// Id could be ImdbId or TmdbId
|
||||
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var dataFilePath = GetDataFilePath(item);
|
||||
|
||||
var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
|
||||
|
||||
if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath))
|
||||
{
|
||||
var mainResult = await FetchMainResult(id, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (mainResult == null) return;
|
||||
|
||||
tmdbId = mainResult.id.ToString(_usCulture);
|
||||
|
||||
dataFilePath = GetDataFilePath(tmdbId, language);
|
||||
|
||||
var directory = Path.GetDirectoryName(dataFilePath);
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
|
||||
}
|
||||
|
||||
if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item))
|
||||
{
|
||||
dataFilePath = GetDataFilePath(tmdbId, language);
|
||||
|
||||
if (!string.IsNullOrEmpty(dataFilePath))
|
||||
{
|
||||
var mainResult = JsonSerializer.DeserializeFromFile<CompleteMovieData>(dataFilePath);
|
||||
|
||||
ProcessMainInfo(item, mainResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the movie info.
|
||||
/// </summary>
|
||||
@ -363,60 +160,49 @@ namespace MediaBrowser.Providers.Movies
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
|
||||
|
||||
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
|
||||
_jsonSerializer.SerializeToFile(mainResult, dataFilePath);
|
||||
}
|
||||
|
||||
private readonly Task _cachedTask = Task.FromResult(true);
|
||||
internal Task EnsureMovieInfo(BaseItem item, CancellationToken cancellationToken)
|
||||
internal Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
var path = GetDataFilePath(item);
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
return _cachedTask;
|
||||
throw new ArgumentNullException("tmdbId");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(language))
|
||||
{
|
||||
throw new ArgumentNullException("language");
|
||||
}
|
||||
|
||||
var path = GetDataFilePath(tmdbId, language);
|
||||
|
||||
var fileInfo = _fileSystem.GetFileSystemInfo(path);
|
||||
|
||||
if (fileInfo.Exists)
|
||||
{
|
||||
// If it's recent or automatic updates are enabled, don't re-download
|
||||
if ((ConfigurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
|
||||
if ((_configurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
|
||||
{
|
||||
return _cachedTask;
|
||||
}
|
||||
}
|
||||
|
||||
var id = item.GetProviderId(MetadataProviders.Tmdb);
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return _cachedTask;
|
||||
}
|
||||
|
||||
return DownloadMovieInfo(id, item.GetPreferredMetadataLanguage(), cancellationToken);
|
||||
return DownloadMovieInfo(tmdbId, language, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data file path.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
internal string GetDataFilePath(BaseItem item)
|
||||
internal string GetDataFilePath(string tmdbId, string preferredLanguage)
|
||||
{
|
||||
var id = item.GetProviderId(MetadataProviders.Tmdb);
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
return null;
|
||||
throw new ArgumentNullException("tmdbId");
|
||||
}
|
||||
if (string.IsNullOrEmpty(preferredLanguage))
|
||||
{
|
||||
throw new ArgumentNullException("preferredLanguage");
|
||||
}
|
||||
|
||||
return GetDataFilePath(id, item.GetPreferredMetadataLanguage());
|
||||
}
|
||||
|
||||
private string GetDataFilePath(string tmdbId, string preferredLanguage)
|
||||
{
|
||||
var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId);
|
||||
var path = GetMovieDataPath(_configurationManager.ApplicationPaths, tmdbId);
|
||||
|
||||
var filename = string.Format("all-{0}.json",
|
||||
preferredLanguage ?? string.Empty);
|
||||
@ -431,7 +217,7 @@ namespace MediaBrowser.Providers.Movies
|
||||
/// <param name="language">The language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>Task{CompleteMovieData}.</returns>
|
||||
private async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken)
|
||||
internal async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
var url = string.Format(GetMovieInfo3, id, ApiKey);
|
||||
|
||||
@ -461,7 +247,7 @@ namespace MediaBrowser.Providers.Movies
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
|
||||
mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@ -470,7 +256,7 @@ namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
|
||||
_logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
|
||||
|
||||
url = string.Format(GetMovieInfo3, id, ApiKey) + "&include_image_language=en,null&language=en";
|
||||
|
||||
@ -482,12 +268,12 @@ namespace MediaBrowser.Providers.Movies
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
|
||||
mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(mainResult.overview))
|
||||
{
|
||||
Logger.Error("MovieDbProvider - Unable to find information for (id:" + id + ")");
|
||||
_logger.Error("MovieDbProvider - Unable to find information for (id:" + id + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -495,183 +281,6 @@ namespace MediaBrowser.Providers.Movies
|
||||
return mainResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the main info.
|
||||
/// </summary>
|
||||
/// <param name="movie">The movie.</param>
|
||||
/// <param name="movieData">The movie data.</param>
|
||||
private void ProcessMainInfo(BaseItem movie, CompleteMovieData movieData)
|
||||
{
|
||||
if (!movie.LockedFields.Contains(MetadataFields.Name))
|
||||
{
|
||||
movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name;
|
||||
}
|
||||
if (!movie.LockedFields.Contains(MetadataFields.Overview))
|
||||
{
|
||||
// Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException.
|
||||
movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null;
|
||||
movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null;
|
||||
}
|
||||
movie.HomePageUrl = movieData.homepage;
|
||||
|
||||
var hasBudget = movie as IHasBudget;
|
||||
if (hasBudget != null)
|
||||
{
|
||||
hasBudget.Budget = movieData.budget;
|
||||
hasBudget.Revenue = movieData.revenue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(movieData.tagline))
|
||||
{
|
||||
var hasTagline = movie as IHasTaglines;
|
||||
if (hasTagline != null)
|
||||
{
|
||||
hasTagline.Taglines.Clear();
|
||||
hasTagline.AddTagline(movieData.tagline);
|
||||
}
|
||||
}
|
||||
|
||||
movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture));
|
||||
movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id);
|
||||
|
||||
if (movieData.belongs_to_collection != null)
|
||||
{
|
||||
movie.SetProviderId(MetadataProviders.TmdbCollection,
|
||||
movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var movieItem = movie as Movie;
|
||||
|
||||
if (movieItem != null)
|
||||
{
|
||||
movieItem.TmdbCollectionName = movieData.belongs_to_collection.name;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry
|
||||
}
|
||||
|
||||
float rating;
|
||||
string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// tmdb appears to have unified their numbers to always report "7.3" regardless of country
|
||||
// so I removed the culture-specific processing here because it was not working for other countries -ebr
|
||||
// Movies get this from imdb
|
||||
if (!(movie is Movie) && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
|
||||
{
|
||||
movie.CommunityRating = rating;
|
||||
}
|
||||
|
||||
// Movies get this from imdb
|
||||
if (!(movie is Movie))
|
||||
{
|
||||
movie.VoteCount = movieData.vote_count;
|
||||
}
|
||||
|
||||
var preferredCountryCode = movie.GetPreferredMetadataCountryCode();
|
||||
|
||||
//release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match
|
||||
if (movieData.releases != null && movieData.releases.countries != null)
|
||||
{
|
||||
var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country();
|
||||
var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country();
|
||||
var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country();
|
||||
|
||||
if (!movie.LockedFields.Contains(MetadataFields.OfficialRating))
|
||||
{
|
||||
var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
|
||||
movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
|
||||
? ratingPrefix + ourRelease.certification
|
||||
: !string.IsNullOrEmpty(usRelease.certification)
|
||||
? usRelease.certification
|
||||
: !string.IsNullOrEmpty(minimunRelease.certification)
|
||||
? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
if (movieData.release_date.Year != 1)
|
||||
{
|
||||
//no specific country release info at all
|
||||
movie.PremiereDate = movieData.release_date.ToUniversalTime();
|
||||
movie.ProductionYear = movieData.release_date.Year;
|
||||
}
|
||||
|
||||
//studios
|
||||
if (movieData.production_companies != null && !movie.LockedFields.Contains(MetadataFields.Studios))
|
||||
{
|
||||
movie.Studios.Clear();
|
||||
|
||||
foreach (var studio in movieData.production_companies.Select(c => c.name))
|
||||
{
|
||||
movie.AddStudio(studio);
|
||||
}
|
||||
}
|
||||
|
||||
// genres
|
||||
// Movies get this from imdb
|
||||
var genres = movieData.genres ?? new List<GenreItem>();
|
||||
if (!movie.LockedFields.Contains(MetadataFields.Genres))
|
||||
{
|
||||
// Only grab them if a boxset or there are no genres.
|
||||
// For movies and trailers we'll use imdb via omdb
|
||||
// But omdb data is for english users only so fetch if language is not english
|
||||
if (!(movie is Movie) || movie.Genres.Count == 0 || !string.Equals(movie.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
movie.Genres.Clear();
|
||||
|
||||
foreach (var genre in genres.Select(g => g.name))
|
||||
{
|
||||
movie.AddGenre(genre);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!movie.LockedFields.Contains(MetadataFields.Cast))
|
||||
{
|
||||
movie.People.Clear();
|
||||
|
||||
//Actors, Directors, Writers - all in People
|
||||
//actors come from cast
|
||||
if (movieData.casts != null && movieData.casts.cast != null)
|
||||
{
|
||||
foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
|
||||
}
|
||||
|
||||
//and the rest from crew
|
||||
if (movieData.casts != null && movieData.casts.crew != null)
|
||||
{
|
||||
foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
|
||||
}
|
||||
}
|
||||
|
||||
if (movieData.keywords != null && movieData.keywords.keywords != null && !movie.LockedFields.Contains(MetadataFields.Keywords))
|
||||
{
|
||||
var hasTags = movie as IHasKeywords;
|
||||
if (hasTags != null)
|
||||
{
|
||||
hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (movieData.trailers != null && movieData.trailers.youtube != null &&
|
||||
movieData.trailers.youtube.Count > 0)
|
||||
{
|
||||
var hasTrailers = movie as IHasTrailers;
|
||||
if (hasTrailers != null)
|
||||
{
|
||||
hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
|
||||
{
|
||||
Url = string.Format("http://www.youtube.com/watch?v={0}", i.source),
|
||||
IsDirectLink = false,
|
||||
Name = i.name,
|
||||
VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime _lastRequestDate = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
@ -695,7 +304,7 @@ namespace MediaBrowser.Providers.Movies
|
||||
|
||||
_lastRequestDate = DateTime.Now;
|
||||
|
||||
return await HttpClient.Get(options).ConfigureAwait(false);
|
||||
return await _httpClient.Get(options).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -897,18 +506,5 @@ namespace MediaBrowser.Providers.Movies
|
||||
public Keywords keywords { get; set; }
|
||||
public Trailers trailers { get; set; }
|
||||
}
|
||||
|
||||
internal class TmdbImageSettings
|
||||
{
|
||||
public List<string> backdrop_sizes { get; set; }
|
||||
public string base_url { get; set; }
|
||||
public List<string> poster_sizes { get; set; }
|
||||
public List<string> profile_sizes { get; set; }
|
||||
}
|
||||
|
||||
internal class TmdbSettingsResult
|
||||
{
|
||||
public TmdbImageSettings images { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
43
MediaBrowser.Providers/Movies/MovieMetadataService.cs
Normal file
43
MediaBrowser.Providers/Movies/MovieMetadataService.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
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.Movies
|
||||
{
|
||||
public class MovieMetadataService : MetadataService<Movie, ItemId>
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public MovieMetadataService(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(Movie source, Movie target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
protected override Task SaveItem(Movie item, ItemUpdateType reason, CancellationToken cancellationToken)
|
||||
{
|
||||
return _libraryManager.UpdateItem(item, reason, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,112 +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 MediaBrowser.Providers.Savers;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
/// <summary>
|
||||
/// Class MovieProviderFromXml
|
||||
/// </summary>
|
||||
public class MovieProviderFromXml : BaseMetadataProvider
|
||||
{
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public MovieProviderFromXml(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)
|
||||
{
|
||||
if (item.LocationType != LocationType.FileSystem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var trailer = item as Trailer;
|
||||
|
||||
if (trailer != null)
|
||||
{
|
||||
return !trailer.IsLocalTrailer;
|
||||
}
|
||||
|
||||
// Check parent for null to avoid running this against things like video backdrops
|
||||
return item is Video && !(item is Episode) && item.Parent != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get { return MetadataProviderPriority.First; }
|
||||
}
|
||||
|
||||
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
|
||||
{
|
||||
var savePath = MovieXmlSaver.GetMovieSavePath((Video)item);
|
||||
|
||||
var xml = item.ResolveArgs.GetMetaFileByPath(savePath) ?? new FileInfo(savePath);
|
||||
|
||||
if (!xml.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
|
||||
}
|
||||
|
||||
/// <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 video = (Video)item;
|
||||
|
||||
var path = MovieXmlSaver.GetMovieSavePath(video);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
new MovieXmlParser(Logger).FetchAsync(video, path, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
}
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,11 +4,10 @@ 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>
|
||||
public class MovieXmlProvider : BaseXmlProvider<Movie>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -18,61 +17,34 @@ namespace MediaBrowser.Providers.Movies
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Movie>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(Movie item, 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;
|
||||
new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
return GetXmlFileInfo(info, FileSystem);
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
public static FileInfo GetXmlFileInfo(ItemInfo info, IFileSystem fileSystem)
|
||||
{
|
||||
return GetXmlFileInfo(path, FileSystem);
|
||||
}
|
||||
|
||||
public static FileInfo GetXmlFileInfo(string path, IFileSystem _fileSystem)
|
||||
{
|
||||
var fileInfo = _fileSystem.GetFileSystemInfo(path);
|
||||
var fileInfo = fileSystem.GetFileSystemInfo(info.Path);
|
||||
|
||||
var directoryInfo = fileInfo as DirectoryInfo;
|
||||
|
||||
if (directoryInfo == null)
|
||||
{
|
||||
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
|
||||
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path));
|
||||
}
|
||||
|
||||
var directoryPath = directoryInfo.FullName;
|
||||
|
||||
var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml");
|
||||
var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml");
|
||||
|
||||
var file = new FileInfo(specificFile);
|
||||
|
||||
return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml"));
|
||||
return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,285 +0,0 @@
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
public class OpenMovieDatabaseProvider : BaseMetadataProvider
|
||||
{
|
||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the json serializer.
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
protected IJsonSerializer JsonSerializer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP client.
|
||||
/// </summary>
|
||||
/// <value>The HTTP client.</value>
|
||||
protected IHttpClient HttpClient { get; private set; }
|
||||
|
||||
public OpenMovieDatabaseProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient)
|
||||
: base(logManager, configurationManager)
|
||||
{
|
||||
JsonSerializer = jsonSerializer;
|
||||
HttpClient = httpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the provider version.
|
||||
/// </summary>
|
||||
/// <value>The provider version.</value>
|
||||
protected override string ProviderVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return "13";
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Supports 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)
|
||||
{
|
||||
var trailer = item as Trailer;
|
||||
|
||||
// Don't support local trailers
|
||||
if (trailer != null)
|
||||
{
|
||||
return !trailer.IsLocalTrailer;
|
||||
}
|
||||
|
||||
return item is Movie || item is MusicVideo || item is Series;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get
|
||||
{
|
||||
// Run after moviedb and xml providers
|
||||
return MetadataProviderPriority.Fifth;
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var imdbId = item.GetProviderId(MetadataProviders.Imdb);
|
||||
|
||||
if (string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
|
||||
|
||||
var url = string.Format("http://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
|
||||
|
||||
using (var stream = await HttpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
ResourcePool = _resourcePool,
|
||||
CancellationToken = cancellationToken
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
var result = JsonSerializer.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
var hasCriticRating = item as IHasCriticRating;
|
||||
if (hasCriticRating != null)
|
||||
{
|
||||
// Seeing some bogus RT data on omdb for series, so filter it out here
|
||||
// RT doesn't even have tv series
|
||||
int tomatoMeter;
|
||||
|
||||
if (!string.IsNullOrEmpty(result.tomatoMeter)
|
||||
&& int.TryParse(result.tomatoMeter, NumberStyles.Integer, UsCulture, out tomatoMeter)
|
||||
&& tomatoMeter >= 0)
|
||||
{
|
||||
hasCriticRating.CriticRating = tomatoMeter;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.tomatoConsensus)
|
||||
&& !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus);
|
||||
}
|
||||
}
|
||||
|
||||
int voteCount;
|
||||
|
||||
if (!string.IsNullOrEmpty(result.imdbVotes)
|
||||
&& int.TryParse(result.imdbVotes, NumberStyles.Number, UsCulture, out voteCount)
|
||||
&& voteCount >= 0)
|
||||
{
|
||||
item.VoteCount = voteCount;
|
||||
}
|
||||
|
||||
float imdbRating;
|
||||
|
||||
if (!string.IsNullOrEmpty(result.imdbRating)
|
||||
&& float.TryParse(result.imdbRating, NumberStyles.Any, UsCulture, out imdbRating)
|
||||
&& imdbRating >= 0)
|
||||
{
|
||||
item.CommunityRating = imdbRating;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Website)
|
||||
&& !string.Equals(result.Website, "n/a", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.HomePageUrl = result.Website;
|
||||
}
|
||||
|
||||
ParseAdditionalMetadata(item, result);
|
||||
}
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ParseAdditionalMetadata(BaseItem item, RootObject result)
|
||||
{
|
||||
// Grab series genres because imdb data is better than tvdb. Leave movies alone
|
||||
// But only do it if english is the preferred language because this data will not be localized
|
||||
if (!item.LockedFields.Contains(MetadataFields.Genres) &&
|
||||
ShouldFetchGenres(item) &&
|
||||
!string.IsNullOrWhiteSpace(result.Genre) &&
|
||||
!string.Equals(result.Genre, "n/a", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.Genres.Clear();
|
||||
|
||||
foreach (var genre in result.Genre
|
||||
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(i => i.Trim())
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i)))
|
||||
{
|
||||
item.AddGenre(genre);
|
||||
}
|
||||
}
|
||||
|
||||
var hasMetascore = item as IHasMetascore;
|
||||
if (hasMetascore != null)
|
||||
{
|
||||
float metascore;
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Metascore) && float.TryParse(result.Metascore, NumberStyles.Any, UsCulture, out metascore) && metascore >= 0)
|
||||
{
|
||||
hasMetascore.Metascore = metascore;
|
||||
}
|
||||
}
|
||||
|
||||
var hasAwards = item as IHasAwards;
|
||||
if (hasAwards != null && !string.IsNullOrEmpty(result.Awards) &&
|
||||
!string.Equals(result.Awards, "n/a", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldFetchGenres(BaseItem item)
|
||||
{
|
||||
var lang = item.GetPreferredMetadataLanguage();
|
||||
|
||||
// The data isn't localized and so can only be used for english users
|
||||
if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only fetch if other providers didn't get anything
|
||||
if (item is Trailer)
|
||||
{
|
||||
return item.Genres.Count == 0;
|
||||
}
|
||||
|
||||
return item is Series || item is Movie;
|
||||
}
|
||||
|
||||
protected class RootObject
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Year { get; set; }
|
||||
public string Rated { get; set; }
|
||||
public string Released { get; set; }
|
||||
public string Runtime { get; set; }
|
||||
public string Genre { get; set; }
|
||||
public string Director { get; set; }
|
||||
public string Writer { get; set; }
|
||||
public string Actors { get; set; }
|
||||
public string Plot { get; set; }
|
||||
public string Poster { get; set; }
|
||||
public string imdbRating { get; set; }
|
||||
public string imdbVotes { get; set; }
|
||||
public string imdbID { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string tomatoMeter { get; set; }
|
||||
public string tomatoImage { get; set; }
|
||||
public string tomatoRating { get; set; }
|
||||
public string tomatoReviews { get; set; }
|
||||
public string tomatoFresh { get; set; }
|
||||
public string tomatoRotten { get; set; }
|
||||
public string tomatoConsensus { get; set; }
|
||||
public string tomatoUserMeter { get; set; }
|
||||
public string tomatoUserRating { get; set; }
|
||||
public string tomatoUserReviews { get; set; }
|
||||
public string DVD { get; set; }
|
||||
public string BoxOffice { get; set; }
|
||||
public string Production { get; set; }
|
||||
public string Website { get; set; }
|
||||
public string Response { get; set; }
|
||||
|
||||
public string Language { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string Awards { get; set; }
|
||||
public string Metascore { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
17
MediaBrowser.Providers/Movies/TmdbSettings.cs
Normal file
17
MediaBrowser.Providers/Movies/TmdbSettings.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
internal class TmdbImageSettings
|
||||
{
|
||||
public List<string> backdrop_sizes { get; set; }
|
||||
public string base_url { get; set; }
|
||||
public List<string> poster_sizes { get; set; }
|
||||
public List<string> profile_sizes { get; set; }
|
||||
}
|
||||
|
||||
internal class TmdbSettingsResult
|
||||
{
|
||||
public TmdbImageSettings images { get; set; }
|
||||
}
|
||||
}
|
43
MediaBrowser.Providers/Movies/TrailerMetadataService.cs
Normal file
43
MediaBrowser.Providers/Movies/TrailerMetadataService.cs
Normal file
@ -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.Movies
|
||||
{
|
||||
public class TrailerMetadataService : MetadataService<Trailer, ItemId>
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public TrailerMetadataService(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(Trailer source, Trailer target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
protected override Task SaveItem(Trailer item, ItemUpdateType reason, CancellationToken cancellationToken)
|
||||
{
|
||||
return _libraryManager.UpdateItem(item, reason, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
30
MediaBrowser.Providers/Movies/TrailerXmlProvider.cs
Normal file
30
MediaBrowser.Providers/Movies/TrailerXmlProvider.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
public class TrailerXmlProvider : BaseXmlProvider<Trailer>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public TrailerXmlProvider(IFileSystem fileSystem, ILogger logger)
|
||||
: base(fileSystem)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override void Fetch(Trailer item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
class AlbumXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicAlbum>
|
||||
class AlbumXmlProvider : BaseXmlProvider<MusicAlbum>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<MusicAlbum>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(MusicAlbum item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<MusicAlbum>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var item = new MusicAlbum();
|
||||
|
||||
new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = item;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return new FileInfo(Path.Combine(path, "album.xml"));
|
||||
return new FileInfo(Path.Combine(info.Path, "album.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
class ArtistXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicArtist>
|
||||
class ArtistXmlProvider : BaseXmlProvider<MusicArtist>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<MusicArtist>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(MusicArtist item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<MusicArtist>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var item = new MusicArtist();
|
||||
|
||||
new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = item;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return new FileInfo(Path.Combine(path, "artist.xml"));
|
||||
return new FileInfo(Path.Combine(info.Path, "artist.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
53
MediaBrowser.Providers/Music/AudioMetadataService.cs
Normal file
53
MediaBrowser.Providers/Music/AudioMetadataService.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
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
|
||||
{
|
||||
public class AudioMetadataService : MetadataService<Audio, ItemId>
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public AudioMetadataService(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(Audio source, Audio target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
if (replaceData || target.Artists.Count == 0)
|
||||
{
|
||||
target.Artists = source.Artists;
|
||||
}
|
||||
|
||||
if (replaceData || string.IsNullOrEmpty(target.Album))
|
||||
{
|
||||
target.Album = source.Album;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task SaveItem(Audio item, ItemUpdateType reason, CancellationToken cancellationToken)
|
||||
{
|
||||
return _libraryManager.UpdateItem(item, reason, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,11 +5,10 @@ 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>
|
||||
class MusicVideoXmlProvider : BaseXmlProvider<MusicVideo>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -19,42 +18,14 @@ namespace MediaBrowser.Providers.Music
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<MusicVideo>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(MusicVideo item, 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;
|
||||
new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return MovieXmlProvider.GetXmlFileInfo(path, FileSystem);
|
||||
return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.People
|
||||
{
|
||||
public class PersonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Person>
|
||||
public class PersonXmlProvider : BaseXmlProvider<Person>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.People
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Person>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(Person item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<Person>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var person = new Person();
|
||||
|
||||
new BaseItemXmlParser<Person>(_logger).Fetch(person, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = person;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new BaseItemXmlParser<Person>(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return new FileInfo(Path.Combine(path, "person.xml"));
|
||||
return new FileInfo(Path.Combine(info.Path, "person.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,10 @@ namespace MediaBrowser.Providers
|
||||
{
|
||||
if (replaceData || !target.RunTimeTicks.HasValue)
|
||||
{
|
||||
target.RunTimeTicks = source.RunTimeTicks;
|
||||
if (!(target is Audio) && !(target is Video))
|
||||
{
|
||||
target.RunTimeTicks = source.RunTimeTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,6 +162,11 @@ namespace MediaBrowser.Providers
|
||||
|
||||
MergeAlbumArtist(source, target, lockedFields, replaceData);
|
||||
MergeBudget(source, target, lockedFields, replaceData);
|
||||
MergeMetascore(source, target, lockedFields, replaceData);
|
||||
MergeCriticRating(source, target, lockedFields, replaceData);
|
||||
MergeAwards(source, target, lockedFields, replaceData);
|
||||
MergeTaglines(source, target, lockedFields, replaceData);
|
||||
MergeTrailers(source, target, lockedFields, replaceData);
|
||||
|
||||
if (mergeMetadataSettings)
|
||||
{
|
||||
@ -218,5 +226,80 @@ namespace MediaBrowser.Providers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MergeMetascore(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
||||
{
|
||||
var sourceCast = source as IHasMetascore;
|
||||
var targetCast = target as IHasMetascore;
|
||||
|
||||
if (sourceCast != null && targetCast != null)
|
||||
{
|
||||
if (replaceData || !targetCast.Metascore.HasValue)
|
||||
{
|
||||
targetCast.Metascore = sourceCast.Metascore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MergeAwards(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
||||
{
|
||||
var sourceCast = source as IHasAwards;
|
||||
var targetCast = target as IHasAwards;
|
||||
|
||||
if (sourceCast != null && targetCast != null)
|
||||
{
|
||||
if (replaceData || string.IsNullOrEmpty(targetCast.AwardSummary))
|
||||
{
|
||||
targetCast.AwardSummary = sourceCast.AwardSummary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MergeCriticRating(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
||||
{
|
||||
var sourceCast = source as IHasCriticRating;
|
||||
var targetCast = target as IHasCriticRating;
|
||||
|
||||
if (sourceCast != null && targetCast != null)
|
||||
{
|
||||
if (replaceData || !targetCast.CriticRating.HasValue)
|
||||
{
|
||||
targetCast.CriticRating = sourceCast.CriticRating;
|
||||
}
|
||||
|
||||
if (replaceData || string.IsNullOrEmpty(targetCast.CriticRatingSummary))
|
||||
{
|
||||
targetCast.CriticRatingSummary = sourceCast.CriticRatingSummary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MergeTaglines(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
||||
{
|
||||
var sourceCast = source as IHasTaglines;
|
||||
var targetCast = target as IHasTaglines;
|
||||
|
||||
if (sourceCast != null && targetCast != null)
|
||||
{
|
||||
if (replaceData || targetCast.Taglines.Count == 0)
|
||||
{
|
||||
targetCast.Taglines = sourceCast.Taglines;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MergeTrailers(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
||||
{
|
||||
var sourceCast = source as IHasTrailers;
|
||||
var targetCast = target as IHasTrailers;
|
||||
|
||||
if (sourceCast != null && targetCast != null)
|
||||
{
|
||||
if (replaceData || targetCast.RemoteTrailers.Count == 0)
|
||||
{
|
||||
targetCast.RemoteTrailers = sourceCast.RemoteTrailers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,20 +90,14 @@ namespace MediaBrowser.Providers
|
||||
}
|
||||
|
||||
var dbItem = _libraryManager.GetItemById(item.Id);
|
||||
var isNewItem = false;
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(item.ResolveArgs);
|
||||
item = dbItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
isNewItem = true;
|
||||
}
|
||||
|
||||
// Force the save if it's a new item
|
||||
await item.RefreshMetadata(cancellationToken, isNewItem).ConfigureAwait(false);
|
||||
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.Savers
|
||||
return Path.ChangeExtension(item.Path, ".xml");
|
||||
}
|
||||
|
||||
return Path.Combine(item.MetaLocation, "game.xml");
|
||||
return Path.Combine(item.ContainingFolderPath, "game.xml");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,16 +50,9 @@ namespace MediaBrowser.Providers.Savers
|
||||
// If new metadata has been downloaded and save local is on
|
||||
if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded))
|
||||
{
|
||||
var trailer = item as Trailer;
|
||||
|
||||
// Don't support local trailers
|
||||
if (trailer != null)
|
||||
{
|
||||
return !trailer.IsLocalTrailer;
|
||||
}
|
||||
var video = item as Video;
|
||||
// Check parent for null to avoid running this against things like video backdrops
|
||||
return video != null && !(item is Episode) && video.Parent != null;
|
||||
return video != null && !(item is Episode) && !video.IsOwnedItem;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -145,7 +138,7 @@ namespace MediaBrowser.Providers.Savers
|
||||
return Path.ChangeExtension(item.Path, ".xml");
|
||||
}
|
||||
|
||||
return Path.Combine(item.MetaLocation, "movie.xml");
|
||||
return Path.Combine(item.ContainingFolderPath, "movie.xml");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -102,8 +103,10 @@ namespace MediaBrowser.Providers.TV
|
||||
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);
|
||||
var filename = Path.GetFileName(item.Path);
|
||||
|
||||
item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(filename, item.Parent is Season);
|
||||
item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(filename);
|
||||
|
||||
if (!item.ParentIndexNumber.HasValue)
|
||||
{
|
||||
|
@ -4,11 +4,10 @@ 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>
|
||||
public class EpisodeXmlProvider : BaseXmlProvider<Episode>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -18,43 +17,16 @@ namespace MediaBrowser.Providers.TV
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Episode>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(Episode item, 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;
|
||||
new EpisodeXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
var metadataPath = Path.GetDirectoryName(path);
|
||||
var metadataPath = Path.GetDirectoryName(info.Path);
|
||||
metadataPath = Path.Combine(metadataPath, "metadata");
|
||||
var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(path), ".xml"));
|
||||
var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(info.Path), ".xml"));
|
||||
|
||||
return new FileInfo(metadataFile);
|
||||
}
|
||||
|
@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.TV
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SeriesProviderFromXml
|
||||
/// </summary>
|
||||
public class SeasonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Season>
|
||||
public class SeasonXmlProvider : BaseXmlProvider<Season>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Season>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(Season item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<Season>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var person = new Season();
|
||||
|
||||
new BaseItemXmlParser<Season>(_logger).Fetch(person, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = person;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new BaseItemXmlParser<Season>(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return new FileInfo(Path.Combine(path, "season.xml"));
|
||||
return new FileInfo(Path.Combine(info.Path, "season.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
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 System;
|
||||
@ -53,7 +54,7 @@ namespace MediaBrowser.Providers.TV
|
||||
await new MissingEpisodeProvider(_logger, _config).Run(seriesGroups, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var numComplete = 0;
|
||||
|
||||
|
||||
foreach (var series in seriesList)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@ -171,7 +172,9 @@ namespace MediaBrowser.Providers.TV
|
||||
{
|
||||
foreach (var series in group)
|
||||
{
|
||||
await series.RefreshMetadata(cancellationToken, true)
|
||||
await series.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
}, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await series.ValidateChildren(new Progress<double>(), cancellationToken, true)
|
||||
@ -438,7 +441,9 @@ namespace MediaBrowser.Providers.TV
|
||||
|
||||
await season.AddChild(episode, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await episode.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
await episode.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -464,7 +469,9 @@ namespace MediaBrowser.Providers.TV
|
||||
};
|
||||
|
||||
await series.AddChild(season, cancellationToken).ConfigureAwait(false);
|
||||
await season.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
await season.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return season;
|
||||
}
|
||||
|
@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.TV
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SeriesProviderFromXml
|
||||
/// </summary>
|
||||
public class SeriesXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Series>
|
||||
public class SeriesXmlProvider : BaseXmlProvider<Series>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Series>> GetMetadata(string path, CancellationToken cancellationToken)
|
||||
protected override void Fetch(Series item, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = GetXmlFile(path).FullName;
|
||||
|
||||
var result = new MetadataResult<Series>();
|
||||
|
||||
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var person = new Series();
|
||||
|
||||
new SeriesXmlParser(_logger).Fetch(person, path, cancellationToken);
|
||||
result.HasMetadata = true;
|
||||
result.Item = person;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
result.HasMetadata = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
XmlParsingResourcePool.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
new SeriesXmlParser(_logger).Fetch(item, path, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
protected override FileInfo GetXmlFile(ItemInfo info)
|
||||
{
|
||||
get { return "Media Browser Xml"; }
|
||||
}
|
||||
|
||||
protected override FileInfo GetXmlFile(string path)
|
||||
{
|
||||
return new FileInfo(Path.Combine(path, "series.xml"));
|
||||
return new FileInfo(Path.Combine(info.Path, "series.xml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +185,14 @@ namespace MediaBrowser.Providers.TV
|
||||
var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber));
|
||||
var success = false;
|
||||
var usingAbsoluteData = false;
|
||||
var episode = new Episode();
|
||||
|
||||
var episode = new Episode
|
||||
{
|
||||
IndexNumber = id.IndexNumber,
|
||||
ParentIndexNumber = id.ParentIndexNumber,
|
||||
IndexNumberEnd = id.IndexNumberEnd
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
FetchMainEpisodeInfo(episode, file, cancellationToken);
|
||||
|
52
MediaBrowser.Providers/Videos/VideoMetadataService.cs
Normal file
52
MediaBrowser.Providers/Videos/VideoMetadataService.cs
Normal file
@ -0,0 +1,52 @@
|
||||
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.Videos
|
||||
{
|
||||
public class VideoMetadataService : MetadataService<Video, ItemId>
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public VideoMetadataService(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(Video source, Video target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
protected override Task SaveItem(Video item, ItemUpdateType reason, CancellationToken cancellationToken)
|
||||
{
|
||||
return _libraryManager.UpdateItem(item, reason, cancellationToken);
|
||||
}
|
||||
|
||||
public override int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
// Make sure the type-specific services get picked first
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers
|
||||
{
|
||||
public class VirtualItemImageValidator : BaseMetadataProvider
|
||||
{
|
||||
public VirtualItemImageValidator(ILogManager logManager, IServerConfigurationManager configurationManager)
|
||||
: base(logManager, configurationManager)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Supports(BaseItem item)
|
||||
{
|
||||
var locationType = item.LocationType;
|
||||
|
||||
// The regular provider will get virtual seasons
|
||||
if (item.LocationType == LocationType.Virtual)
|
||||
{
|
||||
var season = item as Season;
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
var series = season.Series;
|
||||
|
||||
if (series != null && series.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return locationType == LocationType.Virtual ||
|
||||
locationType == LocationType.Remote;
|
||||
}
|
||||
|
||||
public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
item.ValidateImages();
|
||||
|
||||
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
|
||||
return TrueTaskResult;
|
||||
}
|
||||
|
||||
public override MetadataProviderPriority Priority
|
||||
{
|
||||
get { return MetadataProviderPriority.First; }
|
||||
}
|
||||
}
|
||||
}
|
43
MediaBrowser.Providers/Years/YearMetadataService.cs
Normal file
43
MediaBrowser.Providers/Years/YearMetadataService.cs
Normal file
@ -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.Years
|
||||
{
|
||||
public class YearMetadataService : MetadataService<Year, ItemId>
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public YearMetadataService(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(Year source, Year target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
protected override Task SaveItem(Year item, ItemUpdateType reason, CancellationToken cancellationToken)
|
||||
{
|
||||
return _libraryManager.UpdateItem(item, reason, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -273,21 +273,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (item.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
return collections.Where(i =>
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
return i.LocationType == LocationType.FileSystem &&
|
||||
i.PhysicalLocations.Contains(item.Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting ResolveArgs for {0}", ex, i.Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
}).Cast<T>();
|
||||
return collections.Where(i => i.LocationType == LocationType.FileSystem &&
|
||||
i.PhysicalLocations.Contains(item.Path)).Cast<T>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,18 +162,7 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||
.Children
|
||||
.OfType<Folder>()
|
||||
.Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual)
|
||||
.SelectMany(f =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return f.PhysicalLocations;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new string[] { };
|
||||
}
|
||||
|
||||
})
|
||||
.SelectMany(f => f.PhysicalLocations)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(i => i)
|
||||
.ToList();
|
||||
|
@ -833,9 +833,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
(item as MusicArtist).IsAccessedByName = true;
|
||||
}
|
||||
|
||||
// Set this now so we don't cause additional file system access during provider executions
|
||||
item.ResetResolveArgs(fileInfo);
|
||||
|
||||
return new Tuple<bool, T>(isNew, item);
|
||||
}
|
||||
|
||||
@ -1113,6 +1110,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await userRootFolder.ValidateChildren(new Progress<double>(), cancellationToken, recursive: false).ConfigureAwait(false);
|
||||
var b = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1244,7 +1242,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(video.ResolveArgs);
|
||||
video = dbItem;
|
||||
}
|
||||
}
|
||||
@ -1383,6 +1380,8 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
|
||||
_logger.Debug("Saving {0} to database.", item.Path ?? item.Name);
|
||||
|
||||
await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
UpdateItemInLibraryCache(item);
|
||||
@ -1479,16 +1478,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
return i.PhysicalLocations.Contains(item.Path);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting resolve args for {0}", ex, i.Path);
|
||||
return false;
|
||||
}
|
||||
return i.PhysicalLocations.Contains(item.Path);
|
||||
})
|
||||
.Select(i => i.CollectionType)
|
||||
.Where(i => !string.IsNullOrEmpty(i))
|
||||
|
@ -23,8 +23,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem)
|
||||
{
|
||||
item.ResetResolveArgs(args);
|
||||
|
||||
// If the resolver didn't specify this
|
||||
if (string.IsNullOrEmpty(item.Path))
|
||||
{
|
||||
|
@ -184,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
|
||||
private void SetProviderIdFromPath(Video item)
|
||||
{
|
||||
//we need to only look at the name of this actual item (not parents)
|
||||
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.MetaLocation);
|
||||
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
|
||||
|
||||
var id = justName.GetAttributeValue("tmdbid");
|
||||
|
||||
|
@ -47,17 +47,5 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the initial item values.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
protected override void SetInitialItemValues(Season item, ItemResolveArgs args)
|
||||
{
|
||||
base.SetInitialItemValues(item, args);
|
||||
|
||||
Season.AddMetadataFiles(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,8 +92,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
||||
{
|
||||
base.SetInitialItemValues(item, args);
|
||||
|
||||
Season.AddMetadataFiles(args);
|
||||
|
||||
SetProviderIdFromPath(item, args.Path);
|
||||
}
|
||||
|
||||
|
@ -326,13 +326,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||
item.Name = channelInfo.Name;
|
||||
}
|
||||
|
||||
// Set this now so we don't cause additional file system access during provider executions
|
||||
item.ResetResolveArgs(fileInfo);
|
||||
|
||||
await item.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = isNew,
|
||||
ResetResolveArgs = false
|
||||
ForceSave = isNew
|
||||
|
||||
}, cancellationToken);
|
||||
|
||||
@ -391,8 +387,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||
|
||||
await item.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = isNew,
|
||||
ResetResolveArgs = false
|
||||
ForceSave = isNew
|
||||
|
||||
}, cancellationToken);
|
||||
|
||||
@ -448,8 +443,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||
|
||||
await item.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = isNew,
|
||||
ResetResolveArgs = false
|
||||
ForceSave = isNew
|
||||
|
||||
}, cancellationToken);
|
||||
|
||||
|
@ -501,7 +501,7 @@ namespace MediaBrowser.ServerApplication
|
||||
GetExports<ILibraryPostScanTask>(),
|
||||
GetExports<IPeoplePrescanTask>());
|
||||
|
||||
ProviderManager.AddParts(GetExports<BaseMetadataProvider>(), GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(),
|
||||
ProviderManager.AddParts(GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(),
|
||||
GetExports<IMetadataSaver>());
|
||||
|
||||
ImageProcessor.AddParts(GetExports<IImageEnhancer>());
|
||||
@ -627,7 +627,7 @@ namespace MediaBrowser.ServerApplication
|
||||
list.Add(typeof(IServerApplicationHost).Assembly);
|
||||
|
||||
// Include composable parts in the Providers assembly
|
||||
list.Add(typeof(ImagesByNameProvider).Assembly);
|
||||
list.Add(typeof(ProviderUtils).Assembly);
|
||||
|
||||
// Common implementations
|
||||
list.Add(typeof(TaskManager).Assembly);
|
||||
|
@ -237,4 +237,7 @@ Global
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
Loading…
x
Reference in New Issue
Block a user