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.Plugins;
using MediaBrowser.Common.Serialization; using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Progress; using MediaBrowser.Model.Progress;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Kernel namespace MediaBrowser.Common.Kernel
{ {
@ -51,7 +52,9 @@ namespace MediaBrowser.Common.Kernel
ApplicationPaths = new TApplicationPathsType(); ApplicationPaths = new TApplicationPathsType();
} }
public virtual void Init(IProgress<TaskProgress> progress) public virtual Task Init(IProgress<TaskProgress> progress)
{
return Task.Run(() =>
{ {
ReloadLogger(); ReloadLogger();
@ -63,6 +66,7 @@ namespace MediaBrowser.Common.Kernel
progress.Report(new TaskProgress() { Description = "Loading Plugins", PercentComplete = 10 }); progress.Report(new TaskProgress() { Description = "Loading Plugins", PercentComplete = 10 });
ReloadComposableParts(); ReloadComposableParts();
});
} }
/// <summary> /// <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; private string _FFMpegDirectory = null;
/// <summary> /// <summary>
/// Gets the folder path to ffmpeg /// Gets the folder path to ffmpeg
@ -221,7 +265,7 @@ namespace MediaBrowser.Controller.Configuration
_FFProbePath = Path.Combine(FFMpegDirectory, filename); _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)) if (File.Exists(_FFProbePath))
{ {
File.Delete(_FFProbePath); File.Delete(_FFProbePath);
@ -234,7 +278,7 @@ namespace MediaBrowser.Controller.Configuration
{ {
stream.CopyTo(fileStream); stream.CopyTo(fileStream);
} }
} }*/
} }
return _FFProbePath; return _FFProbePath;

View File

