Forráskód Böngészése

Chaged BaseItem.People to a dictionary to prevent duplicates and improve Contains performance. Tweaked ffprobe and provider execution.

LukePulverenti Luke Pulverenti luke pulverenti 12 éve
szülő
commit
cc25bd579b

+ 5 - 5
MediaBrowser.Api/ApiService.cs

@@ -200,17 +200,17 @@ namespace MediaBrowser.Api
             // Attach People by transforming them into BaseItemPerson (DTO)
             if (item.People != null)
             {
-                IEnumerable<Person> entities = await Task.WhenAll<Person>(item.People.Select(c => Kernel.Instance.ItemController.GetPerson(c.Name))).ConfigureAwait(false);
+                IEnumerable<Person> entities = await Task.WhenAll<Person>(item.People.Select(c => Kernel.Instance.ItemController.GetPerson(c.Key))).ConfigureAwait(false);
 
                 dto.People = item.People.Select(p =>
                 {
                     BaseItemPerson baseItemPerson = new BaseItemPerson();
 
-                    baseItemPerson.Name = p.Name;
-                    baseItemPerson.Overview = p.Overview;
-                    baseItemPerson.Type = p.Type;
+                    baseItemPerson.Name = p.Key;
+                    baseItemPerson.Overview = p.Value.Overview;
+                    baseItemPerson.Type = p.Value.Type;
 
-                    Person ibnObject = entities.First(i => i.Name.Equals(p.Name, StringComparison.OrdinalIgnoreCase));
+                    Person ibnObject = entities.First(i => i.Name.Equals(p.Key, StringComparison.OrdinalIgnoreCase));
 
                     if (ibnObject != null)
                     {

+ 1 - 1
MediaBrowser.Api/HttpHandlers/PersonHandler.cs

@@ -37,7 +37,7 @@ namespace MediaBrowser.Api.HttpHandlers
 
             foreach (var item in allItems)
             {
-                if (item.People != null && item.People.Any(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
+                if (item.People != null && item.People.ContainsKey(name))
                 {
                     count++;
                 }

+ 7 - 38
MediaBrowser.Controller/FFMpeg/FFProbe.cs

@@ -16,12 +16,14 @@ namespace MediaBrowser.Controller.FFMpeg
         /// <summary>
         /// Runs FFProbe against an Audio file, caches the result and returns the output
         /// </summary>
-        public static FFProbeResult Run(Audio item)
+        public static FFProbeResult Run(BaseItem item, string cacheDirectory)
         {
+            string cachePath = GetFFProbeCachePath(item, cacheDirectory);
+
             // Use try catch to avoid having to use File.Exists
             try
             {
-                return GetCachedResult(GetFFProbeCachePath(item));
+                return GetCachedResult(cachePath);
             }
             catch (FileNotFoundException)
             {
@@ -34,7 +36,7 @@ namespace MediaBrowser.Controller.FFMpeg
             FFProbeResult result = Run(item.Path);
 
             // Fire and forget
-            CacheResult(result, GetFFProbeCachePath(item));
+            CacheResult(result, cachePath);
 
             return result;
         }
@@ -65,32 +67,6 @@ namespace MediaBrowser.Controller.FFMpeg
             }).ConfigureAwait(false);
         }
 
-        /// <summary>
-        /// Runs FFProbe against a Video file, caches the result and returns the output
-        /// </summary>
-        public static FFProbeResult Run(Video item)
-        {
-            // Use try catch to avoid having to use File.Exists
-            try
-            {
-                return GetCachedResult(GetFFProbeCachePath(item));
-            }
-            catch (FileNotFoundException)
-            {
-            }
-            catch (Exception ex)
-            {
-                Logger.LogException(ex);
-            }
-
-            FFProbeResult result = Run(item.Path);
-
-            // Fire and forget
-            CacheResult(result, GetFFProbeCachePath(item));
-
-            return result;
-        }
-
         private static FFProbeResult Run(string input)
         {
             ProcessStartInfo startInfo = new ProcessStartInfo();
@@ -148,16 +124,9 @@ namespace MediaBrowser.Controller.FFMpeg
             (sender as Process).Dispose();
         }
 
-        private static string GetFFProbeCachePath(Audio item)
-        {
-            string outputDirectory = Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, item.Id.ToString().Substring(0, 1));
-
-            return Path.Combine(outputDirectory, item.Id + "-" + item.DateModified.Ticks + ".pb");
-        }
-
-        private static string GetFFProbeCachePath(Video item)
+        private static string GetFFProbeCachePath(BaseItem item, string cacheDirectory)
         {
-            string outputDirectory = Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeVideoCacheDirectory, item.Id.ToString().Substring(0, 1));
+            string outputDirectory = Path.Combine(cacheDirectory, item.Id.ToString().Substring(0, 1));
 
             return Path.Combine(outputDirectory, item.Id + "-" + item.DateModified.Ticks + ".pb");
         }

+ 16 - 8
MediaBrowser.Controller/Kernel.cs

@@ -41,7 +41,13 @@ namespace MediaBrowser.Controller
         /// Gets the list of currently registered metadata prvoiders
         /// </summary>
         [ImportMany(typeof(BaseMetadataProvider))]
-        public IEnumerable<BaseMetadataProvider> MetadataProviders { get; private set; }
+        private IEnumerable<BaseMetadataProvider> MetadataProvidersEnumerable { get; set; }
+        
+        /// <summary>
+        /// Once MEF has loaded the resolvers, sort them by priority and store them in this array
+        /// Given the sheer number of times they'll be iterated over it'll be faster to loop through an array
+        /// </summary>
+        private BaseMetadataProvider[] MetadataProviders { get; set; }
 
         /// <summary>
         /// Gets the list of currently registered entity resolvers
@@ -92,9 +98,9 @@ namespace MediaBrowser.Controller
 
             // Sort the resolvers by priority
             EntityResolvers = EntityResolversEnumerable.OrderBy(e => e.Priority).ToArray();
-
+            
             // Sort the providers by priority
-            MetadataProviders = MetadataProviders.OrderBy(e => e.Priority);
+            MetadataProviders = MetadataProvidersEnumerable.OrderBy(e => e.Priority).ToArray();
 
             // Initialize the metadata providers
             Parallel.ForEach(MetadataProviders, provider =>
@@ -232,13 +238,15 @@ namespace MediaBrowser.Controller
         /// </summary>
         internal async Task ExecuteMetadataProviders(BaseEntity item, ItemResolveEventArgs args, bool allowInternetProviders = true)
         {
-            // Get all supported providers
-            BaseMetadataProvider[] supportedProviders = Kernel.Instance.MetadataProviders.Where(i => i.Supports(item)).ToArray();
-
             // Run them sequentially in order of priority
-            for (int i = 0; i < supportedProviders.Length; i++)
+            for (int i = 0; i < MetadataProviders.Length; i++)
             {
-                var provider = supportedProviders[i];
+                var provider = MetadataProviders[i];
+
+                if (!provider.Supports(item))
+                {
+                    continue;
+                }
 
                 if (provider.RequiresInternet && (!Configuration.EnableInternetProviders || !allowInternetProviders))
                 {

+ 98 - 64
MediaBrowser.Controller/Providers/AudioInfoProvider.cs

@@ -12,36 +12,26 @@ using MediaBrowser.Model.Entities;
 namespace MediaBrowser.Controller.Providers
 {
     [Export(typeof(BaseMetadataProvider))]
-    public class AudioInfoProvider : BaseMetadataProvider
+    public class AudioInfoProvider : BaseMediaInfoProvider<Audio>
     {
-        public override bool Supports(BaseEntity item)
-        {
-            return item is Audio;
-        }
-
         public override MetadataProviderPriority Priority
         {
             get { return MetadataProviderPriority.First; }
         }
 
-        public override async Task FetchAsync(BaseEntity item, ItemResolveEventArgs args)
+        protected override string CacheDirectory
         {
-            await Task.Run(() =>
-            {
-                Audio audio = item as Audio;
-
-                Fetch(audio, FFProbe.Run(audio));
-            });
+            get { return Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory; }
         }
 
-        private void Fetch(Audio audio, FFProbeResult data)
+        protected override void Fetch(Audio audio, FFProbeResult data)
         {
             if (data == null)
             {
                 Logger.LogInfo("Null FFProbeResult for {0} {1}", audio.Id, audio.Name);
                 return;
             }
-            
+
             MediaStream stream = data.streams.First(s => s.codec_type.Equals("audio", StringComparison.OrdinalIgnoreCase));
 
             string bitrate = null;
@@ -96,9 +86,7 @@ namespace MediaBrowser.Controller.Providers
 
             if (!string.IsNullOrEmpty(composer))
             {
-                var list = audio.People ?? new List<PersonInfo>();
-                list.Add(new PersonInfo() { Name = composer, Type = "Composer" });
-                audio.People = list;
+                audio.AddPerson(new PersonInfo() { Name = composer, Type = "Composer" });
             }
 
             audio.Album = GetDictionaryValue(tags, "album");
@@ -132,7 +120,7 @@ namespace MediaBrowser.Controller.Providers
                 audio.Studios = list;
             }
         }
-        
+
         private void FetchGenres(Audio audio, Dictionary<string, string> tags)
         {
             string val = GetDictionaryValue(tags, "genre");
@@ -163,30 +151,102 @@ namespace MediaBrowser.Controller.Providers
 
             return null;
         }
+    }
+
+    public abstract class BaseMediaInfoProvider<T> : BaseMetadataProvider
+        where T : BaseItem
+    {
+        protected abstract string CacheDirectory { get; }
+
+        public override bool Supports(BaseEntity item)
+        {
+            return item is T;
+        }
 
-        internal static string GetDictionaryValue(Dictionary<string, string> tags, string key)
+        public override async Task FetchAsync(BaseEntity item, ItemResolveEventArgs args)
         {
-            if (tags == null)
+            await Task.Run(() =>
             {
-                return null;
-            }
+                T myItem = item as T;
 
-            string[] keys = tags.Keys.ToArray();
+                if (CanSkipFFProbe(myItem))
+                {
+                    return;
+                }
 
-            for (int i = 0; i < keys.Length; i++)
-            {
-                string currentKey = keys[i];
+                FFProbeResult result = FFProbe.Run(myItem, CacheDirectory);
+
+                if (result.format.tags != null)
+                {
+                    result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags);
+                }
 
-                if (key.Equals(currentKey, StringComparison.OrdinalIgnoreCase))
+                foreach (MediaStream stream in result.streams)
                 {
-                    return tags[currentKey];
+                    if (stream.tags != null)
+                    {
+                        stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags);
+                    }
                 }
+
+                Fetch(myItem, result);
+            });
+        }
+
+        protected abstract void Fetch(T item, FFProbeResult result);
+
+        public override void Init()
+        {
+            base.Init();
+
+            EnsureCacheSubFolders(CacheDirectory);
+        }
+
+        private void EnsureCacheSubFolders(string root)
+        {
+            // Do this now so that we don't have to do this on every operation, which would require us to create a lock in order to maintain thread-safety
+            for (int i = 0; i <= 9; i++)
+            {
+                EnsureDirectory(Path.Combine(root, i.ToString()));
             }
 
-            return null;
+            EnsureDirectory(Path.Combine(root, "a"));
+            EnsureDirectory(Path.Combine(root, "b"));
+            EnsureDirectory(Path.Combine(root, "c"));
+            EnsureDirectory(Path.Combine(root, "d"));
+            EnsureDirectory(Path.Combine(root, "e"));
+            EnsureDirectory(Path.Combine(root, "f"));
+        }
+
+        private void EnsureDirectory(string path)
+        {
+            if (!Directory.Exists(path))
+            {
+                Directory.CreateDirectory(path);
+            }
+        }
+
+        protected virtual bool CanSkipFFProbe(T item)
+        {
+            return false;
+        }
+
+        protected string GetDictionaryValue(Dictionary<string, string> tags, string key)
+        {
+            if (tags == null)
+            {
+                return null;
+            }
+
+            if (!tags.ContainsKey(key))
+            {
+                return null;
+            }
+
+            return tags[key];
         }
 
-        private int? GetDictionaryNumericValue(Dictionary<string, string> tags, string key)
+        protected int? GetDictionaryNumericValue(Dictionary<string, string> tags, string key)
         {
             string val = GetDictionaryValue(tags, key);
 
@@ -203,7 +263,7 @@ namespace MediaBrowser.Controller.Providers
             return null;
         }
 
-        private DateTime? GetDictionaryDateTime(Dictionary<string, string> tags, string key)
+        protected DateTime? GetDictionaryDateTime(Dictionary<string, string> tags, string key)
         {
             string val = GetDictionaryValue(tags, key);
 
@@ -219,43 +279,17 @@ namespace MediaBrowser.Controller.Providers
 
             return null;
         }
-
-        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()
+        
+        private Dictionary<string, string> ConvertDictionaryToCaseInSensitive(Dictionary<string, string> dict)
         {
-            base.Init();
+            Dictionary<string, string> newDict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
-            EnsureCacheSubFolders(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory);
-        }
-
-        internal static void EnsureCacheSubFolders(string root)
-        {
-            // Do this now so that we don't have to do this on every operation, which would require us to create a lock in order to maintain thread-safety
-            for (int i = 0; i <= 9; i++)
+            foreach (string key in dict.Keys)
             {
-                EnsureDirectory(Path.Combine(root, i.ToString()));
+                newDict[key] = dict[key];
             }
 
-            EnsureDirectory(Path.Combine(root, "a"));
-            EnsureDirectory(Path.Combine(root, "b"));
-            EnsureDirectory(Path.Combine(root, "c"));
-            EnsureDirectory(Path.Combine(root, "d"));
-            EnsureDirectory(Path.Combine(root, "e"));
-            EnsureDirectory(Path.Combine(root, "f"));
-        }
-
-        private static void EnsureDirectory(string path)
-        {
-            if (!Directory.Exists(path))
-            {
-                Directory.CreateDirectory(path);
-            }
+            return newDict;
         }
     }
 }

+ 13 - 30
MediaBrowser.Controller/Providers/VideoInfoProvider.cs

@@ -11,13 +11,8 @@ using MediaBrowser.Model.Entities;
 namespace MediaBrowser.Controller.Providers
 {
     [Export(typeof(BaseMetadataProvider))]
-    public class VideoInfoProvider : BaseMetadataProvider
+    public class VideoInfoProvider : BaseMediaInfoProvider<Video>
     {
-        public override bool Supports(BaseEntity item)
-        {
-            return item is Video;
-        }
-
         public override MetadataProviderPriority Priority
         {
             // Give this second priority
@@ -25,28 +20,12 @@ namespace MediaBrowser.Controller.Providers
             get { return MetadataProviderPriority.Second; }
         }
 
-        public override async Task FetchAsync(BaseEntity item, ItemResolveEventArgs args)
+        protected override string CacheDirectory
         {
-            await Task.Run(() =>
-            {
-                Video video = item as Video;
-
-                if (video.VideoType != VideoType.VideoFile)
-                {
-                    // Not supported yet
-                    return;
-                }
-
-                if (CanSkip(video))
-                {
-                    return;
-                }
-
-                Fetch(video, FFProbe.Run(video));
-            });
+            get { return Kernel.Instance.ApplicationPaths.FFProbeVideoCacheDirectory; }
         }
-
-        private void Fetch(Video video, FFProbeResult data)
+        
+        protected override void Fetch(Video video, FFProbeResult data)
         {
             if (data == null)
             {
@@ -126,7 +105,7 @@ namespace MediaBrowser.Controller.Providers
                 audio.SampleRate = int.Parse(stream.sample_rate);
             }
 
-            audio.Language = AudioInfoProvider.GetDictionaryValue(stream.tags, "language");
+            audio.Language = GetDictionaryValue(stream.tags, "language");
 
             List<AudioStream> streams = video.AudioStreams ?? new List<AudioStream>();
             streams.Add(audio);
@@ -136,8 +115,14 @@ namespace MediaBrowser.Controller.Providers
         /// <summary>
         /// Determines if there's already enough info in the Video object to allow us to skip running ffprobe
         /// </summary>
-        private bool CanSkip(Video video)
+        protected override bool CanSkipFFProbe(Video video)
         {
+            if (video.VideoType != VideoType.VideoFile)
+            {
+                // Not supported yet
+                return true;
+            }
+            
             if (video.AudioStreams == null || !video.AudioStreams.Any())
             {
                 return false;
@@ -175,8 +160,6 @@ namespace MediaBrowser.Controller.Providers
         {
             base.Init();
 
-            AudioInfoProvider.EnsureCacheSubFolders(Kernel.Instance.ApplicationPaths.FFProbeVideoCacheDirectory);
-
             // This is an optimzation. Do this now so that it doesn't have to be done upon first serialization.
             ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(FFProbeResult), true);
             ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(MediaStream), true);

+ 13 - 17
MediaBrowser.Controller/Xml/BaseItemXmlParser.cs

@@ -157,28 +157,28 @@ namespace MediaBrowser.Controller.Xml
 
                 case "Director":
                     {
-                        var list = item.People ?? new List<PersonInfo>();
-                        list.AddRange(GetSplitValues(reader.ReadElementContentAsString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Director" }));
-
-                        item.People = list;
+                        foreach (PersonInfo p in GetSplitValues(reader.ReadElementContentAsString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Director" }))
+                        {
+                            item.AddPerson(p);
+                        }
                         break;
                     }
                 case "Writer":
                     {
-                        var list = item.People ?? new List<PersonInfo>();
-                        list.AddRange(GetSplitValues(reader.ReadElementContentAsString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Writer" }));
-
-                        item.People = list;
+                        foreach (PersonInfo p in GetSplitValues(reader.ReadElementContentAsString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Writer" }))
+                        {
+                            item.AddPerson(p);
+                        }
                         break;
                     }
 
                 case "Actors":
                 case "GuestStars":
                     {
-                        var list = item.People ?? new List<PersonInfo>();
-                        list.AddRange(GetSplitValues(reader.ReadElementContentAsString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Actor" }));
-
-                        item.People = list;
+                        foreach (PersonInfo p in GetSplitValues(reader.ReadElementContentAsString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Actor" }))
+                        {
+                            item.AddPerson(p);
+                        }
                         break;
                     }
 
@@ -556,8 +556,6 @@ namespace MediaBrowser.Controller.Xml
 
         private void FetchDataFromPersonsNode(XmlReader reader, T item)
         {
-            var list = item.People ?? new List<PersonInfo>();
-
             reader.MoveToContent();
 
             while (reader.Read())
@@ -568,7 +566,7 @@ namespace MediaBrowser.Controller.Xml
                     {
                         case "Person":
                             {
-                                list.Add(GetPersonFromXmlNode(reader.ReadSubtree()));
+                                item.AddPerson(GetPersonFromXmlNode(reader.ReadSubtree()));
                                 break;
                             }
 
@@ -578,8 +576,6 @@ namespace MediaBrowser.Controller.Xml
                     }
                 }
             }
-
-            item.People = list;
         }
 
         private void FetchFromStudiosNode(XmlReader reader, T item)

+ 14 - 1
MediaBrowser.Model/Entities/BaseItem.cs

@@ -58,7 +58,10 @@ namespace MediaBrowser.Model.Entities
         public string Overview { get; set; }
         public List<string> Taglines { get; set; }
 
-        public List<PersonInfo> People { get; set; }
+        /// <summary>
+        /// Using a Dictionary to prevent duplicates
+        /// </summary>
+        public Dictionary<string,PersonInfo> People { get; set; }
 
         public List<string> Studios { get; set; }
 
@@ -152,5 +155,15 @@ namespace MediaBrowser.Model.Entities
         {
             return (DateTime.Now - DateCreated).TotalDays < user.RecentItemDays;
         }
+
+        public void AddPerson(PersonInfo person)
+        {
+            if (People == null)
+            {
+                People = new Dictionary<string, PersonInfo>(StringComparer.OrdinalIgnoreCase);
+            }
+
+            People[person.Name] = person;
+        }
     }
 }

+ 2 - 2
MediaBrowser.Model/Entities/Folder.cs

@@ -97,7 +97,7 @@ namespace MediaBrowser.Model.Entities
             {
                 if (c.People != null)
                 {
-                    return c.People.Any(p => p.Name.Equals(person, StringComparison.OrdinalIgnoreCase));
+                    return c.People.ContainsKey(person);
                 }
 
                 return false;
@@ -114,7 +114,7 @@ namespace MediaBrowser.Model.Entities
             {
                 if (c.People != null)
                 {
-                    return c.People.Any(p => p.Name.Equals(person, StringComparison.OrdinalIgnoreCase) && p.Type == personType);
+                    return c.People.ContainsKey(person) && c.People[person].Type.Equals(personType, StringComparison.OrdinalIgnoreCase);
                 }
 
                 return false;