diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2013-05-03 11:08:02 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2013-05-03 11:08:02 -0400 |
| commit | 43845b40523f91e7816f6ea9e250a93c6f648f4f (patch) | |
| tree | 0e22c2a18e468f1d9aa45ae60e6b375bc4ee0e3f | |
| parent | ceaae430c62c37996de131733b60cfd3f14fe31b (diff) | |
extract images for small numbers of items on discovery
5 files changed, 346 insertions, 174 deletions
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index cbf35f870..dd28218ce 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -1,5 +1,5 @@ -using System; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -10,6 +10,11 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> public class Audio : BaseItem, IHasMediaStreams { + public Audio() + { + MediaStreams = new List<MediaStream>(); + } + /// <summary> /// Gets or sets the media streams. /// </summary> diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs index aee33ad2e..087207024 100644 --- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs +++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs @@ -5,9 +5,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers.MediaInfo; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MoreLinq; using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -39,22 +37,6 @@ namespace MediaBrowser.Controller.MediaInfo private readonly ILogger _logger; /// <summary> - /// Holds the list of new items to generate chapter image for when the NewItemTimer expires - /// </summary> - private readonly List<Video> _newlyAddedItems = new List<Video>(); - - /// <summary> - /// The amount of time to wait before generating chapter images - /// </summary> - private const int NewItemDelay = 300000; - - /// <summary> - /// The current new item timer - /// </summary> - /// <value>The new item timer.</value> - private Timer NewItemTimer { get; set; } - - /// <summary> /// Initializes a new instance of the <see cref="FFMpegManager" /> class. /// </summary> /// <param name="appPaths">The app paths.</param> @@ -71,38 +53,6 @@ namespace MediaBrowser.Controller.MediaInfo VideoImageCache = new FileSystemRepository(VideoImagesDataPath); SubtitleCache = new FileSystemRepository(SubtitleCachePath); - - libraryManager.ItemAdded += libraryManager_ItemAdded; - libraryManager.ItemUpdated += libraryManager_ItemAdded; - } - - /// <summary> - /// Handles the ItemAdded event of the libraryManager control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> - void libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) - { - var video = e.Item as Video; - - if (video == null) - { - return; - } - - lock (_newlyAddedItems) - { - _newlyAddedItems.Add(video); - - if (NewItemTimer == null) - { - NewItemTimer = new Timer(NewItemTimerCallback, null, NewItemDelay, Timeout.Infinite); - } - else - { - NewItemTimer.Change(NewItemDelay, Timeout.Infinite); - } - } } /// <summary> @@ -182,40 +132,6 @@ namespace MediaBrowser.Controller.MediaInfo return _subtitleCachePath; } } - - /// <summary> - /// Called when the new item timer expires - /// </summary> - /// <param name="state">The state.</param> - private async void NewItemTimerCallback(object state) - { - List<Video> newItems; - - // Lock the list and release all resources - lock (_newlyAddedItems) - { - newItems = _newlyAddedItems.DistinctBy(i => i.Id).ToList(); - _newlyAddedItems.Clear(); - - NewItemTimer.Dispose(); - NewItemTimer = null; - } - - // Limit the number of videos we generate images for - // The idea is to catch new items that are added here and there - // Mass image generation can be left to the scheduled task - foreach (var video in newItems.Where(c => c.Chapters != null).Take(5)) - { - try - { - await PopulateChapterImages(video, CancellationToken.None, true, true).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error creating chapter images for {0}", ex, video.Name); - } - } - } /// <summary> /// The first chapter ticks diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs index 2d10917d3..b9a51ab78 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs @@ -11,6 +11,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Logging; +using MoreLinq; namespace MediaBrowser.Server.Implementations.ScheduledTasks { @@ -34,25 +36,102 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// </summary> private readonly IMediaEncoder _mediaEncoder; + private readonly ILogger _logger; + + /// <summary> /// The _locks /// </summary> private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>(); + private readonly List<Audio> _newlyAddedItems = new List<Audio>(); + + private const int NewItemDelay = 300000; + + /// <summary> + /// The current new item timer + /// </summary> + /// <value>The new item timer.</value> + private Timer NewItemTimer { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="AudioImagesTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="mediaEncoder">The media encoder.</param> - public AudioImagesTask(ILibraryManager libraryManager, IMediaEncoder mediaEncoder) + public AudioImagesTask(ILibraryManager libraryManager, IMediaEncoder mediaEncoder, ILogManager logManager) { _libraryManager = libraryManager; _mediaEncoder = mediaEncoder; + _logger = logManager.GetLogger(GetType().Name); ImageCache = new FileSystemRepository(Kernel.Instance.FFMpegManager.AudioImagesDataPath); + + libraryManager.ItemAdded += libraryManager_ItemAdded; + libraryManager.ItemUpdated += libraryManager_ItemAdded; } /// <summary> + /// Handles the ItemAdded event of the libraryManager control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> + void libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + var audio = e.Item as Audio; + + if (audio != null) + { + lock (_newlyAddedItems) + { + _newlyAddedItems.Add(audio); + + if (NewItemTimer == null) + { + NewItemTimer = new Timer(NewItemTimerCallback, null, NewItemDelay, Timeout.Infinite); + } + else + { + NewItemTimer.Change(NewItemDelay, Timeout.Infinite); + } + } + } + } + + /// <summary> + /// News the item timer callback. + /// </summary> + /// <param name="state">The state.</param> + private async void NewItemTimerCallback(object state) + { + List<Audio> newSongs; + + // Lock the list and release all resources + lock (_newlyAddedItems) + { + newSongs = _newlyAddedItems.DistinctBy(i => i.Id).ToList(); + _newlyAddedItems.Clear(); + + NewItemTimer.Dispose(); + NewItemTimer = null; + } + + foreach (var item in newSongs + .Where(i => i.LocationType == LocationType.FileSystem && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video)) + .Take(20)) + { + try + { + await CreateImagesForSong(item, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error creating image for {0}", ex, item.Name); + } + } + } + + /// <summary> /// Gets the name of the task /// </summary> /// <value>The name.</value> @@ -89,7 +168,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks { var items = _libraryManager.RootFolder.RecursiveChildren .OfType<Audio>() - .Where(i => i.LocationType == LocationType.FileSystem && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams != null && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video)) + .Where(i => i.LocationType == LocationType.FileSystem && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video)) .ToList(); progress.Report(0); @@ -98,63 +177,78 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks foreach (var item in items) { - cancellationToken.ThrowIfCancellationRequested(); + try + { + await CreateImagesForSong(item, cancellationToken).ConfigureAwait(false); + } + catch + { + // Already logged at lower levels. + // Just don't let the task fail + } + + numComplete++; + double percent = numComplete; + percent /= items.Count; + + progress.Report(100 * percent); + } + + progress.Report(100); + } + + /// <summary> + /// Creates the images for song. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task CreateImagesForSong(Audio item, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (item.MediaStreams.All(i => i.Type != MediaStreamType.Video)) + { + throw new InvalidOperationException("Can't extract an image unless the audio file has an embedded image."); + } - var album = item.Parent as MusicAlbum; + var album = item.Parent as MusicAlbum; - var filename = item.Album ?? string.Empty; + var filename = item.Album ?? string.Empty; - filename += album == null ? item.Id.ToString() + item.DateModified.Ticks : album.Id.ToString() + album.DateModified.Ticks; + filename += album == null ? item.Id.ToString() + item.DateModified.Ticks : album.Id.ToString() + album.DateModified.Ticks; - var path = ImageCache.GetResourcePath(filename + "_primary", ".jpg"); + var path = ImageCache.GetResourcePath(filename + "_primary", ".jpg"); - var success = true; + if (!ImageCache.ContainsFilePath(path)) + { + var semaphore = GetLock(path); + // Acquire a lock + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + // Check again if (!ImageCache.ContainsFilePath(path)) { - var semaphore = GetLock(path); - - // Acquire a lock - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - // Check again - if (!ImageCache.ContainsFilePath(path)) + try { - try - { - await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.AudioFile, null, path, cancellationToken).ConfigureAwait(false); - } - catch - { - success = false; - } - finally - { - semaphore.Release(); - } + await _mediaEncoder.ExtractImage(new[] {item.Path}, InputType.AudioFile, null, path, cancellationToken).ConfigureAwait(false); } - else + finally { semaphore.Release(); } - } - numComplete++; - double percent = numComplete; - percent /= items.Count; - - progress.Report(100 * percent); - - if (success) - { // Image is already in the cache item.PrimaryImagePath = path; await _libraryManager.UpdateItem(item, cancellationToken).ConfigureAwait(false); } + else + { + semaphore.Release(); + } } - - progress.Report(100); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index 7f158f1f2..87b99973d 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MoreLinq; namespace MediaBrowser.Server.Implementations.ScheduledTasks { @@ -30,19 +31,83 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// </summary> private readonly ILibraryManager _libraryManager; + private readonly List<Video> _newlyAddedItems = new List<Video>(); + + private const int NewItemDelay = 300000; + + /// <summary> + /// The current new item timer + /// </summary> + /// <value>The new item timer.</value> + private Timer NewItemTimer { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class. /// </summary> /// <param name="kernel">The kernel.</param> - /// <param name="logger">The logger.</param> + /// <param name="logManager">The log manager.</param> /// <param name="libraryManager">The library manager.</param> - public ChapterImagesTask(Kernel kernel, ILogger logger, ILibraryManager libraryManager) + public ChapterImagesTask(Kernel kernel, ILogManager logManager, ILibraryManager libraryManager) { _kernel = kernel; - _logger = logger; + _logger = logManager.GetLogger(GetType().Name); _libraryManager = libraryManager; + + libraryManager.ItemAdded += libraryManager_ItemAdded; + libraryManager.ItemUpdated += libraryManager_ItemAdded; + } + + void libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + var video = e.Item as Video; + + if (video != null) + { + lock (_newlyAddedItems) + { + _newlyAddedItems.Add(video); + + if (NewItemTimer == null) + { + NewItemTimer = new Timer(NewItemTimerCallback, null, NewItemDelay, Timeout.Infinite); + } + else + { + NewItemTimer.Change(NewItemDelay, Timeout.Infinite); + } + } + } } + private async void NewItemTimerCallback(object state) + { + List<Video> newItems; + + // Lock the list and release all resources + lock (_newlyAddedItems) + { + newItems = _newlyAddedItems.DistinctBy(i => i.Id).ToList(); + _newlyAddedItems.Clear(); + + NewItemTimer.Dispose(); + NewItemTimer = null; + } + + foreach (var item in newItems + .Where(i => i.LocationType == LocationType.FileSystem && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video)) + .Take(5)) + { + try + { + await _kernel.FFMpegManager.PopulateChapterImages(item, CancellationToken.None, true, true); + } + catch (Exception ex) + { + _logger.ErrorException("Error creating image for {0}", ex, item.Name); + } + } + } + /// <summary> /// Creates the triggers that define when the task will run /// </summary> diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs index c1f3173ee..d7863c0ba 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs @@ -13,6 +13,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Logging; +using MoreLinq; namespace MediaBrowser.Server.Implementations.ScheduledTasks { @@ -41,27 +43,97 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// </summary> private readonly IIsoManager _isoManager; + private readonly ILogger _logger; + /// <summary> /// The _locks /// </summary> private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>(); + private readonly List<BaseItem> _newlyAddedItems = new List<BaseItem>(); + + private const int NewItemDelay = 300000; + + /// <summary> + /// The current new item timer + /// </summary> + /// <value>The new item timer.</value> + private Timer NewItemTimer { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="AudioImagesTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> + /// <param name="logManager">The log manager.</param> /// <param name="mediaEncoder">The media encoder.</param> /// <param name="isoManager">The iso manager.</param> - public VideoImagesTask(ILibraryManager libraryManager, IMediaEncoder mediaEncoder, IIsoManager isoManager) + public VideoImagesTask(ILibraryManager libraryManager, ILogManager logManager, IMediaEncoder mediaEncoder, IIsoManager isoManager) { _libraryManager = libraryManager; _mediaEncoder = mediaEncoder; _isoManager = isoManager; + _logger = logManager.GetLogger(GetType().Name); ImageCache = new FileSystemRepository(Kernel.Instance.FFMpegManager.VideoImagesDataPath); + + libraryManager.ItemAdded += libraryManager_ItemAdded; + libraryManager.ItemUpdated += libraryManager_ItemAdded; + } + + /// <summary> + /// Handles the ItemAdded event of the libraryManager control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> + void libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + lock (_newlyAddedItems) + { + _newlyAddedItems.Add(e.Item); + + if (NewItemTimer == null) + { + NewItemTimer = new Timer(NewItemTimerCallback, null, NewItemDelay, Timeout.Infinite); + } + else + { + NewItemTimer.Change(NewItemDelay, Timeout.Infinite); + } + } } /// <summary> + /// News the item timer callback. + /// </summary> + /// <param name="state">The state.</param> + private async void NewItemTimerCallback(object state) + { + List<BaseItem> newItems; + + // Lock the list and release all resources + lock (_newlyAddedItems) + { + newItems = _newlyAddedItems.DistinctBy(i => i.Id).ToList(); + _newlyAddedItems.Clear(); + + NewItemTimer.Dispose(); + NewItemTimer = null; + } + + foreach (var item in GetItemsForExtraction(newItems.Take(5))) + { + try + { + await ExtractImage(item, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error creating image for {0}", ex, item.Name); + } + } + } + + /// <summary> /// Gets the name of the task /// </summary> /// <value>The name.</value> @@ -96,7 +168,42 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// <returns>Task.</returns> public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { - var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList(); + var items = GetItemsForExtraction(_libraryManager.RootFolder.RecursiveChildren).ToList(); + + progress.Report(0); + + var numComplete = 0; + + foreach (var item in items) + { + try + { + await ExtractImage(item, cancellationToken).ConfigureAwait(false); + } + catch + { + // Already logged at lower levels. + // Just don't let the task fail + } + + numComplete++; + double percent = numComplete; + percent /= items.Count; + + progress.Report(100 * percent); + } + + progress.Report(100); + } + + /// <summary> + /// Gets the items for extraction. + /// </summary> + /// <param name="sourceItems">The source items.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + private IEnumerable<Video> GetItemsForExtraction(IEnumerable<BaseItem> sourceItems) + { + var allItems = sourceItems.ToList(); var localTrailers = allItems.SelectMany(i => i.LocalTrailers); var themeVideos = allItems.SelectMany(i => i.ThemeVideos); @@ -108,7 +215,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks items.AddRange(themeVideos); items.AddRange(videos.OfType<Movie>().SelectMany(i => i.SpecialFeatures).ToList()); - items = items.Where(i => + return items.Where(i => { if (!string.IsNullOrEmpty(i.PrimaryImagePath)) { @@ -131,69 +238,54 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks } return i.MediaStreams != null && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video); - }).ToList(); - - progress.Report(0); + }); + } - var numComplete = 0; + /// <summary> + /// Extracts the image. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task ExtractImage(Video item, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - foreach (var item in items) - { - cancellationToken.ThrowIfCancellationRequested(); + var filename = item.Id + "_" + item.DateModified.Ticks + "_primary"; - var filename = item.Id + "_" + item.DateModified.Ticks + "_primary"; + var path = ImageCache.GetResourcePath(filename, ".jpg"); - var path = ImageCache.GetResourcePath(filename, ".jpg"); + if (!ImageCache.ContainsFilePath(path)) + { + var semaphore = GetLock(path); - var success = true; + // Acquire a lock + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + // Check again if (!ImageCache.ContainsFilePath(path)) { - var semaphore = GetLock(path); - - // Acquire a lock - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - // Check again - if (!ImageCache.ContainsFilePath(path)) + try { - try - { - await ExtractImage(item, path, cancellationToken).ConfigureAwait(false); - } - catch - { - success = false; - } - finally - { - semaphore.Release(); - } + await ExtractImageInternal(item, path, cancellationToken).ConfigureAwait(false); } - else + finally { semaphore.Release(); } - } - numComplete++; - double percent = numComplete; - percent /= items.Count; - - progress.Report(100 * percent); - - if (success) - { // Image is already in the cache item.PrimaryImagePath = path; await _libraryManager.UpdateItem(item, cancellationToken).ConfigureAwait(false); } + else + { + semaphore.Release(); + } } - - progress.Report(100); } - + /// <summary> /// Extracts the image. /// </summary> @@ -201,7 +293,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// <param name="path">The path.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task ExtractImage(Video video, string path, CancellationToken cancellationToken) + private async Task ExtractImageInternal(Video video, string path, CancellationToken cancellationToken) { var isoMount = await MountIsoIfNeeded(video, cancellationToken).ConfigureAwait(false); |
