FFProbe.cs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. using MediaBrowser.Common.Logging;
  2. using MediaBrowser.Common.Serialization;
  3. using MediaBrowser.Controller.Entities;
  4. using System;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Threading.Tasks;
  8. namespace MediaBrowser.Controller.FFMpeg
  9. {
  10. /// <summary>
  11. /// Runs FFProbe against a media file and returns metadata.
  12. /// </summary>
  13. public static class FFProbe
  14. {
  15. /// <summary>
  16. /// Runs FFProbe against an Audio file, caches the result and returns the output
  17. /// </summary>
  18. public static FFProbeResult Run(BaseItem item, string cacheDirectory)
  19. {
  20. string cachePath = GetFfProbeCachePath(item, cacheDirectory);
  21. // Use try catch to avoid having to use File.Exists
  22. try
  23. {
  24. return GetCachedResult(cachePath);
  25. }
  26. catch (FileNotFoundException)
  27. {
  28. }
  29. catch (Exception ex)
  30. {
  31. Logger.LogException(ex);
  32. }
  33. FFProbeResult result = Run(item.Path);
  34. if (result != null)
  35. {
  36. // Fire and forget
  37. CacheResult(result, cachePath);
  38. }
  39. return result;
  40. }
  41. /// <summary>
  42. /// Gets the cached result of an FFProbe operation
  43. /// </summary>
  44. private static FFProbeResult GetCachedResult(string path)
  45. {
  46. return ProtobufSerializer.DeserializeFromFile<FFProbeResult>(path);
  47. }
  48. /// <summary>
  49. /// Caches the result of an FFProbe operation
  50. /// </summary>
  51. private static async void CacheResult(FFProbeResult result, string outputCachePath)
  52. {
  53. await Task.Run(() =>
  54. {
  55. try
  56. {
  57. ProtobufSerializer.SerializeToFile(result, outputCachePath);
  58. }
  59. catch (Exception ex)
  60. {
  61. Logger.LogException(ex);
  62. }
  63. }).ConfigureAwait(false);
  64. }
  65. private static FFProbeResult Run(string input)
  66. {
  67. var startInfo = new ProcessStartInfo { };
  68. startInfo.CreateNoWindow = true;
  69. startInfo.UseShellExecute = false;
  70. // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
  71. startInfo.RedirectStandardOutput = true;
  72. startInfo.RedirectStandardError = true;
  73. startInfo.FileName = Kernel.Instance.ApplicationPaths.FFProbePath;
  74. startInfo.WorkingDirectory = Kernel.Instance.ApplicationPaths.FFMpegDirectory;
  75. startInfo.Arguments = string.Format("\"{0}\" -v quiet -print_format json -show_streams -show_format", input);
  76. //Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments);
  77. var process = new Process { };
  78. process.StartInfo = startInfo;
  79. process.EnableRaisingEvents = true;
  80. process.Exited += ProcessExited;
  81. try
  82. {
  83. process.Start();
  84. // MUST read both stdout and stderr asynchronously or a deadlock may occurr
  85. // If we ever decide to disable the ffmpeg log then you must uncomment the below line.
  86. process.BeginErrorReadLine();
  87. return JsonSerializer.DeserializeFromStream<FFProbeResult>(process.StandardOutput.BaseStream);
  88. }
  89. catch (Exception ex)
  90. {
  91. Logger.LogException(ex);
  92. // Hate having to do this
  93. try
  94. {
  95. process.Kill();
  96. }
  97. catch
  98. {
  99. }
  100. return null;
  101. }
  102. }
  103. static void ProcessExited(object sender, EventArgs e)
  104. {
  105. (sender as Process).Dispose();
  106. }
  107. private static string GetFfProbeCachePath(BaseItem item, string cacheDirectory)
  108. {
  109. string outputDirectory = Path.Combine(cacheDirectory, item.Id.ToString().Substring(0, 1));
  110. return Path.Combine(outputDirectory, item.Id + "-" + item.DateModified.Ticks + ".pb");
  111. }
  112. }
  113. }