converted movie providers to new system

pull/702/head
Luke Pulverenti 11 years ago
parent 64eb8c82a3
commit 821a3d29a2

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

@ -331,21 +331,8 @@ namespace MediaBrowser.Api
{
if (item.Parent is AggregateFolder)
{
return user.RootFolder.GetChildren(user, true).FirstOrDefault(i =>
{
try
{
return i.LocationType == LocationType.FileSystem &&
i.PhysicalLocations.Contains(item.Path);
}
catch (Exception ex)
{
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, i.Path);
return false;
}
});
return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.LocationType == LocationType.FileSystem &&
i.PhysicalLocations.Contains(item.Path));
}
return item;

@ -3,6 +3,10 @@ namespace MediaBrowser.Controller.Entities
{
public class AdultVideo : Video, IHasPreferredMetadataLanguage
{
/// <summary>
/// Gets or sets the preferred metadata language.
/// </summary>
/// <value>The preferred metadata language.</value>
public string PreferredMetadataLanguage { get; set; }
/// <summary>

@ -1,7 +1,11 @@
using System;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.Entities
{
@ -11,6 +15,11 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class AggregateFolder : Folder
{
public AggregateFolder()
{
PhysicalLocationsList = new List<string>();
}
/// <summary>
/// We don't support manual shortcuts
/// </summary>
@ -36,6 +45,60 @@ namespace MediaBrowser.Controller.Entities
get { return _virtualChildren; }
}
[IgnoreDataMember]
public override IEnumerable<string> PhysicalLocations
{
get
{
return PhysicalLocationsList;
}
}
public List<string> PhysicalLocationsList { get; set; }
protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
{
return CreateResolveArgs().FileSystemChildren;
}
private ItemResolveArgs CreateResolveArgs()
{
var path = ContainingFolderPath;
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
{
FileInfo = new DirectoryInfo(path),
Path = path,
Parent = Parent
};
// Gather child folder and files
if (args.IsDirectory)
{
var isPhysicalRoot = args.IsPhysicalRoot;
// When resolving the root, we need it's grandchildren (children of user views)
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
// Need to remove subpaths that may have been resolved from shortcuts
// Example: if \\server\movies exists, then strip out \\server\movies\action
if (isPhysicalRoot)
{
var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
}
args.FileSystemDictionary = fileSystemDictionary;
}
PhysicalLocationsList = args.PhysicalLocations.ToList();
return args;
}
/// <summary>
/// Adds the virtual child.
/// </summary>

@ -1,12 +1,10 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
@ -102,6 +100,35 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
protected internal bool IsOffline { get; set; }
/// <summary>
/// Returns the folder containing the item.
/// If the item is a folder, it returns the folder itself
/// </summary>
[IgnoreDataMember]
public virtual string ContainingFolderPath
{
get
{
if (IsFolder)
{
return Path;
}
return System.IO.Path.GetDirectoryName(Path);
}
}
[IgnoreDataMember]
public bool IsOwnedItem
{
get
{
// Local trailer, special feature, theme video, etc.
// An item that belongs to another item but is not part of the Parent-Child tree
return !IsFolder && Parent == null;
}
}
/// <summary>
/// Gets or sets the type of the location.
/// </summary>
@ -189,19 +216,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The locked fields.</value>
public List<MetadataFields> LockedFields { get; set; }
/// <summary>
/// Should be overridden to return the proper folder where metadata lives
/// </summary>
/// <value>The meta location.</value>
[IgnoreDataMember]
public virtual string MetaLocation
{
get
{
return Path ?? "";
}
}
/// <summary>
/// Gets the type of the media.
/// </summary>
@ -215,160 +229,19 @@ namespace MediaBrowser.Controller.Entities
}
}
/// <summary>
/// The _resolve args
/// </summary>
private ItemResolveArgs _resolveArgs;
/// <summary>
/// We attach these to the item so that we only ever have to hit the file system once
/// (this includes the children of the containing folder)
/// </summary>
/// <value>The resolve args.</value>
[IgnoreDataMember]
public ItemResolveArgs ResolveArgs
public virtual IEnumerable<string> PhysicalLocations
{
get
{
if (_resolveArgs == null)
{
try
{
_resolveArgs = CreateResolveArgs();
}
catch (IOException ex)
{
Logger.ErrorException("Error creating resolve args for {0}", ex, Path);
IsOffline = true;
var locationType = LocationType;
throw;
}
}
return _resolveArgs;
}
set
{
_resolveArgs = value;
}
}
[IgnoreDataMember]
public IEnumerable<string> PhysicalLocations
{
get
{
return ResolveArgs.PhysicalLocations;
}
}
/// <summary>
/// Resets the resolve args.
/// </summary>
/// <param name="pathInfo">The path info.</param>
public void ResetResolveArgs(FileSystemInfo pathInfo)
{
ResetResolveArgs(CreateResolveArgs(pathInfo));
}
/// <summary>
/// Resets the resolve args.
/// </summary>
public void ResetResolveArgs()
{
_resolveArgs = null;
}
/// <summary>
/// Resets the resolve args.
/// </summary>
/// <param name="args">The args.</param>
public void ResetResolveArgs(ItemResolveArgs args)
{
_resolveArgs = args;
}
/// <summary>
/// Creates ResolveArgs on demand
/// </summary>
/// <param name="pathInfo">The path info.</param>
/// <returns>ItemResolveArgs.</returns>
/// <exception cref="System.IO.IOException">Unable to retrieve file system info for + path</exception>
protected internal virtual ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
{
var path = Path;
var locationType = LocationType;
if (locationType == LocationType.Remote ||
locationType == LocationType.Virtual)
{
return new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager);
}
var isDirectory = false;
if (UseParentPathToCreateResolveArgs)
{
path = System.IO.Path.GetDirectoryName(path);
isDirectory = true;
}
pathInfo = pathInfo ?? (isDirectory ? new DirectoryInfo(path) : FileSystem.GetFileSystemInfo(path));
if (pathInfo == null || !pathInfo.Exists)
{
throw new IOException("Unable to retrieve file system info for " + path);
}
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
{
FileInfo = pathInfo,
Path = path,
Parent = Parent
};
// Gather child folder and files
if (args.IsDirectory)
{
var isPhysicalRoot = args.IsPhysicalRoot;
// When resolving the root, we need it's grandchildren (children of user views)
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
// Need to remove subpaths that may have been resolved from shortcuts
// Example: if \\server\movies exists, then strip out \\server\movies\action
if (isPhysicalRoot)
if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
{
var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
return new string[] { };
}
args.FileSystemDictionary = fileSystemDictionary;
}
//update our dates
EntityResolutionHelper.EnsureDates(FileSystem, this, args, false);
IsOffline = false;
return args;
}
/// <summary>
/// Some subclasses will stop resolving at a directory and point their Path to a file within. This will help ensure the on-demand resolve args are identical to the
/// original ones.
/// </summary>
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
[IgnoreDataMember]
protected virtual bool UseParentPathToCreateResolveArgs
{
get
{
return false;
return new[] { Path };
}
}
@ -583,122 +456,93 @@ namespace MediaBrowser.Controller.Entities
/// Loads local trailers from the file system
/// </summary>
/// <returns>List{Video}.</returns>
private IEnumerable<Trailer> LoadLocalTrailers()
{
ItemResolveArgs resolveArgs;
try
{
resolveArgs = ResolveArgs;
if (!resolveArgs.IsDirectory)
{
return new List<Trailer>();
}
}
catch (IOException ex)
{
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
return new List<Trailer>();
}
var files = new List<FileSystemInfo>();
var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
// Path doesn't exist. No biggie
if (folder != null)
{
try
{
files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
}
catch (IOException ex)
{
Logger.ErrorException("Error loading trailers for {0}", ex, Name);
}
}
// Support xbmc trailers (-trailer suffix on video file names)
files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
{
try
{
if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
{
if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
catch (IOException ex)
{
Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
}
return false;
}));
return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
if (dbItem != null)
{
dbItem.ResetResolveArgs(video.ResolveArgs);
video = dbItem;
}
return video;
}).ToList();
private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren)
{
return new List<Trailer>();
//ItemResolveArgs resolveArgs;
//try
//{
// resolveArgs = ResolveArgs;
// if (!resolveArgs.IsDirectory)
// {
// return new List<Trailer>();
// }
//}
//catch (IOException ex)
//{
// Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
// return new List<Trailer>();
//}
//var files = new List<FileSystemInfo>();
//var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
//// Path doesn't exist. No biggie
//if (folder != null)
//{
// try
// {
// files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
// }
// catch (IOException ex)
// {
// Logger.ErrorException("Error loading trailers for {0}", ex, Name);
// }
//}
//// Support xbmc trailers (-trailer suffix on video file names)
//files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
//{
// try
// {
// if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
// {
// if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
// {
// return true;
// }
// }
// }
// catch (IOException ex)
// {
// Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
// }
// return false;
//}));
//return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
//{
// // Try to retrieve it from the db. If we don't find it, use the resolved version
// var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
// if (dbItem != null)
// {
// video = dbItem;
// }
// return video;
//}).ToList();
}
/// <summary>
/// Loads the theme songs.
/// </summary>
/// <returns>List{Audio.Audio}.</returns>
private IEnumerable<Audio.Audio> LoadThemeSongs()
private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren)
{
ItemResolveArgs resolveArgs;
try
{
resolveArgs = ResolveArgs;
if (!resolveArgs.IsDirectory)
{
return new List<Audio.Audio>();
}
}
catch (IOException ex)
{
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
return new List<Audio.Audio>();
}
var files = new List<FileSystemInfo>();
var folder = resolveArgs.GetFileSystemEntryByName(ThemeSongsFolderName);
// Path doesn't exist. No biggie
if (folder != null)
{
try
{
files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
}
catch (IOException ex)
{
Logger.ErrorException("Error loading theme songs for {0}", ex, Name);
}
}
var files = fileSystemChildren.OfType<DirectoryInfo>()
.Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
.ToList();
// Support plex/xbmc convention
files.AddRange(resolveArgs.FileSystemChildren
.Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsAudioFile(i.Name))
files.AddRange(fileSystemChildren.OfType<FileInfo>()
.Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
);
return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio =>
@ -708,7 +552,6 @@ namespace MediaBrowser.Controller.Entities
if (dbItem != null)
{
dbItem.ResetResolveArgs(audio.ResolveArgs);
audio = dbItem;
}
@ -720,44 +563,11 @@ namespace MediaBrowser.Controller.Entities
/// Loads the video backdrops.
/// </summary>
/// <returns>List{Video}.</returns>
private IEnumerable<Video> LoadThemeVideos()
private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren)
{
ItemResolveArgs resolveArgs;
try
{
resolveArgs = ResolveArgs;
if (!resolveArgs.IsDirectory)
{
return new List<Video>();
}
}
catch (IOException ex)
{
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
return new List<Video>();
}
var folder = resolveArgs.GetFileSystemEntryByName(ThemeVideosFolderName);
// Path doesn't exist. No biggie
if (folder == null)
{
return new List<Video>();
}
IEnumerable<FileSystemInfo> files;
try
{
files = new DirectoryInfo(folder.FullName).EnumerateFiles();
}
catch (IOException ex)
{
Logger.ErrorException("Error loading video backdrops for {0}", ex, Name);
return new List<Video>();
}
var files = fileSystemChildren.OfType<DirectoryInfo>()
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
return LibraryManager.ResolvePaths<Video>(files, null).Select(item =>
{
@ -766,7 +576,6 @@ namespace MediaBrowser.Controller.Entities
if (dbItem != null)
{
dbItem.ResetResolveArgs(item.ResolveArgs);
item = dbItem;
}
@ -774,9 +583,9 @@ namespace MediaBrowser.Controller.Entities
}).ToList();
}
public Task<bool> RefreshMetadata(CancellationToken cancellationToken, bool resetResolveArgs = true)
public Task RefreshMetadata(CancellationToken cancellationToken)
{
return RefreshMetadata(new MetadataRefreshOptions { ResetResolveArgs = resetResolveArgs }, cancellationToken);
return RefreshMetadata(new MetadataRefreshOptions(), cancellationToken);
}
/// <summary>
@ -785,35 +594,24 @@ namespace MediaBrowser.Controller.Entities
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>true if a provider reports we changed</returns>
public async Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
public async Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
{
if (options.ResetResolveArgs)
var locationType = LocationType;
if (IsFolder || Parent != null)
{
// Reload this
ResetResolveArgs();
}
var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ?
GetFileSystemChildren().ToList() :
new List<FileSystemInfo>();
await BeforeRefreshMetadata(options, cancellationToken).ConfigureAwait(false);
await BeforeRefreshMetadata(options, files, cancellationToken).ConfigureAwait(false);
}
await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
return false;
}
private readonly Task _cachedTask = Task.FromResult(true);
protected virtual Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
return _cachedTask;
}
[Obsolete]
public virtual async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
{
// Refresh for the item
var itemRefreshTask = ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh);
cancellationToken.ThrowIfCancellationRequested();
var themeSongsChanged = false;
var themeVideosChanged = false;
@ -825,102 +623,83 @@ namespace MediaBrowser.Controller.Entities
var hasThemeMedia = this as IHasThemeMedia;
if (hasThemeMedia != null)
{
themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
if (!IsInMixedFolder)
{
themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
}
}
var hasTrailers = this as IHasTrailers;
if (hasTrailers != null)
{
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
}
}
cancellationToken.ThrowIfCancellationRequested();
// Get the result from the item task
var updateReason = await itemRefreshTask.ConfigureAwait(false);
var changed = updateReason.HasValue;
if (changed || forceSave || themeSongsChanged || themeVideosChanged || localTrailersChanged)
if (themeSongsChanged || themeVideosChanged || localTrailersChanged)
{
cancellationToken.ThrowIfCancellationRequested();
await LibraryManager.UpdateItem(this, updateReason ?? ItemUpdateType.Unspecified, cancellationToken).ConfigureAwait(false);
options.ForceSave = true;
}
}
return changed;
protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren()
{
var path = ContainingFolderPath;
return new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly);
}
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var newItems = LoadLocalTrailers().ToList();
var newItems = LoadLocalTrailers(fileSystemChildren).ToList();
var newItemIds = newItems.Select(i => i.Id).ToList();
var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds);
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh,
ResetResolveArgs = false
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
}, cancellationToken));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
await Task.WhenAll(tasks).ConfigureAwait(false);
item.LocalTrailerIds = newItemIds;
return itemsChanged || results.Contains(true);
return itemsChanged;
}
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var newThemeVideos = LoadThemeVideos().ToList();
var newThemeVideos = LoadThemeVideos(fileSystemChildren).ToList();
var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList();
var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
var tasks = newThemeVideos.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh,
ResetResolveArgs = false
}, cancellationToken));
var tasks = newThemeVideos.Select(i => i.RefreshMetadata(options, cancellationToken));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
await Task.WhenAll(tasks).ConfigureAwait(false);
item.ThemeVideoIds = newThemeVideoIds;
return themeVideosChanged || results.Contains(true);
return themeVideosChanged;
}
/// <summary>
/// Refreshes the theme songs.
/// </summary>
private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var newThemeSongs = LoadThemeSongs().ToList();
var newThemeSongs = LoadThemeSongs(fileSystemChildren).ToList();
var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList();
var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds);
var tasks = newThemeSongs.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh,
ResetResolveArgs = false
}, cancellationToken));
var tasks = newThemeSongs.Select(i => i.RefreshMetadata(options, cancellationToken));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
await Task.WhenAll(tasks).ConfigureAwait(false);
item.ThemeSongIds = newThemeSongIds;
return themeSongsChanged || results.Contains(true);
return themeSongsChanged;
}
/// <summary>
@ -1655,27 +1434,8 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentNullException("imagePath");
}
var locationType = LocationType;
if (locationType == LocationType.Remote ||
locationType == LocationType.Virtual)
{
return FileSystem.GetLastWriteTimeUtc(imagePath);
}
var metaFileEntry = ResolveArgs.GetMetaFileByPath(imagePath);
// If we didn't the metafile entry, check the Season
if (metaFileEntry == null)
{
if (Parent != null)
{
metaFileEntry = Parent.ResolveArgs.GetMetaFileByPath(imagePath);
}
}
// See if we can avoid a file system lookup by looking for the file in ResolveArgs
return metaFileEntry == null ? FileSystem.GetLastWriteTimeUtc(imagePath) : FileSystem.GetLastWriteTimeUtc(metaFileEntry);
return FileSystem.GetLastWriteTimeUtc(imagePath);
}
/// <summary>

