converted movie providers to new system

This commit is contained in:
Luke Pulverenti 2014-02-05 23:39:16 -05:00
parent 64eb8c82a3
commit 821a3d29a2
94 changed files with 1677 additions and 5369 deletions

View File

@ -75,15 +75,7 @@ namespace MediaBrowser.Api.Library
if (locationType != LocationType.Remote && locationType != LocationType.Virtual) if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
{ {
try return c.PhysicalLocations;
{
return c.PhysicalLocations;
}
catch (Exception ex)
{
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, c.Path);
}
} }
return new List<string>(); return new List<string>();

View File

@ -331,21 +331,8 @@ namespace MediaBrowser.Api
{ {
if (item.Parent is AggregateFolder) if (item.Parent is AggregateFolder)
{ {
return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.LocationType == LocationType.FileSystem &&
{ i.PhysicalLocations.Contains(item.Path));
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 item; return item;

View File

@ -3,6 +3,10 @@ namespace MediaBrowser.Controller.Entities
{ {
public class AdultVideo : Video, IHasPreferredMetadataLanguage 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; } public string PreferredMetadataLanguage { get; set; }
/// <summary> /// <summary>

View File

@ -1,7 +1,11 @@
using System; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {
@ -11,6 +15,11 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class AggregateFolder : Folder public class AggregateFolder : Folder
{ {
public AggregateFolder()
{
PhysicalLocationsList = new List<string>();
}
/// <summary> /// <summary>
/// We don't support manual shortcuts /// We don't support manual shortcuts
/// </summary> /// </summary>
@ -36,6 +45,60 @@ namespace MediaBrowser.Controller.Entities
get { return _virtualChildren; } 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> /// <summary>
/// Adds the virtual child. /// Adds the virtual child.
/// </summary> /// </summary>

View File

@ -1,12 +1,10 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -102,6 +100,35 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember] [IgnoreDataMember]
protected internal bool IsOffline { get; set; } 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> /// <summary>
/// Gets or sets the type of the location. /// Gets or sets the type of the location.
/// </summary> /// </summary>
@ -189,19 +216,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The locked fields.</value> /// <value>The locked fields.</value>
public List<MetadataFields> LockedFields { get; set; } 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> /// <summary>
/// Gets the type of the media. /// Gets the type of the media.
/// </summary> /// </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] [IgnoreDataMember]
public ItemResolveArgs ResolveArgs public virtual IEnumerable<string> PhysicalLocations
{ {
get get
{ {
if (_resolveArgs == null) var locationType = LocationType;
if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
{ {
try return new string[] { };
{
_resolveArgs = CreateResolveArgs();
}
catch (IOException ex)
{
Logger.ErrorException("Error creating resolve args for {0}", ex, Path);
IsOffline = true;
throw;
}
} }
return _resolveArgs; return new[] { Path };
}
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;
} }
} }
@ -583,122 +456,93 @@ namespace MediaBrowser.Controller.Entities
/// Loads local trailers from the file system /// Loads local trailers from the file system
/// </summary> /// </summary>
/// <returns>List{Video}.</returns> /// <returns>List{Video}.</returns>
private IEnumerable<Trailer> LoadLocalTrailers() private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren)
{ {
ItemResolveArgs resolveArgs; return new List<Trailer>();
//ItemResolveArgs resolveArgs;
try //try
{ //{
resolveArgs = ResolveArgs; // resolveArgs = ResolveArgs;
if (!resolveArgs.IsDirectory) // if (!resolveArgs.IsDirectory)
{ // {
return new List<Trailer>(); // return new List<Trailer>();
} // }
} //}
catch (IOException ex) //catch (IOException ex)
{ //{
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); // Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
return new List<Trailer>(); // 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 //// Path doesn't exist. No biggie
if (folder != null) //if (folder != null)
{ //{
try // try
{ // {
files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles()); // files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
} // }
catch (IOException ex) // catch (IOException ex)
{ // {
Logger.ErrorException("Error loading trailers for {0}", ex, Name); // Logger.ErrorException("Error loading trailers for {0}", ex, Name);
} // }
} //}
// Support xbmc trailers (-trailer suffix on video file names) //// Support xbmc trailers (-trailer suffix on video file names)
files.AddRange(resolveArgs.FileSystemChildren.Where(i => //files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
{ //{
try // try
{ // {
if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) // 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)) // if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
{ // {
return true; // return true;
} // }
} // }
} // }
catch (IOException ex) // catch (IOException ex)
{ // {
Logger.ErrorException("Error accessing path {0}", ex, i.FullName); // Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
} // }
return false; // return false;
})); //}));
return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video => //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 // // 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; // var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
if (dbItem != null) // if (dbItem != null)
{ // {
dbItem.ResetResolveArgs(video.ResolveArgs); // video = dbItem;
video = dbItem; // }
}
return video; // return video;
}).ToList(); //}).ToList();
} }
/// <summary> /// <summary>
/// Loads the theme songs. /// Loads the theme songs.
/// </summary> /// </summary>
/// <returns>List{Audio.Audio}.</returns> /// <returns>List{Audio.Audio}.</returns>
private IEnumerable<Audio.Audio> LoadThemeSongs() private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren)
{ {
ItemResolveArgs resolveArgs; var files = fileSystemChildren.OfType<DirectoryInfo>()
.Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
try .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
{ .ToList();
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);
}
}
// Support plex/xbmc convention // Support plex/xbmc convention
files.AddRange(resolveArgs.FileSystemChildren files.AddRange(fileSystemChildren.OfType<FileInfo>()
.Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsAudioFile(i.Name)) .Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
); );
return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio => return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio =>
@ -708,7 +552,6 @@ namespace MediaBrowser.Controller.Entities
if (dbItem != null) if (dbItem != null)
{ {
dbItem.ResetResolveArgs(audio.ResolveArgs);
audio = dbItem; audio = dbItem;
} }
@ -720,44 +563,11 @@ namespace MediaBrowser.Controller.Entities
/// Loads the video backdrops. /// Loads the video backdrops.
/// </summary> /// </summary>
/// <returns>List{Video}.</returns> /// <returns>List{Video}.</returns>
private IEnumerable<Video> LoadThemeVideos() private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren)
{ {
ItemResolveArgs resolveArgs; var files = fileSystemChildren.OfType<DirectoryInfo>()
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
try .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
{
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>();
}
return LibraryManager.ResolvePaths<Video>(files, null).Select(item => return LibraryManager.ResolvePaths<Video>(files, null).Select(item =>
{ {
@ -766,7 +576,6 @@ namespace MediaBrowser.Controller.Entities
if (dbItem != null) if (dbItem != null)
{ {
dbItem.ResetResolveArgs(item.ResolveArgs);
item = dbItem; item = dbItem;
} }
@ -774,9 +583,9 @@ namespace MediaBrowser.Controller.Entities
}).ToList(); }).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> /// <summary>
@ -785,35 +594,24 @@ namespace MediaBrowser.Controller.Entities
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>true if a provider reports we changed</returns> /// <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 var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ?
ResetResolveArgs(); 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); await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
return false;
} }
private readonly Task _cachedTask = Task.FromResult(true); protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
protected virtual Task BeforeRefreshMetadata(MetadataRefreshOptions options, 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 themeSongsChanged = false;
var themeVideosChanged = false; var themeVideosChanged = false;
@ -825,102 +623,83 @@ namespace MediaBrowser.Controller.Entities
var hasThemeMedia = this as IHasThemeMedia; var hasThemeMedia = this as IHasThemeMedia;
if (hasThemeMedia != null) 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; var hasTrailers = this as IHasTrailers;
if (hasTrailers != null) if (hasTrailers != null)
{ {
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
} }
} }
cancellationToken.ThrowIfCancellationRequested(); if (themeSongsChanged || themeVideosChanged || localTrailersChanged)
// Get the result from the item task
var updateReason = await itemRefreshTask.ConfigureAwait(false);
var changed = updateReason.HasValue;
if (changed || forceSave || themeSongsChanged || themeVideosChanged || localTrailersChanged)
{ {
cancellationToken.ThrowIfCancellationRequested(); options.ForceSave = true;
await LibraryManager.UpdateItem(this, updateReason ?? ItemUpdateType.Unspecified, cancellationToken).ConfigureAwait(false);
} }
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 newItemIds = newItems.Select(i => i.Id).ToList();
var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds); var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds);
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh,
ResetResolveArgs = false
}, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false);
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
item.LocalTrailerIds = newItemIds; 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 newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList();
var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds); var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
var tasks = newThemeVideos.Select(i => i.RefreshMetadata(new MetadataRefreshOptions var tasks = newThemeVideos.Select(i => i.RefreshMetadata(options, cancellationToken));
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh,
ResetResolveArgs = false
}, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false);
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
item.ThemeVideoIds = newThemeVideoIds; item.ThemeVideoIds = newThemeVideoIds;
return themeVideosChanged || results.Contains(true); return themeVideosChanged;
} }
/// <summary> /// <summary>
/// Refreshes the theme songs. /// Refreshes the theme songs.
/// </summary> /// </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 newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList();
var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds); var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds);
var tasks = newThemeSongs.Select(i => i.RefreshMetadata(new MetadataRefreshOptions var tasks = newThemeSongs.Select(i => i.RefreshMetadata(options, cancellationToken));
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh,
ResetResolveArgs = false
}, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false);
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
item.ThemeSongIds = newThemeSongIds; item.ThemeSongIds = newThemeSongIds;
return themeSongsChanged || results.Contains(true); return themeSongsChanged;
} }
/// <summary> /// <summary>
@ -1655,27 +1434,8 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentNullException("imagePath"); 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 // 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> /// <summary>

