Added initial implementation of the metadata provider network, along with the first few providers

This commit is contained in:
LukePulverenti Luke Pulverenti luke pulverenti 2012-08-19 11:58:35 -04:00
parent 803ce0968e
commit d794eecec4
18 changed files with 492 additions and 284 deletions

View File

@ -12,6 +12,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Progress;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Kernel
{
@ -51,7 +52,9 @@ namespace MediaBrowser.Common.Kernel
ApplicationPaths = new TApplicationPathsType();
}
public virtual void Init(IProgress<TaskProgress> progress)
public virtual Task Init(IProgress<TaskProgress> progress)
{
return Task.Run(() =>
{
ReloadLogger();
@ -63,6 +66,7 @@ namespace MediaBrowser.Common.Kernel
progress.Report(new TaskProgress() { Description = "Loading Plugins", PercentComplete = 10 });
ReloadComposableParts();
});
}
/// <summary>

View File

@ -151,6 +151,50 @@ namespace MediaBrowser.Controller.Configuration
}
}
private string _CacheDirectory = null;
/// <summary>
/// Gets the folder path to the cache directory
/// </summary>
public string CacheDirectory
{
get
{
if (_CacheDirectory == null)
{
_CacheDirectory = Path.Combine(Kernel.Instance.ApplicationPaths.ProgramDataPath, "cache");
if (!Directory.Exists(_CacheDirectory))
{
Directory.CreateDirectory(_CacheDirectory);
}
}
return _CacheDirectory;
}
}
private string _FFProbeAudioCacheDirectory = null;
/// <summary>
/// Gets the folder path to the ffprobe audio cache directory
/// </summary>
public string FFProbeAudioCacheDirectory
{
get
{
if (_FFProbeAudioCacheDirectory == null)
{
_FFProbeAudioCacheDirectory = Path.Combine(Kernel.Instance.ApplicationPaths.CacheDirectory, "ffprobe-audio");
if (!Directory.Exists(_FFProbeAudioCacheDirectory))
{
Directory.CreateDirectory(_FFProbeAudioCacheDirectory);
}
}
return _FFProbeAudioCacheDirectory;
}
}
private string _FFMpegDirectory = null;
/// <summary>
/// Gets the folder path to ffmpeg
@ -221,7 +265,7 @@ namespace MediaBrowser.Controller.Configuration
_FFProbePath = Path.Combine(FFMpegDirectory, filename);
// Always re-extract the first time to handle new versions
/*// Always re-extract the first time to handle new versions
if (File.Exists(_FFProbePath))
{
File.Delete(_FFProbePath);
@ -234,7 +278,7 @@ namespace MediaBrowser.Controller.Configuration
{
stream.CopyTo(fileStream);
}
}
}*/
}
return _FFProbePath;

View File