@ -29,25 +29,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The preferred metadata country code.</value>
public string PreferredMetadataCountryCode { get; set; }
/// <summary>
///
/// </summary>
public override string MetaLocation
{
get
{
return System.IO.Path.GetDirectoryName(Path);
}
}
protected override bool UseParentPathToCreateResolveArgs
{
get
{
return !IsInMixedFolder;
}
}
public Book()
{
Tags = new List<string>();

@ -1,4 +1,6 @@
using System;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -14,6 +16,11 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class CollectionFolder : Folder, ICollectionFolder
{
public CollectionFolder()
{
PhysicalLocationsList = new List<string>();
}
/// <summary>
/// Gets a value indicating whether this instance is virtual folder.
/// </summary>
@ -42,6 +49,60 @@ namespace MediaBrowser.Controller.Entities
}
}
[IgnoreDataMember]
public override IEnumerable<string> PhysicalLocations
{
get
{
return PhysicalLocationsList;
}
}
public List<string> PhysicalLocationsList { get; set; }
protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
{
return CreateResolveArgs().FileSystemChildren;
}
private ItemResolveArgs CreateResolveArgs()
{
var path = ContainingFolderPath;
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
{
FileInfo = new DirectoryInfo(path),
Path = path,
Parent = Parent
};
// Gather child folder and files
if (args.IsDirectory)
{
var isPhysicalRoot = args.IsPhysicalRoot;
// When resolving the root, we need it's grandchildren (children of user views)
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
// Need to remove subpaths that may have been resolved from shortcuts
// Example: if \\server\movies exists, then strip out \\server\movies\action
if (isPhysicalRoot)
{
var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
}
args.FileSystemDictionary = fileSystemDictionary;
}
PhysicalLocationsList = args.PhysicalLocations.ToList();
return args;
}
// Cache this since it will be used a lot
/// <summary>
/// The null task result
@ -59,13 +120,14 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns>
protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
{
CreateResolveArgs();
ResetDynamicChildren();
return NullTaskResult;
}
private List<LinkedChild> _linkedChildren;
/// <summary>
/// Our children are actually just references to the ones in the physical root...
/// </summary>

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

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

@ -99,6 +99,12 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value>The backdrop image paths.</value>
List<string> BackdropImagePaths { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is owned item.
/// </summary>
/// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
bool IsOwnedItem { get; }
}
public static class HasImagesExtensions

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

@ -11,28 +11,6 @@ namespace MediaBrowser.Controller.Entities.TV
/// </summary>
public class Episode : Video
{
/// <summary>
/// Episodes have a special Metadata folder
/// </summary>
/// <value>The meta location.</value>
[IgnoreDataMember]
public override string MetaLocation
{
get
{
return System.IO.Path.Combine(Parent.Path, "metadata");
}
}
[IgnoreDataMember]
protected override bool UseParentPathToCreateResolveArgs
{
get
{
return false;
}
}
/// <summary>
/// Gets the season in which it aired.
/// </summary>

@ -131,34 +131,6 @@ namespace MediaBrowser.Controller.Entities.TV
get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; }
}
/// <summary>
/// Add files from the metadata folder to ResolveArgs
/// </summary>
/// <param name="args">The args.</param>
public static void AddMetadataFiles(ItemResolveArgs args)
{
var folder = args.GetFileSystemEntryByName("metadata");
if (folder != null)
{
args.AddMetadataFiles(new DirectoryInfo(folder.FullName).EnumerateFiles());
}
}
/// <summary>
/// Creates ResolveArgs on demand
/// </summary>
/// <param name="pathInfo">The path info.</param>
/// <returns>ItemResolveArgs.</returns>
protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
{
var args = base.CreateResolveArgs(pathInfo);
AddMetadataFiles(args);
return args;
}
/// <summary>
/// Creates the name of the sort.
/// </summary>

@ -111,20 +111,6 @@ namespace MediaBrowser.Controller.Entities.TV
};
}
/// <summary>
/// Creates ResolveArgs on demand
/// </summary>
/// <param name="pathInfo">The path info.</param>
/// <returns>ItemResolveArgs.</returns>
protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
{
var args = base.CreateResolveArgs(pathInfo);
Season.AddMetadataFiles(args);
return args;
}
[IgnoreDataMember]
public bool ContainsEpisodesWithoutSeasonFolders
{

@ -89,33 +89,6 @@ namespace MediaBrowser.Controller.Entities
}
}
/// <summary>
/// Should be overridden to return the proper folder where metadata lives
/// </summary>
/// <value>The meta location.</value>
[IgnoreDataMember]
public override string MetaLocation
{
get
{
if (!IsLocalTrailer)
{
return System.IO.Path.GetDirectoryName(Path);
}
return base.MetaLocation;
}
}
/// <summary>
/// Needed because the resolver stops at the trailer folder and we find the video inside.
/// </summary>
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
protected override bool UseParentPathToCreateResolveArgs
{
get { return !IsLocalTrailer; }
}
public override string GetUserDataKey()
{
var key = this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tvcom);

@ -84,33 +84,23 @@ namespace MediaBrowser.Controller.Entities
/// <value>The aspect ratio.</value>
public string AspectRatio { get; set; }
/// <summary>
/// Should be overridden to return the proper folder where metadata lives
/// </summary>
/// <value>The meta location.</value>
[IgnoreDataMember]
public override string MetaLocation
public override string ContainingFolderPath
{
get
{
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path;
}
}
if (IsMultiPart)
{
return System.IO.Path.GetDirectoryName(Path);
}
/// <summary>
/// Needed because the resolver stops at the movie folder and we find the video inside.
/// </summary>
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
protected override bool UseParentPathToCreateResolveArgs
{
get
{
if (IsInMixedFolder)
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd ||
VideoType == VideoType.HdDvd)
{
return false;
return Path;
}
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart;
return base.ContainingFolderPath;
}
}
@ -159,106 +149,73 @@ namespace MediaBrowser.Controller.Entities
}
}
/// <summary>
/// Overrides the base implementation to refresh metadata for local trailers
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="forceSave">if set to <c>true</c> [is new item].</param>
/// <param name="forceRefresh">if set to <c>true</c> [force].</param>
/// <returns>true if a provider reports we changed</returns>
public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
// Kick off a task to refresh the main item
var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
var additionalPartsChanged = false;
await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
// Must have a parent to have additional parts
// In other words, it must be part of the Parent/Child tree
// The additional parts won't have additional parts themselves
if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null)
{
try
{
additionalPartsChanged = await RefreshAdditionalParts(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
}
catch (IOException ex)
var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
if (additionalPartsChanged)
{
Logger.ErrorException("Error loading additional parts for {0}.", ex, Name);
options.ForceSave = true;
}
}
return additionalPartsChanged || result;
}
/// <summary>
/// Refreshes the additional parts.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="fileSystemChildren">The file system children.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="forceSave">if set to <c>true</c> [force save].</param>
/// <param name="forceRefresh">if set to <c>true</c> [force refresh].</param>
/// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param>
/// <returns>Task{System.Boolean}.</returns>
private async Task<bool> RefreshAdditionalParts(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true)
private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var newItems = LoadAdditionalParts().ToList();
var newItems = LoadAdditionalParts(fileSystemChildren).ToList();
var newItemIds = newItems.Select(i => i.Id).ToList();
var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds);
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
{
ForceSave = forceSave,
ReplaceAllMetadata = forceRefresh
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
}, cancellationToken));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
await Task.WhenAll(tasks).ConfigureAwait(false);
AdditionalPartIds = newItemIds;
return itemsChanged || results.Contains(true);
return itemsChanged;
}
/// <summary>
/// Loads the additional parts.
/// </summary>
/// <returns>IEnumerable{Video}.</returns>
private IEnumerable<Video> LoadAdditionalParts()
private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren)
{
IEnumerable<FileSystemInfo> files;
var path = Path;
if (string.IsNullOrEmpty(path))
{
throw new ApplicationException(string.Format("Item {0} has a null path.", Name ?? Id.ToString()));
}
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
{
var parentPath = System.IO.Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(parentPath))
files = fileSystemChildren.Where(i =>
{
throw new IOException("Unable to get parent path info from " + path);
}
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
}
files = new DirectoryInfo(parentPath)
.EnumerateDirectories()
.Where(i => !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFile(i.Name));
return false;
});
}
else
{
var resolveArgs = ResolveArgs;
if (resolveArgs == null)
{
throw new IOException("ResolveArgs are null for " + path);
}
files = resolveArgs.FileSystemChildren.Where(i =>
files = fileSystemChildren.Where(i =>
{
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
@ -276,7 +233,6 @@ namespace MediaBrowser.Controller.Entities
if (dbItem != null)
{
dbItem.ResetResolveArgs(video.ResolveArgs);
video = dbItem;
}

@ -195,77 +195,6 @@ namespace MediaBrowser.Controller.Library
}
}
/// <summary>
/// Store these to reduce disk access in Resolvers
/// </summary>
/// <value>The metadata file dictionary.</value>
private Dictionary<string, FileSystemInfo> MetadataFileDictionary { get; set; }
/// <summary>
/// Gets the metadata files.
/// </summary>
/// <value>The metadata files.</value>
public IEnumerable<FileSystemInfo> MetadataFiles
{
get
{
if (MetadataFileDictionary != null)
{
return MetadataFileDictionary.Values;
}
return new FileSystemInfo[] { };
}
}
/// <summary>
/// Adds the metadata file.
/// </summary>
/// <param name="path">The path.</param>
/// <exception cref="System.IO.FileNotFoundException"></exception>
public void AddMetadataFile(string path)
{
var file = new FileInfo(path);
if (!file.Exists)
{
throw new FileNotFoundException(path);
}
AddMetadataFile(file);
}
/// <summary>
/// Adds the metadata file.
/// </summary>
/// <param name="fileInfo">The file info.</param>
public void AddMetadataFile(FileSystemInfo fileInfo)
{
AddMetadataFiles(new[] { fileInfo });
}
/// <summary>
/// Adds the metadata files.
/// </summary>
/// <param name="files">The files.</param>
/// <exception cref="System.ArgumentNullException"></exception>
public void AddMetadataFiles(IEnumerable<FileSystemInfo> files)
{
if (files == null)
{
throw new ArgumentNullException();
}
if (MetadataFileDictionary == null)
{
MetadataFileDictionary = new Dictionary<string, FileSystemInfo>(StringComparer.OrdinalIgnoreCase);
}
foreach (var file in files)
{
MetadataFileDictionary[file.Name] = file;
}
}
/// <summary>
/// Gets the name of the file system entry by.
/// </summary>
@ -320,16 +249,6 @@ namespace MediaBrowser.Controller.Library
{
throw new ArgumentNullException();
}
if (MetadataFileDictionary != null)
{
FileSystemInfo entry;
if (MetadataFileDictionary.TryGetValue(System.IO.Path.GetFileName(path), out entry))
{
return entry;
}
}
return GetFileSystemEntryByPath(path);
}
@ -346,16 +265,6 @@ namespace MediaBrowser.Controller.Library
{
throw new ArgumentNullException();
}
if (MetadataFileDictionary != null)
{
FileSystemInfo entry;
if (MetadataFileDictionary.TryGetValue(name, out entry))
{
return entry;
}
}
return GetFileSystemEntryByName(name);
}

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

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

@ -2,13 +2,8 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -146,19 +141,6 @@ namespace MediaBrowser.Controller.Providers
providerInfo.LastRefreshed = value;
providerInfo.LastRefreshStatus = status;
providerInfo.ProviderVersion = providerVersion;
// Save the file system stamp for future comparisons
if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem)
{
try
{
providerInfo.FileStamp = GetCurrentFileSystemStamp(item);
}
catch (IOException ex)
{
Logger.ErrorException("Error getting file stamp for {0}", ex, item.Path);
}
}
}
/// <summary>
@ -233,12 +215,6 @@ namespace MediaBrowser.Controller.Providers
return true;
}
if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem &&
HasFileSystemStampChanged(item, providerInfo))
{
return true;
}
if (RefreshOnVersionChange && !String.Equals(ProviderVersion, providerInfo.ProviderVersion))
{
return true;
@ -263,17 +239,6 @@ namespace MediaBrowser.Controller.Providers
return CompareDate(item) > providerInfo.LastRefreshed;
}
/// <summary>
/// Determines if the item's file system stamp has changed from the last time the provider refreshed
/// </summary>
/// <param name="item">The item.</param>
/// <param name="providerInfo">The provider info.</param>
/// <returns><c>true</c> if [has file system stamp changed] [the specified item]; otherwise, <c>false</c>.</returns>
protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo)
{
return GetCurrentFileSystemStamp(item) != providerInfo.FileStamp;
}
/// <summary>
/// Override this to return the date that should be compared to the last refresh date
/// to determine if this provider should be re-fetched.
@ -301,159 +266,5 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <value>The priority.</value>
public abstract MetadataProviderPriority Priority { get; }
/// <summary>
/// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
/// </summary>
/// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
protected virtual bool RefreshOnFileSystemStampChange
{
get
{
return false;
}
}
protected virtual string[] FilestampExtensions
{
get { return new string[] { }; }
}
/// <summary>
/// Determines if the parent's file system stamp should be used for comparison
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
protected virtual bool UseParentFileSystemStamp(BaseItem item)
{
// True when the current item is just a file
return !item.ResolveArgs.IsDirectory;
}
protected virtual IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
{
if (UseParentFileSystemStamp(item) && item.Parent != null)
{
return new[] { item.Parent };
}
return new[] { item };
}
/// <summary>
/// Gets the item's current file system stamp
/// </summary>
/// <param name="item">The item.</param>
/// <returns>Guid.</returns>
private Guid GetCurrentFileSystemStamp(BaseItem item)
{
return GetFileSystemStamp(GetItemsForFileStampComparison(item));
}
private Dictionary<string, string> _fileStampExtensionsDictionary;
private Dictionary<string, string> FileStampExtensionsDictionary
{
get
{
return _fileStampExtensionsDictionary ??
(_fileStampExtensionsDictionary =
FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase));
}
}
/// <summary>
/// Gets the file system stamp.
/// </summary>
/// <param name="items">The items.</param>
/// <returns>Guid.</returns>
protected virtual Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
{
var sb = new StringBuilder();
var extensions = FileStampExtensionsDictionary;
var numExtensions = FilestampExtensions.Length;
foreach (var item in items)
{
// If there's no path or the item is a file, there's nothing to do
if (item.LocationType == LocationType.FileSystem)
{
var resolveArgs = item.ResolveArgs;
if (resolveArgs.IsDirectory)
{
// Record the name of each file
// Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
AddFiles(sb, resolveArgs.FileSystemChildren, extensions, numExtensions);
AddFiles(sb, resolveArgs.MetadataFiles, extensions, numExtensions);
}
}
}
var stamp = sb.ToString();
if (string.IsNullOrEmpty(stamp))
{
return Guid.Empty;
}
return stamp.GetMD5();
}
private static readonly Dictionary<string, string> FoldersToMonitor = new[] { "extrafanart", "extrathumbs" }
.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
protected Guid GetFileSystemStamp(IEnumerable<FileSystemInfo> files)
{
var sb = new StringBuilder();
var extensions = FileStampExtensionsDictionary;
var numExtensions = FilestampExtensions.Length;
// Record the name of each file
// Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
AddFiles(sb, files, extensions, numExtensions);
return sb.ToString().GetMD5();
}
/// <summary>
/// Adds the files.
/// </summary>
/// <param name="sb">The sb.</param>
/// <param name="files">The files.</param>
/// <param name="extensions">The extensions.</param>
/// <param name="numExtensions">The num extensions.</param>
private void AddFiles(StringBuilder sb, IEnumerable<FileSystemInfo> files, Dictionary<string, string> extensions, int numExtensions)
{
foreach (var file in files
.OrderBy(f => f.Name))
{
try
{
if ((file.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
if (FoldersToMonitor.ContainsKey(file.Name))
{
sb.Append(file.Name);
var children = ((DirectoryInfo)file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList();
AddFiles(sb, children, extensions, numExtensions);
}
}
// It's a file
else if (numExtensions == 0 || extensions.ContainsKey(file.Extension))
{
sb.Append(file.Name);
}
}
catch (IOException ex)
{
Logger.ErrorException("Error accessing file attributes for {0}", ex, file.FullName);
}
}
}
}
}

@ -39,5 +39,11 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns>
bool IsSaveLocalMetadataEnabled();
/// <summary>
/// Gets a value indicating whether this instance is in mixed folder.
/// </summary>
/// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
bool IsInMixedFolder { get; }
}
}

@ -5,12 +5,6 @@ namespace MediaBrowser.Controller.Providers
{
public interface ILocalMetadataProvider : IMetadataProvider
{
/// <summary>
/// Determines whether [has local metadata] [the specified item].
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if [has local metadata] [the specified item]; otherwise, <c>false</c>.</returns>
bool HasLocalMetadata(IHasMetadata item);
}
public interface ILocalMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ILocalMetadataProvider
@ -19,9 +13,16 @@ namespace MediaBrowser.Controller.Providers
/// <summary>
/// Gets the metadata.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="info">The information.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MetadataResult{`0}}.</returns>
Task<MetadataResult<TItemType>> GetMetadata(string path, CancellationToken cancellationToken);
Task<MetadataResult<TItemType>> GetMetadata(ItemInfo info, CancellationToken cancellationToken);
}
public class ItemInfo
{
public string Path { get; set; }
public bool IsInMixedFolder { get; set; }
}
}

