AudioHandler.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Common.Configuration;
  9. using MediaBrowser.Common.Logging;
  10. using MediaBrowser.Common.Net;
  11. using MediaBrowser.Common.Net.Handlers;
  12. using MediaBrowser.Controller;
  13. using MediaBrowser.Model.Entities;
  14. namespace MediaBrowser.Api.HttpHandlers
  15. {
  16. public class AudioHandler : BaseMediaHandler<Audio>
  17. {
  18. public IEnumerable<string> AudioFormats
  19. {
  20. get
  21. {
  22. string val = QueryString["audioformats"];
  23. if (string.IsNullOrEmpty(val))
  24. {
  25. return new string[] { "mp3" };
  26. }
  27. return val.Split(',');
  28. }
  29. }
  30. public IEnumerable<int> AudioBitRates
  31. {
  32. get
  33. {
  34. string val = QueryString["audioformats"];
  35. if (string.IsNullOrEmpty(val))
  36. {
  37. return new int[] { };
  38. }
  39. return val.Split(',').Select(v => int.Parse(v));
  40. }
  41. }
  42. private int? GetMaxAcceptedBitRate(string audioFormat)
  43. {
  44. int index = AudioFormats.ToList().IndexOf(audioFormat);
  45. if (!AudioBitRates.Any())
  46. {
  47. return null;
  48. }
  49. return AudioBitRates.ElementAt(index);
  50. }
  51. /// <summary>
  52. /// Determines whether or not the original file requires transcoding
  53. /// </summary>
  54. protected override bool RequiresConversion()
  55. {
  56. string currentFormat = Path.GetExtension(LibraryItem.Path).Replace(".", string.Empty);
  57. // If it's not in a format the consumer accepts, return true
  58. if (!AudioFormats.Any(f => currentFormat.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
  59. {
  60. return true;
  61. }
  62. int? bitrate = GetMaxAcceptedBitRate(currentFormat);
  63. // If the bitrate is greater than our desired bitrate, we need to transcode
  64. if (bitrate.HasValue && bitrate.Value < LibraryItem.BitRate)
  65. {
  66. return true;
  67. }
  68. // If the number of channels is greater than our desired channels, we need to transcode
  69. if (AudioChannels.HasValue && AudioChannels.Value < LibraryItem.Channels)
  70. {
  71. return true;
  72. }
  73. // If the sample rate is greater than our desired sample rate, we need to transcode
  74. if (AudioSampleRate.HasValue && AudioSampleRate.Value < LibraryItem.SampleRate)
  75. {
  76. return true;
  77. }
  78. // Yay
  79. return false;
  80. }
  81. /// <summary>
  82. /// Gets the format we'll be converting to
  83. /// </summary>
  84. protected override string GetOutputFormat()
  85. {
  86. return AudioFormats.First();
  87. }
  88. /// <summary>
  89. /// Creates arguments to pass to ffmpeg
  90. /// </summary>
  91. protected override string GetCommandLineArguments()
  92. {
  93. List<string> audioTranscodeParams = new List<string>();
  94. string outputFormat = GetOutputFormat();
  95. int? bitrate = GetMaxAcceptedBitRate(outputFormat);
  96. if (bitrate.HasValue)
  97. {
  98. audioTranscodeParams.Add("-ab " + bitrate.Value);
  99. }
  100. if (AudioChannels.HasValue)
  101. {
  102. audioTranscodeParams.Add("-ac " + AudioChannels.Value);
  103. }
  104. if (AudioSampleRate.HasValue)
  105. {
  106. audioTranscodeParams.Add("-ar " + AudioSampleRate.Value);
  107. }
  108. audioTranscodeParams.Add("-f " + outputFormat);
  109. return "-i \"" + LibraryItem.Path + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " -";
  110. }
  111. }
  112. public abstract class BaseMediaHandler<T> : BaseHandler
  113. where T : BaseItem, new()
  114. {
  115. private T _LibraryItem;
  116. /// <summary>
  117. /// Gets the library item that will be played, if any
  118. /// </summary>
  119. protected T LibraryItem
  120. {
  121. get
  122. {
  123. if (_LibraryItem == null)
  124. {
  125. string id = QueryString["id"];
  126. if (!string.IsNullOrEmpty(id))
  127. {
  128. _LibraryItem = Kernel.Instance.GetItemById(Guid.Parse(id)) as T;
  129. }
  130. }
  131. return _LibraryItem;
  132. }
  133. }
  134. public int? AudioChannels
  135. {
  136. get
  137. {
  138. string val = QueryString["audiochannels"];
  139. if (string.IsNullOrEmpty(val))
  140. {
  141. return null;
  142. }
  143. return int.Parse(val);
  144. }
  145. }
  146. public int? AudioSampleRate
  147. {
  148. get
  149. {
  150. string val = QueryString["audiosamplerate"];
  151. if (string.IsNullOrEmpty(val))
  152. {
  153. return 44100;
  154. }
  155. return int.Parse(val);
  156. }
  157. }
  158. public override string ContentType
  159. {
  160. get
  161. {
  162. return MimeTypes.GetMimeType("." + GetOutputFormat());
  163. }
  164. }
  165. public override bool CompressResponse
  166. {
  167. get
  168. {
  169. return false;
  170. }
  171. }
  172. public override void ProcessRequest(HttpListenerContext ctx)
  173. {
  174. HttpListenerContext = ctx;
  175. if (!RequiresConversion())
  176. {
  177. new StaticFileHandler() { Path = LibraryItem.Path }.ProcessRequest(ctx);
  178. return;
  179. }
  180. base.ProcessRequest(ctx);
  181. }
  182. protected abstract string GetCommandLineArguments();
  183. protected abstract string GetOutputFormat();
  184. protected abstract bool RequiresConversion();
  185. protected async override Task WriteResponseToOutputStream(Stream stream)
  186. {
  187. ProcessStartInfo startInfo = new ProcessStartInfo();
  188. startInfo.CreateNoWindow = true;
  189. startInfo.UseShellExecute = false;
  190. startInfo.RedirectStandardOutput = true;
  191. startInfo.RedirectStandardError = true;
  192. startInfo.FileName = ApiService.FFMpegPath;
  193. startInfo.WorkingDirectory = ApiService.FFMpegDirectory;
  194. startInfo.Arguments = GetCommandLineArguments();
  195. Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments);
  196. Process process = new Process();
  197. process.StartInfo = startInfo;
  198. // FFMpeg writes debug info to StdErr. This is useful when debugging so let's put it in the log directory.
  199. FileStream logStream = new FileStream(Path.Combine(ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid().ToString() + ".txt"), FileMode.Create);
  200. try
  201. {
  202. process.Start();
  203. // MUST read both stdout and stderr asynchronously or a deadlock may occurr
  204. // If we ever decide to disable the ffmpeg log then you must uncomment the below line.
  205. //process.BeginErrorReadLine();
  206. Task errorTask = Task.Run(async () => { await process.StandardError.BaseStream.CopyToAsync(logStream); });
  207. await process.StandardOutput.BaseStream.CopyToAsync(stream);
  208. process.WaitForExit();
  209. await errorTask;
  210. Logger.LogInfo("FFMpeg exited with code " + process.ExitCode);
  211. }
  212. catch (Exception ex)
  213. {
  214. Logger.LogException(ex);
  215. }
  216. finally
  217. {
  218. logStream.Dispose();
  219. process.Dispose();
  220. }
  221. }
  222. }
  223. }