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 progress) + public virtual Task Init(IProgress 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(); + }); } /// 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; + /// + /// Gets the folder path to the cache directory + /// + 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; + /// + /// Gets the folder path to the ffprobe audio cache directory + /// + 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; /// /// 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 { + /// + /// Runs FFProbe against a media file and returns metadata. + /// public static class FFProbe { - public static FFProbeResult Run(string path) + public async static Task 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(stream); + } + } + catch (FileNotFoundException) + { + } + + await Run(item.Path, outputCachePath); + + using (FileStream stream = File.OpenRead(outputCachePath)) + { + return JsonSerializer.DeserializeFromStream(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(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; } } + /// + /// Represents a stream within the output + /// A number of properties are commented out to improve deserialization performance + /// Enable them as needed. + /// 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 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 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 paths) { - List itemsToRefresh = new List(); + /*List itemsToRefresh = new List(); 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; @@ -35,6 +37,12 @@ namespace MediaBrowser.Controller } } + /// + /// Gets the list of currently registered metadata prvoiders + /// + [ImportMany(typeof(BaseMetadataProvider))] + public IEnumerable MetadataProviders { get; private set; } + /// /// Gets the list of currently registered entity resolvers /// @@ -56,35 +64,63 @@ namespace MediaBrowser.Controller ItemController.BeginResolvePath += ItemController_BeginResolvePath; } - public override void Init(IProgress progress) + public async override Task Init(IProgress 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 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 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 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(); + }); + } + /// /// 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 /// /// Reloads the root media folder /// - 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 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 - /// - /// Called when an item is being created. - /// This should be used to fill item values, such as metadata - /// - public event EventHandler> BaseItemCreating; - - /// - /// Called when an item has been created. - /// This should be used to process or modify item values. - /// - public event EventHandler> BaseItemCreated; - #endregion - - /// - /// Called when an item has been created - /// - private void OnBaseItemCreated(BaseItem item, Folder parent) - { - GenericItemEventArgs args = new GenericItemEventArgs { 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 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 /// /// Resolves a path into a BaseItem /// - public BaseItem GetItem(string path) + public async Task GetItem(Folder parent, string path) { - return GetItem(null, path); + return await GetItemInternal(parent, path, File.GetAttributes(path)); } /// /// Resolves a path into a BaseItem /// - 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; - } - - /// - /// Resolves a path into a BaseItem - /// - private BaseItem GetItemInternal(Folder parent, string path, FileAttributes attributes) + private async Task 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 /// /// Finds child BaseItems for a given Folder /// - private void AttachChildren(Folder folder, IEnumerable> fileSystemChildren) + private async Task AttachChildren(Folder folder, IEnumerable> fileSystemChildren) { - List baseItemChildren = new List(); + KeyValuePair[] fileSystemChildrenArray = fileSystemChildren.ToArray(); - int count = fileSystemChildren.Count(); + int count = fileSystemChildrenArray.Length; - // Resolve the child folder paths into entities - Parallel.For(0, count, i => + Task[] tasks = new Task[count]; + + for (int i = 0; i < count; i++) { - KeyValuePair 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.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 /// private T CreateImagesByNameItem(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 @@ + + + + + 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 + { + /// + /// If the provider needs any startup routines, add them here + /// + 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); + } + }); + } + + /// + /// Fills in image paths based on files win the folder + /// + private void PopulateImages(BaseItem item, ItemResolveEventArgs args) + { + List backdropFiles = new List(); + + foreach (KeyValuePair 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