@ -24,15 +24,6 @@ namespace MediaBrowser.Controller.Providers
/// <returns>Task.</returns>
Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken);
/// <summary>
/// Executes the metadata providers.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <returns>Task{System.Boolean}.</returns>
Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false);
/// <summary>
/// Saves the image.
/// </summary>
@ -60,12 +51,11 @@ namespace MediaBrowser.Controller.Providers
/// <summary>
/// Adds the metadata providers.
/// </summary>
/// <param name="providers">The providers.</param>
/// <param name="imageProviders">The image providers.</param>
/// <param name="metadataServices">The metadata services.</param>
/// <param name="metadataProviders">The metadata providers.</param>
/// <param name="savers">The savers.</param>
void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
IEnumerable<IMetadataSaver> savers);
/// <summary>

@ -16,17 +16,6 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
[Obsolete]
public bool ForceSave { get; set; }
/// <summary>
/// TODO: deprecate. Keeping this for now, for api compatibility
/// </summary>
[Obsolete]
public bool ResetResolveArgs { get; set; }
public MetadataRefreshOptions()
{
ResetResolveArgs = true;
}
}
public class ImageRefreshOptions

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

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

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

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

@ -3,32 +3,81 @@ using MediaBrowser.Controller.Providers;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers
{
public abstract class BaseXmlProvider: IHasChangeMonitor
public abstract class BaseXmlProvider<T> : ILocalMetadataProvider<T>, IHasChangeMonitor
where T : IHasMetadata, new()
{
protected static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4);
protected IFileSystem FileSystem;
public async Task<MetadataResult<T>> GetMetadata(ItemInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<T>();
var file = GetXmlFile(info);
if (file == null)
{
return result;
}
var path = file.FullName;
await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
result.Item = new T();
Fetch(result.Item, path, cancellationToken);
result.HasMetadata = true;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlProviderUtils.XmlParsingResourcePool.Release();
}
return result;
}
protected abstract void Fetch(T item, string path, CancellationToken cancellationToken);
protected BaseXmlProvider(IFileSystem fileSystem)
{
FileSystem = fileSystem;
}
protected abstract FileInfo GetXmlFile(string path);
protected abstract FileInfo GetXmlFile(ItemInfo info);
public bool HasChanged(IHasMetadata item, DateTime date)
{
var file = GetXmlFile(item.Path);
var file = GetXmlFile(new ItemInfo { IsInMixedFolder = item.IsInMixedFolder, Path = item.Path });
if (file == null)
{
return false;
}
return FileSystem.GetLastWriteTimeUtc(file) > date;
}
public bool HasLocalMetadata(IHasMetadata item)
public string Name
{
return GetXmlFile(item.Path).Exists;
get
{
return "Media Browser Xml";
}
}
}
static class XmlProviderUtils
{
internal static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4);
}
}

@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.BoxSets
{
/// <summary>
/// Class BoxSetXmlProvider.
/// </summary>
public class BoxSetXmlProvider : BaseXmlProvider, ILocalMetadataProvider<BoxSet>
public class BoxSetXmlProvider : BaseXmlProvider<BoxSet>
{
private readonly ILogger _logger;
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.BoxSets
_logger = logger;
}
public async Task<MetadataResult<BoxSet>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(BoxSet item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<BoxSet>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var item = new BoxSet();
new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken);
result.HasMetadata = true;
result.Item = item;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(path, "collection.xml"));
return new FileInfo(Path.Combine(info.Path, "collection.xml"));
}
}
}

@ -1,56 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace MediaBrowser.Providers
{
public class CollectionFolderImageProvider : ImageFromMediaLocationProvider
{
public CollectionFolderImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
: base(logManager, configurationManager, fileSystem)
{
}
public override bool Supports(BaseItem item)
{
return item is CollectionFolder && item.LocationType == LocationType.FileSystem;
}
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.Second; }
}
protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
{
return item.PhysicalLocations
.Select(i => GetImageFromLocation(i, filenameWithoutExtension))
.FirstOrDefault(i => i != null);
}
protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
{
var files = items.SelectMany(i => i.PhysicalLocations)
.Select(i => new DirectoryInfo(i))
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
.Where(i =>
{
var ext = i.Extension;
return !string.IsNullOrEmpty(ext) &&
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
})
.ToList();
return GetFileSystemStamp(files);
}
}
}

@ -1,93 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers
{
/// <summary>
/// Provides metadata for Folders and all subclasses by parsing folder.xml
/// </summary>
public class FolderProviderFromXml : BaseMetadataProvider
{
private readonly IFileSystem _fileSystem;
public FolderProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
: base(logManager, configurationManager)
{
_fileSystem = fileSystem;
}
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
return item.IsFolder && item.LocationType == LocationType.FileSystem;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
private const string XmlFileName = "folder.xml";
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
if (xml == null)
{
return false;
}
return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="providerInfo">The provider information.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
if (metadataFile != null)
{
var path = metadataFile.FullName;
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
new BaseItemXmlParser<Folder>(Logger).Fetch((Folder)item, path, cancellationToken);
}
finally
{
XmlParsingResourcePool.Release();
}
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
}
}

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

@ -0,0 +1,33 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
namespace MediaBrowser.Providers.Folders
{
/// <summary>
/// Provides metadata for Folders and all subclasses by parsing folder.xml
/// </summary>
public class FolderXmlProvider : BaseXmlProvider<Folder>
{
private readonly ILogger _logger;
public FolderXmlProvider(IFileSystem fileSystem, ILogger logger)
: base(fileSystem)
{
_logger = logger;
}
protected override void Fetch(Folder item, string path, CancellationToken cancellationToken)
{
new BaseItemXmlParser<Folder>(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(info.Path, "folder.xml"));
}
}
}

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

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Games
{
public class GameSystemXmlProvider : BaseXmlProvider, ILocalMetadataProvider<GameSystem>
public class GameSystemXmlProvider : BaseXmlProvider<GameSystem>
{
private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Games
_logger = logger;
}
public async Task<MetadataResult<GameSystem>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(GameSystem item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<GameSystem>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var item = new GameSystem();
new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken);
result.HasMetadata = true;
result.Item = item;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(path, "gamesystem.xml"));
return new FileInfo(Path.Combine(info.Path, "gamesystem.xml"));
}
}
}

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Games
{
public class GameXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Game>
public class GameXmlProvider : BaseXmlProvider<Game>
{
private readonly ILogger _logger;
@ -18,57 +17,29 @@ namespace MediaBrowser.Providers.Games
_logger = logger;
}
public async Task<MetadataResult<Game>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(Game item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<Game>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var item = new Game();
new GameXmlParser(_logger).Fetch(item, path, cancellationToken);
result.HasMetadata = true;
result.Item = item;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new GameXmlParser(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
var fileInfo = FileSystem.GetFileSystemInfo(path);
var fileInfo = FileSystem.GetFileSystemInfo(info.Path);
var directoryInfo = fileInfo as DirectoryInfo;
if (directoryInfo == null)
{
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path));
}
var directoryPath = directoryInfo.FullName;
var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml");
var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml");
var file = new FileInfo(specificFile);
return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml"));
return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml"));
}
}
}

@ -1,637 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers
{
/// <summary>
/// Provides images for all types by looking for standard images - folder, backdrop, logo, etc.
/// </summary>
public class ImageFromMediaLocationProvider : BaseMetadataProvider
{
protected readonly IFileSystem FileSystem;
public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
: base(logManager, configurationManager)
{
FileSystem = fileSystem;
}
public override ItemUpdateType ItemUpdateType
{
get
{
return ItemUpdateType.ImageUpdate;
}
}
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
if (item.LocationType == LocationType.FileSystem)
{
if (item.ResolveArgs.IsDirectory)
{
return true;
}
return item.IsInMixedFolder && item.Parent != null && !(item is Episode);
}
if (item.LocationType == LocationType.Virtual)
{
var season = item as Season;
if (season != null)
{
var series = season.Series;
if (series != null && series.LocationType == LocationType.FileSystem)
{
return true;
}
}
}
return false;
}
protected override IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
{
var season = item as Season;
if (season != null)
{
var list = new List<BaseItem>();
if (season.LocationType == LocationType.FileSystem)
{
list.Add(season);
}
var series = season.Series;
if (series != null && series.LocationType == LocationType.FileSystem)
{
list.Add(series);
}
return list;
}
return base.GetItemsForFileStampComparison(item);
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
/// <summary>
/// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
/// </summary>
/// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnFileSystemStampChange
{
get
{
return true;
}
}
/// <summary>
/// Gets the filestamp extensions.
/// </summary>
/// <value>The filestamp extensions.</value>
protected override string[] FilestampExtensions
{
get
{
return BaseItem.SupportedImageExtensions;
}
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
// Make sure current image paths still exist
item.ValidateImages();
cancellationToken.ThrowIfCancellationRequested();
var args = GetResolveArgsContainingImages(item);
PopulateBaseItemImages(item, args);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return TrueTaskResult;
}
private ItemResolveArgs GetResolveArgsContainingImages(BaseItem item)
{
if (item.LocationType != LocationType.FileSystem)
{
return null;
}
if (item.IsInMixedFolder)
{
if (item.Parent == null)
{
return item.ResolveArgs;
}
return item.Parent.ResolveArgs;
}
return item.ResolveArgs;
}
/// <summary>
/// Gets the image.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
/// <param name="filenameWithoutExtension">The filename without extension.</param>
/// <returns>FileSystemInfo.</returns>
protected virtual FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
{
if (string.IsNullOrEmpty(item.MetaLocation))
{
return null;
}
return BaseItem.SupportedImageExtensions
.Select(i => args.GetMetaFileByPath(GetFullImagePath(item, args, filenameWithoutExtension, i)))
.FirstOrDefault(i => i != null);
}
protected virtual string GetFullImagePath(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension, string extension)
{
var path = item.MetaLocation;
if (item.IsInMixedFolder)
{
var pathFilenameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path);
// If the image filename and path file name match, just look for an image using the same full path as the item
if (string.Equals(pathFilenameWithoutExtension, filenameWithoutExtension))
{
return Path.ChangeExtension(item.Path, extension);
}
return Path.Combine(path, pathFilenameWithoutExtension + "-" + filenameWithoutExtension + extension);
}
return Path.Combine(path, filenameWithoutExtension + extension);
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
/// <summary>
/// Fills in image paths based on files win the folder
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
private void PopulateBaseItemImages(BaseItem item, ItemResolveArgs args)
{
PopulatePrimaryImage(item, args);
// Logo Image
var image = GetImage(item, args, "logo");
if (image != null)
{
item.SetImagePath(ImageType.Logo, image.FullName);
}
// Clearart
image = GetImage(item, args, "clearart");
if (image != null)
{
item.SetImagePath(ImageType.Art, image.FullName);
}
// Disc
image = GetImage(item, args, "disc") ??
GetImage(item, args, "cdart");
if (image != null)
{
item.SetImagePath(ImageType.Disc, image.FullName);
}
// Box Image
image = GetImage(item, args, "box");
if (image != null)
{
item.SetImagePath(ImageType.Box, image.FullName);
}
// BoxRear Image
image = GetImage(item, args, "boxrear");
if (image != null)
{
item.SetImagePath(ImageType.BoxRear, image.FullName);
}
// Thumbnail Image
image = GetImage(item, args, "menu");
if (image != null)
{
item.SetImagePath(ImageType.Menu, image.FullName);
}
PopulateBanner(item, args);
PopulateThumb(item, args);
// Backdrop Image
PopulateBackdrops(item, args);
PopulateScreenshots(item, args);
}
private void PopulatePrimaryImage(BaseItem item, ItemResolveArgs args)
{
// Primary Image
var image = GetImage(item, args, "folder") ??
GetImage(item, args, "poster") ??
GetImage(item, args, "cover") ??
GetImage(item, args, "default");
// Support plex/xbmc convention
if (image == null && item is Series)
{
image = GetImage(item, args, "show");
}
// Support plex/xbmc convention
if (image == null)
{
// Supprt xbmc conventions
var season = item as Season;
if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
{
image = GetSeasonImageFromSeriesFolder(season, "-poster");
}
}
// Support plex/xbmc convention
if (image == null && (item is Movie || item is MusicVideo || item is AdultVideo))
{
image = GetImage(item, args, "movie");
}
// Look for a file with the same name as the item
if (image == null && !string.IsNullOrEmpty(item.Path))
{
var name = Path.GetFileNameWithoutExtension(item.Path);
if (!string.IsNullOrEmpty(name))
{
image = GetImage(item, args, name) ??
GetImage(item, args, name + "-poster");
}
}
if (image != null)
{
item.SetImagePath(ImageType.Primary, image.FullName);
}
}
/// <summary>
/// Populates the banner.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
private void PopulateBanner(BaseItem item, ItemResolveArgs args)
{
// Banner Image
var image = GetImage(item, args, "banner");
if (image == null)
{
// Supprt xbmc conventions
var season = item as Season;
if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
{
image = GetSeasonImageFromSeriesFolder(season, "-banner");
}
}
if (image != null)
{
item.SetImagePath(ImageType.Banner, image.FullName);
}
}
/// <summary>
/// Populates the thumb.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
private void PopulateThumb(BaseItem item, ItemResolveArgs args)
{
// Thumbnail Image
var image = GetImage(item, args, "thumb") ??
GetImage(item, args, "landscape");
if (image == null)
{
// Supprt xbmc conventions
var season = item as Season;
if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
{
image = GetSeasonImageFromSeriesFolder(season, "-landscape");
}
}
if (image != null)
{
item.SetImagePath(ImageType.Thumb, image.FullName);
}
}
/// <summary>
/// Populates the backdrops.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
private void PopulateBackdrops(BaseItem item, ItemResolveArgs args)
{
var backdropFiles = new List<string>();
PopulateBackdrops(item, args, backdropFiles, "backdrop", "backdrop");
// Support {name}-fanart.ext
if (!string.IsNullOrEmpty(item.Path))
{
var name = Path.GetFileNameWithoutExtension(item.Path);
if (!string.IsNullOrEmpty(name))
{
var image = GetImage(item, args, name + "-fanart");
if (image != null)
{
backdropFiles.Add(image.FullName);
}
}
}
// Support plex/xbmc conventions
PopulateBackdrops(item, args, backdropFiles, "fanart", "fanart-");
PopulateBackdrops(item, args, backdropFiles, "background", "background-");
PopulateBackdrops(item, args, backdropFiles, "art", "art-");
var season = item as Season;
if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
{
var image = GetSeasonImageFromSeriesFolder(season, "-fanart");
if (image != null)
{
backdropFiles.Add(image.FullName);
}
}
if (item.LocationType == LocationType.FileSystem)
{
PopulateBackdropsFromExtraFanart(args, backdropFiles);
}
if (backdropFiles.Count > 0)
{
item.BackdropImagePaths = backdropFiles;
}
}
private FileSystemInfo GetSeasonImageFromSeriesFolder(Season season, string imageSuffix)
{
var series = season.Series;
var seriesFolderArgs = series.ResolveArgs;
var seasonNumber = season.IndexNumber;
string filename = null;
FileSystemInfo image;
if (seasonNumber.HasValue)
{
var seasonMarker = seasonNumber.Value == 0
? "-specials"
: seasonNumber.Value.ToString("00", _usCulture);
// Get this one directly from the file system since we have to go up a level
filename = "season" + seasonMarker + imageSuffix;
image = GetImage(series, seriesFolderArgs, filename);
if (image != null && image.Exists)
{
return image;
}
}
var previousFilename = filename;
// Try using the season name
filename = season.Name.ToLower().Replace(" ", string.Empty) + imageSuffix;
if (!string.Equals(previousFilename, filename))
{
image = GetImage(series, seriesFolderArgs, filename);
if (image != null && image.Exists)
{
return image;
}
}
return null;
}
/// <summary>
/// Populates the backdrops from extra fanart.
/// </summary>
/// <param name="args">The args.</param>
/// <param name="backdrops">The backdrops.</param>
private void PopulateBackdropsFromExtraFanart(ItemResolveArgs args, List<string> backdrops)
{
if (!args.IsDirectory)
{
return;
}
if (args.ContainsFileSystemEntryByName("extrafanart"))
{
var path = Path.Combine(args.Path, "extrafanart");
var imageFiles = Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly)
.Where(i =>
{
var extension = Path.GetExtension(i);
if (string.IsNullOrEmpty(extension))
{
return false;
}
return BaseItem.SupportedImageExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
})
.ToList();
backdrops.AddRange(imageFiles);
}
}
/// <summary>
/// Populates the backdrops.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
/// <param name="backdropFiles">The backdrop files.</param>
/// <param name="filename">The filename.</param>
/// <param name="numberedSuffix">The numbered suffix.</param>
private void PopulateBackdrops(BaseItem item, ItemResolveArgs args, List<string> backdropFiles, string filename, string numberedSuffix)
{
var image = GetImage(item, args, filename);
if (image != null)
{
backdropFiles.Add(image.FullName);
}
var unfound = 0;
for (var i = 1; i <= 20; i++)
{
// Backdrop Image
image = GetImage(item, args, numberedSuffix + i);
if (image != null)
{
backdropFiles.Add(image.FullName);
}
else
{
unfound++;
if (unfound >= 3)
{
break;
}
}
}
}
/// <summary>
/// Populates the screenshots.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
private void PopulateScreenshots(BaseItem item, ItemResolveArgs args)
{
// Screenshot Image
var image = GetImage(item, args, "screenshot");
var screenshotFiles = new List<string>();
if (image != null)
{
screenshotFiles.Add(image.FullName);
}
var unfound = 0;
for (var i = 1; i <= 20; i++)
{
// Screenshot Image
image = GetImage(item, args, "screenshot" + i);
if (image != null)
{
screenshotFiles.Add(image.FullName);
}
else
{
unfound++;
if (unfound >= 3)
{
break;
}
}
}
if (screenshotFiles.Count > 0)
{
var hasScreenshots = item as IHasScreenshots;
if (hasScreenshots != null)
{
hasScreenshots.ScreenshotImagePaths = screenshotFiles;
}
}
}
protected FileSystemInfo GetImageFromLocation(string path, string filenameWithoutExtension)
{
try
{
var files = new DirectoryInfo(path)
.EnumerateFiles()
.Where(i =>
{
var fileName = Path.GetFileNameWithoutExtension(i.FullName);
if (!string.Equals(fileName, filenameWithoutExtension, StringComparison.OrdinalIgnoreCase))
{
return false;
}
var ext = i.Extension;
return !string.IsNullOrEmpty(ext) &&
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
})
.ToList();
return BaseItem.SupportedImageExtensions
.Select(ext => files.FirstOrDefault(i => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault(file => file != null);
}
catch (DirectoryNotFoundException)
{
return null;
}
}
}
}

@ -1,100 +0,0 @@
using System.Collections.Generic;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Linq;
namespace MediaBrowser.Providers
{
/// <summary>
/// Provides images for generic types by looking for standard images in the IBN
/// </summary>
public class ImagesByNameProvider : ImageFromMediaLocationProvider
{
public ImagesByNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
: base(logManager, configurationManager, fileSystem)
{
}
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
// Only run for these generic types since we are expensive in file i/o
return item is ICollectionFolder;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get
{
return MetadataProviderPriority.Last;
}
}
/// <summary>
/// Gets the location.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
protected string GetLocation(BaseItem item)
{
var name = FileSystem.GetValidFilename(item.Name);
return Path.Combine(ConfigurationManager.ApplicationPaths.GeneralPath, name);
}
/// <summary>
/// Gets the image.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
/// <param name="filenameWithoutExtension">The filename without extension.</param>
/// <returns>FileSystemInfo.</returns>
protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
{
var location = GetLocation(item);
return GetImageFromLocation(location, filenameWithoutExtension);
}
protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
{
var location = GetLocation(items.First());
try
{
var files = new DirectoryInfo(location)
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
.Where(i =>
{
var ext = i.Extension;
return !string.IsNullOrEmpty(ext) &&
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
})
.ToList();
return GetFileSystemStamp(files);
}
catch (DirectoryNotFoundException)
{
// User doesn't have the folder. No need to log or blow up
return Guid.Empty;
}
}
}
}

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.LiveTv
{
public class ChannelXmlProvider : BaseXmlProvider, ILocalMetadataProvider<LiveTvChannel>
public class ChannelXmlProvider : BaseXmlProvider<LiveTvChannel>
{
private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.LiveTv
_logger = logger;
}
public async Task<MetadataResult<LiveTvChannel>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(LiveTvChannel item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<LiveTvChannel>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var item = new LiveTvChannel();
new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken);
result.HasMetadata = true;
result.Item = item;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(path, "channel.xml"));
return new FileInfo(Path.Combine(info.Path, "channel.xml"));
}
}
}

