2
0

VideoHandler.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. using MediaBrowser.Common.Drawing;
  2. using MediaBrowser.Common.Net.Handlers;
  3. using MediaBrowser.Model.Entities;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.ComponentModel.Composition;
  7. using System.Drawing;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Net;
  11. namespace MediaBrowser.Api.HttpHandlers
  12. {
  13. /// <summary>
  14. /// Supported output formats: mkv,m4v,mp4,asf,wmv,mov,webm,ogv,3gp,avi,ts,flv
  15. /// </summary>
  16. [Export(typeof(BaseHandler))]
  17. class VideoHandler : BaseMediaHandler<Video, string>
  18. {
  19. public override bool HandlesRequest(HttpListenerRequest request)
  20. {
  21. return ApiService.IsApiUrlMatch("video", request);
  22. }
  23. /// <summary>
  24. /// We can output these files directly, but we can't encode them
  25. /// </summary>
  26. protected override IEnumerable<string> UnsupportedOutputEncodingFormats
  27. {
  28. get
  29. {
  30. // mp4, 3gp, mov - muxer does not support non-seekable output
  31. // avi, mov, mkv, m4v - can't stream these when encoding. the player will try to download them completely before starting playback.
  32. // wmv - can't seem to figure out the output format name
  33. return new string[] { "mp4", "3gp", "m4v", "mkv", "avi", "mov", "wmv" };
  34. }
  35. }
  36. protected override bool RequiresConversion()
  37. {
  38. string currentFormat = Path.GetExtension(LibraryItem.Path).Replace(".", string.Empty);
  39. // For now we won't allow these to pass through.
  40. // Later we'll add some intelligence to allow it when possible
  41. if (currentFormat.Equals("mp4", StringComparison.OrdinalIgnoreCase) || currentFormat.Equals("mkv", StringComparison.OrdinalIgnoreCase) || currentFormat.Equals("m4v", StringComparison.OrdinalIgnoreCase))
  42. {
  43. return true;
  44. }
  45. if (base.RequiresConversion())
  46. {
  47. return true;
  48. }
  49. if (RequiresVideoConversion())
  50. {
  51. return true;
  52. }
  53. AudioStream audioStream = (LibraryItem.AudioStreams ?? new List<AudioStream>()).FirstOrDefault();
  54. if (audioStream != null)
  55. {
  56. if (RequiresAudioConversion(audioStream))
  57. {
  58. return true;
  59. }
  60. }
  61. // Yay
  62. return false;
  63. }
  64. /// <summary>
  65. /// Translates the file extension to the format param that follows "-f" on the ffmpeg command line
  66. /// </summary>
  67. private string GetFFMpegOutputFormat(string outputFormat)
  68. {
  69. if (outputFormat.Equals("mkv", StringComparison.OrdinalIgnoreCase))
  70. {
  71. return "matroska";
  72. }
  73. else if (outputFormat.Equals("ts", StringComparison.OrdinalIgnoreCase))
  74. {
  75. return "mpegts";
  76. }
  77. else if (outputFormat.Equals("ogv", StringComparison.OrdinalIgnoreCase))
  78. {
  79. return "ogg";
  80. }
  81. return outputFormat;
  82. }
  83. /// <summary>
  84. /// Creates arguments to pass to ffmpeg
  85. /// </summary>
  86. protected override string GetCommandLineArguments()
  87. {
  88. List<string> audioTranscodeParams = new List<string>();
  89. string outputFormat = GetConversionOutputFormat();
  90. return string.Format("-i \"{0}\" -threads 0 {1} {2} -f {3} -",
  91. LibraryItem.Path,
  92. GetVideoArguments(outputFormat),
  93. GetAudioArguments(outputFormat),
  94. GetFFMpegOutputFormat(outputFormat)
  95. );
  96. }
  97. private string GetVideoArguments(string outputFormat)
  98. {
  99. string codec = GetVideoCodec(outputFormat);
  100. string args = "-vcodec " + codec;
  101. if (!codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
  102. {
  103. if (Width.HasValue || Height.HasValue || MaxHeight.HasValue || MaxWidth.HasValue)
  104. {
  105. Size size = DrawingUtils.Resize(LibraryItem.Width, LibraryItem.Height, Width, Height, MaxWidth, MaxHeight);
  106. args += string.Format(" -s {0}x{1}", size.Width, size.Height);
  107. }
  108. }
  109. return args;
  110. }
  111. private string GetAudioArguments(string outputFormat)
  112. {
  113. AudioStream audioStream = (LibraryItem.AudioStreams ?? new List<AudioStream>()).FirstOrDefault();
  114. if (audioStream == null)
  115. {
  116. return string.Empty;
  117. }
  118. string codec = GetAudioCodec(audioStream, outputFormat);
  119. string args = "-acodec " + codec;
  120. if (!codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
  121. {
  122. int? channels = GetNumAudioChannelsParam(codec, audioStream.Channels);
  123. if (channels.HasValue)
  124. {
  125. args += " -ac " + channels.Value;
  126. }
  127. int? sampleRate = GetSampleRateParam(audioStream.SampleRate);
  128. if (sampleRate.HasValue)
  129. {
  130. args += " -ar " + sampleRate.Value;
  131. }
  132. }
  133. return args;
  134. }
  135. private string GetVideoCodec(string outputFormat)
  136. {
  137. if (outputFormat.Equals("webm"))
  138. {
  139. // Per webm specification, it must be vpx
  140. return "libvpx";
  141. }
  142. else if (outputFormat.Equals("asf"))
  143. {
  144. return "wmv2";
  145. }
  146. else if (outputFormat.Equals("wmv"))
  147. {
  148. return "wmv2";
  149. }
  150. else if (outputFormat.Equals("ogv"))
  151. {
  152. return "libtheora";
  153. }
  154. if (!RequiresVideoConversion())
  155. {
  156. return "copy";
  157. }
  158. return "libx264";
  159. }
  160. private string GetAudioCodec(AudioStream audioStream, string outputFormat)
  161. {
  162. if (outputFormat.Equals("webm"))
  163. {
  164. // Per webm specification, it must be vorbis
  165. return "libvorbis";
  166. }
  167. else if (outputFormat.Equals("asf"))
  168. {
  169. return "wmav2";
  170. }
  171. else if (outputFormat.Equals("wmv"))
  172. {
  173. return "wmav2";
  174. }
  175. else if (outputFormat.Equals("ogv"))
  176. {
  177. return "libvorbis";
  178. }
  179. // See if we can just copy the stream
  180. if (!RequiresAudioConversion(audioStream))
  181. {
  182. return "copy";
  183. }
  184. return "libvo_aacenc";
  185. }
  186. private int? GetNumAudioChannelsParam(string audioCodec, int libraryItemChannels)
  187. {
  188. if (libraryItemChannels > 2)
  189. {
  190. if (audioCodec.Equals("libvo_aacenc"))
  191. {
  192. // libvo_aacenc currently only supports two channel output
  193. return 2;
  194. }
  195. else if (audioCodec.Equals("wmav2"))
  196. {
  197. // wmav2 currently only supports two channel output
  198. return 2;
  199. }
  200. }
  201. return GetNumAudioChannelsParam(libraryItemChannels);
  202. }
  203. private bool RequiresVideoConversion()
  204. {
  205. // Check dimensions
  206. if (Width.HasValue)
  207. {
  208. if (Width.Value != LibraryItem.Width)
  209. {
  210. return true;
  211. }
  212. }
  213. if (Height.HasValue)
  214. {
  215. if (Height.Value != LibraryItem.Height)
  216. {
  217. return true;
  218. }
  219. }
  220. if (MaxWidth.HasValue)
  221. {
  222. if (MaxWidth.Value < LibraryItem.Width)
  223. {
  224. return true;
  225. }
  226. }
  227. if (MaxHeight.HasValue)
  228. {
  229. if (MaxHeight.Value < LibraryItem.Height)
  230. {
  231. return true;
  232. }
  233. }
  234. if (LibraryItem.Codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || LibraryItem.Codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1)
  235. {
  236. return false;
  237. }
  238. return false;
  239. }
  240. private bool RequiresAudioConversion(AudioStream audio)
  241. {
  242. if (AudioChannels.HasValue)
  243. {
  244. if (audio.Channels > AudioChannels.Value)
  245. {
  246. return true;
  247. }
  248. }
  249. if (audio.Codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1)
  250. {
  251. return false;
  252. }
  253. if (audio.Codec.IndexOf("ac-3", StringComparison.OrdinalIgnoreCase) != -1 || audio.Codec.IndexOf("ac3", StringComparison.OrdinalIgnoreCase) != -1)
  254. {
  255. return false;
  256. }
  257. if (audio.Codec.IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1 || audio.Codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
  258. {
  259. return false;
  260. }
  261. return true;
  262. }
  263. private int? Height
  264. {
  265. get
  266. {
  267. string val = QueryString["height"];
  268. if (string.IsNullOrEmpty(val))
  269. {
  270. return null;
  271. }
  272. return int.Parse(val);
  273. }
  274. }
  275. private int? Width
  276. {
  277. get
  278. {
  279. string val = QueryString["width"];
  280. if (string.IsNullOrEmpty(val))
  281. {
  282. return null;
  283. }
  284. return int.Parse(val);
  285. }
  286. }
  287. private int? MaxHeight
  288. {
  289. get
  290. {
  291. string val = QueryString["maxheight"];
  292. if (string.IsNullOrEmpty(val))
  293. {
  294. return null;
  295. }
  296. return int.Parse(val);
  297. }
  298. }
  299. private int? MaxWidth
  300. {
  301. get
  302. {
  303. string val = QueryString["maxwidth"];
  304. if (string.IsNullOrEmpty(val))
  305. {
  306. return null;
  307. }
  308. return int.Parse(val);
  309. }
  310. }
  311. }
  312. }