@ -1,13 +1,41 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.FFMpeg
{
/// <summary>
/// Runs FFProbe against a media file and returns metadata.
/// </summary>
public static class FFProbe
{
public static FFProbeResult Run(string path)
public async static Task<FFProbeResult> Run(Audio item, string outputCachePath)
{
// Use try catch to avoid having to use File.Exists
try
{
using (FileStream stream = File.OpenRead(outputCachePath))
{
return JsonSerializer.DeserializeFromStream<FFProbeResult>(stream);
}
}
catch (FileNotFoundException)
{
}
await Run(item.Path, outputCachePath);
using (FileStream stream = File.OpenRead(outputCachePath))
{
return JsonSerializer.DeserializeFromStream<FFProbeResult>(stream);
}
}
private async static Task Run(string input, string output)
{
ProcessStartInfo startInfo = new ProcessStartInfo();
@ -21,13 +49,15 @@ namespace MediaBrowser.Controller.FFMpeg
startInfo.FileName = Kernel.Instance.ApplicationPaths.FFProbePath;
startInfo.WorkingDirectory = Kernel.Instance.ApplicationPaths.FFMpegDirectory;
startInfo.Arguments = string.Format("\"{0}\" -v quiet -print_format json -show_streams -show_format", path);
startInfo.Arguments = string.Format("\"{0}\" -v quiet -print_format json -show_streams -show_format", input);
Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments);
//Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments);
Process process = new Process();
process.StartInfo = startInfo;
FileStream stream = new FileStream(output, FileMode.Create);
try
{
process.Start();
@ -36,18 +66,23 @@ namespace MediaBrowser.Controller.FFMpeg
// If we ever decide to disable the ffmpeg log then you must uncomment the below line.
process.BeginErrorReadLine();
FFProbeResult result = JsonSerializer.DeserializeFromStream<FFProbeResult>(process.StandardOutput.BaseStream);
await process.StandardOutput.BaseStream.CopyToAsync(stream);
process.WaitForExit();
Logger.LogInfo("FFMpeg exited with code " + process.ExitCode);
stream.Dispose();
return result;
if (process.ExitCode != 0)
{
Logger.LogInfo("FFProbe exited with code {0} for {1}", process.ExitCode, input);
}
}
catch (Exception ex)
{
Logger.LogException(ex);
stream.Dispose();
// Hate having to do this
try
{
@ -56,8 +91,7 @@ namespace MediaBrowser.Controller.FFMpeg
catch
{
}
return null;
File.Delete(output);
}
finally
{

View File

@ -13,6 +13,11 @@ namespace MediaBrowser.Controller.FFMpeg
public MediaFormat format { get; set; }
}
/// <summary>
/// Represents a stream within the output
/// A number of properties are commented out to improve deserialization performance
/// Enable them as needed.
/// </summary>
public class MediaStream
{
public int index { get; set; }
@ -20,28 +25,28 @@ namespace MediaBrowser.Controller.FFMpeg
public string codec_name { get; set; }
public string codec_long_name { get; set; }
public string codec_type { get; set; }
public string codec_time_base { get; set; }
public string codec_tag { get; set; }
public string codec_tag_string { get; set; }
public string sample_fmt { get; set; }
//public string codec_time_base { get; set; }
//public string codec_tag { get; set; }
//public string codec_tag_string { get; set; }
//public string sample_fmt { get; set; }
public string sample_rate { get; set; }
public int channels { get; set; }
public int bits_per_sample { get; set; }
public string r_frame_rate { get; set; }
public string avg_frame_rate { get; set; }
public string time_base { get; set; }
public string start_time { get; set; }
//public int bits_per_sample { get; set; }
//public string r_frame_rate { get; set; }
//public string avg_frame_rate { get; set; }
//public string time_base { get; set; }
//public string start_time { get; set; }
public string duration { get; set; }
public string bit_rate { get; set; }
public int width { get; set; }
public int height { get; set; }
public int has_b_frames { get; set; }
public string sample_aspect_ratio { get; set; }
public string display_aspect_ratio { get; set; }
public string pix_fmt { get; set; }
public int level { get; set; }
public MediaTags tags { get; set; }
//public int has_b_frames { get; set; }
//public string sample_aspect_ratio { get; set; }
//public string display_aspect_ratio { get; set; }
//public string pix_fmt { get; set; }
//public int level { get; set; }
public Dictionary<string,string> tags { get; set; }
}
public class MediaFormat
@ -54,23 +59,6 @@ namespace MediaBrowser.Controller.FFMpeg
public string duration { get; set; }
public string size { get; set; }
public string bit_rate { get; set; }
public MediaTags tags { get; set; }
}
public class MediaTags
{
public string title { get; set; }
public string comment { get; set; }
public string artist { get; set; }
public string album { get; set; }
public string album_artist { get; set; }
public string composer { get; set; }
public string copyright { get; set; }
public string publisher { get; set; }
public string track { get; set; }
public string disc { get; set; }
public string genre { get; set; }
public string date { get; set; }
public string language { get; set; }
public Dictionary<string, string> tags { get; set; }
}
}

View File

@ -88,7 +88,7 @@ namespace MediaBrowser.Controller.IO
private void ProcessPathChanges(IEnumerable<string> paths)
{
List<BaseItem> itemsToRefresh = new List<BaseItem>();
/*List<BaseItem> itemsToRefresh = new List<BaseItem>();
foreach (BaseItem item in paths.Select(p => GetAffectedBaseItem(p)))
{
@ -113,7 +113,7 @@ namespace MediaBrowser.Controller.IO
{
Kernel.Instance.ReloadItem(itemsToRefresh[i]);
});
}
}*/
}
private BaseItem GetAffectedBaseItem(string path)

View File

@ -5,11 +5,13 @@ using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Kernel;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Progress;
@ -35,6 +37,12 @@ namespace MediaBrowser.Controller
}
}
/// <summary>
/// Gets the list of currently registered metadata prvoiders
/// </summary>
[ImportMany(typeof(BaseMetadataProvider))]
public IEnumerable<BaseMetadataProvider> MetadataProviders { get; private set; }
/// <summary>
/// Gets the list of currently registered entity resolvers
/// </summary>
@ -56,35 +64,63 @@ namespace MediaBrowser.Controller
ItemController.BeginResolvePath += ItemController_BeginResolvePath;
}
public override void Init(IProgress<TaskProgress> progress)
public async override Task Init(IProgress<TaskProgress> progress)
{
base.Init(progress);
await base.Init(progress);
progress.Report(new TaskProgress() { Description = "Loading Users", PercentComplete = 15 });
ReloadUsers();
progress.Report(new TaskProgress() { Description = "Loading Media Library", PercentComplete = 20 });
ReloadRoot();
await ReloadRoot();
progress.Report(new TaskProgress() { Description = "Loading Complete", PercentComplete = 100 });
}
protected override void OnComposablePartsLoaded()
{
List<IBaseItemResolver> resolvers = EntityResolvers.ToList();
// Add the internal resolvers
resolvers.Add(new VideoResolver());
resolvers.Add(new AudioResolver());
resolvers.Add(new VirtualFolderResolver());
resolvers.Add(new FolderResolver());
EntityResolvers = resolvers;
AddCoreResolvers();
AddCoreProviders();
// The base class will start up all the plugins
base.OnComposablePartsLoaded();
}
private void AddCoreResolvers()
{
List<IBaseItemResolver> list = EntityResolvers.ToList();
// Add the core resolvers
list.AddRange(new IBaseItemResolver[]{
new AudioResolver(),
new VideoResolver(),
new VirtualFolderResolver(),
new FolderResolver()
});
EntityResolvers = list;
}
private void AddCoreProviders()
{
List<BaseMetadataProvider> list = MetadataProviders.ToList();
// Add the core resolvers
list.InsertRange(0, new BaseMetadataProvider[]{
new ImageFromMediaLocationProvider(),
new LocalTrailerProvider(),
new AudioInfoProvider(),
new FolderProviderFromXml()
});
MetadataProviders = list;
Parallel.ForEach(MetadataProviders, provider =>
{
provider.Init();
});
}
/// <summary>
/// Fires when a path is about to be resolved, but before child folders and files
/// have been collected from the file system.
@ -129,7 +165,7 @@ namespace MediaBrowser.Controller
/// <summary>
/// Reloads the root media folder
/// </summary>
public void ReloadRoot()
public async Task ReloadRoot()
{
if (!Directory.Exists(MediaRootFolderPath))
{
@ -138,7 +174,7 @@ namespace MediaBrowser.Controller
DirectoryWatchers.Stop();
RootFolder = ItemController.GetItem(MediaRootFolderPath) as Folder;
RootFolder = await ItemController.GetItem(null, MediaRootFolderPath) as Folder;
DirectoryWatchers.Start();
}
@ -152,23 +188,23 @@ namespace MediaBrowser.Controller
}
}
public void ReloadItem(BaseItem item)
public async Task ReloadItem(BaseItem item)
{
Folder folder = item as Folder;
if (folder != null && folder.IsRoot)
{
ReloadRoot();
await ReloadRoot();
}
else
{
if (!Directory.Exists(item.Path) && !File.Exists(item.Path))
{
ReloadItem(item.Parent);
await ReloadItem(item.Parent);
return;
}
BaseItem newItem = ItemController.GetItem(item.Parent, item.Path);
BaseItem newItem = await ItemController.GetItem(item.Parent, item.Path);
List<BaseItem> children = item.Parent.Children.ToList();

View File

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Resolvers;
@ -58,67 +56,12 @@ namespace MediaBrowser.Controller.Library
}
#endregion
#region BaseItem Events
/// <summary>
/// Called when an item is being created.
/// This should be used to fill item values, such as metadata
/// </summary>
public event EventHandler<GenericItemEventArgs<BaseItem>> BaseItemCreating;
/// <summary>
/// Called when an item has been created.
/// This should be used to process or modify item values.
/// </summary>
public event EventHandler<GenericItemEventArgs<BaseItem>> BaseItemCreated;
#endregion
/// <summary>
/// Called when an item has been created
/// </summary>
private void OnBaseItemCreated(BaseItem item, Folder parent)
{
GenericItemEventArgs<BaseItem> args = new GenericItemEventArgs<BaseItem> { Item = item };
if (BaseItemCreating != null)
{
BaseItemCreating(this, args);
}
if (BaseItemCreated != null)
{
BaseItemCreated(this, args);
}
}
private void FireCreateEventsRecursive(Folder folder, Folder parent)
{
OnBaseItemCreated(folder, parent);
int count = folder.Children.Length;
Parallel.For(0, count, i =>
{
BaseItem item = folder.Children[i];
Folder childFolder = item as Folder;
if (childFolder != null)
{
FireCreateEventsRecursive(childFolder, folder);
}
else
{
OnBaseItemCreated(item, folder);
}
});
}
private BaseItem ResolveItem(ItemResolveEventArgs args)
private async Task<BaseItem> ResolveItem(ItemResolveEventArgs args)
{
// If that didn't pan out, try the slow ones
foreach (IBaseItemResolver resolver in Kernel.Instance.EntityResolvers)
{
var item = resolver.ResolvePath(args);
var item = await resolver.ResolvePath(args);
if (item != null)
{
@ -132,39 +75,15 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Resolves a path into a BaseItem
/// </summary>
public BaseItem GetItem(string path)
public async Task<BaseItem> GetItem(Folder parent, string path)
{
return GetItem(null, path);
return await GetItemInternal(parent, path, File.GetAttributes(path));
}
/// <summary>
/// Resolves a path into a BaseItem
/// </summary>
public BaseItem GetItem(Folder parent, string path)
{
BaseItem item = GetItemInternal(parent, path, File.GetAttributes(path));
if (item != null)
{
var folder = item as Folder;
if (folder != null)
{
FireCreateEventsRecursive(folder, parent);
}
else
{
OnBaseItemCreated(item, parent);
}
}
return item;
}
/// <summary>
/// Resolves a path into a BaseItem
/// </summary>
private BaseItem GetItemInternal(Folder parent, string path, FileAttributes attributes)
private async Task<BaseItem> GetItemInternal(Folder parent, string path, FileAttributes attributes)
{
if (!OnPreBeginResolvePath(parent, path, attributes))
{
@ -201,14 +120,14 @@ namespace MediaBrowser.Controller.Library
return null;
}
BaseItem item = ResolveItem(args);
BaseItem item = await ResolveItem(args);
var folder = item as Folder;
if (folder != null)
{
// If it's a folder look for child entities
AttachChildren(folder, fileSystemChildren);
await AttachChildren(folder, fileSystemChildren);
}
return item;
@ -217,30 +136,25 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Finds child BaseItems for a given Folder
/// </summary>
private void AttachChildren(Folder folder, IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren)
private async Task AttachChildren(Folder folder, IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren)
{
List<BaseItem> baseItemChildren = new List<BaseItem>();
KeyValuePair<string, FileAttributes>[] fileSystemChildrenArray = fileSystemChildren.ToArray();
int count = fileSystemChildren.Count();
int count = fileSystemChildrenArray.Length;
// Resolve the child folder paths into entities
Parallel.For(0, count, i =>
Task<BaseItem>[] tasks = new Task<BaseItem>[count];
for (int i = 0; i < count; i++)
{
KeyValuePair<string, FileAttributes> child = fileSystemChildren.ElementAt(i);
var child = fileSystemChildrenArray[i];
BaseItem item = GetItemInternal(folder, child.Key, child.Value);
if (item != null)
{
lock (baseItemChildren)
{
baseItemChildren.Add(item);
tasks[i] = GetItemInternal(folder, child.Key, child.Value);
}
}
});
BaseItem[] baseItemChildren = await Task<BaseItem>.WhenAll(tasks);
// Sort them
folder.Children = baseItemChildren.OrderBy(f =>
folder.Children = baseItemChildren.Where(i => i != null).OrderBy(f =>
{
return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;

View File

@ -59,6 +59,11 @@
<Compile Include="Library\ItemController.cs" />
<Compile Include="Kernel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\BaseMetadataProvider.cs" />
<Compile Include="Providers\AudioInfoProvider.cs" />
<Compile Include="Providers\FolderProviderFromXml.cs" />
<Compile Include="Providers\ImageFromMediaLocationProvider.cs" />
<Compile Include="Providers\LocalTrailerProvider.cs" />
<Compile Include="Resolvers\AudioResolver.cs" />
<Compile Include="Resolvers\BaseItemResolver.cs" />
<Compile Include="Resolvers\FolderResolver.cs" />

View File

@ -0,0 +1,86 @@
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.FFMpeg;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
[Export(typeof(BaseMetadataProvider))]
public class AudioInfoProvider : BaseMetadataProvider
{
public override bool Supports(BaseItem item)
{
return item is Audio;
}
public async override Task Fetch(BaseItem item, ItemResolveEventArgs args)
{
Audio audio = item as Audio;
string outputDirectory = Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, item.Id.ToString().Substring(0, 1));
string outputPath = Path.Combine(outputDirectory, item.Id + "-" + item.DateModified.Ticks + ".js");
FFProbeResult data = await FFProbe.Run(audio, outputPath);
MediaStream stream = data.streams.FirstOrDefault(s => s.codec_type.Equals("audio", StringComparison.OrdinalIgnoreCase));
audio.Channels = stream.channels;
string bitrate = null;
if (!string.IsNullOrEmpty(stream.sample_rate))
{
audio.SampleRate = int.Parse(stream.sample_rate);
bitrate = stream.bit_rate;
}
if (string.IsNullOrEmpty(bitrate))
{
bitrate = data.format.bit_rate;
}
if (!string.IsNullOrEmpty(bitrate))
{
audio.BitRate = int.Parse(bitrate);
}
}
private string GetOutputCachePath(BaseItem item)
{
string outputDirectory = Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, item.Id.ToString().Substring(0, 1));
return Path.Combine(outputDirectory, item.Id + "-" + item.DateModified.Ticks + ".js");
}
public override void Init()
{
base.Init();
for (int i = 0; i <= 9; i++)
{
EnsureDirectory(Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, i.ToString()));
}
EnsureDirectory(Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, "a"));
EnsureDirectory(Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, "b"));
EnsureDirectory(Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, "c"));
EnsureDirectory(Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, "d"));
EnsureDirectory(Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, "e"));
EnsureDirectory(Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, "f"));
}
private void EnsureDirectory(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
}
}