@ -379,14 +379,19 @@ namespace MediaBrowser.Providers.Manager
if (saveLocally)
{
if (item.IsInMixedFolder && !(item is Episode))
if (item is Episode)
{
path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
}
else if (item.IsInMixedFolder)
{
path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
}
if (string.IsNullOrEmpty(path))
{
path = Path.Combine(item.MetaLocation, filename + extension);
path = Path.Combine(item.ContainingFolderPath, filename + extension);
}
}
@ -468,7 +473,7 @@ namespace MediaBrowser.Providers.Manager
return new[]
{
Path.Combine(item.MetaLocation, "fanart" + extension)
Path.Combine(item.ContainingFolderPath, "fanart" + extension)
};
}
@ -483,8 +488,8 @@ namespace MediaBrowser.Providers.Manager
return new[]
{
Path.Combine(item.MetaLocation, "extrafanart", extraFanartFilename + extension),
Path.Combine(item.MetaLocation, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)
Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension),
Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)
};
}
@ -519,10 +524,10 @@ namespace MediaBrowser.Providers.Manager
if (item is MusicAlbum || item is MusicArtist)
{
return new[] { Path.Combine(item.MetaLocation, "folder" + extension) };
return new[] { Path.Combine(item.ContainingFolderPath, "folder" + extension) };
}
return new[] { Path.Combine(item.MetaLocation, "poster" + extension) };
return new[] { Path.Combine(item.ContainingFolderPath, "poster" + extension) };
}
if (type == ImageType.Banner)
@ -561,7 +566,7 @@ namespace MediaBrowser.Providers.Manager
return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) };
}
return new[] { Path.Combine(item.MetaLocation, "landscape" + extension) };
return new[] { Path.Combine(item.ContainingFolderPath, "landscape" + extension) };
}
// All other paths are the same

@ -93,8 +93,6 @@ namespace MediaBrowser.Providers.Manager
/// <returns>Task.</returns>
private async Task RefreshFromProvider(IHasImages item, IDynamicImageProvider provider, MetadataOptions savedOptions, RefreshResult result, CancellationToken cancellationToken)
{
_logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
try
{
var images = provider.GetSupportedImages(item);
@ -103,6 +101,8 @@ namespace MediaBrowser.Providers.Manager
{
if (!item.HasImage(imageType) && savedOptions.IsEnabled(imageType))
{
_logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false);
if (response.HasImage)

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

@ -48,12 +48,6 @@ namespace MediaBrowser.Providers.Manager
/// <value>The configuration manager.</value>
private IServerConfigurationManager ConfigurationManager { get; set; }
/// <summary>
/// Gets the list of currently registered metadata prvoiders
/// </summary>
/// <value>The metadata providers enumerable.</value>
private BaseMetadataProvider[] MetadataProviders { get; set; }
private IImageProvider[] ImageProviders { get; set; }
private readonly IFileSystem _fileSystem;
@ -86,15 +80,12 @@ namespace MediaBrowser.Providers.Manager
/// <summary>
/// Adds the metadata providers.
/// </summary>
/// <param name="providers">The providers.</param>
/// <param name="imageProviders">The image providers.</param>
/// <param name="metadataServices">The metadata services.</param>
/// <param name="metadataProviders">The metadata providers.</param>
/// <param name="metadataSavers">The metadata savers.</param>
public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers)
public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers)
{
MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
ImageProviders = imageProviders.ToArray();
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
@ -111,174 +102,8 @@ namespace MediaBrowser.Providers.Manager
return service.RefreshMetadata(item, options, cancellationToken);
}
return ((BaseItem)item).RefreshMetadataDirect(cancellationToken, options.ForceSave, options.ReplaceAllMetadata);
}
/// <summary>
/// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <returns>Task{System.Boolean}.</returns>
/// <exception cref="System.ArgumentNullException">item</exception>
public async Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
ItemUpdateType? result = null;
cancellationToken.ThrowIfCancellationRequested();
var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders;
var providerHistories = item.DateLastSaved == default(DateTime) ?
new List<BaseProviderInfo>() :
_providerRepo.GetProviderHistory(item.Id).ToList();
// Run the normal providers sequentially in order of priority
foreach (var provider in MetadataProviders)
{
cancellationToken.ThrowIfCancellationRequested();
if (!ProviderSupportsItem(provider, item))
{
continue;
}
// Skip if internet providers are currently disabled
if (provider.RequiresInternet && !enableInternetProviders)
{
continue;
}
// Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running
// This is the case for the fan art provider which depends on the movie and tv providers having run before them
if (provider.RequiresInternet && item.DontFetchMeta && provider.EnforceDontFetchMetadata)
{
continue;
}
var providerInfo = providerHistories.FirstOrDefault(i => i.ProviderId == provider.Id);
if (providerInfo == null)
{
providerInfo = new BaseProviderInfo
{
ProviderId = provider.Id
};
providerHistories.Add(providerInfo);
}
try
{
if (!force && !provider.NeedsRefresh(item, providerInfo))
{
continue;
}
}
catch (Exception ex)
{
_logger.Error("Error determining NeedsRefresh for {0}", ex, item.Path);
}
var updateType = await FetchAsync(provider, item, providerInfo, force, cancellationToken).ConfigureAwait(false);
if (updateType.HasValue)
{
if (result.HasValue)
{
result = result.Value | updateType.Value;
}
else
{
result = updateType;
}
}
}
if (result.HasValue || force)
{
await _providerRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken);
}
return result;
}
/// <summary>
/// Providers the supports item.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
private bool ProviderSupportsItem(BaseMetadataProvider provider, BaseItem item)
{
try
{
return provider.Supports(item);
}
catch (Exception ex)
{
_logger.ErrorException("{0} failed in Supports for type {1}", ex, provider.GetType().Name, item.GetType().Name);
return false;
}
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="item">The item.</param>
/// <param name="providerInfo">The provider information.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
/// <exception cref="System.ArgumentNullException">item</exception>
private async Task<ItemUpdateType?> FetchAsync(BaseMetadataProvider provider, BaseItem item, BaseProviderInfo providerInfo, bool force, CancellationToken cancellationToken)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
cancellationToken.ThrowIfCancellationRequested();
_logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--");
try
{
var changed = await provider.FetchAsync(item, force, providerInfo, cancellationToken).ConfigureAwait(false);
if (changed)
{
return provider.ItemUpdateType;
}
return null;
}
catch (OperationCanceledException ex)
{
_logger.Debug("{0} canceled for {1}", provider.GetType().Name, item.Name);
// If the outer cancellation token is the one that caused the cancellation, throw it
if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken)
{
throw;
}
return null;
}
catch (Exception ex)
{
_logger.ErrorException("{0} failed refreshing {1} {2}", ex, provider.GetType().Name, item.Name, item.Path ?? string.Empty);
provider.SetLastRefreshed(item, DateTime.UtcNow, providerInfo, ProviderRefreshStatus.Failure);
return ItemUpdateType.Unspecified;
}
_logger.Error("Unable to find a metadata service for item of type " + item.GetType().Name);
return Task.FromResult(true);
}
/// <summary>
@ -328,9 +153,6 @@ namespace MediaBrowser.Providers.Manager
await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
// If this is ever used for something other than metadata we can add a file type param
item.ResolveArgs.AddMetadataFile(path);
}
finally
{
@ -517,6 +339,15 @@ namespace MediaBrowser.Providers.Manager
return false;
}
// If this restriction is ever lifted, movie xml providers will have to be updated to prevent owned items like trailers from reading those files
if (item.IsOwnedItem)
{
if (provider is ILocalMetadataProvider || provider is IRemoteMetadataProvider)
{
return false;
}
}
return true;
}
@ -581,6 +412,7 @@ namespace MediaBrowser.Providers.Manager
list.Add(GetPluginSummary<AdultVideo>());
list.Add(GetPluginSummary<MusicVideo>());
list.Add(GetPluginSummary<Video>());
list.Add(GetPluginSummary<LiveTvChannel>());
list.Add(GetPluginSummary<LiveTvProgram>());
@ -678,6 +510,8 @@ namespace MediaBrowser.Providers.Manager
{
foreach (var saver in _savers.Where(i => i.IsEnabledFor(item, updateType)))
{
_logger.Debug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
var fileSaver = saver as IMetadataFileSaver;
if (fileSaver != null)

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

@ -1,16 +1,13 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.IO;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -19,207 +16,83 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary>
/// Uses ffmpeg to create video images
/// </summary>
public class AudioImageProvider : BaseMetadataProvider
public class AudioImageProvider : IDynamicImageProvider, IHasChangeMonitor
{
/// <summary>
/// The _locks
/// </summary>
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
/// <summary>
/// The _media encoder
/// </summary>
private readonly IIsoManager _isoManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
/// </summary>
/// <param name="logManager">The log manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
public AudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder)
: base(logManager, configurationManager)
public AudioImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config)
{
_isoManager = isoManager;
_mediaEncoder = mediaEncoder;
_config = config;
}
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// The null mount task result
/// </summary>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnVersionChange
{
get
{
return true;
}
}
protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
/// <summary>
/// Gets the provider version.
/// Mounts the iso if needed.
/// </summary>
/// <value>The provider version.</value>
protected override string ProviderVersion
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IIsoMount}.</returns>
protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
{
get
if (item.VideoType == VideoType.Iso)
{
return "1";
return _isoManager.Mount(item.Path, cancellationToken);
}
}
/// <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 Audio;
return NullMountTaskResult;
}
/// <summary>
/// Override this to return the date that should be compared to the last refresh date
/// to determine if this provider should be re-fetched.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>DateTime.</returns>
protected override DateTime CompareDate(BaseItem item)
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
{
return item.DateModified;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.Last; }
}
public override ItemUpdateType ItemUpdateType
{
get
{
return ItemUpdateType.ImageUpdate;
}
return new List<ImageType> { ImageType.Primary };
}
/// <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)
public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
{
item.ValidateImages();
var audio = (Audio)item;
if (string.IsNullOrEmpty(audio.PrimaryImagePath) && audio.HasEmbeddedImage)
// Can't extract if we didn't find a video stream in the file
if (!audio.HasEmbeddedImage)
{
try
{
await CreateImagesForSong(audio, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.ErrorException("Error extracting image for {0}", ex, item.Name);
}
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
return GetVideoImage((Audio)item, cancellationToken);
}
/// <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)
public async Task<DynamicImageResponse> GetVideoImage(Audio item, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var stream = await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, cancellationToken).ConfigureAwait(false);
var path = GetAudioImagePath(item);
if (!File.Exists(path))
return new DynamicImageResponse
{
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);
Format = ImageFormat.Jpg,
HasImage = true,
Stream = stream
};
}
/// <summary>
/// Gets the audio image path.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
private string GetAudioImagePath(Audio item)
public string Name
{
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);
get { return "Embedded Image"; }
}
/// <summary>
/// Gets the audio images data path.
/// </summary>
/// <value>The audio images data path.</value>
public string AudioImagesPath
public bool Supports(IHasImages item)
{
get
{
return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-audio-images");
}
return item.LocationType == LocationType.FileSystem && item is Audio;
}
/// <summary>
/// Gets the lock.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>SemaphoreSlim.</returns>
private SemaphoreSlim GetLock(string filename)
public bool HasChanged(IHasMetadata item, DateTime date)
{
return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
return item.DateModified > date;
}
}
}

@ -1,145 +0,0 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.MediaInfo
{
/// <summary>
/// Provides a base class for extracting media information through ffprobe
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BaseFFProbeProvider<T> : BaseMetadataProvider
where T : BaseItem, IHasMediaStreams
{
protected BaseFFProbeProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer)
: base(logManager, configurationManager)
{
JsonSerializer = jsonSerializer;
MediaEncoder = mediaEncoder;
}
protected readonly IMediaEncoder MediaEncoder;
protected readonly IJsonSerializer JsonSerializer;
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
return item.LocationType == LocationType.FileSystem && item is T;
}
/// <summary>
/// Override this to return the date that should be compared to the last refresh date
/// to determine if this provider should be re-fetched.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>DateTime.</returns>
protected override DateTime CompareDate(BaseItem item)
{
return item.DateModified;
}
/// <summary>
/// The null mount task result
/// </summary>
protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
/// <summary>
/// Gets the provider version.
/// </summary>
/// <value>The provider version.</value>
protected override string ProviderVersion
{
get
{
return "ffmpeg20131209";
}
}
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// </summary>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnVersionChange
{
get
{
return true;
}
}
/// <summary>
/// Gets the media info.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="isoMount">The iso mount.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MediaInfoResult}.</returns>
/// <exception cref="System.ArgumentNullException">inputPath
/// or
/// cache</exception>
protected async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var type = InputType.File;
var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath };
var video = item as Video;
if (video != null)
{
inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
}
return await MediaEncoder.GetMediaInfo(inputPath, type, item is Audio, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Mounts the iso if needed.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IsoMount.</returns>
protected virtual Task<IIsoMount> MountIsoIfNeeded(T item, CancellationToken cancellationToken)
{
return NullMountTaskResult;
}
/// <summary>
/// Called when [pre fetch].
/// </summary>
/// <param name="item">The item.</param>
/// <param name="mount">The mount.</param>
protected virtual void OnPreFetch(T item, IIsoMount mount)
{
}
}
}

