FFProbe.cs 5.3 KB

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