View File

@ -0,0 +1,23 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Events;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
public abstract class BaseMetadataProvider
{
/// <summary>
/// If the provider needs any startup routines, add them here
/// </summary>
public virtual void Init()
{
}
public virtual bool Supports(BaseItem item)
{
return true;
}
public abstract Task Fetch(BaseItem item, ItemResolveEventArgs args);
}
}

View File

@ -0,0 +1,30 @@
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Xml;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
[Export(typeof(BaseMetadataProvider))]
public class FolderProviderFromXml : BaseMetadataProvider
{
public override bool Supports(BaseItem item)
{
return item is Folder;
}
public override Task Fetch(BaseItem item, ItemResolveEventArgs args)
{
return Task.Run(() =>
{
var metadataFile = args.GetFileByName("folder.xml");
if (metadataFile.HasValue)
{
new FolderXmlParser().Fetch(item as Folder, metadataFile.Value.Key);
}
});
}
}
}

View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Events;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
[Export(typeof(BaseMetadataProvider))]
public class ImageFromMediaLocationProvider : BaseMetadataProvider
{
public override Task Fetch(BaseItem item, ItemResolveEventArgs args)
{
return Task.Run(() =>
{
if (args.IsFolder)
{
PopulateImages(item, args);
}
});
}
/// <summary>
/// Fills in image paths based on files win the folder
/// </summary>
private void PopulateImages(BaseItem item, ItemResolveEventArgs args)
{
List<string> backdropFiles = new List<string>();
foreach (KeyValuePair<string, FileAttributes> file in args.FileSystemChildren)
{
if (file.Value.HasFlag(FileAttributes.Directory))
{
continue;
}
string filePath = file.Key;
string ext = Path.GetExtension(filePath);
// Only support png and jpg files
if (!ext.EndsWith("png", StringComparison.OrdinalIgnoreCase) && !ext.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))
{
continue;
}
string name = Path.GetFileNameWithoutExtension(filePath);
if (name.Equals("folder", StringComparison.OrdinalIgnoreCase))
{
item.PrimaryImagePath = filePath;
}
else if (name.StartsWith("backdrop", StringComparison.OrdinalIgnoreCase))
{
backdropFiles.Add(filePath);
}
if (name.Equals("logo", StringComparison.OrdinalIgnoreCase))
{
item.LogoImagePath = filePath;
}
if (name.Equals("banner", StringComparison.OrdinalIgnoreCase))
{
item.BannerImagePath = filePath;
}
if (name.Equals("art", StringComparison.OrdinalIgnoreCase))
{
item.ArtImagePath = filePath;
}
if (name.Equals("thumb", StringComparison.OrdinalIgnoreCase))
{
item.ThumbnailImagePath = filePath;
}
}
if (backdropFiles.Any())
{
item.BackdropImagePaths = backdropFiles;
}
}
}
}