@ -1,41 +1,36 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.MediaInfo
{
/// <summary>
/// Extracts audio information using ffprobe
/// </summary>
public class FFProbeAudioInfoProvider : BaseFFProbeProvider<Audio>
class FFProbeAudioInfo
{
private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo;
public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer, IItemRepository itemRepo)
: base(logManager, configurationManager, mediaEncoder, jsonSerializer)
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public FFProbeAudioInfo(IMediaEncoder mediaEncoder, IItemRepository itemRepo)
{
_mediaEncoder = mediaEncoder;
_itemRepo = itemRepo;
}
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken)
where T : Audio
{
var myItem = (Audio)item;
OnPreFetch(myItem, null);
var result = await GetMediaInfo(item, null, cancellationToken).ConfigureAwait(false);
var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@ -43,11 +38,19 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested();
await Fetch(myItem, cancellationToken, result).ConfigureAwait(false);
await Fetch(item, cancellationToken, result).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return ItemUpdateType.MetadataImport;
}
return true;
private async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
const InputType type = InputType.File;
var inputPath = new[] { item.Path };
return await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@ -82,7 +85,7 @@ namespace MediaBrowser.Providers.MediaInfo
// If we got something, parse it
if (!string.IsNullOrEmpty(duration))
{
audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, UsCulture)).Ticks;
audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
}
}
}
@ -277,6 +280,6 @@ namespace MediaBrowser.Providers.MediaInfo
return null;
}
}
}
}

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

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

@ -1,686 +0,0 @@
using DvdLib.Ifo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.MediaInfo
{
/// <summary>
/// Extracts video information using ffprobe
/// </summary>
public class FFProbeVideoInfoProvider : BaseFFProbeProvider<Video>
{
private readonly IItemRepository _itemRepo;
public FFProbeVideoInfoProvider(IIsoManager isoManager, IBlurayExaminer blurayExaminer, IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, ILocalizationManager localization, IItemRepository itemRepo)
: base(logManager, configurationManager, mediaEncoder, jsonSerializer)
{
if (isoManager == null)
{
throw new ArgumentNullException("isoManager");
}
if (blurayExaminer == null)
{
throw new ArgumentNullException("blurayExaminer");
}
_blurayExaminer = blurayExaminer;
_localization = localization;
_itemRepo = itemRepo;
_isoManager = isoManager;
}
/// <summary>
/// Gets or sets the bluray examiner.
/// </summary>
/// <value>The bluray examiner.</value>
private readonly IBlurayExaminer _blurayExaminer;
/// <summary>
/// The _iso manager
/// </summary>
private readonly IIsoManager _isoManager;
private readonly ILocalizationManager _localization;
/// <summary>
/// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
/// </summary>
/// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnFileSystemStampChange
{
get
{
// Need this in case external subtitle files change
return true;
}
}
/// <summary>
/// Gets the filestamp extensions.
/// </summary>
/// <value>The filestamp extensions.</value>
protected override string[] FilestampExtensions
{
get
{
return new[] { ".srt", ".ssa", ".ass" };
}
}
public override MetadataProviderPriority Priority
{
get
{
return MetadataProviderPriority.Second;
}
}
/// <summary>
/// Supports video files and dvd structures
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
if (item.LocationType != LocationType.FileSystem)
{
return false;
}
var video = item as Video;
if (video != null)
{
if (video.VideoType == VideoType.Iso)
{
return _isoManager.CanMount(item.Path);
}
return video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Dvd || video.VideoType == VideoType.BluRay;
}
return false;
}
/// <summary>
/// Called when [pre fetch].
/// </summary>
/// <param name="item">The item.</param>
/// <param name="mount">The mount.</param>
protected override void OnPreFetch(Video item, IIsoMount mount)
{
if (item.VideoType == VideoType.Iso)
{
item.IsoType = DetermineIsoType(mount);
}
if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd))
{
FetchFromDvdLib(item, mount);
}
base.OnPreFetch(item, mount);
}
private void FetchFromDvdLib(Video item, IIsoMount mount)
{
var path = mount == null ? item.Path : mount.MountedPath;
var dvd = new Dvd(path);
item.RunTimeTicks = dvd.Titles.Select(GetRuntime).Max();
var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
uint? titleNumber = null;
if (primaryTitle != null)
{
titleNumber = primaryTitle.TitleNumber;
}
item.PlayableStreamFileNames = GetPrimaryPlaylistVobFiles(item, mount, titleNumber)
.Select(Path.GetFileName)
.ToList();
}
private long GetRuntime(Title title)
{
return title.ProgramChains
.Select(i => (TimeSpan)i.PlaybackTime)
.Select(i => i.Ticks)
.Sum();
}
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
var video = (Video)item;
var isoMount = await MountIsoIfNeeded(video, cancellationToken).ConfigureAwait(false);
try
{
OnPreFetch(video, isoMount);
// If we didn't find any satisfying the min length, just take them all
if (video.VideoType == VideoType.Dvd || (video.IsoType.HasValue && video.IsoType == IsoType.Dvd))
{
if (video.PlayableStreamFileNames.Count == 0)
{
Logger.Error("No playable vobs found in dvd structure, skipping ffprobe.");
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
}
var result = await GetMediaInfo(item, isoMount, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
FFProbeHelpers.NormalizeFFProbeResult(result);
cancellationToken.ThrowIfCancellationRequested();
await Fetch(video, force, providerInfo, cancellationToken, result, isoMount).ConfigureAwait(false);
}
finally
{
if (isoMount != null)
{
isoMount.Dispose();
}
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
/// <summary>
/// Mounts the iso if needed.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IsoMount.</returns>
protected override Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
{
if (item.VideoType == VideoType.Iso)
{
return _isoManager.Mount(item.Path, cancellationToken);
}
return base.MountIsoIfNeeded(item, cancellationToken);
}
/// <summary>
/// Determines the type of the iso.
/// </summary>
/// <param name="isoMount">The iso mount.</param>
/// <returns>System.Nullable{IsoType}.</returns>
private IsoType? DetermineIsoType(IIsoMount isoMount)
{
var folders = Directory.EnumerateDirectories(isoMount.MountedPath).Select(Path.GetFileName).ToList();
if (folders.Contains("video_ts", StringComparer.OrdinalIgnoreCase))
{
return IsoType.Dvd;
}
if (folders.Contains("bdmv", StringComparer.OrdinalIgnoreCase))
{
return IsoType.BluRay;
}
return null;
}
private IEnumerable<string> GetPrimaryPlaylistVobFiles(Video video, IIsoMount isoMount, uint? titleNumber)
{
// min size 300 mb
const long minPlayableSize = 314572800;
var root = isoMount != null ? isoMount.MountedPath : video.Path;
// Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size
// Once we reach a file that is at least the minimum, return all subsequent ones
var allVobs = Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories)
.Where(file => string.Equals(Path.GetExtension(file), ".vob", StringComparison.OrdinalIgnoreCase))
.ToList();
// If we didn't find any satisfying the min length, just take them all
if (allVobs.Count == 0)
{
Logger.Error("No vobs found in dvd structure.");
return new List<string>();
}
if (titleNumber.HasValue)
{
var prefix = string.Format("VTS_0{0}_", titleNumber.Value.ToString(UsCulture));
var vobs = allVobs.Where(i => Path.GetFileName(i).StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
if (vobs.Count > 0)
{
return vobs;
}
Logger.Debug("Could not determine vob file list for {0} using DvdLib. Will scan using file sizes.", video.Path);
}
var files = allVobs
.SkipWhile(f => new FileInfo(f).Length < minPlayableSize)
.ToList();
// If we didn't find any satisfying the min length, just take them all
if (files.Count == 0)
{
Logger.Warn("Vob size filter resulted in zero matches. Taking all vobs.");
files = allVobs;
}
// Assuming they're named "vts_05_01", take all files whose second part matches that of the first file
if (files.Count > 0)
{
var parts = Path.GetFileNameWithoutExtension(files[0]).Split('_');
if (parts.Length == 3)
{
var title = parts[1];
files = files.TakeWhile(f =>
{
var fileParts = Path.GetFileNameWithoutExtension(f).Split('_');
return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase);
}).ToList();
// If this resulted in not getting any vobs, just take them all
if (files.Count == 0)
{
Logger.Warn("Vob filename filter resulted in zero matches. Taking all vobs.");
files = allVobs;
}
}
}
return files;
}
/// <summary>
/// Fetches the specified video.
/// </summary>
/// <param name="video">The video.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="data">The data.</param>
/// <param name="isoMount">The iso mount.</param>
/// <returns>Task.</returns>
protected async Task Fetch(Video video, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount)
{
if (data.format != null)
{
// For dvd's this may not always be accurate, so don't set the runtime if the item already has one
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
{
video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, UsCulture)).Ticks;
}
}
var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;
var chapters = data.Chapters ?? new List<ChapterInfo>();
if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
{
var inputPath = isoMount != null ? isoMount.MountedPath : video.Path;
FetchBdInfo(video, chapters, mediaStreams, inputPath, cancellationToken);
}
AddExternalSubtitles(video, mediaStreams);
FetchWtvInfo(video, force, data);
video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);
if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
{
AddDummyChapters(video, chapters);
}
var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
video.VideoBitRate = videoStream == null ? null : videoStream.BitRate;
video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
await FFMpegManager.Instance.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
var videoFileChanged = CompareDate(video) > providerInfo.LastRefreshed;
await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);
// Only save chapters if forcing, if the video changed, or if there are not already any saved ones
if (force || videoFileChanged || _itemRepo.GetChapter(video.Id, 0) == null)
{
await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Fetches the WTV info.
/// </summary>
/// <param name="video">The video.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="data">The data.</param>
private void FetchWtvInfo(Video video, bool force, InternalMediaInfoResult data)
{
if (data.format == null || data.format.tags == null)
{
return;
}
if (force || video.Genres.Count == 0)
{
if (!video.LockedFields.Contains(MetadataFields.Genres))
{
var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
if (!string.IsNullOrEmpty(genres))
{
video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim())
.ToList();
}
}
}
if (force || string.IsNullOrEmpty(video.Overview))
{
if (!video.LockedFields.Contains(MetadataFields.Overview))
{
var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
if (!string.IsNullOrWhiteSpace(overview))
{
video.Overview = overview;
}
}
}
if (force || string.IsNullOrEmpty(video.OfficialRating))
{
var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
if (!string.IsNullOrWhiteSpace(officialRating))
{
if (!video.LockedFields.Contains(MetadataFields.OfficialRating))
{
video.OfficialRating = officialRating;
}
}
}
if (force || video.People.Count == 0)
{
if (!video.LockedFields.Contains(MetadataFields.Cast))
{
var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");
if (!string.IsNullOrEmpty(people))
{
video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor })
.ToList();
}
}
}
if (force || !video.ProductionYear.HasValue)
{
var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
if (!string.IsNullOrWhiteSpace(year))
{
int val;
if (int.TryParse(year, NumberStyles.Integer, UsCulture, out val))
{
video.ProductionYear = val;
}
}
}
}
/// <summary>
/// Adds the external subtitles.
/// </summary>
/// <param name="video">The video.</param>
/// <param name="currentStreams">The current streams.</param>
private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams)
{
var useParent = !video.ResolveArgs.IsDirectory;
if (useParent && video.Parent == null)
{
return;
}
var fileSystemChildren = useParent
? video.Parent.ResolveArgs.FileSystemChildren
: video.ResolveArgs.FileSystemChildren;
var startIndex = currentStreams.Count;
var streams = new List<MediaStream>();
var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
foreach (var file in fileSystemChildren
.Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && FilestampExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase)))
{
var fullName = file.FullName;
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
// If the subtitle file matches the video file name
if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
{
streams.Add(new MediaStream
{
Index = startIndex++,
Type = MediaStreamType.Subtitle,
IsExternal = true,
Path = fullName,
Codec = Path.GetExtension(fullName).ToLower().TrimStart('.')
});
}
else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
{
// Support xbmc naming conventions - 300.spanish.srt
var language = fileNameWithoutExtension.Split('.').LastOrDefault();
// Try to translate to three character code
// Be flexible and check against both the full and three character versions
var culture = _localization.GetCultures()
.FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
if (culture != null)
{
language = culture.ThreeLetterISOLanguageName;
}
streams.Add(new MediaStream
{
Index = startIndex++,
Type = MediaStreamType.Subtitle,
IsExternal = true,
Path = fullName,
Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'),
Language = language
});
}
}
currentStreams.AddRange(streams);
}
/// <summary>
/// The dummy chapter duration
/// </summary>
private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
/// <summary>
/// Adds the dummy chapters.
/// </summary>
/// <param name="video">The video.</param>
/// <param name="chapters">The chapters.</param>
private void AddDummyChapters(Video video, List<ChapterInfo> chapters)
{
var runtime = video.RunTimeTicks ?? 0;
if (runtime < 0)
{
throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime));
}
if (runtime < _dummyChapterDuration)
{
return;
}
long currentChapterTicks = 0;
var index = 1;
// Limit to 100 chapters just in case there's some incorrect metadata here
while (currentChapterTicks < runtime && index < 100)
{
chapters.Add(new ChapterInfo
{
Name = "Chapter " + index,
StartPositionTicks = currentChapterTicks
});
index++;
currentChapterTicks += _dummyChapterDuration;
}
}
/// <summary>
/// Fetches the bd info.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="chapters">The chapters.</param>
/// <param name="mediaStreams">The media streams.</param>
/// <param name="inputPath">The input path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, string inputPath, CancellationToken cancellationToken)
{
var video = (Video)item;
var result = GetBDInfo(inputPath);
cancellationToken.ThrowIfCancellationRequested();
int? currentHeight = null;
int? currentWidth = null;
int? currentBitRate = null;
var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
// Grab the values that ffprobe recorded
if (videoStream != null)
{
currentBitRate = videoStream.BitRate;
currentWidth = videoStream.Width;
currentHeight = videoStream.Height;
}
// Fill video properties from the BDInfo result
Fetch(video, mediaStreams, result, chapters);
videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
// Use the ffprobe values if these are empty
if (videoStream != null)
{
videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate;
videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width;
videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height;
}
}
/// <summary>
/// Determines whether the specified num is empty.
/// </summary>
/// <param name="num">The num.</param>
/// <returns><c>true</c> if the specified num is empty; otherwise, <c>false</c>.</returns>
private bool IsEmpty(int? num)
{
return !num.HasValue || num.Value == 0;
}
/// <summary>
/// Fills video properties from the VideoStream of the largest playlist
/// </summary>
/// <param name="video">The video.</param>
/// <param name="mediaStreams">The media streams.</param>
/// <param name="stream">The stream.</param>
/// <param name="chapters">The chapters.</param>
private void Fetch(Video video, List<MediaStream> mediaStreams, BlurayDiscInfo stream, List<ChapterInfo> chapters)
{
// Check all input for null/empty/zero
mediaStreams.Clear();
mediaStreams.AddRange(stream.MediaStreams);
video.MainFeaturePlaylistName = stream.PlaylistName;
if (stream.RunTimeTicks.HasValue && stream.RunTimeTicks.Value > 0)
{
video.RunTimeTicks = stream.RunTimeTicks;
}
video.PlayableStreamFileNames = stream.Files.ToList();
if (stream.Chapters != null)
{
chapters.Clear();
chapters.AddRange(stream.Chapters.Select(c => new ChapterInfo
{
StartPositionTicks = TimeSpan.FromSeconds(c).Ticks
}));
}
}
/// <summary>
/// Gets information about the longest playlist on a bdrom
/// </summary>
/// <param name="path">The path.</param>
/// <returns>VideoStream.</returns>
private BlurayDiscInfo GetBDInfo(string path)
{
return _blurayExaminer.GetDiscInfo(path);
}
}
}

@ -12,7 +12,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.MediaInfo
{
public class VideoImageProvider : IDynamicImageProvider
public class VideoImageProvider : IDynamicImageProvider, IHasChangeMonitor
{
private readonly IIsoManager _isoManager;
private readonly IMediaEncoder _mediaEncoder;
@ -25,34 +25,6 @@ namespace MediaBrowser.Providers.MediaInfo
_config = config;
}
/// <summary>
/// Qualifieses for extraction.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
private bool QualifiesForExtraction(Video item)
{
// No support for this
if (item.VideoType == VideoType.HdDvd)
{
return false;
}
// Can't extract from iso's if we weren't unable to determine iso type
if (item.VideoType == VideoType.Iso && !item.IsoType.HasValue)
{
return false;
}
// Can't extract if we didn't find a video stream in the file
if (!item.DefaultVideoStreamIndex.HasValue)
{
return false;
}
return true;
}
/// <summary>
/// The null mount task result
/// </summary>
@ -81,7 +53,27 @@ namespace MediaBrowser.Providers.MediaInfo
public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
{
return GetVideoImage((Video)item, cancellationToken);
var video = (Video)item;
// No support for this
if (video.VideoType == VideoType.HdDvd)
{
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}
// Can't extract from iso's if we weren't unable to determine iso type
if (video.VideoType == VideoType.Iso && !video.IsoType.HasValue)
{
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}
// Can't extract if we didn't find a video stream in the file
if (!video.DefaultVideoStreamIndex.HasValue)
{
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}
return GetVideoImage(video, cancellationToken);
}
public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken)
@ -133,5 +125,10 @@ namespace MediaBrowser.Providers.MediaInfo
return item.LocationType == LocationType.FileSystem && item is Video;
}
public bool HasChanged(IHasMetadata item, DateTime date)
{
return item.DateModified > date;
}
}
}

