/// <summary> /// Reads all tags from matroska file and parses the XML for the requested tags (<paramref name="tagsToExtract"/>). /// </summary> /// <param name="tagsToExtract">Dictionary with tag names as keys.</param> public async Task ReadTagsAsync(IDictionary <string, IList <string> > tagsToExtract) { ProcessExecutionResult executionResult = null; // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess var arguments = string.Format("tags \"{0}\"", _lfsra.LocalFileSystemPath); await MKVEXTRACT_THROTTLE_LOCK.WaitAsync().ConfigureAwait(false); try { executionResult = await _lfsra.ExecuteWithResourceAccessAsync(_mkvExtractPath, arguments, _priorityClass, PROCESS_TIMEOUT_MS).ConfigureAwait(false); } catch (TaskCanceledException) { ServiceRegistration.Get <ILogger>().Warn("MatroskaInfoReader.ReadTags: External process aborted due to timeout: Executable='{0}', Arguments='{1}'", _mkvExtractPath, arguments); } finally { MKVEXTRACT_THROTTLE_LOCK.Release(); } if (executionResult != null && executionResult.Success && !string.IsNullOrEmpty(executionResult.StandardOutput)) { XDocument doc = XDocument.Parse(executionResult.StandardOutput); foreach (string key in new List <string>(tagsToExtract.Keys)) { string[] parts = key.Split('.'); int? targetType = null; string tagName; if (parts.Length == 2) { targetType = int.Parse(parts[0]); tagName = parts[1]; } else { tagName = parts[0]; } var result = from simpleTag in GetTagsForTargetType(doc, targetType).Elements("Simple") let nameElement = simpleTag.Element("Name") let stringElement = simpleTag.Element("String") where nameElement != null && nameElement.Value == tagName && stringElement != null && !string.IsNullOrWhiteSpace(stringElement.Value) select stringElement.Value; var resultList = result.ToList(); if (resultList.Any()) { tagsToExtract[key] = resultList.ToList(); } } } }
/// <summary> /// Executes FFProbe and ensures that it has access to the respective resource /// </summary> /// <param name="lfsra"><see cref="ILocalFsResourceAccessor"/> to which FFProbe needs access to</param> /// <param name="arguments">Arguments for FFProbe</param> /// <param name="priorityClass">Process priority</param> /// <param name="maxWaitMs">Maximum time to wait for completion</param> /// <returns>A <see cref="Task"/> representing the result of executing FFProbe</returns> /// <remarks> /// This is a convenience method that enables executing FFProbe directly on the <see cref="ILocalFsResourceAccessor"/> /// interface to which FFProbe needs access. The purpose of an <see cref="ILocalFsResourceAccessor"/> is providing /// access to a resource - not executing programs which is why this method is implemented as an extension method instead of /// a method directly on the interface. /// </remarks> public static Task <ProcessExecutionResult> FFProbeExecuteWithResourceAccessAsync(ILocalFsResourceAccessor lfsra, string arguments, ProcessPriorityClass priorityClass, int maxWaitMs) { return(lfsra.ExecuteWithResourceAccessAsync(_ffProbeBinPath, arguments, priorityClass, maxWaitMs)); }
private bool ExtractThumbnail(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). // VideoAspect must be present to be sure it is actually a video resource. if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) return false; byte[] thumb; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumb) && thumb != null) return true; // Check for a reasonable time offset long defaultVideoOffset = 720; long videoDuration; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_DURATION, out videoDuration)) { if (defaultVideoOffset > videoDuration * 1 / 3) defaultVideoOffset = videoDuration * 1 / 3; } string downscale = ",scale=iw/2:-1"; // Reduces the video frame size to a half of original int videoWidth; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_WIDTH, out videoWidth)) { // Don't downscale SD video frames, quality is already quite low. if (videoWidth > 0 && videoWidth <= 720) downscale = ""; } // ToDo: Move creation of temp file names to FileUtils class string tempFileName = Path.GetTempPath() + Guid.NewGuid() + ".jpg"; string executable = FileUtils.BuildAssemblyRelativePath("ffmpeg.exe"); string arguments = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1{3}\" -y \"{2}\"", defaultVideoOffset, // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess lfsra.LocalFileSystemPath, tempFileName, downscale); try { bool success; lock (FFMPEG_THROTTLE_LOCK) success = lfsra.ExecuteWithResourceAccessAsync(executable, arguments, ProcessPriorityClass.Idle, PROCESS_TIMEOUT_MS).Result.Success; if (success && File.Exists(tempFileName)) { var binary = FileUtils.ReadFile(tempFileName); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } else // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get<ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } catch (AggregateException ae) { ae.Handle(e => { if (e is TaskCanceledException) { ServiceRegistration.Get<ILogger>().Warn("VideoThumbnailer.ExtractThumbnail: External process aborted due to timeout: Executable='{0}', Arguments='{1}', Timeout='{2}'", executable, arguments, PROCESS_TIMEOUT_MS); return true; } return false; }); } finally { if (File.Exists(tempFileName)) File.Delete(tempFileName); } return true; }
private bool ExtractThumbnail(ILocalFsResourceAccessor lfsra, IDictionary <Guid, MediaItemAspect> extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). // VideoAspect must be present to be sure it is actually a video resource. if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) { return(false); } byte[] thumb; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumb) && thumb != null) { return(true); } // Check for a reasonable time offset long defaultVideoOffset = 720; long videoDuration; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_DURATION, out videoDuration)) { if (defaultVideoOffset > videoDuration * 1 / 3) { defaultVideoOffset = videoDuration * 1 / 3; } } string downscale = ",scale=iw/2:-1"; // Reduces the video frame size to a half of original int videoWidth; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_WIDTH, out videoWidth)) { // Don't downscale SD video frames, quality is already quite low. if (videoWidth > 0 && videoWidth <= 720) { downscale = ""; } } // ToDo: Move creation of temp file names to FileUtils class string tempFileName = Path.GetTempPath() + Guid.NewGuid() + ".jpg"; string executable = FileUtils.BuildAssemblyRelativePath("ffmpeg.exe"); string arguments = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1{3}\" -y \"{2}\"", defaultVideoOffset, // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess lfsra.LocalFileSystemPath, tempFileName, downscale); try { bool success; lock (FFMPEG_THROTTLE_LOCK) success = lfsra.ExecuteWithResourceAccessAsync(executable, arguments, ProcessPriorityClass.Idle, PROCESS_TIMEOUT_MS).Result.Success; if (success && File.Exists(tempFileName)) { var binary = FileUtils.ReadFile(tempFileName); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get <ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } else { // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } } catch (AggregateException ae) { ae.Handle(e => { if (e is TaskCanceledException) { ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer.ExtractThumbnail: External process aborted due to timeout: Executable='{0}', Arguments='{1}', Timeout='{2}'", executable, arguments, PROCESS_TIMEOUT_MS); return(true); } return(false); }); } finally { if (File.Exists(tempFileName)) { File.Delete(tempFileName); } } return(true); }