View File

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Events;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
[Export(typeof(BaseMetadataProvider))]
public class LocalTrailerProvider : BaseMetadataProvider
{
public async override Task Fetch(BaseItem item, ItemResolveEventArgs args)
{
var trailerPath = args.GetFolderByName("trailers");
if (trailerPath.HasValue)
{
string[] allFiles = Directory.GetFileSystemEntries(trailerPath.Value.Key, "*", SearchOption.TopDirectoryOnly);
List<Video> localTrailers = new List<Video>();
foreach (string file in allFiles)
{
BaseItem child = await Kernel.Instance.ItemController.GetItem(null, file);
Video video = child as Video;
if (video != null)
{
localTrailers.Add(video);
}
}
item.LocalTrailers = localTrailers;
}
}
}
}

View File

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Resolvers
@ -33,19 +33,15 @@ namespace MediaBrowser.Controller.Resolvers
}
item.Id = Kernel.GetMD5(item.Path);
PopulateImages(item, args);
PopulateLocalTrailers(item, args);
}
public BaseItem ResolvePath(ItemResolveEventArgs args)
public async Task<BaseItem> ResolvePath(ItemResolveEventArgs args)
{
T item = Resolve(args);
if (item != null)
{
// Set initial values on the newly resolved item
SetItemValues(item, args);
// Make sure the item has a name
@ -53,11 +49,24 @@ namespace MediaBrowser.Controller.Resolvers
// Make sure DateCreated and DateModified have values
EnsureDates(item);
await FetchMetadataFromProviders(item, args);
}
return item;
}
private async Task FetchMetadataFromProviders(T item, ItemResolveEventArgs args)
{
foreach (BaseMetadataProvider provider in Kernel.Instance.MetadataProviders)
{
if (provider.Supports(item))
{
await provider.Fetch(item, args);
}
}
}
private void EnsureName(T item)
{
// If the subclass didn't supply a name, add it here
@ -84,76 +93,6 @@ namespace MediaBrowser.Controller.Resolvers
item.DateModified = Path.IsPathRooted(item.Path) ? File.GetLastWriteTime(item.Path) : DateTime.Now;
}
}
/// <summary>
/// Fills in image paths based on files win the folder
/// </summary>
protected virtual void PopulateImages(T item, ItemResolveEventArgs args)
{
List<string> backdropFiles = new List<string>();
foreach (KeyValuePair<string,FileAttributes> file in args.FileSystemChildren)
{
if (file.Value.HasFlag(FileAttributes.Directory))
{
continue;
}
string filePath = file.Key;
string ext = Path.GetExtension(filePath);
// Only support png and jpg files
if (!ext.EndsWith("png", StringComparison.OrdinalIgnoreCase) && !ext.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))
{
continue;
}
string name = Path.GetFileNameWithoutExtension(filePath);
if (name.Equals("folder", StringComparison.OrdinalIgnoreCase))
{
item.PrimaryImagePath = filePath;
}
else if (name.StartsWith("backdrop", StringComparison.OrdinalIgnoreCase))
{
backdropFiles.Add(filePath);
}
if (name.Equals("logo", StringComparison.OrdinalIgnoreCase))
{
item.LogoImagePath = filePath;
}
if (name.Equals("banner", StringComparison.OrdinalIgnoreCase))
{
item.BannerImagePath = filePath;
}
if (name.Equals("art", StringComparison.OrdinalIgnoreCase))
{
item.ArtImagePath = filePath;
}
if (name.Equals("thumb", StringComparison.OrdinalIgnoreCase))
{
item.ThumbnailImagePath = filePath;
}
}
if (backdropFiles.Any())
{
item.BackdropImagePaths = backdropFiles;
}
}
protected virtual void PopulateLocalTrailers(T item, ItemResolveEventArgs args)
{
var trailerPath = args.GetFolderByName("trailers");
if (trailerPath.HasValue)
{
string[] allFiles = Directory.GetFileSystemEntries(trailerPath.Value.Key, "*", SearchOption.TopDirectoryOnly);
item.LocalTrailers = allFiles.Select(f => Kernel.Instance.ItemController.GetItem(f)).OfType<Video>();
}
}
}
/// <summary>
@ -161,6 +100,6 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary>
public interface IBaseItemResolver
{
BaseItem ResolvePath(ItemResolveEventArgs args);
Task<BaseItem> ResolvePath(ItemResolveEventArgs args);
}
}