@ -1,356 +0,0 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Providers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using System.Net;
using MediaBrowser.Providers.Music;
namespace MediaBrowser.Providers.Movies
{
/// <summary>
/// Class FanArtMovieProvider
/// </summary>
class FanartMovieProvider : BaseMetadataProvider
{
/// <summary>
/// Gets the HTTP client.
/// </summary>
/// <value>The HTTP client.</value>
protected IHttpClient HttpClient { get; private set; }
/// <summary>
/// The _provider manager
/// </summary>
private readonly IProviderManager _providerManager;
internal static FanartMovieProvider Current { get; private set; }
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="FanartMovieProvider" /> class.
/// </summary>
/// <param name="httpClient">The HTTP client.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="providerManager">The provider manager.</param>
/// <exception cref="System.ArgumentNullException">httpClient</exception>
public FanartMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
: base(logManager, configurationManager)
{
if (httpClient == null)
{
throw new ArgumentNullException("httpClient");
}
HttpClient = httpClient;
_providerManager = providerManager;
_fileSystem = fileSystem;
Current = this;
}
public override ItemUpdateType ItemUpdateType
{
get
{
return ItemUpdateType.ImageUpdate;
}
}
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// </summary>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnVersionChange
{
get
{
return true;
}
}
/// <summary>
/// Gets the provider version.
/// </summary>
/// <value>The provider version.</value>
protected override string ProviderVersion
{
get
{
return "13";
}
}
public override MetadataProviderPriority Priority
{
get
{
return MetadataProviderPriority.Fifth;
}
}
/// <summary>
/// The fan art base URL
/// </summary>
protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/1";
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
var trailer = item as Trailer;
if (trailer != null)
{
return !trailer.IsLocalTrailer;
}
return item is Movie || item is MusicVideo;
}
/// <summary>
/// Needses the refresh internal.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="providerInfo">The provider info.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
{
return false;
}
return base.NeedsRefreshInternal(item, providerInfo);
}
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
var id = item.GetProviderId(MetadataProviders.Tmdb);
if (!string.IsNullOrEmpty(id))
{
// Process images
var xmlPath = GetFanartXmlPath(id);
var fileInfo = new FileInfo(xmlPath);
return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
}
return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
}
/// <summary>
/// Gets the movie data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <param name="tmdbId">The TMDB id.</param>
/// <returns>System.String.</returns>
internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId)
{
var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId);
return dataPath;
}
/// <summary>
/// Gets the movie data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <returns>System.String.</returns>
internal static string GetMoviesDataPath(IApplicationPaths appPaths)
{
var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies");
return dataPath;
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var movieId = item.GetProviderId(MetadataProviders.Tmdb);
if (!string.IsNullOrEmpty(movieId))
{
var xmlPath = GetFanartXmlPath(movieId);
// Only download the xml if it doesn't already exist. The prescan task will take care of getting updates
if (!File.Exists(xmlPath))
{
await DownloadMovieXml(movieId, cancellationToken).ConfigureAwait(false);
}
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartMovieImageProvider.ProviderName).ConfigureAwait(false);
await FetchImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
public string GetFanartXmlPath(string tmdbId)
{
var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId);
return Path.Combine(movieDataPath, "fanart.xml");
}
/// <summary>
/// Downloads the movie XML.
/// </summary>
/// <param name="tmdbId">The TMDB id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId);
var xmlPath = GetFanartXmlPath(tmdbId);
Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
using (var response = await HttpClient.Get(new HttpRequestOptions
{
Url = url,
ResourcePool = FanartArtistProvider.FanArtResourcePool,
CancellationToken = cancellationToken
}).ConfigureAwait(false))
{
using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
{
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
}
}
}
private readonly Task _cachedTask = Task.FromResult(true);
internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken)
{
var path = GetFanartXmlPath(tmdbId);
var fileInfo = _fileSystem.GetFileSystemInfo(path);
if (fileInfo.Exists)
{
if (ConfigurationManager.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
{
return _cachedTask;
}
}
return DownloadMovieXml(tmdbId, cancellationToken);
}
private async Task FetchImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
if (options.IsEnabled(ImageType.Primary) && !item.HasImage(ImageType.Primary))
{
await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Logo) && !item.HasImage(ImageType.Logo))
{
await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Art) && !item.HasImage(ImageType.Art))
{
await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Disc) && !item.HasImage(ImageType.Disc))
{
await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
{
await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Thumb) && !item.HasImage(ImageType.Thumb))
{
await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
var backdropLimit = options.GetLimit(ImageType.Backdrop);
if (backdropLimit > 0 &&
item.BackdropImagePaths.Count < backdropLimit)
{
foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
{
await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
.ConfigureAwait(false);
if (item.BackdropImagePaths.Count >= backdropLimit) break;
}
}
}
private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
{
foreach (var image in images.Where(i => i.Type == type))
{
try
{
await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
break;
}
catch (HttpException ex)
{
// Sometimes fanart has bad url's in their xml
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
{
continue;
}
break;
}
}
}
}
}

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

@ -1,4 +1,5 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@ -7,6 +8,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Music;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -16,22 +18,27 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Providers.Music;
namespace MediaBrowser.Providers.Movies
{
public class ManualFanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
public class FanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
public ManualFanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
private const string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/1";
internal static FanartMovieImageProvider Current;
public FanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
_config = config;
_httpClient = httpClient;
_fileSystem = fileSystem;
Current = this;
}
public string Name
@ -86,9 +93,9 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(movieId))
{
await FanartMovieProvider.Current.EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false);
await EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false);
var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(movieId);
var xmlPath = GetFanartXmlPath(movieId);
try
{
@ -344,7 +351,7 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(id))
{
// Process images
var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(id);
var xmlPath = GetFanartXmlPath(id);
var fileInfo = new FileInfo(xmlPath);
@ -353,5 +360,85 @@ namespace MediaBrowser.Providers.Movies
return false;
}
/// <summary>
/// Gets the movie data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <param name="tmdbId">The TMDB id.</param>
/// <returns>System.String.</returns>
internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId)
{
var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId);
return dataPath;
}
/// <summary>
/// Gets the movie data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <returns>System.String.</returns>
internal static string GetMoviesDataPath(IApplicationPaths appPaths)
{
var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies");
return dataPath;
}
public string GetFanartXmlPath(string tmdbId)
{
var movieDataPath = GetMovieDataPath(_config.ApplicationPaths, tmdbId);
return Path.Combine(movieDataPath, "fanart.xml");
}
/// <summary>
/// Downloads the movie XML.
/// </summary>
/// <param name="tmdbId">The TMDB id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId);
var xmlPath = GetFanartXmlPath(tmdbId);
Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
using (var response = await _httpClient.Get(new HttpRequestOptions
{
Url = url,
ResourcePool = FanartArtistProvider.FanArtResourcePool,
CancellationToken = cancellationToken
}).ConfigureAwait(false))
{
using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
{
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
}
}
}
private readonly Task _cachedTask = Task.FromResult(true);
internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken)
{
var path = GetFanartXmlPath(tmdbId);
var fileInfo = _fileSystem.GetFileSystemInfo(path);
if (fileInfo.Exists)
{
if (_config.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
{
return _cachedTask;
}
}
return DownloadMovieXml(tmdbId, cancellationToken);
}
}
}

@ -0,0 +1,245 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
public class GenericMovieDbInfo<T>
where T : Video, new()
{
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public GenericMovieDbInfo(ILogger logger, IJsonSerializer jsonSerializer)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
}
public async Task<MetadataResult<T>> GetMetadata(ItemId itemId, CancellationToken cancellationToken)
{
var result = new MetadataResult<T>();
var tmdbId = itemId.GetProviderId(MetadataProviders.Tmdb);
var imdbId = itemId.GetProviderId(MetadataProviders.Imdb);
// Don't search for music video id's because it is very easy to misidentify.
if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId) && typeof(T) != typeof(MusicVideo))
{
tmdbId = await new MovieDbSearch(_logger, _jsonSerializer)
.FindMovieId(itemId, cancellationToken).ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(tmdbId) || !string.IsNullOrEmpty(imdbId))
{
cancellationToken.ThrowIfCancellationRequested();
result.Item = await FetchMovieData(tmdbId, imdbId, itemId.MetadataLanguage, itemId.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
result.HasMetadata = result.Item != null;
}
return result;
}
/// <summary>
/// Fetches the movie data.
/// </summary>
/// <param name="tmdbId">The TMDB identifier.</param>
/// <param name="imdbId">The imdb identifier.</param>
/// <param name="language">The language.</param>
/// <param name="preferredCountryCode">The preferred country code.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{`0}.</returns>
private async Task<T> FetchMovieData(string tmdbId, string imdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
{
string dataFilePath = null;
MovieDbProvider.CompleteMovieData movieInfo = null;
// Id could be ImdbId or TmdbId
if (string.IsNullOrEmpty(tmdbId))
{
movieInfo = await MovieDbProvider.Current.FetchMainResult(imdbId, language, cancellationToken).ConfigureAwait(false);
if (movieInfo == null) return null;
tmdbId = movieInfo.id.ToString(_usCulture);
dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
_jsonSerializer.SerializeToFile(movieInfo, dataFilePath);
}
await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
dataFilePath = dataFilePath ?? MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
movieInfo = movieInfo ?? _jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(dataFilePath);
var item = new T();
ProcessMainInfo(item, preferredCountryCode, movieInfo);
return item;
}
/// <summary>
/// Processes the main info.
/// </summary>
/// <param name="movie">The movie.</param>
/// <param name="preferredCountryCode">The preferred country code.</param>
/// <param name="movieData">The movie data.</param>
private void ProcessMainInfo(T movie, string preferredCountryCode, MovieDbProvider.CompleteMovieData movieData)
{
movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name;
// Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException.
movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null;
movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null;
movie.HomePageUrl = movieData.homepage;
var hasBudget = movie as IHasBudget;
if (hasBudget != null)
{
hasBudget.Budget = movieData.budget;
hasBudget.Revenue = movieData.revenue;
}
if (!string.IsNullOrEmpty(movieData.tagline))
{
var hasTagline = movie as IHasTaglines;
if (hasTagline != null)
{
hasTagline.Taglines.Clear();
hasTagline.AddTagline(movieData.tagline);
}
}
movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture));
movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id);
if (movieData.belongs_to_collection != null)
{
movie.SetProviderId(MetadataProviders.TmdbCollection,
movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture));
var movieItem = movie as Movie;
if (movieItem != null)
{
movieItem.TmdbCollectionName = movieData.belongs_to_collection.name;
}
}
else
{
movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry
}
float rating;
string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture);
if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
{
movie.CommunityRating = rating;
}
movie.VoteCount = movieData.vote_count;
//release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match
if (movieData.releases != null && movieData.releases.countries != null)
{
var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new MovieDbProvider.Country();
var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new MovieDbProvider.Country();
var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new MovieDbProvider.Country();
var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
? ratingPrefix + ourRelease.certification
: !string.IsNullOrEmpty(usRelease.certification)
? usRelease.certification
: !string.IsNullOrEmpty(minimunRelease.certification)
? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification
: null;
}
if (movieData.release_date.Year != 1)
{
//no specific country release info at all
movie.PremiereDate = movieData.release_date.ToUniversalTime();
movie.ProductionYear = movieData.release_date.Year;
}
//studios
if (movieData.production_companies != null)
{
movie.Studios.Clear();
foreach (var studio in movieData.production_companies.Select(c => c.name))
{
movie.AddStudio(studio);
}
}
// genres
// Movies get this from imdb
var genres = movieData.genres ?? new List<MovieDbProvider.GenreItem>();
foreach (var genre in genres.Select(g => g.name))
{
movie.AddGenre(genre);
}
//Actors, Directors, Writers - all in People
//actors come from cast
if (movieData.casts != null && movieData.casts.cast != null)
{
foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
}
//and the rest from crew
if (movieData.casts != null && movieData.casts.crew != null)
{
foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
}
if (movieData.keywords != null && movieData.keywords.keywords != null)
{
var hasTags = movie as IHasKeywords;
if (hasTags != null)
{
hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
}
}
if (movieData.trailers != null && movieData.trailers.youtube != null &&
movieData.trailers.youtube.Count > 0)
{
var hasTrailers = movie as IHasTrailers;
if (hasTrailers != null)
{
hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
{
Url = string.Format("http://www.youtube.com/watch?v={0}", i.source),
IsDirectLink = false,
Name = i.name,
VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
}).ToList();
}
}
}
}
}

@ -15,12 +15,12 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
class ManualMovieDbImageProvider : IRemoteImageProvider, IHasOrder
class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
public ManualMovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
public MovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
@ -168,9 +168,17 @@ namespace MediaBrowser.Providers.Movies
private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer,
CancellationToken cancellationToken)
{
await MovieDbProvider.Current.EnsureMovieInfo(item, cancellationToken).ConfigureAwait(false);
var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
var language = item.GetPreferredMetadataLanguage();
if (string.IsNullOrEmpty(tmdbId))
{
return null;
}
await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
var path = MovieDbProvider.Current.GetDataFilePath(item);
var path = MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
if (!string.IsNullOrEmpty(path))
{

@ -1,247 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Providers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
/// <summary>
/// Class MovieDbImagesProvider
/// </summary>
public class MovieDbImagesProvider : BaseMetadataProvider
{
/// <summary>
/// The _provider manager
/// </summary>
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="MovieDbImagesProvider"/> class.
/// </summary>
/// <param name="logManager">The log manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="providerManager">The provider manager.</param>
public MovieDbImagesProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
: base(logManager, configurationManager)
{
_providerManager = providerManager;
_fileSystem = fileSystem;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.Fourth; }
}
/// <summary>
/// Supports the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
var trailer = item as Trailer;
if (trailer != null)
{
return !trailer.IsLocalTrailer;
}
// Don't support local trailers
return item is Movie || item is MusicVideo;
}
public override ItemUpdateType ItemUpdateType
{
get
{
return ItemUpdateType.ImageUpdate;
}
}
/// <summary>
/// Gets a value indicating whether [requires internet].
/// </summary>
/// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
public override bool RequiresInternet
{
get
{
return true;
}
}
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// </summary>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnVersionChange
{
get
{
return true;
}
}
/// <summary>
/// Gets the provider version.
/// </summary>
/// <value>The provider version.</value>
protected override string ProviderVersion
{
get
{
return "3";
}
}
/// <summary>
/// Needses the refresh internal.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="providerInfo">The provider info.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
{
return false;
}
var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
// Don't refresh if we already have both poster and backdrop and we're not refreshing images
if (item.HasImage(ImageType.Primary) &&
item.BackdropImagePaths.Count >= options.GetLimit(ImageType.Backdrop) &&
!item.LockedFields.Contains(MetadataFields.Images))
{
return false;
}
return base.NeedsRefreshInternal(item, providerInfo);
}
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
var path = MovieDbProvider.Current.GetDataFilePath(item);
if (!string.IsNullOrEmpty(path))
{
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{
return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
}
}
return false;
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbImageProvider.ProviderName).ConfigureAwait(false);
await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
/// <summary>
/// Processes the images.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="images">The images.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>Task.</returns>
private async Task ProcessImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var eligiblePosters = images
.Where(i => i.Type == ImageType.Primary)
.ToList();
// poster
if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var poster = eligiblePosters[0];
var url = poster.Url;
var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
{
Url = url,
CancellationToken = cancellationToken
}).ConfigureAwait(false);
await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Primary, null, cancellationToken)
.ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
var eligibleBackdrops = images
.Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= options.GetMinWidth(ImageType.Backdrop))
.ToList();
var backdropLimit = options.GetLimit(ImageType.Backdrop);
// backdrops - only download if earlier providers didn't find any (fanart)
if (eligibleBackdrops.Count > 0 &&
options.IsEnabled(ImageType.Backdrop) &&
item.BackdropImagePaths.Count < backdropLimit &&
!item.LockedFields.Contains(MetadataFields.Backdrops))
{
for (var i = 0; i < eligibleBackdrops.Count; i++)
{
var url = eligibleBackdrops[i].Url;
var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
{
Url = url,
CancellationToken = cancellationToken
}).ConfigureAwait(false);
await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, null, cancellationToken)
.ConfigureAwait(false);
if (item.BackdropImagePaths.Count >= backdropLimit)
{
break;
}
}
}
}
}
}

