VideoImagesTask.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. using MediaBrowser.Common.IO;
  2. using MediaBrowser.Common.MediaInfo;
  3. using MediaBrowser.Common.ScheduledTasks;
  4. using MediaBrowser.Controller;
  5. using MediaBrowser.Controller.Entities;
  6. using MediaBrowser.Controller.Library;
  7. using MediaBrowser.Controller.Providers.MediaInfo;
  8. using MediaBrowser.Model.Entities;
  9. using System;
  10. using System.Collections.Concurrent;
  11. using System.Collections.Generic;
  12. using System.Linq;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. namespace MediaBrowser.Server.Implementations.ScheduledTasks
  16. {
  17. /// <summary>
  18. /// Class VideoImagesTask
  19. /// </summary>
  20. public class VideoImagesTask : IScheduledTask
  21. {
  22. /// <summary>
  23. /// Gets or sets the image cache.
  24. /// </summary>
  25. /// <value>The image cache.</value>
  26. public FileSystemRepository ImageCache { get; set; }
  27. /// <summary>
  28. /// The _library manager
  29. /// </summary>
  30. private readonly ILibraryManager _libraryManager;
  31. /// <summary>
  32. /// The _media encoder
  33. /// </summary>
  34. private readonly IMediaEncoder _mediaEncoder;
  35. /// <summary>
  36. /// The _iso manager
  37. /// </summary>
  38. private readonly IIsoManager _isoManager;
  39. /// <summary>
  40. /// The _locks
  41. /// </summary>
  42. private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
  43. /// <summary>
  44. /// Initializes a new instance of the <see cref="AudioImagesTask" /> class.
  45. /// </summary>
  46. /// <param name="libraryManager">The library manager.</param>
  47. /// <param name="mediaEncoder">The media encoder.</param>
  48. /// <param name="isoManager">The iso manager.</param>
  49. public VideoImagesTask(ILibraryManager libraryManager, IMediaEncoder mediaEncoder, IIsoManager isoManager)
  50. {
  51. _libraryManager = libraryManager;
  52. _mediaEncoder = mediaEncoder;
  53. _isoManager = isoManager;
  54. ImageCache = new FileSystemRepository(Kernel.Instance.FFMpegManager.VideoImagesDataPath);
  55. }
  56. /// <summary>
  57. /// Gets the name of the task
  58. /// </summary>
  59. /// <value>The name.</value>
  60. public string Name
  61. {
  62. get { return "Video image extraction"; }
  63. }
  64. /// <summary>
  65. /// Gets the description.
  66. /// </summary>
  67. /// <value>The description.</value>
  68. public string Description
  69. {
  70. get { return "Extracts images from audio files that do not have external images."; }
  71. }
  72. /// <summary>
  73. /// Gets the category.
  74. /// </summary>
  75. /// <value>The category.</value>
  76. public string Category
  77. {
  78. get { return "Library"; }
  79. }
  80. /// <summary>
  81. /// Executes the task
  82. /// </summary>
  83. /// <param name="cancellationToken">The cancellation token.</param>
  84. /// <param name="progress">The progress.</param>
  85. /// <returns>Task.</returns>
  86. public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
  87. {
  88. var items = _libraryManager.RootFolder.RecursiveChildren
  89. .OfType<Video>()
  90. .Where(i =>
  91. {
  92. if (!string.IsNullOrEmpty(i.PrimaryImagePath))
  93. {
  94. return false;
  95. }
  96. if (i.LocationType != LocationType.FileSystem)
  97. {
  98. return false;
  99. }
  100. if (i.VideoType == VideoType.HdDvd)
  101. {
  102. return false;
  103. }
  104. if (i.VideoType == VideoType.Iso && !i.IsoType.HasValue)
  105. {
  106. return false;
  107. }
  108. return i.MediaStreams != null && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video);
  109. })
  110. .ToList();
  111. progress.Report(0);
  112. var numComplete = 0;
  113. foreach (var item in items)
  114. {
  115. cancellationToken.ThrowIfCancellationRequested();
  116. var filename = item.Id + "_" + item.DateModified.Ticks + "_primary";
  117. var path = ImageCache.GetResourcePath(filename, ".jpg");
  118. var success = true;
  119. if (!ImageCache.ContainsFilePath(path))
  120. {
  121. var semaphore = GetLock(path);
  122. // Acquire a lock
  123. await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  124. // Check again
  125. if (!ImageCache.ContainsFilePath(path))
  126. {
  127. try
  128. {
  129. await ExtractImage(item, path, cancellationToken).ConfigureAwait(false);
  130. }
  131. catch
  132. {
  133. success = false;
  134. }
  135. finally
  136. {
  137. semaphore.Release();
  138. }
  139. }
  140. else
  141. {
  142. semaphore.Release();
  143. }
  144. }
  145. numComplete++;
  146. double percent = numComplete;
  147. percent /= items.Count;
  148. progress.Report(100 * percent);
  149. if (success)
  150. {
  151. // Image is already in the cache
  152. item.PrimaryImagePath = path;
  153. }
  154. }
  155. progress.Report(100);
  156. }
  157. /// <summary>
  158. /// Extracts the image.
  159. /// </summary>
  160. /// <param name="video">The video.</param>
  161. /// <param name="path">The path.</param>
  162. /// <param name="cancellationToken">The cancellation token.</param>
  163. /// <returns>Task.</returns>
  164. private async Task ExtractImage(Video video, string path, CancellationToken cancellationToken)
  165. {
  166. var isoMount = await MountIsoIfNeeded(video, cancellationToken).ConfigureAwait(false);
  167. try
  168. {
  169. // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
  170. // Always use 10 seconds for dvd because our duration could be out of whack
  171. var imageOffset = video.VideoType != VideoType.Dvd && video.RunTimeTicks.HasValue &&
  172. video.RunTimeTicks.Value > 0
  173. ? TimeSpan.FromTicks(Convert.ToInt64(video.RunTimeTicks.Value * .1))
  174. : TimeSpan.FromSeconds(10);
  175. InputType type;
  176. var inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
  177. await _mediaEncoder.ExtractImage(inputPath, type, imageOffset, path, cancellationToken).ConfigureAwait(false);
  178. video.PrimaryImagePath = path;
  179. }
  180. finally
  181. {
  182. if (isoMount != null)
  183. {
  184. isoMount.Dispose();
  185. }
  186. }
  187. }
  188. /// <summary>
  189. /// The null mount task result
  190. /// </summary>
  191. protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
  192. /// <summary>
  193. /// Mounts the iso if needed.
  194. /// </summary>
  195. /// <param name="item">The item.</param>
  196. /// <param name="cancellationToken">The cancellation token.</param>
  197. /// <returns>Task{IIsoMount}.</returns>
  198. protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
  199. {
  200. if (item.VideoType == VideoType.Iso)
  201. {
  202. return _isoManager.Mount(item.Path, cancellationToken);
  203. }
  204. return NullMountTaskResult;
  205. }
  206. /// <summary>
  207. /// Gets the default triggers.
  208. /// </summary>
  209. /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
  210. public IEnumerable<ITaskTrigger> GetDefaultTriggers()
  211. {
  212. return new ITaskTrigger[]
  213. {
  214. new DailyTrigger { TimeOfDay = TimeSpan.FromHours(2) }
  215. };
  216. }
  217. /// <summary>
  218. /// Gets the lock.
  219. /// </summary>
  220. /// <param name="filename">The filename.</param>
  221. /// <returns>System.Object.</returns>
  222. private SemaphoreSlim GetLock(string filename)
  223. {
  224. return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
  225. }
  226. }
  227. }