View File

@ -1,6 +1,5 @@
using System.ComponentModel.Composition;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Xml;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Resolvers
@ -27,19 +26,6 @@ namespace MediaBrowser.Controller.Resolvers
base.SetItemValues(item, args);
item.IsRoot = args.Parent == null;
// Read data from folder.xml, if it exists
PopulateFolderMetadata(item, args);
}
private void PopulateFolderMetadata(TItemType folder, ItemResolveEventArgs args)
{
var metadataFile = args.GetFileByName("folder.xml");
if (metadataFile.HasValue)
{
new FolderXmlParser().Fetch(folder, metadataFile.Value.Key);
}
}
}
}

View File

@ -85,7 +85,7 @@ namespace MediaBrowser.Movies.Resolvers
{
string[] allFiles = Directory.GetFileSystemEntries(trailerPath.Value.Key, "*", SearchOption.TopDirectoryOnly);
item.SpecialFeatures = allFiles.Select(f => Kernel.Instance.ItemController.GetItem(f)).OfType<Video>();
item.SpecialFeatures = allFiles.Select(f => Kernel.Instance.ItemController.GetItem(null, f)).OfType<Video>();
}
}

View File

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tb="http://www.hardcodet.net/taskbar"
Title="MainWindow" Height="350" Width="525" AllowsTransparency="True" Background="Transparent" WindowStyle="None" ShowInTaskbar="False" Loaded="MainWindow_Loaded">
Title="MainWindow" Height="350" Width="525" AllowsTransparency="True" Background="Transparent" WindowStyle="None" ShowInTaskbar="False">
<Grid>
<tb:TaskbarIcon Name="MbTaskbarIcon" IconSource="/Icons/Icon.ico" ToolTipText="MediaBrowser Server" Visibility="Hidden">