@ -1,13 +1,41 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Common.Logging; using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Serialization; using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.FFMpeg namespace MediaBrowser.Controller.FFMpeg
{ {
/// <summary>
/// Runs FFProbe against a media file and returns metadata.
/// </summary>
public static class FFProbe 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(); ProcessStartInfo startInfo = new ProcessStartInfo();
@ -21,13 +49,15 @@ namespace MediaBrowser.Controller.FFMpeg
startInfo.FileName = Kernel.Instance.ApplicationPaths.FFProbePath; startInfo.FileName = Kernel.Instance.ApplicationPaths.FFProbePath;
startInfo.WorkingDirectory = Kernel.Instance.ApplicationPaths.FFMpegDirectory; 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 process = new Process();
process.StartInfo = startInfo; process.StartInfo = startInfo;
FileStream stream = new FileStream(output, FileMode.Create);
try try
{ {
process.Start(); 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. // If we ever decide to disable the ffmpeg log then you must uncomment the below line.
process.BeginErrorReadLine(); process.BeginErrorReadLine();
FFProbeResult result = JsonSerializer.DeserializeFromStream<FFProbeResult>(process.StandardOutput.BaseStream); await process.StandardOutput.BaseStream.CopyToAsync(stream);
process.WaitForExit(); 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) catch (Exception ex)
{ {
Logger.LogException(ex); Logger.LogException(ex);
stream.Dispose();
// Hate having to do this // Hate having to do this
try try
{ {
@ -56,8 +91,7 @@ namespace MediaBrowser.Controller.FFMpeg
catch catch
{ {
} }
File.Delete(output);
return null;
} }
finally finally
{ {

View File

@ -13,6 +13,11 @@ namespace MediaBrowser.Controller.FFMpeg
public MediaFormat format { get; set; } 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 class MediaStream
{ {
public int index { get; set; } public int index { get; set; }
@ -20,28 +25,28 @@ namespace MediaBrowser.Controller.FFMpeg
public string codec_name { get; set; } public string codec_name { get; set; }
public string codec_long_name { get; set; } public string codec_long_name { get; set; }
public string codec_type { get; set; } public string codec_type { get; set; }
public string codec_time_base { get; set; } //public string codec_time_base { get; set; }
public string codec_tag { get; set; } //public string codec_tag { get; set; }
public string codec_tag_string { get; set; } //public string codec_tag_string { get; set; }
public string sample_fmt { get; set; } //public string sample_fmt { get; set; }
public string sample_rate { get; set; } public string sample_rate { get; set; }
public int channels { get; set; } public int channels { get; set; }
public int bits_per_sample { get; set; } //public int bits_per_sample { get; set; }
public string r_frame_rate { get; set; } //public string r_frame_rate { get; set; }
public string avg_frame_rate { get; set; } //public string avg_frame_rate { get; set; }
public string time_base { get; set; } //public string time_base { get; set; }
public string start_time { get; set; } //public string start_time { get; set; }
public string duration { get; set; } public string duration { get; set; }
public string bit_rate { get; set; } public string bit_rate { get; set; }
public int width { get; set; } public int width { get; set; }
public int height { get; set; } public int height { get; set; }
public int has_b_frames { get; set; } //public int has_b_frames { get; set; }
public string sample_aspect_ratio { get; set; } //public string sample_aspect_ratio { get; set; }
public string display_aspect_ratio { get; set; } //public string display_aspect_ratio { get; set; }
public string pix_fmt { get; set; } //public string pix_fmt { get; set; }
public int level { get; set; } //public int level { get; set; }
public MediaTags tags { get; set; } public Dictionary<string,string> tags { get; set; }
} }
public class MediaFormat public class MediaFormat
@ -54,23 +59,6 @@ namespace MediaBrowser.Controller.FFMpeg
public string duration { get; set; } public string duration { get; set; }
public string size { get; set; } public string size { get; set; }
public string bit_rate { get; set; } public string bit_rate { get; set; }
public MediaTags tags { get; set; } public Dictionary<string, string> 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; }
} }
} }

View File

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

View File

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

View File

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
@ -58,67 +56,12 @@ namespace MediaBrowser.Controller.Library
} }
#endregion #endregion
#region BaseItem Events private async Task<BaseItem> ResolveItem(ItemResolveEventArgs args)
/// <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)
{ {
// If that didn't pan out, try the slow ones // If that didn't pan out, try the slow ones
foreach (IBaseItemResolver resolver in Kernel.Instance.EntityResolvers) foreach (IBaseItemResolver resolver in Kernel.Instance.EntityResolvers)
{ {
var item = resolver.ResolvePath(args); var item = await resolver.ResolvePath(args);
if (item != null) if (item != null)
{ {
@ -132,39 +75,15 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Resolves a path into a BaseItem /// Resolves a path into a BaseItem
/// </summary> /// </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> /// <summary>
/// Resolves a path into a BaseItem /// Resolves a path into a BaseItem
/// </summary> /// </summary>
public BaseItem GetItem(Folder parent, string path) private async Task<BaseItem> GetItemInternal(Folder parent, string path, FileAttributes attributes)
{
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)
{ {
if (!OnPreBeginResolvePath(parent, path, attributes)) if (!OnPreBeginResolvePath(parent, path, attributes))
{ {
@ -201,14 +120,14 @@ namespace MediaBrowser.Controller.Library
return null; return null;
} }
BaseItem item = ResolveItem(args); BaseItem item = await ResolveItem(args);
var folder = item as Folder; var folder = item as Folder;
if (folder != null) if (folder != null)
{ {
// If it's a folder look for child entities // If it's a folder look for child entities
AttachChildren(folder, fileSystemChildren); await AttachChildren(folder, fileSystemChildren);
} }
return item; return item;
@ -217,30 +136,25 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Finds child BaseItems for a given Folder /// Finds child BaseItems for a given Folder
/// </summary> /// </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 Task<BaseItem>[] tasks = new Task<BaseItem>[count];
Parallel.For(0, count, i =>
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); tasks[i] = GetItemInternal(folder, child.Key, child.Value);
if (item != null)
{
lock (baseItemChildren)
{
baseItemChildren.Add(item);
} }
}
}); BaseItem[] baseItemChildren = await Task<BaseItem>.WhenAll(tasks);
// Sort them // 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; return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;

View File

@ -59,6 +59,11 @@
<Compile Include="Library\ItemController.cs" /> <Compile Include="Library\ItemController.cs" />
<Compile Include="Kernel.cs" /> <Compile Include="Kernel.cs" />
<Compile Include="Properties\AssemblyInfo.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\AudioResolver.cs" />
<Compile Include="Resolvers\BaseItemResolver.cs" /> <Compile Include="Resolvers\BaseItemResolver.cs" />
<Compile Include="Resolvers\FolderResolver.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;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Threading.Tasks;
using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Resolvers namespace MediaBrowser.Controller.Resolvers
@ -33,19 +33,15 @@ namespace MediaBrowser.Controller.Resolvers
} }
item.Id = Kernel.GetMD5(item.Path); 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); T item = Resolve(args);
if (item != null) if (item != null)
{ {
// Set initial values on the newly resolved item // Set initial values on the newly resolved item
SetItemValues(item, args); SetItemValues(item, args);
// Make sure the item has a name // Make sure the item has a name
@ -53,11 +49,24 @@ namespace MediaBrowser.Controller.Resolvers
// Make sure DateCreated and DateModified have values // Make sure DateCreated and DateModified have values
EnsureDates(item); EnsureDates(item);
await FetchMetadataFromProviders(item, args);
} }
return item; 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) private void EnsureName(T item)
{ {
// If the subclass didn't supply a name, add it here // 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; 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> /// <summary>
@ -161,6 +100,6 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary> /// </summary>
public interface IBaseItemResolver public interface IBaseItemResolver
{ {
BaseItem ResolvePath(ItemResolveEventArgs args); Task<BaseItem> ResolvePath(ItemResolveEventArgs args);
} }
} }

View File

@ -1,6 +1,5 @@
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Xml;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Resolvers namespace MediaBrowser.Controller.Resolvers
@ -27,19 +26,6 @@ namespace MediaBrowser.Controller.Resolvers
base.SetItemValues(item, args); base.SetItemValues(item, args);
item.IsRoot = args.Parent == null; 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); 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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tb="http://www.hardcodet.net/taskbar" 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> <Grid>
<tb:TaskbarIcon Name="MbTaskbarIcon" IconSource="/Icons/Icon.ico" ToolTipText="MediaBrowser Server" Visibility="Hidden"> <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.Common.Logging;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Model.Progress; using MediaBrowser.Model.Progress;
using System.Threading.Tasks;
using MediaBrowser.Common.UI;
namespace MediaBrowser.ServerApplication namespace MediaBrowser.ServerApplication
{ {
@ -18,10 +20,10 @@ namespace MediaBrowser.ServerApplication
LoadKernel(); LoadKernel();
} }
private void LoadKernel() private async void LoadKernel()
{ {
Progress<TaskProgress> progress = new Progress<TaskProgress>(); Progress<TaskProgress> progress = new Progress<TaskProgress>();
Common.UI.Splash splash = new Common.UI.Splash(progress); Splash splash = new Splash(progress);
splash.Show(); splash.Show();
@ -29,11 +31,14 @@ namespace MediaBrowser.ServerApplication
{ {
DateTime now = DateTime.Now; DateTime now = DateTime.Now;
new Kernel().Init(progress); await new Kernel().Init(progress);
double seconds = (DateTime.Now - now).TotalSeconds; double seconds = (DateTime.Now - now).TotalSeconds;
Logger.LogInfo("Kernel.Init completed in {0} seconds.", seconds); 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) 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 #region Context Menu events
private void cmOpenDashboard_click(object sender, RoutedEventArgs e) private void cmOpenDashboard_click(object sender, RoutedEventArgs e)