View File

@ -29,25 +29,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The preferred metadata country code.</value> /// <value>The preferred metadata country code.</value>
public string PreferredMetadataCountryCode { get; set; } 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() public Book()
{ {
Tags = new List<string>(); Tags = new List<string>();

View File

@ -1,4 +1,6 @@
using System; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -14,6 +16,11 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class CollectionFolder : Folder, ICollectionFolder public class CollectionFolder : Folder, ICollectionFolder
{ {
public CollectionFolder()
{
PhysicalLocationsList = new List<string>();
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is virtual folder. /// Gets a value indicating whether this instance is virtual folder.
/// </summary> /// </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 // Cache this since it will be used a lot
/// <summary> /// <summary>
/// The null task result /// The null task result
@ -59,13 +120,14 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns> /// <returns>Task.</returns>
protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
{ {
CreateResolveArgs();
ResetDynamicChildren(); ResetDynamicChildren();
return NullTaskResult; return NullTaskResult;
} }
private List<LinkedChild> _linkedChildren; private List<LinkedChild> _linkedChildren;
/// <summary> /// <summary>
/// Our children are actually just references to the ones in the physical root... /// Our children are actually just references to the ones in the physical root...
/// </summary> /// </summary>

View File

@ -1,6 +1,7 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Entities
public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; } public List<Guid> ThemeVideoIds { get; set; }
public Folder() public Folder()
{ {
LinkedChildren = new List<LinkedChild>(); LinkedChildren = new List<LinkedChild>();
@ -379,7 +380,7 @@ namespace MediaBrowser.Controller.Entities
} }
catch (IOException ex) catch (IOException ex)
{ {
nonCachedChildren = new BaseItem[] {}; nonCachedChildren = new BaseItem[] { };
Logger.ErrorException("Error getting file system entries for {0}", ex, Path); 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)) if (currentChildren.TryGetValue(child.Id, out currentChild))
{ {
currentChild.ResetResolveArgs(child.ResolveArgs);
//existing item - check if it has changed //existing item - check if it has changed
if (currentChild.HasChanged(child)) if (currentChild.HasChanged(child))
{ {
@ -411,7 +410,7 @@ namespace MediaBrowser.Controller.Entities
if (currentChildLocationType != LocationType.Remote && if (currentChildLocationType != LocationType.Remote &&
currentChildLocationType != LocationType.Virtual) currentChildLocationType != LocationType.Virtual)
{ {
EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false); currentChild.DateModified = child.DateModified;
} }
currentChild.IsInMixedFolder = child.IsInMixedFolder; currentChild.IsInMixedFolder = child.IsInMixedFolder;
@ -539,8 +538,7 @@ namespace MediaBrowser.Controller.Entities
await child.RefreshMetadata(new MetadataRefreshOptions await child.RefreshMetadata(new MetadataRefreshOptions
{ {
ForceSave = currentTuple.Item2, ForceSave = currentTuple.Item2,
ReplaceAllMetadata = forceRefreshMetadata, ReplaceAllMetadata = forceRefreshMetadata
ResetResolveArgs = false
}, cancellationToken).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false);
} }
@ -581,16 +579,6 @@ namespace MediaBrowser.Controller.Entities
}); });
await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); 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 else
{ {
@ -661,14 +649,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IEnumerable{BaseItem}.</returns> /// <returns>IEnumerable{BaseItem}.</returns>
protected virtual IEnumerable<BaseItem> GetNonCachedChildren() protected virtual IEnumerable<BaseItem> GetNonCachedChildren()
{ {
var resolveArgs = ResolveArgs; return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(), this);
if (resolveArgs == null || resolveArgs.FileSystemDictionary == null)
{
Logger.Error("ResolveArgs null for {0}", Path);
}
return LibraryManager.ResolvePaths<BaseItem>(resolveArgs.FileSystemChildren, this);
} }
/// <summary> /// <summary>
@ -914,43 +895,29 @@ namespace MediaBrowser.Controller.Entities
return item; 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) if (SupportsShortcutChildren && LocationType == LocationType.FileSystem)
{ {
RefreshLinkedChildren(); if (RefreshLinkedChildren(fileSystemChildren))
{
options.ForceSave = true;
}
} }
return base.BeforeRefreshMetadata(options, cancellationToken); return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken);
} }
/// <summary> /// <summary>
/// Refreshes the linked children. /// Refreshes the linked children.
/// </summary> /// </summary>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> /// <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 currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).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)) .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName))
.Select(i => .Select(i =>
{ {
@ -1058,7 +1025,7 @@ namespace MediaBrowser.Controller.Entities
return this; return this;
} }
if (locationType != LocationType.Virtual && ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) if (locationType != LocationType.Virtual && PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
{ {
return this; return this;
} }

View File

@ -79,17 +79,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The game system.</value> /// <value>The game system.</value>
public string GameSystem { get; set; } public string GameSystem { get; set; }
/// <summary>
///
/// </summary>
public override string MetaLocation
{
get
{
return System.IO.Path.GetDirectoryName(Path);
}
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is multi part. /// Gets or sets a value indicating whether this instance is multi part.
/// </summary> /// </summary>
@ -101,17 +90,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public List<string> MultiPartGameFiles { get; set; } public List<string> MultiPartGameFiles { get; set; }
/// <summary>
///
/// </summary>
protected override bool UseParentPathToCreateResolveArgs
{
get
{
return !IsInMixedFolder;
}
}
public override string GetUserDataKey() public override string GetUserDataKey()
{ {
var id = this.GetProviderId(MetadataProviders.Gamesdb); var id = this.GetProviderId(MetadataProviders.Gamesdb);

View File

@ -99,6 +99,12 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
/// <value>The backdrop image paths.</value> /// <value>The backdrop image paths.</value>
List<string> BackdropImagePaths { get; set; } 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 public static class HasImagesExtensions

View File

@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Entities.Movies
public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; } public List<Guid> ThemeVideoIds { get; set; }
/// <summary> /// <summary>
/// Gets or sets the preferred metadata country code. /// Gets or sets the preferred metadata country code.
/// </summary> /// </summary>
@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Entities.Movies
public string PreferredMetadataCountryCode { get; set; } public string PreferredMetadataCountryCode { get; set; }
public string PreferredMetadataLanguage { get; set; } public string PreferredMetadataLanguage { get; set; }
public Movie() public Movie()
{ {
SpecialFeatureIds = new List<Guid>(); SpecialFeatureIds = new List<Guid>();
@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies
public List<Guid> LocalTrailerIds { get; set; } public List<Guid> LocalTrailerIds { get; set; }
public List<string> Keywords { get; set; } public List<string> Keywords { get; set; }
public List<MediaUrl> RemoteTrailers { get; set; } public List<MediaUrl> RemoteTrailers { get; set; }
/// <summary> /// <summary>
@ -103,88 +103,48 @@ namespace MediaBrowser.Controller.Entities.Movies
return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey(); return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
} }
/// <summary> protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
/// 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)
{ {
// Kick off a task to refresh the main item await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
var specialFeaturesChanged = false;
// Must have a parent to have special features // Must have a parent to have special features
// In other words, it must be part of the Parent/Child tree // In other words, it must be part of the Parent/Child tree
if (LocationType == LocationType.FileSystem && Parent != null && !IsInMixedFolder) 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 newItemIds = newItems.Select(i => i.Id).ToList();
var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds); var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds);
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh,
ResetResolveArgs = false
}, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false);
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
SpecialFeatureIds = newItemIds; SpecialFeatureIds = newItemIds;
return itemsChanged || results.Contains(true); return itemsChanged;
} }
/// <summary> /// <summary>
/// Loads the special features. /// Loads the special features.
/// </summary> /// </summary>
/// <returns>IEnumerable{Video}.</returns> /// <returns>IEnumerable{Video}.</returns>
private IEnumerable<Video> LoadSpecialFeatures() private IEnumerable<Video> LoadSpecialFeatures(IEnumerable<FileSystemInfo> fileSystemChildren)
{ {
FileSystemInfo folder; var files = fileSystemChildren.OfType<DirectoryInfo>()
.Where(i => string.Equals(i.Name, "extras", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "specials", StringComparison.OrdinalIgnoreCase))
try .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
{
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>();
}
return LibraryManager.ResolvePaths<Video>(files, null).Select(video => return LibraryManager.ResolvePaths<Video>(files, null).Select(video =>
{ {
@ -193,7 +153,6 @@ namespace MediaBrowser.Controller.Entities.Movies
if (dbItem != null) if (dbItem != null)
{ {
dbItem.ResetResolveArgs(video.ResolveArgs);
video = dbItem; video = dbItem;
} }

View File

@ -11,28 +11,6 @@ namespace MediaBrowser.Controller.Entities.TV
/// </summary> /// </summary>
public class Episode : Video 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> /// <summary>
/// Gets the season in which it aired. /// Gets the season in which it aired.
/// </summary> /// </summary>

View File

@ -131,34 +131,6 @@ namespace MediaBrowser.Controller.Entities.TV
get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; } 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> /// <summary>
/// Creates the name of the sort. /// Creates the name of the sort.
/// </summary> /// </summary>

View File

@ -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] [IgnoreDataMember]
public bool ContainsEpisodesWithoutSeasonFolders public bool ContainsEpisodesWithoutSeasonFolders
{ {

View File

@ -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() public override string GetUserDataKey()
{ {
var key = this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tvcom); var key = this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tvcom);

View File

@ -84,33 +84,23 @@ namespace MediaBrowser.Controller.Entities
/// <value>The aspect ratio.</value> /// <value>The aspect ratio.</value>
public string AspectRatio { get; set; } public string AspectRatio { get; set; }
/// <summary>
/// Should be overridden to return the proper folder where metadata lives
/// </summary>
/// <value>The meta location.</value>
[IgnoreDataMember] [IgnoreDataMember]
public override string MetaLocation public override string ContainingFolderPath
{ {
get get
{ {
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path; if (IsMultiPart)
}
}
/// <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)
{ {
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> protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
/// 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)
{ {
// Kick off a task to refresh the main item await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
var additionalPartsChanged = false;
// Must have a parent to have additional parts // Must have a parent to have additional parts
// In other words, it must be part of the Parent/Child tree // In other words, it must be part of the Parent/Child tree
// The additional parts won't have additional parts themselves // The additional parts won't have additional parts themselves
if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null) 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); options.ForceSave = true;
}
catch (IOException ex)
{
Logger.ErrorException("Error loading additional parts for {0}.", ex, Name);
} }
} }
return additionalPartsChanged || result;
} }
/// <summary> /// <summary>
/// Refreshes the additional parts. /// Refreshes the additional parts.
/// </summary> /// </summary>
/// <param name="options">The options.</param>
/// <param name="fileSystemChildren">The file system children.</param>
/// <param name="cancellationToken">The cancellation token.</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> /// <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 newItemIds = newItems.Select(i => i.Id).ToList();
var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds); var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds);
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh
}, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false);
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
AdditionalPartIds = newItemIds; AdditionalPartIds = newItemIds;
return itemsChanged || results.Contains(true); return itemsChanged;
} }
/// <summary> /// <summary>
/// Loads the additional parts. /// Loads the additional parts.
/// </summary> /// </summary>
/// <returns>IEnumerable{Video}.</returns> /// <returns>IEnumerable{Video}.</returns>
private IEnumerable<Video> LoadAdditionalParts() private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren)
{ {
IEnumerable<FileSystemInfo> files; IEnumerable<FileSystemInfo> files;
var path = Path; 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) if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
{ {
var parentPath = System.IO.Path.GetDirectoryName(path); files = fileSystemChildren.Where(i =>
if (string.IsNullOrEmpty(parentPath))
{ {
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) return false;
.EnumerateDirectories() });
.Where(i => !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFile(i.Name));
} }
else else
{ {
var resolveArgs = ResolveArgs; files = fileSystemChildren.Where(i =>
if (resolveArgs == null)
{
throw new IOException("ResolveArgs are null for " + path);
}
files = resolveArgs.FileSystemChildren.Where(i =>
{ {
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{ {
@ -276,7 +233,6 @@ namespace MediaBrowser.Controller.Entities
if (dbItem != null) if (dbItem != null)
{ {
dbItem.ResetResolveArgs(video.ResolveArgs);
video = dbItem; video = dbItem;
} }

View File

@ -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> /// <summary>
/// Gets the name of the file system entry by. /// Gets the name of the file system entry by.
/// </summary> /// </summary>
@ -320,16 +249,6 @@ namespace MediaBrowser.Controller.Library
{ {
throw new ArgumentNullException(); throw new ArgumentNullException();
} }
if (MetadataFileDictionary != null)
{
FileSystemInfo entry;
if (MetadataFileDictionary.TryGetValue(System.IO.Path.GetFileName(path), out entry))
{
return entry;
}
}
return GetFileSystemEntryByPath(path); return GetFileSystemEntryByPath(path);
} }
@ -346,16 +265,6 @@ namespace MediaBrowser.Controller.Library
{ {
throw new ArgumentNullException(); throw new ArgumentNullException();
} }
if (MetadataFileDictionary != null)
{
FileSystemInfo entry;
if (MetadataFileDictionary.TryGetValue(name, out entry))
{
return entry;
}
}
return GetFileSystemEntryByName(name); return GetFileSystemEntryByName(name);
} }

View File

@ -19,6 +19,6 @@ namespace MediaBrowser.Controller.LiveTv
bool IsParentalAllowed(User user); bool IsParentalAllowed(User user);
Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken); Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
} }
} }

