diff options
18 files changed, 492 insertions, 284 deletions
diff --git a/MediaBrowser.Common/Kernel/BaseKernel.cs b/MediaBrowser.Common/Kernel/BaseKernel.cs index ecfe11e2c..7b6f6844c 100644 --- a/MediaBrowser.Common/Kernel/BaseKernel.cs +++ b/MediaBrowser.Common/Kernel/BaseKernel.cs @@ -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,18 +52,21 @@ namespace MediaBrowser.Common.Kernel ApplicationPaths = new TApplicationPathsType();
}
- public virtual void Init(IProgress<TaskProgress> progress)
+ public virtual Task Init(IProgress<TaskProgress> progress)
{
- ReloadLogger();
+ return Task.Run(() =>
+ {
+ ReloadLogger();
- progress.Report(new TaskProgress() { Description = "Loading configuration", PercentComplete = 0 });
- ReloadConfiguration();
+ progress.Report(new TaskProgress() { Description = "Loading configuration", PercentComplete = 0 });
+ ReloadConfiguration();
- progress.Report(new TaskProgress() { Description = "Starting Http server", PercentComplete = 5 });
- ReloadHttpServer();
+ progress.Report(new TaskProgress() { Description = "Starting Http server", PercentComplete = 5 });
+ ReloadHttpServer();
- progress.Report(new TaskProgress() { Description = "Loading Plugins", PercentComplete = 10 });
- ReloadComposableParts();
+ progress.Report(new TaskProgress() { Description = "Loading Plugins", PercentComplete = 10 });
+ ReloadComposableParts();
+ });
}
/// <summary>
diff --git a/MediaBrowser.Controller/Configuration/ServerApplicationPaths.cs b/MediaBrowser.Controller/Configuration/ServerApplicationPaths.cs index e008f7b17..b0c4904c9 100644 --- a/MediaBrowser.Controller/Configuration/ServerApplicationPaths.cs +++ b/MediaBrowser.Controller/Configuration/ServerApplicationPaths.cs @@ -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;
diff --git a/MediaBrowser.Controller/FFMpeg/FFProbe.cs b/MediaBrowser.Controller/FFMpeg/FFProbe.cs index efd5491de..fd9b2ff43 100644 --- a/MediaBrowser.Controller/FFMpeg/FFProbe.cs +++ b/MediaBrowser.Controller/FFMpeg/FFProbe.cs @@ -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
{
diff --git a/MediaBrowser.Controller/FFMpeg/FFProbeResult.cs b/MediaBrowser.Controller/FFMpeg/FFProbeResult.cs index cc449991d..43167b521 100644 --- a/MediaBrowser.Controller/FFMpeg/FFProbeResult.cs +++ b/MediaBrowser.Controller/FFMpeg/FFProbeResult.cs @@ -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; }
}
}
diff --git a/MediaBrowser.Controller/IO/DirectoryWatchers.cs b/MediaBrowser.Controller/IO/DirectoryWatchers.cs index 8d102e80c..e2fb09a16 100644 --- a/MediaBrowser.Controller/IO/DirectoryWatchers.cs +++ b/MediaBrowser.Controller/IO/DirectoryWatchers.cs @@ -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)
diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 21405e572..b696abef5 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -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;
@@ -36,6 +38,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>
[ImportMany(typeof(IBaseItemResolver))]
@@ -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();
diff --git a/MediaBrowser.Controller/Library/ItemController.cs b/MediaBrowser.Controller/Library/ItemController.cs index 8d269679f..4b0d9a983 100644 --- a/MediaBrowser.Controller/Library/ItemController.cs +++ b/MediaBrowser.Controller/Library/ItemController.cs @@ -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)
- {
- return GetItem(null, path);
- }
-
- /// <summary>
- /// Resolves a path into a BaseItem
- /// </summary>
- public BaseItem GetItem(Folder parent, string path)
+ public async Task<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;
+ return await GetItemInternal(parent, path, File.GetAttributes(path));
}
/// <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 =>
- {
- KeyValuePair<string, FileAttributes> child = fileSystemChildren.ElementAt(i);
+ Task<BaseItem>[] tasks = new Task<BaseItem>[count];
- BaseItem item = GetItemInternal(folder, child.Key, child.Value);
+ for (int i = 0; i < count; i++)
+ {
+ var child = fileSystemChildrenArray[i];
- 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;
@@ -363,7 +277,7 @@ namespace MediaBrowser.Controller.Library /// Creates an IBN item based on a given path
/// </summary>
private T CreateImagesByNameItem<T>(string path, string name)
- where T : BaseEntity, new ()
+ where T : BaseEntity, new()
{
T item = new T();
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 048f6993b..6ac2ddd49 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -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" />
diff --git a/MediaBrowser.Controller/Providers/AudioInfoProvider.cs b/MediaBrowser.Controller/Providers/AudioInfoProvider.cs new file mode 100644 index 000000000..934f082d5 --- /dev/null +++ b/MediaBrowser.Controller/Providers/AudioInfoProvider.cs @@ -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);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs new file mode 100644 index 000000000..93d9ef10e --- /dev/null +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -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);
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs b/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs new file mode 100644 index 000000000..5ba02b38d --- /dev/null +++ b/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs @@ -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);
+ }
+ });
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs new file mode 100644 index 000000000..2df07251a --- /dev/null +++ b/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs @@ -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;
+ }
+ }
+
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/LocalTrailerProvider.cs b/MediaBrowser.Controller/Providers/LocalTrailerProvider.cs new file mode 100644 index 000000000..027c2f75d --- /dev/null +++ b/MediaBrowser.Controller/Providers/LocalTrailerProvider.cs @@ -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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs index 200a2444d..75e516487 100644 --- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs @@ -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);
}
}
diff --git a/MediaBrowser.Controller/Resolvers/FolderResolver.cs b/MediaBrowser.Controller/Resolvers/FolderResolver.cs index 09d3cebe4..ff326669f 100644 --- a/MediaBrowser.Controller/Resolvers/FolderResolver.cs +++ b/MediaBrowser.Controller/Resolvers/FolderResolver.cs @@ -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);
- }
}
}
}
diff --git a/MediaBrowser.Movies/Resolvers/MovieResolver.cs b/MediaBrowser.Movies/Resolvers/MovieResolver.cs index fae0bdc51..26b4082f3 100644 --- a/MediaBrowser.Movies/Resolvers/MovieResolver.cs +++ b/MediaBrowser.Movies/Resolvers/MovieResolver.cs @@ -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>();
}
}
diff --git a/MediaBrowser.ServerApplication/MainWindow.xaml b/MediaBrowser.ServerApplication/MainWindow.xaml index 35e78424d..753db4832 100644 --- a/MediaBrowser.ServerApplication/MainWindow.xaml +++ b/MediaBrowser.ServerApplication/MainWindow.xaml @@ -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">
diff --git a/MediaBrowser.ServerApplication/MainWindow.xaml.cs b/MediaBrowser.ServerApplication/MainWindow.xaml.cs index 3cc29e7df..abca9149f 100644 --- a/MediaBrowser.ServerApplication/MainWindow.xaml.cs +++ b/MediaBrowser.ServerApplication/MainWindow.xaml.cs @@ -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)
|