@ -5,16 +5,11 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Savers;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
@ -23,114 +18,55 @@ namespace MediaBrowser.Providers.Movies
/// <summary>
/// Class MovieDbProvider
/// </summary>
public class MovieDbProvider : BaseMetadataProvider, IDisposable
public class MovieDbProvider : IRemoteMetadataProvider<Movie>, IDisposable
{
protected static CultureInfo EnUs = new CultureInfo("en-US");
protected readonly IProviderManager ProviderManager;
/// <summary>
/// The movie db
/// </summary>
internal readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1);
internal static MovieDbProvider Current { get; private set; }
/// <summary>
/// Gets the json serializer.
/// </summary>
/// <value>The json serializer.</value>
protected IJsonSerializer JsonSerializer { get; private set; }
/// <summary>
/// Gets the HTTP client.
/// </summary>
/// <value>The HTTP client.</value>
protected IHttpClient HttpClient { get; private set; }
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="MovieDbProvider" /> class.
/// </summary>
/// <param name="logManager">The log manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="httpClient">The HTTP client.</param>
/// <param name="providerManager">The provider manager.</param>
public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager, IFileSystem fileSystem)
: base(logManager, configurationManager)
{
JsonSerializer = jsonSerializer;
HttpClient = httpClient;
ProviderManager = providerManager;
public MovieDbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_logger = logger;
Current = this;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
public Task<MetadataResult<Movie>> GetMetadata(ItemId id, CancellationToken cancellationToken)
{
if (dispose)
{
MovieDbResourcePool.Dispose();
}
return GetItemMetadata<Movie>(id, cancellationToken);
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
public Task<MetadataResult<T>> GetItemMetadata<T>(ItemId id, CancellationToken cancellationToken)
where T : Video, new ()
{
get { return MetadataProviderPriority.Third; }
var movieDb = new GenericMovieDbInfo<T>(_logger, _jsonSerializer);
return movieDb.GetMetadata(id, cancellationToken);
}
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
public string Name
{
var trailer = item as Trailer;
if (trailer != null)
{
return !trailer.IsLocalTrailer;
}
// Don't support local trailers
return item is Movie || item is MusicVideo;
get { return "TheMovieDb"; }
}
/// <summary>
/// Gets a value indicating whether [requires internet].
/// Releases unmanaged and - optionally - managed resources.
/// </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
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
get
if (dispose)
{
return "3";
MovieDbResourcePool.Dispose();
}
}
@ -170,7 +106,7 @@ namespace MediaBrowser.Providers.Movies
}).ConfigureAwait(false))
{
_tmdbSettings = JsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
_tmdbSettings = _jsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
return _tmdbSettings;
}
@ -187,30 +123,6 @@ namespace MediaBrowser.Providers.Movies
internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669";
internal static string AcceptHeader = "application/json,image/*";
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
{
return true;
}
return base.NeedsRefreshInternal(item, providerInfo);
}
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
var path = GetDataFilePath(item);
if (!string.IsNullOrEmpty(path))
{
var fileInfo = new FileInfo(path);
return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
}
return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
}
/// <summary>
/// Gets the movie data path.
/// </summary>
@ -231,121 +143,6 @@ namespace MediaBrowser.Providers.Movies
return dataPath;
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var id = item.GetProviderId(MetadataProviders.Tmdb);
if (string.IsNullOrEmpty(id))
{
id = item.GetProviderId(MetadataProviders.Imdb);
}
// Don't search for music video id's because it is very easy to misidentify.
if (string.IsNullOrEmpty(id) && !(item is MusicVideo))
{
id = await new MovieDbSearch(Logger, JsonSerializer)
.FindMovieId(GetId(item), cancellationToken).ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(id))
{
cancellationToken.ThrowIfCancellationRequested();
await FetchMovieData(item, id, force, cancellationToken).ConfigureAwait(false);
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
private ItemId GetId(IHasMetadata item)
{
return new ItemId
{
MetadataCountryCode = item.GetPreferredMetadataCountryCode(),
MetadataLanguage = item.GetPreferredMetadataLanguage(),
Name = item.Name,
ProviderIds = item.ProviderIds
};
}
/// <summary>
/// Determines whether [has alt meta] [the specified item].
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if [has alt meta] [the specified item]; otherwise, <c>false</c>.</returns>
internal static bool HasAltMeta(BaseItem item)
{
var path = MovieXmlSaver.GetMovieSavePath((Video)item);
if (item.LocationType == LocationType.FileSystem)
{
// If mixed with multiple movies in one folder, resolve args won't have the file system children
return item.ResolveArgs.ContainsMetaFileByName(Path.GetFileName(path)) || File.Exists(path);
}
return false;
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
/// <summary>
/// Fetches the movie data.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="id">The id.</param>
/// <param name="isForcedRefresh">if set to <c>true</c> [is forced refresh].</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>Task.</returns>
private async Task FetchMovieData(BaseItem item, string id, bool isForcedRefresh, CancellationToken cancellationToken)
{
// Id could be ImdbId or TmdbId
var language = item.GetPreferredMetadataLanguage();
var dataFilePath = GetDataFilePath(item);
var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath))
{
var mainResult = await FetchMainResult(id, language, cancellationToken).ConfigureAwait(false);
if (mainResult == null) return;
tmdbId = mainResult.id.ToString(_usCulture);
dataFilePath = GetDataFilePath(tmdbId, language);
var directory = Path.GetDirectoryName(dataFilePath);
Directory.CreateDirectory(directory);
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
}
if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item))
{
dataFilePath = GetDataFilePath(tmdbId, language);
if (!string.IsNullOrEmpty(dataFilePath))
{
var mainResult = JsonSerializer.DeserializeFromFile<CompleteMovieData>(dataFilePath);
ProcessMainInfo(item, mainResult);
}
}
}
/// <summary>
/// Downloads the movie info.
/// </summary>
@ -363,60 +160,49 @@ namespace MediaBrowser.Providers.Movies
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
_jsonSerializer.SerializeToFile(mainResult, dataFilePath);
}
private readonly Task _cachedTask = Task.FromResult(true);
internal Task EnsureMovieInfo(BaseItem item, CancellationToken cancellationToken)
internal Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken)
{
var path = GetDataFilePath(item);
if (string.IsNullOrEmpty(path))
if (string.IsNullOrEmpty(tmdbId))
{
throw new ArgumentNullException("tmdbId");
}
if (string.IsNullOrEmpty(language))
{
return _cachedTask;
throw new ArgumentNullException("language");
}
var path = GetDataFilePath(tmdbId, language);
var fileInfo = _fileSystem.GetFileSystemInfo(path);
if (fileInfo.Exists)
{
// If it's recent or automatic updates are enabled, don't re-download
if ((ConfigurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
if ((_configurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
{
return _cachedTask;
}
}
var id = item.GetProviderId(MetadataProviders.Tmdb);
if (string.IsNullOrEmpty(id))
{
return _cachedTask;
}
return DownloadMovieInfo(id, item.GetPreferredMetadataLanguage(), cancellationToken);
return DownloadMovieInfo(tmdbId, language, cancellationToken);
}
/// <summary>
/// Gets the data file path.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
internal string GetDataFilePath(BaseItem item)
internal string GetDataFilePath(string tmdbId, string preferredLanguage)
{
var id = item.GetProviderId(MetadataProviders.Tmdb);
if (string.IsNullOrEmpty(id))
if (string.IsNullOrEmpty(tmdbId))
{
return null;
throw new ArgumentNullException("tmdbId");
}
if (string.IsNullOrEmpty(preferredLanguage))
{
throw new ArgumentNullException("preferredLanguage");
}
return GetDataFilePath(id, item.GetPreferredMetadataLanguage());
}
private string GetDataFilePath(string tmdbId, string preferredLanguage)
{
var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId);
var path = GetMovieDataPath(_configurationManager.ApplicationPaths, tmdbId);
var filename = string.Format("all-{0}.json",
preferredLanguage ?? string.Empty);
@ -431,7 +217,7 @@ namespace MediaBrowser.Providers.Movies
/// <param name="language">The language.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>Task{CompleteMovieData}.</returns>
private async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken)
internal async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken)
{
var url = string.Format(GetMovieInfo3, id, ApiKey);
@ -461,7 +247,7 @@ namespace MediaBrowser.Providers.Movies
}).ConfigureAwait(false))
{
mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
}
cancellationToken.ThrowIfCancellationRequested();
@ -470,7 +256,7 @@ namespace MediaBrowser.Providers.Movies
{
if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
{
Logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
_logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
url = string.Format(GetMovieInfo3, id, ApiKey) + "&include_image_language=en,null&language=en";
@ -482,12 +268,12 @@ namespace MediaBrowser.Providers.Movies
}).ConfigureAwait(false))
{
mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
}
if (String.IsNullOrEmpty(mainResult.overview))
{
Logger.Error("MovieDbProvider - Unable to find information for (id:" + id + ")");
_logger.Error("MovieDbProvider - Unable to find information for (id:" + id + ")");
return null;
}
}
@ -495,183 +281,6 @@ namespace MediaBrowser.Providers.Movies
return mainResult;
}
/// <summary>
/// Processes the main info.
/// </summary>
/// <param name="movie">The movie.</param>
/// <param name="movieData">The movie data.</param>
private void ProcessMainInfo(BaseItem movie, CompleteMovieData movieData)
{
if (!movie.LockedFields.Contains(MetadataFields.Name))
{
movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name;
}
if (!movie.LockedFields.Contains(MetadataFields.Overview))
{
// Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException.
movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null;
movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null;
}
movie.HomePageUrl = movieData.homepage;
var hasBudget = movie as IHasBudget;
if (hasBudget != null)
{
hasBudget.Budget = movieData.budget;
hasBudget.Revenue = movieData.revenue;
}
if (!string.IsNullOrEmpty(movieData.tagline))
{
var hasTagline = movie as IHasTaglines;
if (hasTagline != null)
{
hasTagline.Taglines.Clear();
hasTagline.AddTagline(movieData.tagline);
}
}
movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture));
movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id);
if (movieData.belongs_to_collection != null)
{
movie.SetProviderId(MetadataProviders.TmdbCollection,
movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture));
var movieItem = movie as Movie;
if (movieItem != null)
{
movieItem.TmdbCollectionName = movieData.belongs_to_collection.name;
}
}
else
{
movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry
}
float rating;
string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture);
// tmdb appears to have unified their numbers to always report "7.3" regardless of country
// so I removed the culture-specific processing here because it was not working for other countries -ebr
// Movies get this from imdb
if (!(movie is Movie) && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
{
movie.CommunityRating = rating;
}
// Movies get this from imdb
if (!(movie is Movie))
{
movie.VoteCount = movieData.vote_count;
}
var preferredCountryCode = movie.GetPreferredMetadataCountryCode();
//release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match
if (movieData.releases != null && movieData.releases.countries != null)
{
var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country();
var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country();
var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country();
if (!movie.LockedFields.Contains(MetadataFields.OfficialRating))
{
var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
? ratingPrefix + ourRelease.certification
: !string.IsNullOrEmpty(usRelease.certification)
? usRelease.certification
: !string.IsNullOrEmpty(minimunRelease.certification)
? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification
: null;
}
}
if (movieData.release_date.Year != 1)
{
//no specific country release info at all
movie.PremiereDate = movieData.release_date.ToUniversalTime();
movie.ProductionYear = movieData.release_date.Year;
}
//studios
if (movieData.production_companies != null && !movie.LockedFields.Contains(MetadataFields.Studios))
{
movie.Studios.Clear();
foreach (var studio in movieData.production_companies.Select(c => c.name))
{
movie.AddStudio(studio);
}
}
// genres
// Movies get this from imdb
var genres = movieData.genres ?? new List<GenreItem>();
if (!movie.LockedFields.Contains(MetadataFields.Genres))
{
// Only grab them if a boxset or there are no genres.
// For movies and trailers we'll use imdb via omdb
// But omdb data is for english users only so fetch if language is not english
if (!(movie is Movie) || movie.Genres.Count == 0 || !string.Equals(movie.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase))
{
movie.Genres.Clear();
foreach (var genre in genres.Select(g => g.name))
{
movie.AddGenre(genre);
}
}
}
if (!movie.LockedFields.Contains(MetadataFields.Cast))
{
movie.People.Clear();
//Actors, Directors, Writers - all in People
//actors come from cast
if (movieData.casts != null && movieData.casts.cast != null)
{
foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
}
//and the rest from crew
if (movieData.casts != null && movieData.casts.crew != null)
{
foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
}
}
if (movieData.keywords != null && movieData.keywords.keywords != null && !movie.LockedFields.Contains(MetadataFields.Keywords))
{
var hasTags = movie as IHasKeywords;
if (hasTags != null)
{
hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
}
}
if (movieData.trailers != null && movieData.trailers.youtube != null &&
movieData.trailers.youtube.Count > 0)
{
var hasTrailers = movie as IHasTrailers;
if (hasTrailers != null)
{
hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
{
Url = string.Format("http://www.youtube.com/watch?v={0}", i.source),
IsDirectLink = false,
Name = i.name,
VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
}).ToList();
}
}
}
private DateTime _lastRequestDate = DateTime.MinValue;
/// <summary>
@ -695,7 +304,7 @@ namespace MediaBrowser.Providers.Movies
_lastRequestDate = DateTime.Now;
return await HttpClient.Get(options).ConfigureAwait(false);
return await _httpClient.Get(options).ConfigureAwait(false);
}
finally
{
@ -897,18 +506,5 @@ namespace MediaBrowser.Providers.Movies
public Keywords keywords { get; set; }
public Trailers trailers { get; set; }
}
internal class TmdbImageSettings
{
public List<string> backdrop_sizes { get; set; }
public string base_url { get; set; }
public List<string> poster_sizes { get; set; }
public List<string> profile_sizes { get; set; }
}
internal class TmdbSettingsResult
{
public TmdbImageSettings images { get; set; }
}
}
}

@ -0,0 +1,43 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
public class MovieMetadataService : MetadataService<Movie, ItemId>
{
private readonly ILibraryManager _libraryManager;
public MovieMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
: base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Merges the specified source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="lockedFields">The locked fields.</param>
/// <param name="replaceData">if set to <c>true</c> [replace data].</param>
/// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
protected override void MergeData(Movie source, Movie target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
protected override Task SaveItem(Movie item, ItemUpdateType reason, CancellationToken cancellationToken)
{
return _libraryManager.UpdateItem(item, reason, cancellationToken);
}
}
}

@ -1,112 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Savers;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
/// <summary>
/// Class MovieProviderFromXml
/// </summary>
public class MovieProviderFromXml : BaseMetadataProvider
{
private readonly IItemRepository _itemRepo;
private readonly IFileSystem _fileSystem;
public MovieProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IItemRepository itemRepo, IFileSystem fileSystem)
: base(logManager, configurationManager)
{
_itemRepo = itemRepo;
_fileSystem = fileSystem;
}
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
if (item.LocationType != LocationType.FileSystem)
{
return false;
}
var trailer = item as Trailer;
if (trailer != null)
{
return !trailer.IsLocalTrailer;
}
// Check parent for null to avoid running this against things like video backdrops
return item is Video && !(item is Episode) && item.Parent != null;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
var savePath = MovieXmlSaver.GetMovieSavePath((Video)item);
var xml = item.ResolveArgs.GetMetaFileByPath(savePath) ?? new FileInfo(savePath);
if (!xml.Exists)
{
return false;
}
return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var video = (Video)item;
var path = MovieXmlSaver.GetMovieSavePath(video);
if (File.Exists(path))
{
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
new MovieXmlParser(Logger).FetchAsync(video, path, cancellationToken);
}
finally
{
XmlParsingResourcePool.Release();
}
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
}
}

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
public class MovieXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Movie>
public class MovieXmlProvider : BaseXmlProvider<Movie>
{
private readonly ILogger _logger;
@ -18,61 +17,34 @@ namespace MediaBrowser.Providers.Movies
_logger = logger;
}
public async Task<MetadataResult<Movie>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(Movie item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<Movie>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
result.Item = new Movie();
new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
result.HasMetadata = true;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return GetXmlFileInfo(path, FileSystem);
return GetXmlFileInfo(info, FileSystem);
}
public static FileInfo GetXmlFileInfo(string path, IFileSystem _fileSystem)
public static FileInfo GetXmlFileInfo(ItemInfo info, IFileSystem fileSystem)
{
var fileInfo = _fileSystem.GetFileSystemInfo(path);
var fileInfo = fileSystem.GetFileSystemInfo(info.Path);
var directoryInfo = fileInfo as DirectoryInfo;
if (directoryInfo == null)
{
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path));
}
var directoryPath = directoryInfo.FullName;
var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml");
var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml");
var file = new FileInfo(specificFile);
return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml"));
return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml"));
}
}
}