View File

@ -4,6 +4,8 @@ using System.Windows;
using MediaBrowser.Common.Logging;
using MediaBrowser.Controller;
using MediaBrowser.Model.Progress;
using System.Threading.Tasks;
using MediaBrowser.Common.UI;
namespace MediaBrowser.ServerApplication
{
@ -18,10 +20,10 @@ namespace MediaBrowser.ServerApplication
LoadKernel();
}
private void LoadKernel()
private async void LoadKernel()
{
Progress<TaskProgress> progress = new Progress<TaskProgress>();
Common.UI.Splash splash = new Common.UI.Splash(progress);
Splash splash = new Splash(progress);
splash.Show();
@ -29,11 +31,14 @@ namespace MediaBrowser.ServerApplication
{
DateTime now = DateTime.Now;
new Kernel().Init(progress);
await new Kernel().Init(progress);
double seconds = (DateTime.Now - now).TotalSeconds;
Logger.LogInfo("Kernel.Init completed in {0} seconds.", seconds);
// Don't show the system tray icon until the kernel finishes.
this.MbTaskbarIcon.Visibility = System.Windows.Visibility.Visible;
}
catch (Exception ex)
{
@ -46,16 +51,6 @@ namespace MediaBrowser.ServerApplication
}
}
#region Main Window Events
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// Don't show the system tray icon until the app has loaded.
this.MbTaskbarIcon.Visibility = System.Windows.Visibility.Visible;
}
#endregion
#region Context Menu events
private void cmOpenDashboard_click(object sender, RoutedEventArgs e)