View File

@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public class BaseItemXmlParser<T> public class BaseItemXmlParser<T>
where T : BaseItem, new() where T : BaseItem
{ {
/// <summary> /// <summary>
/// The logger /// The logger
@ -422,11 +422,7 @@ namespace MediaBrowser.Controller.Providers
int runtime; int runtime;
if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out runtime)) if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out runtime))
{ {
// For audio and video don't replace ffmpeg data item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
if (!(item is Video || item is Audio))
{
item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
}
} }
} }
break; break;

View File

@ -2,13 +2,8 @@
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -146,19 +141,6 @@ namespace MediaBrowser.Controller.Providers
providerInfo.LastRefreshed = value; providerInfo.LastRefreshed = value;
providerInfo.LastRefreshStatus = status; providerInfo.LastRefreshStatus = status;
providerInfo.ProviderVersion = providerVersion; 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> /// <summary>
@ -233,12 +215,6 @@ namespace MediaBrowser.Controller.Providers
return true; return true;
} }
if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem &&
HasFileSystemStampChanged(item, providerInfo))
{
return true;
}
if (RefreshOnVersionChange && !String.Equals(ProviderVersion, providerInfo.ProviderVersion)) if (RefreshOnVersionChange && !String.Equals(ProviderVersion, providerInfo.ProviderVersion))
{ {
return true; return true;
@ -263,17 +239,6 @@ namespace MediaBrowser.Controller.Providers
return CompareDate(item) > providerInfo.LastRefreshed; 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> /// <summary>
/// Override this to return the date that should be compared to the last refresh date /// Override this to return the date that should be compared to the last refresh date
/// to determine if this provider should be re-fetched. /// to determine if this provider should be re-fetched.
@ -301,159 +266,5 @@ namespace MediaBrowser.Controller.Providers
/// </summary> /// </summary>
/// <value>The priority.</value> /// <value>The priority.</value>
public abstract MetadataProviderPriority Priority { get; } 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);
}
}
}
} }
} }

View File

@ -39,5 +39,11 @@ namespace MediaBrowser.Controller.Providers
/// </summary> /// </summary>
/// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns>
bool IsSaveLocalMetadataEnabled(); 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; }
} }
} }

View File