@ -1,285 +0,0 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
public class OpenMovieDatabaseProvider : BaseMetadataProvider
{
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
/// <summary>
/// Gets the json serializer.
/// </summary>
/// <value>The json serializer.</value>
protected IJsonSerializer JsonSerializer { get; private set; }
/// <summary>
/// Gets the HTTP client.
/// </summary>
/// <value>The HTTP client.</value>
protected IHttpClient HttpClient { get; private set; }
public OpenMovieDatabaseProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient)
: base(logManager, configurationManager)
{
JsonSerializer = jsonSerializer;
HttpClient = httpClient;
}
/// <summary>
/// Gets the provider version.
/// </summary>
/// <value>The provider version.</value>
protected override string ProviderVersion
{
get
{
return "13";
}
}
/// <summary>
/// Gets a value indicating whether [requires internet].
/// </summary>
/// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
public override bool RequiresInternet
{
get
{
return true;
}
}
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// </summary>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnVersionChange
{
get
{
return true;
}
}
/// <summary>
/// Supports the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
var trailer = item as Trailer;
// Don't support local trailers
if (trailer != null)
{
return !trailer.IsLocalTrailer;
}
return item is Movie || item is MusicVideo || item is Series;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get
{
// Run after moviedb and xml providers
return MetadataProviderPriority.Fifth;
}
}
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
var imdbId = item.GetProviderId(MetadataProviders.Imdb);
if (string.IsNullOrEmpty(imdbId))
{
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
var url = string.Format("http://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
using (var stream = await HttpClient.Get(new HttpRequestOptions
{
Url = url,
ResourcePool = _resourcePool,
CancellationToken = cancellationToken
}).ConfigureAwait(false))
{
var result = JsonSerializer.DeserializeFromStream<RootObject>(stream);
var hasCriticRating = item as IHasCriticRating;
if (hasCriticRating != null)
{
// Seeing some bogus RT data on omdb for series, so filter it out here
// RT doesn't even have tv series
int tomatoMeter;
if (!string.IsNullOrEmpty(result.tomatoMeter)
&& int.TryParse(result.tomatoMeter, NumberStyles.Integer, UsCulture, out tomatoMeter)
&& tomatoMeter >= 0)
{
hasCriticRating.CriticRating = tomatoMeter;
}
if (!string.IsNullOrEmpty(result.tomatoConsensus)
&& !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase))
{
hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus);
}
}
int voteCount;
if (!string.IsNullOrEmpty(result.imdbVotes)
&& int.TryParse(result.imdbVotes, NumberStyles.Number, UsCulture, out voteCount)
&& voteCount >= 0)
{
item.VoteCount = voteCount;
}
float imdbRating;
if (!string.IsNullOrEmpty(result.imdbRating)
&& float.TryParse(result.imdbRating, NumberStyles.Any, UsCulture, out imdbRating)
&& imdbRating >= 0)
{
item.CommunityRating = imdbRating;
}
if (!string.IsNullOrEmpty(result.Website)
&& !string.Equals(result.Website, "n/a", StringComparison.OrdinalIgnoreCase))
{
item.HomePageUrl = result.Website;
}
ParseAdditionalMetadata(item, result);
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
private void ParseAdditionalMetadata(BaseItem item, RootObject result)
{
// Grab series genres because imdb data is better than tvdb. Leave movies alone
// But only do it if english is the preferred language because this data will not be localized
if (!item.LockedFields.Contains(MetadataFields.Genres) &&
ShouldFetchGenres(item) &&
!string.IsNullOrWhiteSpace(result.Genre) &&
!string.Equals(result.Genre, "n/a", StringComparison.OrdinalIgnoreCase))
{
item.Genres.Clear();
foreach (var genre in result.Genre
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(i => i.Trim())
.Where(i => !string.IsNullOrWhiteSpace(i)))
{
item.AddGenre(genre);
}
}
var hasMetascore = item as IHasMetascore;
if (hasMetascore != null)
{
float metascore;
if (!string.IsNullOrEmpty(result.Metascore) && float.TryParse(result.Metascore, NumberStyles.Any, UsCulture, out metascore) && metascore >= 0)
{
hasMetascore.Metascore = metascore;
}
}
var hasAwards = item as IHasAwards;
if (hasAwards != null && !string.IsNullOrEmpty(result.Awards) &&
!string.Equals(result.Awards, "n/a", StringComparison.OrdinalIgnoreCase))
{
hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
}
}
private bool ShouldFetchGenres(BaseItem item)
{
var lang = item.GetPreferredMetadataLanguage();
// The data isn't localized and so can only be used for english users
if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase))
{
return false;
}
// Only fetch if other providers didn't get anything
if (item is Trailer)
{
return item.Genres.Count == 0;
}
return item is Series || item is Movie;
}
protected class RootObject
{
public string Title { get; set; }
public string Year { get; set; }
public string Rated { get; set; }
public string Released { get; set; }
public string Runtime { get; set; }
public string Genre { get; set; }
public string Director { get; set; }
public string Writer { get; set; }
public string Actors { get; set; }
public string Plot { get; set; }
public string Poster { get; set; }
public string imdbRating { get; set; }
public string imdbVotes { get; set; }
public string imdbID { get; set; }
public string Type { get; set; }
public string tomatoMeter { get; set; }
public string tomatoImage { get; set; }
public string tomatoRating { get; set; }
public string tomatoReviews { get; set; }
public string tomatoFresh { get; set; }
public string tomatoRotten { get; set; }
public string tomatoConsensus { get; set; }
public string tomatoUserMeter { get; set; }
public string tomatoUserRating { get; set; }
public string tomatoUserReviews { get; set; }
public string DVD { get; set; }
public string BoxOffice { get; set; }
public string Production { get; set; }
public string Website { get; set; }
public string Response { get; set; }
public string Language { get; set; }
public string Country { get; set; }
public string Awards { get; set; }
public string Metascore { get; set; }
}
}
}

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

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

@ -0,0 +1,30 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
namespace MediaBrowser.Providers.Movies
{
public class TrailerXmlProvider : BaseXmlProvider<Trailer>
{
private readonly ILogger _logger;
public TrailerXmlProvider(IFileSystem fileSystem, ILogger logger)
: base(fileSystem)
{
_logger = logger;
}
protected override void Fetch(Trailer item, string path, CancellationToken cancellationToken)
{
new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(ItemInfo info)
{
return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
}
}
}

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Music
{
class AlbumXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicAlbum>
class AlbumXmlProvider : BaseXmlProvider<MusicAlbum>
{
private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music
_logger = logger;
}
public async Task<MetadataResult<MusicAlbum>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(MusicAlbum item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<MusicAlbum>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var item = new MusicAlbum();
new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken);
result.HasMetadata = true;
result.Item = item;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(path, "album.xml"));
return new FileInfo(Path.Combine(info.Path, "album.xml"));
}
}
}

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Music
{
class ArtistXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicArtist>
class ArtistXmlProvider : BaseXmlProvider<MusicArtist>
{
private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music
_logger = logger;
}
public async Task<MetadataResult<MusicArtist>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(MusicArtist item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<MusicArtist>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var item = new MusicArtist();
new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken);
result.HasMetadata = true;
result.Item = item;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(path, "artist.xml"));
return new FileInfo(Path.Combine(info.Path, "artist.xml"));
}
}
}

@ -0,0 +1,53 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Music
{
public class AudioMetadataService : MetadataService<Audio, ItemId>
{
private readonly ILibraryManager _libraryManager;
public AudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
: base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Merges the specified source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="lockedFields">The locked fields.</param>
/// <param name="replaceData">if set to <c>true</c> [replace data].</param>
/// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
protected override void MergeData(Audio source, Audio target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
if (replaceData || target.Artists.Count == 0)
{
target.Artists = source.Artists;
}
if (replaceData || string.IsNullOrEmpty(target.Album))
{
target.Album = source.Album;
}
}
protected override Task SaveItem(Audio item, ItemUpdateType reason, CancellationToken cancellationToken)
{
return _libraryManager.UpdateItem(item, reason, cancellationToken);
}
}
}

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

@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.People
{
public class PersonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Person>
public class PersonXmlProvider : BaseXmlProvider<Person>
{
private readonly ILogger _logger;
@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.People
_logger = logger;
}
public async Task<MetadataResult<Person>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(Person item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<Person>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var person = new Person();
new BaseItemXmlParser<Person>(_logger).Fetch(person, path, cancellationToken);
result.HasMetadata = true;
result.Item = person;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new BaseItemXmlParser<Person>(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(path, "person.xml"));
return new FileInfo(Path.Combine(info.Path, "person.xml"));
}
}
}

@ -93,7 +93,10 @@ namespace MediaBrowser.Providers
{
if (replaceData || !target.RunTimeTicks.HasValue)
{
target.RunTimeTicks = source.RunTimeTicks;
if (!(target is Audio) && !(target is Video))
{
target.RunTimeTicks = source.RunTimeTicks;
}
}
}
@ -159,6 +162,11 @@ namespace MediaBrowser.Providers
MergeAlbumArtist(source, target, lockedFields, replaceData);
MergeBudget(source, target, lockedFields, replaceData);
MergeMetascore(source, target, lockedFields, replaceData);
MergeCriticRating(source, target, lockedFields, replaceData);
MergeAwards(source, target, lockedFields, replaceData);
MergeTaglines(source, target, lockedFields, replaceData);
MergeTrailers(source, target, lockedFields, replaceData);
if (mergeMetadataSettings)
{
@ -218,5 +226,80 @@ namespace MediaBrowser.Providers
}
}
}
private static void MergeMetascore(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
{
var sourceCast = source as IHasMetascore;
var targetCast = target as IHasMetascore;
if (sourceCast != null && targetCast != null)
{
if (replaceData || !targetCast.Metascore.HasValue)
{
targetCast.Metascore = sourceCast.Metascore;
}
}
}
private static void MergeAwards(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
{
var sourceCast = source as IHasAwards;
var targetCast = target as IHasAwards;
if (sourceCast != null && targetCast != null)
{
if (replaceData || string.IsNullOrEmpty(targetCast.AwardSummary))
{
targetCast.AwardSummary = sourceCast.AwardSummary;
}
}
}
private static void MergeCriticRating(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
{
var sourceCast = source as IHasCriticRating;
var targetCast = target as IHasCriticRating;
if (sourceCast != null && targetCast != null)
{
if (replaceData || !targetCast.CriticRating.HasValue)
{
targetCast.CriticRating = sourceCast.CriticRating;
}
if (replaceData || string.IsNullOrEmpty(targetCast.CriticRatingSummary))
{
targetCast.CriticRatingSummary = sourceCast.CriticRatingSummary;
}
}
}
private static void MergeTaglines(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
{
var sourceCast = source as IHasTaglines;
var targetCast = target as IHasTaglines;
if (sourceCast != null && targetCast != null)
{
if (replaceData || targetCast.Taglines.Count == 0)
{
targetCast.Taglines = sourceCast.Taglines;
}
}
}
private static void MergeTrailers(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
{
var sourceCast = source as IHasTrailers;
var targetCast = target as IHasTrailers;
if (sourceCast != null && targetCast != null)
{
if (replaceData || targetCast.RemoteTrailers.Count == 0)
{
targetCast.RemoteTrailers = sourceCast.RemoteTrailers;
}
}
}
}
}

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

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

@ -50,16 +50,9 @@ namespace MediaBrowser.Providers.Savers
// If new metadata has been downloaded and save local is on
if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded))
{
var trailer = item as Trailer;
// Don't support local trailers
if (trailer != null)
{
return !trailer.IsLocalTrailer;
}
var video = item as Video;
// Check parent for null to avoid running this against things like video backdrops
return video != null && !(item is Episode) && video.Parent != null;
return video != null && !(item is Episode) && !video.IsOwnedItem;
}
return false;
@ -145,7 +138,7 @@ namespace MediaBrowser.Providers.Savers
return Path.ChangeExtension(item.Path, ".xml");
}
return Path.Combine(item.MetaLocation, "movie.xml");
return Path.Combine(item.ContainingFolderPath, "movie.xml");
}
}
}

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

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

@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
/// <summary>
/// Class SeriesProviderFromXml
/// </summary>
public class SeasonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Season>
public class SeasonXmlProvider : BaseXmlProvider<Season>
{
private readonly ILogger _logger;
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV
_logger = logger;
}
public async Task<MetadataResult<Season>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(Season item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<Season>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var person = new Season();
new BaseItemXmlParser<Season>(_logger).Fetch(person, path, cancellationToken);
result.HasMetadata = true;
result.Item = person;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new BaseItemXmlParser<Season>(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(path, "season.xml"));
return new FileInfo(Path.Combine(info.Path, "season.xml"));
}
}
}

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

@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
/// <summary>
/// Class SeriesProviderFromXml
/// </summary>
public class SeriesXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Series>
public class SeriesXmlProvider : BaseXmlProvider<Series>
{
private readonly ILogger _logger;
@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV
_logger = logger;
}
public async Task<MetadataResult<Series>> GetMetadata(string path, CancellationToken cancellationToken)
protected override void Fetch(Series item, string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<Series>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var person = new Series();
new SeriesXmlParser(_logger).Fetch(person, path, cancellationToken);
result.HasMetadata = true;
result.Item = person;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
new SeriesXmlParser(_logger).Fetch(item, path, cancellationToken);
}
protected override FileInfo GetXmlFile(string path)
protected override FileInfo GetXmlFile(ItemInfo info)
{
return new FileInfo(Path.Combine(path, "series.xml"));
return new FileInfo(Path.Combine(info.Path, "series.xml"));
}
}
}

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

@ -0,0 +1,52 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Videos
{
public class VideoMetadataService : MetadataService<Video, ItemId>
{
private readonly ILibraryManager _libraryManager;
public VideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
: base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Merges the specified source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="lockedFields">The locked fields.</param>
/// <param name="replaceData">if set to <c>true</c> [replace data].</param>
/// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
protected override void MergeData(Video source, Video target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
protected override Task SaveItem(Video item, ItemUpdateType reason, CancellationToken cancellationToken)
{
return _libraryManager.UpdateItem(item, reason, cancellationToken);
}
public override int Order
{
get
{
// Make sure the type-specific services get picked first
return 10;
}
}
}
}

@ -1,57 +0,0 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers
{
public class VirtualItemImageValidator : BaseMetadataProvider
{
public VirtualItemImageValidator(ILogManager logManager, IServerConfigurationManager configurationManager)
: base(logManager, configurationManager)
{
}
public override bool Supports(BaseItem item)
{
var locationType = item.LocationType;
// The regular provider will get virtual seasons
if (item.LocationType == LocationType.Virtual)
{
var season = item as Season;
if (season != null)
{
var series = season.Series;
if (series != null && series.LocationType == LocationType.FileSystem)
{
return false;
}
}
}
return locationType == LocationType.Virtual ||
locationType == LocationType.Remote;
}
public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
item.ValidateImages();
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return TrueTaskResult;
}
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
}
}

@ -0,0 +1,43 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Years
{
public class YearMetadataService : MetadataService<Year, ItemId>
{
private readonly ILibraryManager _libraryManager;
public YearMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
: base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Merges the specified source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="lockedFields">The locked fields.</param>
/// <param name="replaceData">if set to <c>true</c> [replace data].</param>
/// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
protected override void MergeData(Year source, Year target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
protected override Task SaveItem(Year item, ItemUpdateType reason, CancellationToken cancellationToken)
{
return _libraryManager.UpdateItem(item, reason, cancellationToken);
}
}
}

@ -273,21 +273,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
{
if (item.LocationType == LocationType.FileSystem)
{
return collections.Where(i =>
{
try
{
return i.LocationType == LocationType.FileSystem &&
i.PhysicalLocations.Contains(item.Path);
}
catch (Exception ex)
{
_logger.ErrorException("Error getting ResolveArgs for {0}", ex, i.Path);
return false;
}
}).Cast<T>();
return collections.Where(i => i.LocationType == LocationType.FileSystem &&
i.PhysicalLocations.Contains(item.Path)).Cast<T>();
}
}

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

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

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

@ -184,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
private void SetProviderIdFromPath(Video item)
{
//we need to only look at the name of this actual item (not parents)
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.MetaLocation);
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
var id = justName.GetAttributeValue("tmdbid");

@ -47,17 +47,5 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
return null;
}
/// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
protected override void SetInitialItemValues(Season item, ItemResolveArgs args)
{
base.SetInitialItemValues(item, args);
Season.AddMetadataFiles(args);
}
}
}

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

@ -326,13 +326,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.Name = channelInfo.Name;
}
// Set this now so we don't cause additional file system access during provider executions
item.ResetResolveArgs(fileInfo);
await item.RefreshMetadata(new MetadataRefreshOptions
{
ForceSave = isNew,
ResetResolveArgs = false
ForceSave = isNew
}, cancellationToken);
@ -391,8 +387,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
await item.RefreshMetadata(new MetadataRefreshOptions
{
ForceSave = isNew,
ResetResolveArgs = false
ForceSave = isNew
}, cancellationToken);
@ -448,8 +443,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
await item.RefreshMetadata(new MetadataRefreshOptions
{
ForceSave = isNew,
ResetResolveArgs = false
ForceSave = isNew
}, cancellationToken);

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

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

Loading…
Cancel
Save