@ -5,12 +5,6 @@ namespace MediaBrowser.Controller.Providers
{ {
public interface ILocalMetadataProvider : IMetadataProvider 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 public interface ILocalMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ILocalMetadataProvider
@ -19,9 +13,16 @@ namespace MediaBrowser.Controller.Providers
/// <summary> /// <summary>
/// Gets the metadata. /// Gets the metadata.
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="info">The information.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MetadataResult{`0}}.</returns> /// <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; }
} }
} }

View File

@ -24,15 +24,6 @@ namespace MediaBrowser.Controller.Providers
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken); 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> /// <summary>
/// Saves the image. /// Saves the image.
/// </summary> /// </summary>
@ -60,12 +51,11 @@ namespace MediaBrowser.Controller.Providers
/// <summary> /// <summary>
/// Adds the metadata providers. /// Adds the metadata providers.
/// </summary> /// </summary>
/// <param name="providers">The providers.</param>
/// <param name="imageProviders">The image providers.</param> /// <param name="imageProviders">The image providers.</param>
/// <param name="metadataServices">The metadata services.</param> /// <param name="metadataServices">The metadata services.</param>
/// <param name="metadataProviders">The metadata providers.</param> /// <param name="metadataProviders">The metadata providers.</param>
/// <param name="savers">The savers.</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); IEnumerable<IMetadataSaver> savers);
/// <summary> /// <summary>

View File

@ -16,17 +16,6 @@ namespace MediaBrowser.Controller.Providers
/// </summary> /// </summary>
[Obsolete] [Obsolete]
public bool ForceSave { get; set; } 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 public class ImageRefreshOptions

View File

@ -152,12 +152,6 @@ namespace MediaBrowser.Model.Dto
/// <value>The vote count.</value> /// <value>The vote count.</value>
public int? VoteCount { get; set; } 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> /// <summary>
/// Gets or sets the cumulative run time ticks. /// Gets or sets the cumulative run time ticks.
/// </summary> /// </summary>

View File

@ -71,11 +71,6 @@ namespace MediaBrowser.Model.Querying
/// </summary> /// </summary>
Settings, Settings,
/// <summary>
/// The original run time ticks
/// </summary>
OriginalRunTimeTicks,
/// <summary> /// <summary>
/// The item overview /// The item overview
/// </summary> /// </summary>

View File

@ -5,11 +5,10 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Movies; using MediaBrowser.Providers.Movies;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.AdultVideos namespace MediaBrowser.Providers.AdultVideos
{ {
class AdultVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider<AdultVideo> class AdultVideoXmlProvider : BaseXmlProvider<AdultVideo>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -19,41 +18,14 @@ namespace MediaBrowser.Providers.AdultVideos
_logger = logger; _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; new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
var result = new MetadataResult<AdultVideo>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
result.Item = new AdultVideo();
new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
result.HasMetadata = true;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
}
protected override FileInfo GetXmlFile(string path)
{
return MovieXmlProvider.GetXmlFileInfo(path, FileSystem);
} }
} }
} }

View File

@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.All
if (locationType == LocationType.FileSystem) if (locationType == LocationType.FileSystem)
{ {
// Episode has it's own provider // Episode has it's own provider
if (item is Episode || item is Audio) if (item.IsOwnedItem || item is Episode || item is Audio)
{ {
return false; return false;
} }

View File

@ -3,32 +3,81 @@ using MediaBrowser.Controller.Providers;
using System; using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers 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; 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) protected BaseXmlProvider(IFileSystem fileSystem)
{ {
FileSystem = fileSystem; FileSystem = fileSystem;
} }
protected abstract FileInfo GetXmlFile(string path); protected abstract FileInfo GetXmlFile(ItemInfo info);
public bool HasChanged(IHasMetadata item, DateTime date) 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; 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);
}
} }

View File

@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.BoxSets namespace MediaBrowser.Providers.BoxSets
{ {
/// <summary> /// <summary>
/// Class BoxSetXmlProvider. /// Class BoxSetXmlProvider.
/// </summary> /// </summary>
public class BoxSetXmlProvider : BaseXmlProvider, ILocalMetadataProvider<BoxSet> public class BoxSetXmlProvider : BaseXmlProvider<BoxSet>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.BoxSets
_logger = logger; _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; new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return new FileInfo(Path.Combine(info.Path, "collection.xml"));
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "collection.xml"));
} }
} }
} }

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View 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;
}
}
}
}

View 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"));
}
}
}

View File

@ -7,7 +7,7 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MediaBrowser.Providers namespace MediaBrowser.Providers.Folders
{ {
public class UserRootFolderNameProvider : BaseMetadataProvider public class UserRootFolderNameProvider : BaseMetadataProvider
{ {

View File

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Games namespace MediaBrowser.Providers.Games
{ {
public class GameSystemXmlProvider : BaseXmlProvider, ILocalMetadataProvider<GameSystem> public class GameSystemXmlProvider : BaseXmlProvider<GameSystem>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Games
_logger = logger; _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; new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return new FileInfo(Path.Combine(info.Path, "gamesystem.xml"));
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "gamesystem.xml"));
} }
} }
} }

View File

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Games namespace MediaBrowser.Providers.Games
{ {
public class GameXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Game> public class GameXmlProvider : BaseXmlProvider<Game>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,57 +17,29 @@ namespace MediaBrowser.Providers.Games
_logger = logger; _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; new GameXmlParser(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } var fileInfo = FileSystem.GetFileSystemInfo(info.Path);
}
protected override FileInfo GetXmlFile(string path)
{
var fileInfo = FileSystem.GetFileSystemInfo(path);
var directoryInfo = fileInfo as DirectoryInfo; var directoryInfo = fileInfo as DirectoryInfo;
if (directoryInfo == null) if (directoryInfo == null)
{ {
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path)); directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path));
} }
var directoryPath = directoryInfo.FullName; 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); 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"));
} }
} }
} }

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.LiveTv namespace MediaBrowser.Providers.LiveTv
{ {
public class ChannelXmlProvider : BaseXmlProvider, ILocalMetadataProvider<LiveTvChannel> public class ChannelXmlProvider : BaseXmlProvider<LiveTvChannel>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.LiveTv
_logger = logger; _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; new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return new FileInfo(Path.Combine(info.Path, "channel.xml"));
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "channel.xml"));
} }
} }
} }

View File

@ -379,14 +379,19 @@ namespace MediaBrowser.Providers.Manager
if (saveLocally) 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); path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
} }
if (string.IsNullOrEmpty(path)) 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[] return new[]
{ {
Path.Combine(item.MetaLocation, "fanart" + extension) Path.Combine(item.ContainingFolderPath, "fanart" + extension)
}; };
} }
@ -483,8 +488,8 @@ namespace MediaBrowser.Providers.Manager
return new[] return new[]
{ {
Path.Combine(item.MetaLocation, "extrafanart", extraFanartFilename + extension), Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension),
Path.Combine(item.MetaLocation, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + 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) 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) if (type == ImageType.Banner)
@ -561,7 +566,7 @@ namespace MediaBrowser.Providers.Manager
return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) }; 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 // All other paths are the same

View File

@ -93,8 +93,6 @@ namespace MediaBrowser.Providers.Manager
/// <returns>Task.</returns> /// <returns>Task.</returns>
private async Task RefreshFromProvider(IHasImages item, IDynamicImageProvider provider, MetadataOptions savedOptions, RefreshResult result, CancellationToken cancellationToken) 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 try
{ {
var images = provider.GetSupportedImages(item); var images = provider.GetSupportedImages(item);
@ -103,6 +101,8 @@ namespace MediaBrowser.Providers.Manager
{ {
if (!item.HasImage(imageType) && savedOptions.IsEnabled(imageType)) 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); var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false);
if (response.HasImage) if (response.HasImage)

View File

@ -88,12 +88,16 @@ namespace MediaBrowser.Providers.Manager
// Next run metadata providers // Next run metadata providers
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
{ {
updateType = updateType | BeforeMetadataRefresh(itemOfType);
var providers = GetProviders(item, refreshResult.DateLastMetadataRefresh.HasValue, refreshOptions).ToList(); var providers = GetProviders(item, refreshResult.DateLastMetadataRefresh.HasValue, refreshOptions).ToList();
if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue)
{
updateType = updateType | BeforeMetadataRefresh(itemOfType);
}
if (providers.Count > 0) if (providers.Count > 0)
{ {
var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, cancellationToken).ConfigureAwait(false); var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, cancellationToken).ConfigureAwait(false);
updateType = updateType | result.UpdateType; updateType = updateType | result.UpdateType;
@ -145,7 +149,7 @@ namespace MediaBrowser.Providers.Manager
{ {
var type = item.GetType().Name; var type = item.GetType().Name;
return ServerConfigurationManager.Configuration.MetadataOptions return ServerConfigurationManager.Configuration.MetadataOptions
.FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ?? .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ??
_defaultOptions; _defaultOptions;
} }
@ -278,9 +282,11 @@ namespace MediaBrowser.Providers.Manager
{ {
Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
var itemInfo = new ItemInfo { Path = item.Path, IsInMixedFolder = item.IsInMixedFolder };
try try
{ {
var localItem = await provider.GetMetadata(item.Path, cancellationToken).ConfigureAwait(false); var localItem = await provider.GetMetadata(itemInfo, cancellationToken).ConfigureAwait(false);
if (localItem.HasMetadata) if (localItem.HasMetadata)
{ {
@ -327,7 +333,7 @@ namespace MediaBrowser.Providers.Manager
{ {
await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false); await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false);
} }
return refreshResult; return refreshResult;
} }

View File

@ -48,12 +48,6 @@ namespace MediaBrowser.Providers.Manager
/// <value>The configuration manager.</value> /// <value>The configuration manager.</value>
private IServerConfigurationManager ConfigurationManager { get; set; } 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 IImageProvider[] ImageProviders { get; set; }
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
@ -86,15 +80,12 @@ namespace MediaBrowser.Providers.Manager
/// <summary> /// <summary>
/// Adds the metadata providers. /// Adds the metadata providers.
/// </summary> /// </summary>
/// <param name="providers">The providers.</param>
/// <param name="imageProviders">The image providers.</param> /// <param name="imageProviders">The image providers.</param>
/// <param name="metadataServices">The metadata services.</param> /// <param name="metadataServices">The metadata services.</param>
/// <param name="metadataProviders">The metadata providers.</param> /// <param name="metadataProviders">The metadata providers.</param>
/// <param name="metadataSavers">The metadata savers.</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(); ImageProviders = imageProviders.ToArray();
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
@ -111,174 +102,8 @@ namespace MediaBrowser.Providers.Manager
return service.RefreshMetadata(item, options, cancellationToken); return service.RefreshMetadata(item, options, cancellationToken);
} }
return ((BaseItem)item).RefreshMetadataDirect(cancellationToken, options.ForceSave, options.ReplaceAllMetadata); _logger.Error("Unable to find a metadata service for item of type " + item.GetType().Name);
} return Task.FromResult(true);
/// <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;
}
} }
/// <summary> /// <summary>
@ -328,9 +153,6 @@ namespace MediaBrowser.Providers.Manager
await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); 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 finally
{ {
@ -517,6 +339,15 @@ namespace MediaBrowser.Providers.Manager
return false; 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; return true;
} }
@ -581,6 +412,7 @@ namespace MediaBrowser.Providers.Manager
list.Add(GetPluginSummary<AdultVideo>()); list.Add(GetPluginSummary<AdultVideo>());
list.Add(GetPluginSummary<MusicVideo>()); list.Add(GetPluginSummary<MusicVideo>());
list.Add(GetPluginSummary<Video>());
list.Add(GetPluginSummary<LiveTvChannel>()); list.Add(GetPluginSummary<LiveTvChannel>());
list.Add(GetPluginSummary<LiveTvProgram>()); list.Add(GetPluginSummary<LiveTvProgram>());
@ -678,6 +510,8 @@ namespace MediaBrowser.Providers.Manager
{ {
foreach (var saver in _savers.Where(i => i.IsEnabledFor(item, updateType))) 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; var fileSaver = saver as IMetadataFileSaver;
if (fileSaver != null) if (fileSaver != null)

View File

@ -65,11 +65,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="AdultVideos\AdultVideoMetadataService.cs" /> <Compile Include="AdultVideos\AdultVideoMetadataService.cs" />
<Compile Include="AdultVideos\AdultVideoXmlProvider.cs" />
<Compile Include="All\LocalImageProvider.cs" /> <Compile Include="All\LocalImageProvider.cs" />
<Compile Include="Books\BookMetadataService.cs" /> <Compile Include="Books\BookMetadataService.cs" />
<Compile Include="BoxSets\BoxSetMetadataService.cs" /> <Compile Include="BoxSets\BoxSetMetadataService.cs" />
<Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" /> <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" />
<Compile Include="BoxSets\MovieDbBoxSetProvider.cs" /> <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
<Compile Include="Folders\FolderMetadataService.cs" />
<Compile Include="GameGenres\GameGenreMetadataService.cs" /> <Compile Include="GameGenres\GameGenreMetadataService.cs" />
<Compile Include="Games\GameMetadataService.cs" /> <Compile Include="Games\GameMetadataService.cs" />
<Compile Include="Games\GameSystemMetadataService.cs" /> <Compile Include="Games\GameSystemMetadataService.cs" />
@ -83,33 +85,34 @@
<Compile Include="Manager\ProviderManager.cs" /> <Compile Include="Manager\ProviderManager.cs" />
<Compile Include="Manager\MetadataService.cs" /> <Compile Include="Manager\MetadataService.cs" />
<Compile Include="BaseXmlProvider.cs" /> <Compile Include="BaseXmlProvider.cs" />
<Compile Include="CollectionFolderImageProvider.cs" /> <Compile Include="Folders\FolderXmlProvider.cs" />
<Compile Include="FolderProviderFromXml.cs" />
<Compile Include="Games\GameXmlParser.cs" /> <Compile Include="Games\GameXmlParser.cs" />
<Compile Include="Games\GameXmlProvider.cs" /> <Compile Include="Games\GameXmlProvider.cs" />
<Compile Include="Games\GameSystemXmlProvider.cs" /> <Compile Include="Games\GameSystemXmlProvider.cs" />
<Compile Include="ImageFromMediaLocationProvider.cs" /> <Compile Include="MediaInfo\FFProbeAudioInfo.cs" />
<Compile Include="ImagesByNameProvider.cs" />
<Compile Include="MediaInfo\FFProbeHelpers.cs" /> <Compile Include="MediaInfo\FFProbeHelpers.cs" />
<Compile Include="MediaInfo\FFProbeProvider.cs" /> <Compile Include="MediaInfo\FFProbeProvider.cs" />
<Compile Include="MediaInfo\FFProbeVideoInfo.cs" /> <Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
<Compile Include="Movies\TrailerMetadataService.cs" />
<Compile Include="Movies\GenericMovieDbInfo.cs" />
<Compile Include="Movies\MovieDbSearch.cs" /> <Compile Include="Movies\MovieDbSearch.cs" />
<Compile Include="Movies\MovieMetadataService.cs" />
<Compile Include="Movies\MovieXmlProvider.cs" /> <Compile Include="Movies\MovieXmlProvider.cs" />
<Compile Include="Movies\TmdbSettings.cs" />
<Compile Include="Movies\TrailerXmlProvider.cs" />
<Compile Include="MusicGenres\MusicGenreImageProvider.cs" /> <Compile Include="MusicGenres\MusicGenreImageProvider.cs" />
<Compile Include="GameGenres\GameGenreImageProvider.cs" /> <Compile Include="GameGenres\GameGenreImageProvider.cs" />
<Compile Include="Genres\GenreImageProvider.cs" /> <Compile Include="Genres\GenreImageProvider.cs" />
<Compile Include="ImagesByName\ImageUtils.cs" /> <Compile Include="ImagesByName\ImageUtils.cs" />
<Compile Include="MediaInfo\AudioImageProvider.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="MediaInfo\VideoImageProvider.cs" />
<Compile Include="BoxSets\BoxSetXmlProvider.cs" /> <Compile Include="BoxSets\BoxSetXmlProvider.cs" />
<Compile Include="Movies\ManualMovieDbImageProvider.cs" /> <Compile Include="Movies\MovieDbImageProvider.cs" />
<Compile Include="Movies\ManualFanartMovieImageProvider.cs" /> <Compile Include="Movies\FanartMovieImageProvider.cs" />
<Compile Include="MusicGenres\MusicGenreMetadataService.cs" /> <Compile Include="MusicGenres\MusicGenreMetadataService.cs" />
<Compile Include="Music\AlbumMetadataService.cs" /> <Compile Include="Music\AlbumMetadataService.cs" />
<Compile Include="Music\ArtistMetadataService.cs" /> <Compile Include="Music\ArtistMetadataService.cs" />
<Compile Include="Music\AudioMetadataService.cs" />
<Compile Include="Music\LastfmArtistProvider.cs" /> <Compile Include="Music\LastfmArtistProvider.cs" />
<Compile Include="Music\MusicBrainzArtistProvider.cs" /> <Compile Include="Music\MusicBrainzArtistProvider.cs" />
<Compile Include="Music\MusicVideoMetadataService.cs" /> <Compile Include="Music\MusicVideoMetadataService.cs" />
@ -119,12 +122,8 @@
<Compile Include="People\MovieDbPersonImageProvider.cs" /> <Compile Include="People\MovieDbPersonImageProvider.cs" />
<Compile Include="Movies\MovieUpdatesPrescanTask.cs" /> <Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
<Compile Include="Movies\MovieXmlParser.cs" /> <Compile Include="Movies\MovieXmlParser.cs" />
<Compile Include="Movies\FanArtMovieProvider.cs" />
<Compile Include="Movies\FanArtMovieUpdatesPrescanTask.cs" /> <Compile Include="Movies\FanArtMovieUpdatesPrescanTask.cs" />
<Compile Include="Movies\MovieDbImagesProvider.cs" />
<Compile Include="Movies\MovieDbProvider.cs" /> <Compile Include="Movies\MovieDbProvider.cs" />
<Compile Include="Movies\MovieProviderFromXml.cs" />
<Compile Include="Movies\OpenMovieDatabaseProvider.cs" />
<Compile Include="Music\AlbumXmlProvider.cs" /> <Compile Include="Music\AlbumXmlProvider.cs" />
<Compile Include="Music\ArtistXmlProvider.cs" /> <Compile Include="Music\ArtistXmlProvider.cs" />
<Compile Include="Music\FanArtUpdatesPrescanTask.cs" /> <Compile Include="Music\FanArtUpdatesPrescanTask.cs" />
@ -177,9 +176,10 @@
<Compile Include="TV\SeriesXmlProvider.cs" /> <Compile Include="TV\SeriesXmlProvider.cs" />
<Compile Include="TV\SeriesXmlParser.cs" /> <Compile Include="TV\SeriesXmlParser.cs" />
<Compile Include="TV\TvdbPrescanTask.cs" /> <Compile Include="TV\TvdbPrescanTask.cs" />
<Compile Include="UserRootFolderNameProvider.cs" /> <Compile Include="Folders\UserRootFolderNameProvider.cs" />
<Compile Include="Users\UserMetadataService.cs" /> <Compile Include="Users\UserMetadataService.cs" />
<Compile Include="VirtualItemImageValidator.cs" /> <Compile Include="Videos\VideoMetadataService.cs" />
<Compile Include="Years\YearMetadataService.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">

View File

@ -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;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.IO;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -19,207 +16,83 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary> /// <summary>
/// Uses ffmpeg to create video images /// Uses ffmpeg to create video images
/// </summary> /// </summary>
public class AudioImageProvider : BaseMetadataProvider public class AudioImageProvider : IDynamicImageProvider, IHasChangeMonitor
{ {
/// <summary> private readonly IIsoManager _isoManager;
/// The _locks
/// </summary>
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
/// <summary>
/// The _media encoder
/// </summary>
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IServerConfigurationManager _config;
/// <summary> public AudioImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config)
/// 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)
{ {
_isoManager = isoManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_config = config;
} }
/// <summary> /// <summary>
/// Gets a value indicating whether [refresh on version change]. /// The null mount task result
/// </summary> /// </summary>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value> protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
protected override bool RefreshOnVersionChange
{
get
{
return true;
}
}
/// <summary> /// <summary>
/// Gets the provider version. /// Mounts the iso if needed.
/// </summary>
/// <value>The provider version.</value>
protected override string ProviderVersion
{
get
{
return "1";
}
}
/// <summary>
/// Supportses the specified item.
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> /// <param name="cancellationToken">The cancellation token.</param>
public override bool Supports(BaseItem item) /// <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; return item.LocationType == LocationType.FileSystem && item is Audio;
} }
/// <summary> public bool HasChanged(IHasMetadata item, DateTime date)
/// 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; return item.DateModified > date;
}
/// <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));
} }
} }
} }

View File

@ -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)
{
}
}
}

View File

@ -1,41 +1,36 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MediaBrowser.Providers.MediaInfo namespace MediaBrowser.Providers.MediaInfo
{ {
/// <summary> class FFProbeAudioInfo
/// Extracts audio information using ffprobe
/// </summary>
public class FFProbeAudioInfoProvider : BaseFFProbeProvider<Audio>
{ {
private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer, IItemRepository itemRepo) private readonly CultureInfo _usCulture = new CultureInfo("en-US");
: base(logManager, configurationManager, mediaEncoder, jsonSerializer)
public FFProbeAudioInfo(IMediaEncoder mediaEncoder, IItemRepository itemRepo)
{ {
_mediaEncoder = mediaEncoder;
_itemRepo = itemRepo; _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; var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
OnPreFetch(myItem, null);
var result = await GetMediaInfo(item, null, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -43,11 +38,19 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested(); 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> /// <summary>
@ -82,7 +85,7 @@ namespace MediaBrowser.Providers.MediaInfo
// If we got something, parse it // If we got something, parse it
if (!string.IsNullOrEmpty(duration)) 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; return null;
} }
}
}
} }

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -18,10 +19,14 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.MediaInfo namespace MediaBrowser.Providers.MediaInfo
{ {
public class FFProbeProvider : ICustomMetadataProvider<Episode>, public class FFProbeProvider : ICustomMetadataProvider<Episode>,
ICustomMetadataProvider<MusicVideo>, ICustomMetadataProvider<MusicVideo>,
ICustomMetadataProvider<Movie>, ICustomMetadataProvider<Movie>,
ICustomMetadataProvider<AdultVideo>, ICustomMetadataProvider<AdultVideo>,
ICustomMetadataProvider<LiveTvVideoRecording>, ICustomMetadataProvider<LiveTvVideoRecording>,
ICustomMetadataProvider<LiveTvAudioRecording>,
ICustomMetadataProvider<Trailer>,
ICustomMetadataProvider<Video>,
ICustomMetadataProvider<Audio>,
IHasChangeMonitor IHasChangeMonitor
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -30,7 +35,7 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
private readonly IBlurayExaminer _blurayExaminer; private readonly IBlurayExaminer _blurayExaminer;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
public string Name public string Name
{ {
get { return "ffprobe"; } get { return "ffprobe"; }
@ -61,6 +66,26 @@ namespace MediaBrowser.Providers.MediaInfo
return FetchVideoInfo(item, cancellationToken); 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) public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization)
{ {
_logger = logger; _logger = logger;
@ -95,6 +120,19 @@ namespace MediaBrowser.Providers.MediaInfo
return prober.ProbeVideo(item, cancellationToken); 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) public bool HasChanged(IHasMetadata item, DateTime date)
{ {
return item.DateModified > date; return item.DateModified > date;

View File

@ -321,69 +321,69 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="currentStreams">The current streams.</param> /// <param name="currentStreams">The current streams.</param>
private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams) private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams)
{ {
var useParent = !video.ResolveArgs.IsDirectory; //var useParent = !video.ResolveArgs.IsDirectory;
if (useParent && video.Parent == null) //if (useParent && video.Parent == null)
{ //{
return; // return;
} //}
var fileSystemChildren = useParent //var fileSystemChildren = useParent
? video.Parent.ResolveArgs.FileSystemChildren // ? video.Parent.ResolveArgs.FileSystemChildren
: video.ResolveArgs.FileSystemChildren; // : video.ResolveArgs.FileSystemChildren;
var startIndex = currentStreams.Count; //var startIndex = currentStreams.Count;
var streams = new List<MediaStream>(); //var streams = new List<MediaStream>();
var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); //var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
foreach (var file in fileSystemChildren //foreach (var file in fileSystemChildren
.Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase))) // .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase)))
{ //{
var fullName = file.FullName; // var fullName = file.FullName;
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); // var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
// If the subtitle file matches the video file name // // If the subtitle file matches the video file name
if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) // if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
{ // {
streams.Add(new MediaStream // streams.Add(new MediaStream
{ // {
Index = startIndex++, // Index = startIndex++,
Type = MediaStreamType.Subtitle, // Type = MediaStreamType.Subtitle,
IsExternal = true, // IsExternal = true,
Path = fullName, // Path = fullName,
Codec = Path.GetExtension(fullName).ToLower().TrimStart('.') // Codec = Path.GetExtension(fullName).ToLower().TrimStart('.')
}); // });
} // }
else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) // else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
{ // {
// Support xbmc naming conventions - 300.spanish.srt // // Support xbmc naming conventions - 300.spanish.srt
var language = fileNameWithoutExtension.Split('.').LastOrDefault(); // var language = fileNameWithoutExtension.Split('.').LastOrDefault();
// Try to translate to three character code // // Try to translate to three character code
// Be flexible and check against both the full and three character versions // // Be flexible and check against both the full and three character versions
var culture = _localization.GetCultures() // 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)); // .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) // if (culture != null)
{ // {
language = culture.ThreeLetterISOLanguageName; // language = culture.ThreeLetterISOLanguageName;
} // }
streams.Add(new MediaStream // streams.Add(new MediaStream
{ // {
Index = startIndex++, // Index = startIndex++,
Type = MediaStreamType.Subtitle, // Type = MediaStreamType.Subtitle,
IsExternal = true, // IsExternal = true,
Path = fullName, // Path = fullName,
Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'), // Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'),
Language = language // Language = language
}); // });
} // }
} //}
currentStreams.AddRange(streams); //currentStreams.AddRange(streams);
} }
/// <summary> /// <summary>

View File

@ -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);
}
}
}

View File

@ -12,7 +12,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.MediaInfo namespace MediaBrowser.Providers.MediaInfo
{ {
public class VideoImageProvider : IDynamicImageProvider public class VideoImageProvider : IDynamicImageProvider, IHasChangeMonitor
{ {
private readonly IIsoManager _isoManager; private readonly IIsoManager _isoManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
@ -25,34 +25,6 @@ namespace MediaBrowser.Providers.MediaInfo
_config = config; _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> /// <summary>
/// The null mount task result /// The null mount task result
/// </summary> /// </summary>
@ -81,7 +53,27 @@ namespace MediaBrowser.Providers.MediaInfo
public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) 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) 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; return item.LocationType == LocationType.FileSystem && item is Video;
} }
public bool HasChanged(IHasMetadata item, DateTime date)
{
return item.DateModified > date;
}
} }
} }

View File

@ -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;
}
}
}
}
}

View File

@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.Movies
return; return;
} }
var path = FanartMovieProvider.GetMoviesDataPath(_config.CommonApplicationPaths); var path = FanartMovieImageProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Movies
{ {
_logger.Info("Updating movie " + id); _logger.Info("Updating movie " + id);
await FanartMovieProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false); await FanartMovieImageProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false);
numComplete++; numComplete++;
double percent = numComplete; double percent = numComplete;

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -7,6 +8,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Music;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -16,22 +18,27 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using MediaBrowser.Providers.Music;
namespace MediaBrowser.Providers.Movies 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 CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem; 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; _config = config;
_httpClient = httpClient; _httpClient = httpClient;
_fileSystem = fileSystem; _fileSystem = fileSystem;
Current = this;
} }
public string Name public string Name
@ -86,9 +93,9 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(movieId)) 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 try
{ {
@ -344,7 +351,7 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(id)) if (!string.IsNullOrEmpty(id))
{ {
// Process images // Process images
var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(id); var xmlPath = GetFanartXmlPath(id);
var fileInfo = new FileInfo(xmlPath); var fileInfo = new FileInfo(xmlPath);
@ -353,5 +360,85 @@ namespace MediaBrowser.Providers.Movies
return false; 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);
}
} }
} }

View 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();
}
}
}
}
}

View File

@ -15,12 +15,12 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies namespace MediaBrowser.Providers.Movies
{ {
class ManualMovieDbImageProvider : IRemoteImageProvider, IHasOrder class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
{ {
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
public ManualMovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient) public MovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
{ {
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_httpClient = httpClient; _httpClient = httpClient;
@ -168,9 +168,17 @@ namespace MediaBrowser.Providers.Movies
private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer, private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer,
CancellationToken cancellationToken) 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)) if (!string.IsNullOrEmpty(path))
{ {

View File

@ -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;
}
}
}
}
}
}

View File

@ -5,16 +5,11 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Savers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -23,50 +18,46 @@ namespace MediaBrowser.Providers.Movies
/// <summary> /// <summary>
/// Class MovieDbProvider /// Class MovieDbProvider
/// </summary> /// </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 readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1);
internal static MovieDbProvider Current { get; private set; } internal static MovieDbProvider Current { get; private set; }
/// <summary> private readonly IJsonSerializer _jsonSerializer;
/// Gets the json serializer. private readonly IHttpClient _httpClient;
/// </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 IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger _logger;
/// <summary> public MovieDbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger)
/// 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)
{ {
JsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
HttpClient = httpClient; _httpClient = httpClient;
ProviderManager = providerManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_configurationManager = configurationManager;
_logger = logger;
Current = this; 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> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </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> /// <summary>
/// The _TMDB settings task /// The _TMDB settings task
/// </summary> /// </summary>
@ -170,7 +106,7 @@ namespace MediaBrowser.Providers.Movies
}).ConfigureAwait(false)) }).ConfigureAwait(false))
{ {
_tmdbSettings = JsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json); _tmdbSettings = _jsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
return _tmdbSettings; return _tmdbSettings;
} }
@ -187,30 +123,6 @@ namespace MediaBrowser.Providers.Movies
internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669";
internal static string AcceptHeader = "application/json,image/*"; 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> /// <summary>
/// Gets the movie data path. /// Gets the movie data path.
/// </summary> /// </summary>
@ -231,121 +143,6 @@ namespace MediaBrowser.Providers.Movies
return dataPath; 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> /// <summary>
/// Downloads the movie info. /// Downloads the movie info.
/// </summary> /// </summary>
@ -363,60 +160,49 @@ namespace MediaBrowser.Providers.Movies
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
JsonSerializer.SerializeToFile(mainResult, dataFilePath); _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
} }
private readonly Task _cachedTask = Task.FromResult(true); 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(tmdbId))
if (string.IsNullOrEmpty(path))
{ {
return _cachedTask; throw new ArgumentNullException("tmdbId");
} }
if (string.IsNullOrEmpty(language))
{
throw new ArgumentNullException("language");
}
var path = GetDataFilePath(tmdbId, language);
var fileInfo = _fileSystem.GetFileSystemInfo(path); var fileInfo = _fileSystem.GetFileSystemInfo(path);
if (fileInfo.Exists) if (fileInfo.Exists)
{ {
// If it's recent or automatic updates are enabled, don't re-download // 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; return _cachedTask;
} }
} }
var id = item.GetProviderId(MetadataProviders.Tmdb); return DownloadMovieInfo(tmdbId, language, cancellationToken);
if (string.IsNullOrEmpty(id))
{
return _cachedTask;
}
return DownloadMovieInfo(id, item.GetPreferredMetadataLanguage(), cancellationToken);
} }
/// <summary> internal string GetDataFilePath(string tmdbId, string preferredLanguage)
/// Gets the data file path.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
internal string GetDataFilePath(BaseItem item)
{ {
var id = item.GetProviderId(MetadataProviders.Tmdb); if (string.IsNullOrEmpty(tmdbId))
if (string.IsNullOrEmpty(id))
{ {
return null; throw new ArgumentNullException("tmdbId");
}
if (string.IsNullOrEmpty(preferredLanguage))
{
throw new ArgumentNullException("preferredLanguage");
} }
return GetDataFilePath(id, item.GetPreferredMetadataLanguage()); var path = GetMovieDataPath(_configurationManager.ApplicationPaths, tmdbId);
}
private string GetDataFilePath(string tmdbId, string preferredLanguage)
{
var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId);
var filename = string.Format("all-{0}.json", var filename = string.Format("all-{0}.json",
preferredLanguage ?? string.Empty); preferredLanguage ?? string.Empty);
@ -431,7 +217,7 @@ namespace MediaBrowser.Providers.Movies
/// <param name="language">The language.</param> /// <param name="language">The language.</param>
/// <param name="cancellationToken">The cancellation token</param> /// <param name="cancellationToken">The cancellation token</param>
/// <returns>Task{CompleteMovieData}.</returns> /// <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); var url = string.Format(GetMovieInfo3, id, ApiKey);
@ -461,7 +247,7 @@ namespace MediaBrowser.Providers.Movies
}).ConfigureAwait(false)) }).ConfigureAwait(false))
{ {
mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json); mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
} }
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -470,7 +256,7 @@ namespace MediaBrowser.Providers.Movies
{ {
if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) 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"; url = string.Format(GetMovieInfo3, id, ApiKey) + "&include_image_language=en,null&language=en";
@ -482,12 +268,12 @@ namespace MediaBrowser.Providers.Movies
}).ConfigureAwait(false)) }).ConfigureAwait(false))
{ {
mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json); mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
} }
if (String.IsNullOrEmpty(mainResult.overview)) 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; return null;
} }
} }
@ -495,183 +281,6 @@ namespace MediaBrowser.Providers.Movies
return mainResult; 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; private DateTime _lastRequestDate = DateTime.MinValue;
/// <summary> /// <summary>
@ -695,7 +304,7 @@ namespace MediaBrowser.Providers.Movies
_lastRequestDate = DateTime.Now; _lastRequestDate = DateTime.Now;
return await HttpClient.Get(options).ConfigureAwait(false); return await _httpClient.Get(options).ConfigureAwait(false);
} }
finally finally
{ {
@ -897,18 +506,5 @@ namespace MediaBrowser.Providers.Movies
public Keywords keywords { get; set; } public Keywords keywords { get; set; }
public Trailers trailers { 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; }
}
} }
} }

View 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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies namespace MediaBrowser.Providers.Movies
{ {
public class MovieXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Movie> public class MovieXmlProvider : BaseXmlProvider<Movie>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,61 +17,34 @@ namespace MediaBrowser.Providers.Movies
_logger = logger; _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; new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
var result = new MetadataResult<Movie>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
result.Item = new Movie();
new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
result.HasMetadata = true;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
} }
public string Name 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); var fileInfo = fileSystem.GetFileSystemInfo(info.Path);
}
public static FileInfo GetXmlFileInfo(string path, IFileSystem _fileSystem)
{
var fileInfo = _fileSystem.GetFileSystemInfo(path);
var directoryInfo = fileInfo as DirectoryInfo; var directoryInfo = fileInfo as DirectoryInfo;
if (directoryInfo == null) if (directoryInfo == null)
{ {
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path)); directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path));
} }
var directoryPath = directoryInfo.FullName; 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); 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"));
} }
} }
} }

View File

@ -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; }
}
}
}

View 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; }
}
}

View 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);
}
}
}

View 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);
}
}
}

View File

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Music namespace MediaBrowser.Providers.Music
{ {
class AlbumXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicAlbum> class AlbumXmlProvider : BaseXmlProvider<MusicAlbum>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music
_logger = logger; _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; new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return new FileInfo(Path.Combine(info.Path, "album.xml"));
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "album.xml"));
} }
} }
} }

View File

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Music namespace MediaBrowser.Providers.Music
{ {
class ArtistXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicArtist> class ArtistXmlProvider : BaseXmlProvider<MusicArtist>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music
_logger = logger; _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; new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return new FileInfo(Path.Combine(info.Path, "artist.xml"));
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "artist.xml"));
} }
} }
} }

View 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);
}
}
}

View File

@ -5,11 +5,10 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Movies; using MediaBrowser.Providers.Movies;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Music namespace MediaBrowser.Providers.Music
{ {
class MusicVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicVideo> class MusicVideoXmlProvider : BaseXmlProvider<MusicVideo>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -19,42 +18,14 @@ namespace MediaBrowser.Providers.Music
_logger = logger; _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; new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken);
var result = new MetadataResult<MusicVideo>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var item = new MusicVideo();
new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken);
result.HasMetadata = true;
result.Item = item;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
}
protected override FileInfo GetXmlFile(string path)
{
return MovieXmlProvider.GetXmlFileInfo(path, FileSystem);
} }
} }
} }

View File

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.People namespace MediaBrowser.Providers.People
{ {
public class PersonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Person> public class PersonXmlProvider : BaseXmlProvider<Person>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.People
_logger = logger; _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; new BaseItemXmlParser<Person>(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return new FileInfo(Path.Combine(info.Path, "person.xml"));
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "person.xml"));
} }
} }
} }

View File

@ -93,7 +93,10 @@ namespace MediaBrowser.Providers
{ {
if (replaceData || !target.RunTimeTicks.HasValue) 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); MergeAlbumArtist(source, target, lockedFields, replaceData);
MergeBudget(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) 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;
}
}
}
} }
} }

View File

@ -90,20 +90,14 @@ namespace MediaBrowser.Providers
} }
var dbItem = _libraryManager.GetItemById(item.Id); var dbItem = _libraryManager.GetItemById(item.Id);
var isNewItem = false;
if (dbItem != null) if (dbItem != null)
{ {
dbItem.ResetResolveArgs(item.ResolveArgs);
item = dbItem; item = dbItem;
} }
else
{
isNewItem = true;
}
// Force the save if it's a new item // Force the save if it's a new item
await item.RefreshMetadata(cancellationToken, isNewItem).ConfigureAwait(false); await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
} }
} }
} }

View File

@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.Savers
return Path.ChangeExtension(item.Path, ".xml"); return Path.ChangeExtension(item.Path, ".xml");
} }
return Path.Combine(item.MetaLocation, "game.xml"); return Path.Combine(item.ContainingFolderPath, "game.xml");
} }
} }
} }

View File

@ -50,16 +50,9 @@ namespace MediaBrowser.Providers.Savers
// If new metadata has been downloaded and save local is on // If new metadata has been downloaded and save local is on
if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) 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; var video = item as Video;
// Check parent for null to avoid running this against things like video backdrops // 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; return false;
@ -145,7 +138,7 @@ namespace MediaBrowser.Providers.Savers
return Path.ChangeExtension(item.Path, ".xml"); return Path.ChangeExtension(item.Path, ".xml");
} }
return Path.Combine(item.MetaLocation, "movie.xml"); return Path.Combine(item.ContainingFolderPath, "movie.xml");
} }
} }
} }

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.IO; using System.IO;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -102,8 +103,10 @@ namespace MediaBrowser.Providers.TV
var currentIndexNumberEnd = item.IndexNumberEnd; var currentIndexNumberEnd = item.IndexNumberEnd;
var currentParentIndexNumber = item.ParentIndexNumber; var currentParentIndexNumber = item.ParentIndexNumber;
item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(item.Path, item.Parent is Season); var filename = Path.GetFileName(item.Path);
item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(item.Path);
item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(filename, item.Parent is Season);
item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(filename);
if (!item.ParentIndexNumber.HasValue) if (!item.ParentIndexNumber.HasValue)
{ {

View File

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV namespace MediaBrowser.Providers.TV
{ {
public class EpisodeXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Episode> public class EpisodeXmlProvider : BaseXmlProvider<Episode>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,43 +17,16 @@ namespace MediaBrowser.Providers.TV
_logger = logger; _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; new EpisodeXmlParser(_logger).Fetch(item, path, cancellationToken);
var result = new MetadataResult<Episode>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
result.Item = new Episode();
new EpisodeXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
result.HasMetadata = true;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } var metadataPath = Path.GetDirectoryName(info.Path);
}
protected override FileInfo GetXmlFile(string path)
{
var metadataPath = Path.GetDirectoryName(path);
metadataPath = Path.Combine(metadataPath, "metadata"); 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); return new FileInfo(metadataFile);
} }

View File

@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV namespace MediaBrowser.Providers.TV
{ {
/// <summary> /// <summary>
/// Class SeriesProviderFromXml /// Class SeriesProviderFromXml
/// </summary> /// </summary>
public class SeasonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Season> public class SeasonXmlProvider : BaseXmlProvider<Season>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV
_logger = logger; _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; new BaseItemXmlParser<Season>(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return new FileInfo(Path.Combine(info.Path, "season.xml"));
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "season.xml"));
} }
} }
} }

View File

@ -2,6 +2,7 @@
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System; using System;
@ -53,7 +54,7 @@ namespace MediaBrowser.Providers.TV
await new MissingEpisodeProvider(_logger, _config).Run(seriesGroups, cancellationToken).ConfigureAwait(false); await new MissingEpisodeProvider(_logger, _config).Run(seriesGroups, cancellationToken).ConfigureAwait(false);
var numComplete = 0; var numComplete = 0;
foreach (var series in seriesList) foreach (var series in seriesList)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -171,7 +172,9 @@ namespace MediaBrowser.Providers.TV
{ {
foreach (var series in group) foreach (var series in group)
{ {
await series.RefreshMetadata(cancellationToken, true) await series.RefreshMetadata(new MetadataRefreshOptions
{
}, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
await series.ValidateChildren(new Progress<double>(), cancellationToken, true) await series.ValidateChildren(new Progress<double>(), cancellationToken, true)
@ -438,7 +441,9 @@ namespace MediaBrowser.Providers.TV
await season.AddChild(episode, cancellationToken).ConfigureAwait(false); await season.AddChild(episode, cancellationToken).ConfigureAwait(false);
await episode.RefreshMetadata(cancellationToken).ConfigureAwait(false); await episode.RefreshMetadata(new MetadataRefreshOptions
{
}, cancellationToken).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -464,7 +469,9 @@ namespace MediaBrowser.Providers.TV
}; };
await series.AddChild(season, cancellationToken).ConfigureAwait(false); await series.AddChild(season, cancellationToken).ConfigureAwait(false);
await season.RefreshMetadata(cancellationToken).ConfigureAwait(false); await season.RefreshMetadata(new MetadataRefreshOptions
{
}, cancellationToken).ConfigureAwait(false);
return season; return season;
} }

View File

@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV namespace MediaBrowser.Providers.TV
{ {
/// <summary> /// <summary>
/// Class SeriesProviderFromXml /// Class SeriesProviderFromXml
/// </summary> /// </summary>
public class SeriesXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Series> public class SeriesXmlProvider : BaseXmlProvider<Series>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV
_logger = logger; _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; new SeriesXmlParser(_logger).Fetch(item, path, cancellationToken);
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;
} }
public string Name protected override FileInfo GetXmlFile(ItemInfo info)
{ {
get { return "Media Browser Xml"; } return new FileInfo(Path.Combine(info.Path, "series.xml"));
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "series.xml"));
} }
} }
} }

View File

@ -185,7 +185,14 @@ namespace MediaBrowser.Providers.TV
var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber));
var success = false; var success = false;
var usingAbsoluteData = false; var usingAbsoluteData = false;
var episode = new Episode();
var episode = new Episode
{
IndexNumber = id.IndexNumber,
ParentIndexNumber = id.ParentIndexNumber,
IndexNumberEnd = id.IndexNumberEnd
};
try try
{ {
FetchMainEpisodeInfo(episode, file, cancellationToken); FetchMainEpisodeInfo(episode, file, cancellationToken);

View 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;
}
}
}
}

View File

@ -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; }
}
}
}

View 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);
}
}
}

View File

@ -273,21 +273,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
{ {
if (item.LocationType == LocationType.FileSystem) if (item.LocationType == LocationType.FileSystem)
{ {
return collections.Where(i => return collections.Where(i => i.LocationType == LocationType.FileSystem &&
{ i.PhysicalLocations.Contains(item.Path)).Cast<T>();
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>();
} }
} }

View File

@ -162,18 +162,7 @@ namespace MediaBrowser.Server.Implementations.IO
.Children .Children
.OfType<Folder>() .OfType<Folder>()
.Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual)
.SelectMany(f => .SelectMany(f => f.PhysicalLocations)
{
try
{
return f.PhysicalLocations;
}
catch (IOException)
{
return new string[] { };
}
})
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(i => i) .OrderBy(i => i)
.ToList(); .ToList();

View File

@ -833,9 +833,6 @@ namespace MediaBrowser.Server.Implementations.Library
(item as MusicArtist).IsAccessedByName = true; (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); return new Tuple<bool, T>(isNew, item);
} }
@ -1113,6 +1110,7 @@ namespace MediaBrowser.Server.Implementations.Library
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await userRootFolder.ValidateChildren(new Progress<double>(), cancellationToken, recursive: false).ConfigureAwait(false); await userRootFolder.ValidateChildren(new Progress<double>(), cancellationToken, recursive: false).ConfigureAwait(false);
var b = true;
} }
/// <summary> /// <summary>
@ -1244,7 +1242,6 @@ namespace MediaBrowser.Server.Implementations.Library
if (dbItem != null) if (dbItem != null)
{ {
dbItem.ResetResolveArgs(video.ResolveArgs);
video = dbItem; video = dbItem;
} }
} }
@ -1383,6 +1380,8 @@ namespace MediaBrowser.Server.Implementations.Library
item.DateLastSaved = DateTime.UtcNow; item.DateLastSaved = DateTime.UtcNow;
_logger.Debug("Saving {0} to database.", item.Path ?? item.Name);
await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false); await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false);
UpdateItemInLibraryCache(item); UpdateItemInLibraryCache(item);
@ -1479,16 +1478,7 @@ namespace MediaBrowser.Server.Implementations.Library
return true; return true;
} }
try return i.PhysicalLocations.Contains(item.Path);
{
return i.PhysicalLocations.Contains(item.Path);
}
catch (IOException ex)
{
_logger.ErrorException("Error getting resolve args for {0}", ex, i.Path);
return false;
}
}) })
.Select(i => i.CollectionType) .Select(i => i.CollectionType)
.Where(i => !string.IsNullOrEmpty(i)) .Where(i => !string.IsNullOrEmpty(i))

View File

@ -23,8 +23,6 @@ namespace MediaBrowser.Server.Implementations.Library
/// <param name="fileSystem">The file system.</param> /// <param name="fileSystem">The file system.</param>
public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem) public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem)
{ {
item.ResetResolveArgs(args);
// If the resolver didn't specify this // If the resolver didn't specify this
if (string.IsNullOrEmpty(item.Path)) if (string.IsNullOrEmpty(item.Path))
{ {

View File

@ -184,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
private void SetProviderIdFromPath(Video item) private void SetProviderIdFromPath(Video item)
{ {
//we need to only look at the name of this actual item (not parents) //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"); var id = justName.GetAttributeValue("tmdbid");

View File

@ -47,17 +47,5 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
return null; 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);
}
} }
} }

View File

@ -92,8 +92,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{ {
base.SetInitialItemValues(item, args); base.SetInitialItemValues(item, args);
Season.AddMetadataFiles(args);
SetProviderIdFromPath(item, args.Path); SetProviderIdFromPath(item, args.Path);
} }

View File

@ -326,13 +326,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.Name = channelInfo.Name; 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 await item.RefreshMetadata(new MetadataRefreshOptions
{ {
ForceSave = isNew, ForceSave = isNew
ResetResolveArgs = false
}, cancellationToken); }, cancellationToken);
@ -391,8 +387,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
await item.RefreshMetadata(new MetadataRefreshOptions await item.RefreshMetadata(new MetadataRefreshOptions
{ {
ForceSave = isNew, ForceSave = isNew
ResetResolveArgs = false
}, cancellationToken); }, cancellationToken);
@ -448,8 +443,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
await item.RefreshMetadata(new MetadataRefreshOptions await item.RefreshMetadata(new MetadataRefreshOptions
{ {
ForceSave = isNew, ForceSave = isNew
ResetResolveArgs = false
}, cancellationToken); }, cancellationToken);

View File

@ -501,7 +501,7 @@ namespace MediaBrowser.ServerApplication
GetExports<ILibraryPostScanTask>(), GetExports<ILibraryPostScanTask>(),
GetExports<IPeoplePrescanTask>()); GetExports<IPeoplePrescanTask>());
ProviderManager.AddParts(GetExports<BaseMetadataProvider>(), GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(), ProviderManager.AddParts(GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(),
GetExports<IMetadataSaver>()); GetExports<IMetadataSaver>());
ImageProcessor.AddParts(GetExports<IImageEnhancer>()); ImageProcessor.AddParts(GetExports<IImageEnhancer>());
@ -627,7 +627,7 @@ namespace MediaBrowser.ServerApplication
list.Add(typeof(IServerApplicationHost).Assembly); list.Add(typeof(IServerApplicationHost).Assembly);
// Include composable parts in the Providers assembly // Include composable parts in the Providers assembly
list.Add(typeof(ImagesByNameProvider).Assembly); list.Add(typeof(ProviderUtils).Assembly);
// Common implementations // Common implementations
list.Add(typeof(TaskManager).Assembly); list.Add(typeof(TaskManager).Assembly);

View File

@ -237,4 +237,7 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal EndGlobal