BaseStreamingService.cs 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.IO;
  3. using MediaBrowser.Controller.Configuration;
  4. using MediaBrowser.Controller.Dlna;
  5. using MediaBrowser.Controller.Dto;
  6. using MediaBrowser.Controller.Entities;
  7. using MediaBrowser.Controller.Library;
  8. using MediaBrowser.Controller.LiveTv;
  9. using MediaBrowser.Controller.MediaEncoding;
  10. using MediaBrowser.Controller.Persistence;
  11. using MediaBrowser.Model.Configuration;
  12. using MediaBrowser.Model.Dlna;
  13. using MediaBrowser.Model.Drawing;
  14. using MediaBrowser.Model.Entities;
  15. using MediaBrowser.Model.IO;
  16. using MediaBrowser.Model.Library;
  17. using MediaBrowser.Model.LiveTv;
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Globalization;
  22. using System.IO;
  23. using System.Linq;
  24. using System.Text;
  25. using System.Threading;
  26. using System.Threading.Tasks;
  27. namespace MediaBrowser.Api.Playback
  28. {
  29. /// <summary>
  30. /// Class BaseStreamingService
  31. /// </summary>
  32. public abstract class BaseStreamingService : BaseApiService
  33. {
  34. /// <summary>
  35. /// Gets or sets the application paths.
  36. /// </summary>
  37. /// <value>The application paths.</value>
  38. protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
  39. /// <summary>
  40. /// Gets or sets the user manager.
  41. /// </summary>
  42. /// <value>The user manager.</value>
  43. protected IUserManager UserManager { get; private set; }
  44. /// <summary>
  45. /// Gets or sets the library manager.
  46. /// </summary>
  47. /// <value>The library manager.</value>
  48. protected ILibraryManager LibraryManager { get; private set; }
  49. /// <summary>
  50. /// Gets or sets the iso manager.
  51. /// </summary>
  52. /// <value>The iso manager.</value>
  53. protected IIsoManager IsoManager { get; private set; }
  54. /// <summary>
  55. /// Gets or sets the media encoder.
  56. /// </summary>
  57. /// <value>The media encoder.</value>
  58. protected IMediaEncoder MediaEncoder { get; private set; }
  59. protected IEncodingManager EncodingManager { get; private set; }
  60. protected IDtoService DtoService { get; private set; }
  61. protected IFileSystem FileSystem { get; private set; }
  62. protected IItemRepository ItemRepository { get; private set; }
  63. protected ILiveTvManager LiveTvManager { get; private set; }
  64. protected IDlnaManager DlnaManager { get; private set; }
  65. /// <summary>
  66. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
  67. /// </summary>
  68. /// <param name="serverConfig">The server configuration.</param>
  69. /// <param name="userManager">The user manager.</param>
  70. /// <param name="libraryManager">The library manager.</param>
  71. /// <param name="isoManager">The iso manager.</param>
  72. /// <param name="mediaEncoder">The media encoder.</param>
  73. /// <param name="dtoService">The dto service.</param>
  74. /// <param name="fileSystem">The file system.</param>
  75. /// <param name="itemRepository">The item repository.</param>
  76. protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager)
  77. {
  78. DlnaManager = dlnaManager;
  79. EncodingManager = encodingManager;
  80. LiveTvManager = liveTvManager;
  81. ItemRepository = itemRepository;
  82. FileSystem = fileSystem;
  83. DtoService = dtoService;
  84. ServerConfigurationManager = serverConfig;
  85. UserManager = userManager;
  86. LibraryManager = libraryManager;
  87. IsoManager = isoManager;
  88. MediaEncoder = mediaEncoder;
  89. }
  90. /// <summary>
  91. /// Gets the command line arguments.
  92. /// </summary>
  93. /// <param name="outputPath">The output path.</param>
  94. /// <param name="state">The state.</param>
  95. /// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
  96. /// <returns>System.String.</returns>
  97. protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions);
  98. /// <summary>
  99. /// Gets the type of the transcoding job.
  100. /// </summary>
  101. /// <value>The type of the transcoding job.</value>
  102. protected abstract TranscodingJobType TranscodingJobType { get; }
  103. /// <summary>
  104. /// Gets the output file extension.
  105. /// </summary>
  106. /// <param name="state">The state.</param>
  107. /// <returns>System.String.</returns>
  108. protected virtual string GetOutputFileExtension(StreamState state)
  109. {
  110. return Path.GetExtension(state.RequestedUrl);
  111. }
  112. /// <summary>
  113. /// Gets the output file path.
  114. /// </summary>
  115. /// <param name="state">The state.</param>
  116. /// <returns>System.String.</returns>
  117. protected string GetOutputFilePath(StreamState state)
  118. {
  119. var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
  120. var outputFileExtension = GetOutputFileExtension(state);
  121. return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state, false).GetMD5() + (outputFileExtension ?? string.Empty).ToLower());
  122. }
  123. protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
  124. /// <summary>
  125. /// Gets the fast seek command line parameter.
  126. /// </summary>
  127. /// <param name="request">The request.</param>
  128. /// <returns>System.String.</returns>
  129. /// <value>The fast seek command line parameter.</value>
  130. protected string GetFastSeekCommandLineParameter(StreamRequest request)
  131. {
  132. var time = request.StartTimeTicks;
  133. if (time.HasValue)
  134. {
  135. var seconds = TimeSpan.FromTicks(time.Value).TotalSeconds;
  136. if (seconds > 0)
  137. {
  138. return string.Format("-ss {0}", seconds.ToString(UsCulture));
  139. }
  140. }
  141. return string.Empty;
  142. }
  143. /// <summary>
  144. /// Gets the map args.
  145. /// </summary>
  146. /// <param name="state">The state.</param>
  147. /// <returns>System.String.</returns>
  148. protected virtual string GetMapArgs(StreamState state)
  149. {
  150. var args = string.Empty;
  151. if (!state.HasMediaStreams)
  152. {
  153. return state.IsInputVideo ? "-sn" : string.Empty;
  154. }
  155. if (state.VideoStream != null)
  156. {
  157. args += string.Format("-map 0:{0}", state.VideoStream.Index);
  158. }
  159. else
  160. {
  161. args += "-map -0:v";
  162. }
  163. if (state.AudioStream != null)
  164. {
  165. args += string.Format(" -map 0:{0}", state.AudioStream.Index);
  166. }
  167. else
  168. {
  169. args += " -map -0:a";
  170. }
  171. if (state.SubtitleStream == null)
  172. {
  173. args += " -map -0:s";
  174. }
  175. return args;
  176. }
  177. /// <summary>
  178. /// Determines which stream will be used for playback
  179. /// </summary>
  180. /// <param name="allStream">All stream.</param>
  181. /// <param name="desiredIndex">Index of the desired.</param>
  182. /// <param name="type">The type.</param>
  183. /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
  184. /// <returns>MediaStream.</returns>
  185. private MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
  186. {
  187. var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
  188. if (desiredIndex.HasValue)
  189. {
  190. var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
  191. if (stream != null)
  192. {
  193. return stream;
  194. }
  195. }
  196. if (type == MediaStreamType.Video)
  197. {
  198. streams = streams.Where(i => !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)).ToList();
  199. }
  200. if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
  201. {
  202. return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
  203. streams.FirstOrDefault();
  204. }
  205. // Just return the first one
  206. return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
  207. }
  208. protected EncodingQuality GetQualitySetting()
  209. {
  210. var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
  211. if (quality == EncodingQuality.Auto)
  212. {
  213. var cpuCount = Environment.ProcessorCount;
  214. if (cpuCount >= 4)
  215. {
  216. //return EncodingQuality.HighQuality;
  217. }
  218. return EncodingQuality.HighSpeed;
  219. }
  220. return quality;
  221. }
  222. /// <summary>
  223. /// Gets the number of threads.
  224. /// </summary>
  225. /// <returns>System.Int32.</returns>
  226. /// <exception cref="System.Exception">Unrecognized MediaEncodingQuality value.</exception>
  227. protected int GetNumberOfThreads(StreamState state, bool isWebm)
  228. {
  229. // Use more when this is true. -re will keep cpu usage under control
  230. if (state.ReadInputAtNativeFramerate)
  231. {
  232. if (isWebm)
  233. {
  234. return Math.Max(Environment.ProcessorCount - 1, 2);
  235. }
  236. return 0;
  237. }
  238. // Webm: http://www.webmproject.org/docs/encoder-parameters/
  239. // The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads
  240. // for the coefficient data if the encoder selected --token-parts > 0 at encode time.
  241. switch (GetQualitySetting())
  242. {
  243. case EncodingQuality.HighSpeed:
  244. return 2;
  245. case EncodingQuality.HighQuality:
  246. return 2;
  247. case EncodingQuality.MaxQuality:
  248. return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
  249. default:
  250. throw new Exception("Unrecognized MediaEncodingQuality value.");
  251. }
  252. }
  253. /// <summary>
  254. /// Gets the video bitrate to specify on the command line
  255. /// </summary>
  256. /// <param name="state">The state.</param>
  257. /// <param name="videoCodec">The video codec.</param>
  258. /// <param name="isHls">if set to <c>true</c> [is HLS].</param>
  259. /// <returns>System.String.</returns>
  260. protected string GetVideoQualityParam(StreamState state, string videoCodec, bool isHls)
  261. {
  262. var param = string.Empty;
  263. var isVc1 = state.VideoStream != null &&
  264. string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
  265. var qualitySetting = GetQualitySetting();
  266. if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
  267. {
  268. switch (qualitySetting)
  269. {
  270. case EncodingQuality.HighSpeed:
  271. param = "-preset ultrafast";
  272. break;
  273. case EncodingQuality.HighQuality:
  274. param = "-preset superfast";
  275. break;
  276. case EncodingQuality.MaxQuality:
  277. param = "-preset superfast";
  278. break;
  279. }
  280. switch (qualitySetting)
  281. {
  282. case EncodingQuality.HighSpeed:
  283. param += " -crf 23";
  284. break;
  285. case EncodingQuality.HighQuality:
  286. param += " -crf 20";
  287. break;
  288. case EncodingQuality.MaxQuality:
  289. param += " -crf 18";
  290. break;
  291. }
  292. }
  293. // webm
  294. else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
  295. {
  296. // Values 0-3, 0 being highest quality but slower
  297. var profileScore = 0;
  298. string crf;
  299. switch (qualitySetting)
  300. {
  301. case EncodingQuality.HighSpeed:
  302. crf = "12";
  303. profileScore = 2;
  304. break;
  305. case EncodingQuality.HighQuality:
  306. crf = "8";
  307. profileScore = 1;
  308. break;
  309. case EncodingQuality.MaxQuality:
  310. crf = "4";
  311. break;
  312. default:
  313. throw new ArgumentException("Unrecognized quality setting");
  314. }
  315. if (isVc1)
  316. {
  317. profileScore++;
  318. // Max of 2
  319. profileScore = Math.Min(profileScore, 2);
  320. }
  321. // http://www.webmproject.org/docs/encoder-parameters/
  322. param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1}",
  323. profileScore.ToString(UsCulture),
  324. crf);
  325. }
  326. else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase))
  327. {
  328. param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
  329. }
  330. // asf/wmv
  331. else if (string.Equals(videoCodec, "wmv2", StringComparison.OrdinalIgnoreCase))
  332. {
  333. param = "-qmin 2";
  334. }
  335. else if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
  336. {
  337. param = "-mbd 2";
  338. }
  339. param += GetVideoBitrateParam(state, videoCodec, isHls);
  340. var framerate = GetFramerateParam(state);
  341. if (framerate.HasValue)
  342. {
  343. param += string.Format(" -r {0}", framerate.Value.ToString(UsCulture));
  344. }
  345. if (!string.IsNullOrEmpty(state.OutputVideoSync))
  346. {
  347. param += " -vsync " + state.OutputVideoSync;
  348. }
  349. if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
  350. {
  351. param += " -profile:v " + state.VideoRequest.Profile;
  352. }
  353. if (!string.IsNullOrEmpty(state.VideoRequest.Level))
  354. {
  355. param += " -level " + state.VideoRequest.Level;
  356. }
  357. return param;
  358. }
  359. protected string GetAudioFilterParam(StreamState state, bool isHls)
  360. {
  361. var volParam = string.Empty;
  362. var audioSampleRate = string.Empty;
  363. var channels = state.OutputAudioChannels;
  364. // Boost volume to 200% when downsampling from 6ch to 2ch
  365. if (channels.HasValue && channels.Value <= 2)
  366. {
  367. if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
  368. {
  369. volParam = ",volume=" + ServerConfigurationManager.Configuration.DownMixAudioBoost.ToString(UsCulture);
  370. }
  371. }
  372. if (state.OutputAudioSampleRate.HasValue)
  373. {
  374. audioSampleRate = state.OutputAudioSampleRate.Value + ":";
  375. }
  376. var adelay = isHls ? "adelay=1," : string.Empty;
  377. var pts = string.Empty;
  378. if (state.SubtitleStream != null)
  379. {
  380. if (state.SubtitleStream.Codec.IndexOf("srt", StringComparison.OrdinalIgnoreCase) != -1 ||
  381. state.SubtitleStream.Codec.IndexOf("subrip", StringComparison.OrdinalIgnoreCase) != -1 ||
  382. string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
  383. string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
  384. {
  385. var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds;
  386. pts = string.Format(",asetpts=PTS-{0}/TB",
  387. Math.Round(seconds).ToString(UsCulture));
  388. }
  389. }
  390. return string.Format("-af \"{0}aresample={1}async={4}{2}{3}\"",
  391. adelay,
  392. audioSampleRate,
  393. volParam,
  394. pts,
  395. state.OutputAudioSync);
  396. }
  397. /// <summary>
  398. /// If we're going to put a fixed size on the command line, this will calculate it
  399. /// </summary>
  400. /// <param name="state">The state.</param>
  401. /// <param name="outputVideoCodec">The output video codec.</param>
  402. /// <param name="performTextSubtitleConversion">if set to <c>true</c> [perform text subtitle conversion].</param>
  403. /// <returns>System.String.</returns>
  404. protected string GetOutputSizeParam(StreamState state, string outputVideoCodec, bool performTextSubtitleConversion)
  405. {
  406. // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
  407. var assSubtitleParam = string.Empty;
  408. var copyTsParam = string.Empty;
  409. var yadifParam = state.DeInterlace ? "yadif=0:-1:0," : string.Empty;
  410. var request = state.VideoRequest;
  411. if (state.SubtitleStream != null)
  412. {
  413. if (state.SubtitleStream.Codec.IndexOf("srt", StringComparison.OrdinalIgnoreCase) != -1 ||
  414. state.SubtitleStream.Codec.IndexOf("subrip", StringComparison.OrdinalIgnoreCase) != -1 ||
  415. string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
  416. string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
  417. {
  418. assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion);
  419. copyTsParam = " -copyts";
  420. }
  421. }
  422. // If fixed dimensions were supplied
  423. if (request.Width.HasValue && request.Height.HasValue)
  424. {
  425. var widthParam = request.Width.Value.ToString(UsCulture);
  426. var heightParam = request.Height.Value.ToString(UsCulture);
  427. return string.Format("{4} -vf \"{0}scale=trunc({1}/2)*2:trunc({2}/2)*2{3}\"", yadifParam, widthParam, heightParam, assSubtitleParam, copyTsParam);
  428. }
  429. // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
  430. if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
  431. {
  432. var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
  433. var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
  434. return string.Format("{4} -vf \"{0}scale=trunc(min(iw\\,{1})/2)*2:trunc(min((iw/dar)\\,{2})/2)*2{3}\"", yadifParam, maxWidthParam, maxHeightParam, assSubtitleParam, copyTsParam);
  435. }
  436. var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
  437. // If a fixed width was requested
  438. if (request.Width.HasValue)
  439. {
  440. var widthParam = request.Width.Value.ToString(UsCulture);
  441. return isH264Output ?
  442. string.Format("{3} -vf \"{0}scale={1}:trunc(ow/a/2)*2{2}\"", yadifParam, widthParam, assSubtitleParam, copyTsParam) :
  443. string.Format("{3} -vf \"{0}scale={1}:-1{2}\"", yadifParam, widthParam, assSubtitleParam, copyTsParam);
  444. }
  445. // If a fixed height was requested
  446. if (request.Height.HasValue)
  447. {
  448. var heightParam = request.Height.Value.ToString(UsCulture);
  449. return isH264Output ?
  450. string.Format("{3} -vf \"{0}scale=trunc(oh*a*2)/2:{1}{2}\"", yadifParam, heightParam, assSubtitleParam, copyTsParam) :
  451. string.Format("{3} -vf \"{0}scale=-1:{1}{2}\"", yadifParam, heightParam, assSubtitleParam, copyTsParam);
  452. }
  453. // If a max width was requested
  454. if (request.MaxWidth.HasValue && (!request.MaxHeight.HasValue || state.VideoStream == null))
  455. {
  456. var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
  457. return isH264Output ?
  458. string.Format("{3} -vf \"{0}scale=min(iw\\,{1}):trunc(ow/a/2)*2{2}\"", yadifParam, maxWidthParam, assSubtitleParam, copyTsParam) :
  459. string.Format("{3} -vf \"{0}scale=min(iw\\,{1}):-1{2}\"", yadifParam, maxWidthParam, assSubtitleParam, copyTsParam);
  460. }
  461. // If a max height was requested
  462. if (request.MaxHeight.HasValue && (!request.MaxWidth.HasValue || state.VideoStream == null))
  463. {
  464. var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
  465. return isH264Output ?
  466. string.Format("{3} -vf \"{0}scale=trunc(oh*a*2)/2:min(ih\\,{1}){2}\"", yadifParam, maxHeightParam, assSubtitleParam, copyTsParam) :
  467. string.Format("{3} -vf \"{0}scale=-1:min(ih\\,{1}){2}\"", yadifParam, maxHeightParam, assSubtitleParam, copyTsParam);
  468. }
  469. if (state.VideoStream == null)
  470. {
  471. // No way to figure this out
  472. return string.Empty;
  473. }
  474. // Need to perform calculations manually
  475. // Try to account for bad media info
  476. var currentHeight = state.VideoStream.Height ?? request.MaxHeight ?? request.Height ?? 0;
  477. var currentWidth = state.VideoStream.Width ?? request.MaxWidth ?? request.Width ?? 0;
  478. var outputSize = DrawingUtils.Resize(currentWidth, currentHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
  479. // If we're encoding with libx264, it can't handle odd numbered widths or heights, so we'll have to fix that
  480. if (isH264Output)
  481. {
  482. var widthParam = outputSize.Width.ToString(UsCulture);
  483. var heightParam = outputSize.Height.ToString(UsCulture);
  484. return string.Format("{4} -vf \"{0}scale=trunc({1}/2)*2:trunc({2}/2)*2{3}\"", yadifParam, widthParam, heightParam, assSubtitleParam, copyTsParam);
  485. }
  486. // Otherwise use -vf scale since ffmpeg will ensure internally that the aspect ratio is preserved
  487. return string.Format("{3} -vf \"{0}scale={1}:-1{2}\"", yadifParam, Convert.ToInt32(outputSize.Width), assSubtitleParam, copyTsParam);
  488. }
  489. /// <summary>
  490. /// Gets the text subtitle param.
  491. /// </summary>
  492. /// <param name="state">The state.</param>
  493. /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
  494. /// <returns>System.String.</returns>
  495. protected string GetTextSubtitleParam(StreamState state, bool performConversion)
  496. {
  497. var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, performConversion) :
  498. GetExtractedAssPath(state, performConversion);
  499. if (string.IsNullOrEmpty(path))
  500. {
  501. return string.Empty;
  502. }
  503. var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds;
  504. return string.Format(",ass='{0}',setpts=PTS -{1}/TB",
  505. path.Replace('\\', '/').Replace(":/", "\\:/"),
  506. Math.Round(seconds).ToString(UsCulture));
  507. }
  508. /// <summary>
  509. /// Gets the extracted ass path.
  510. /// </summary>
  511. /// <param name="state">The state.</param>
  512. /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
  513. /// <returns>System.String.</returns>
  514. private string GetExtractedAssPath(StreamState state, bool performConversion)
  515. {
  516. var path = EncodingManager.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream.Index, ".ass");
  517. if (performConversion)
  518. {
  519. InputType type;
  520. var inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, null, state.PlayableStreamFileNames, out type);
  521. try
  522. {
  523. var parentPath = Path.GetDirectoryName(path);
  524. Directory.CreateDirectory(parentPath);
  525. // Don't re-encode ass/ssa to ass because ffmpeg ass encoder fails if there's more than one ass rectangle. Affect Anime mostly.
  526. // See https://lists.ffmpeg.org/pipermail/ffmpeg-cvslog/2013-April/063616.html
  527. var isAssSubtitle = string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase);
  528. var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, isAssSubtitle, path, CancellationToken.None);
  529. Task.WaitAll(task);
  530. }
  531. catch
  532. {
  533. return null;
  534. }
  535. }
  536. return path;
  537. }
  538. /// <summary>
  539. /// Gets the converted ass path.
  540. /// </summary>
  541. /// <param name="mediaPath">The media path.</param>
  542. /// <param name="subtitleStream">The subtitle stream.</param>
  543. /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
  544. /// <returns>System.String.</returns>
  545. private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, bool performConversion)
  546. {
  547. var path = EncodingManager.GetSubtitleCachePath(subtitleStream.Path, ".ass");
  548. if (performConversion)
  549. {
  550. try
  551. {
  552. var parentPath = Path.GetDirectoryName(path);
  553. Directory.CreateDirectory(parentPath);
  554. var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, CancellationToken.None);
  555. Task.WaitAll(task);
  556. }
  557. catch
  558. {
  559. return null;
  560. }
  561. }
  562. return path;
  563. }
  564. /// <summary>
  565. /// Gets the internal graphical subtitle param.
  566. /// </summary>
  567. /// <param name="state">The state.</param>
  568. /// <param name="outputVideoCodec">The output video codec.</param>
  569. /// <returns>System.String.</returns>
  570. protected string GetInternalGraphicalSubtitleParam(StreamState state, string outputVideoCodec)
  571. {
  572. var outputSizeParam = string.Empty;
  573. var request = state.VideoRequest;
  574. // Add resolution params, if specified
  575. if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
  576. {
  577. outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false).TrimEnd('"');
  578. outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
  579. }
  580. var videoSizeParam = string.Empty;
  581. if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
  582. {
  583. videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
  584. }
  585. return string.Format(" -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"",
  586. state.SubtitleStream.Index,
  587. state.VideoStream.Index,
  588. outputSizeParam,
  589. videoSizeParam);
  590. }
  591. /// <summary>
  592. /// Gets the probe size argument.
  593. /// </summary>
  594. /// <param name="mediaPath">The media path.</param>
  595. /// <param name="isVideo">if set to <c>true</c> [is video].</param>
  596. /// <param name="videoType">Type of the video.</param>
  597. /// <param name="isoType">Type of the iso.</param>
  598. /// <returns>System.String.</returns>
  599. private string GetProbeSizeArgument(string mediaPath, bool isVideo, VideoType? videoType, IsoType? isoType)
  600. {
  601. var type = !isVideo ? MediaEncoderHelpers.GetInputType(null, null) :
  602. MediaEncoderHelpers.GetInputType(videoType, isoType);
  603. return MediaEncoder.GetProbeSizeArgument(type);
  604. }
  605. /// <summary>
  606. /// Gets the number of audio channels to specify on the command line
  607. /// </summary>
  608. /// <param name="request">The request.</param>
  609. /// <param name="audioStream">The audio stream.</param>
  610. /// <returns>System.Nullable{System.Int32}.</returns>
  611. private int? GetNumAudioChannelsParam(StreamRequest request, MediaStream audioStream)
  612. {
  613. if (audioStream != null)
  614. {
  615. var codec = request.AudioCodec ?? string.Empty;
  616. if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
  617. {
  618. // wmav2 currently only supports two channel output
  619. return 2;
  620. }
  621. }
  622. if (request.MaxAudioChannels.HasValue)
  623. {
  624. if (audioStream != null && audioStream.Channels.HasValue)
  625. {
  626. return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
  627. }
  628. return request.MaxAudioChannels.Value;
  629. }
  630. return request.AudioChannels;
  631. }
  632. /// <summary>
  633. /// Determines whether the specified stream is H264.
  634. /// </summary>
  635. /// <param name="stream">The stream.</param>
  636. /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
  637. protected bool IsH264(MediaStream stream)
  638. {
  639. return stream.Codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 ||
  640. stream.Codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
  641. }
  642. /// <summary>
  643. /// Gets the name of the output audio codec
  644. /// </summary>
  645. /// <param name="request">The request.</param>
  646. /// <returns>System.String.</returns>
  647. protected string GetAudioCodec(StreamRequest request)
  648. {
  649. var codec = request.AudioCodec;
  650. if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
  651. {
  652. return "aac -strict experimental";
  653. }
  654. if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
  655. {
  656. return "libmp3lame";
  657. }
  658. if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
  659. {
  660. return "libvorbis";
  661. }
  662. if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase))
  663. {
  664. return "wmav2";
  665. }
  666. return codec.ToLower();
  667. }
  668. /// <summary>
  669. /// Gets the name of the output video codec
  670. /// </summary>
  671. /// <param name="request">The request.</param>
  672. /// <returns>System.String.</returns>
  673. protected string GetVideoCodec(VideoStreamRequest request)
  674. {
  675. var codec = request.VideoCodec;
  676. if (!string.IsNullOrEmpty(codec))
  677. {
  678. if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
  679. {
  680. return "libx264";
  681. }
  682. if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
  683. {
  684. return "libvpx";
  685. }
  686. if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
  687. {
  688. return "wmv2";
  689. }
  690. if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
  691. {
  692. return "libtheora";
  693. }
  694. return codec.ToLower();
  695. }
  696. return "copy";
  697. }
  698. /// <summary>
  699. /// Gets the input argument.
  700. /// </summary>
  701. /// <param name="state">The state.</param>
  702. /// <returns>System.String.</returns>
  703. protected string GetInputArgument(StreamState state)
  704. {
  705. var type = InputType.File;
  706. var inputPath = new[] { state.MediaPath };
  707. if (state.IsInputVideo)
  708. {
  709. if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
  710. {
  711. inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, state.IsoMount, state.PlayableStreamFileNames, out type);
  712. }
  713. }
  714. return MediaEncoder.GetInputArgument(inputPath, type);
  715. }
  716. /// <summary>
  717. /// Starts the FFMPEG.
  718. /// </summary>
  719. /// <param name="state">The state.</param>
  720. /// <param name="outputPath">The output path.</param>
  721. /// <returns>Task.</returns>
  722. protected async Task StartFfMpeg(StreamState state, string outputPath)
  723. {
  724. if (!File.Exists(MediaEncoder.EncoderPath))
  725. {
  726. throw new InvalidOperationException("ffmpeg was not found at " + MediaEncoder.EncoderPath);
  727. }
  728. Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
  729. if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
  730. {
  731. state.IsoMount = await IsoManager.Mount(state.MediaPath, CancellationToken.None).ConfigureAwait(false);
  732. }
  733. var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
  734. if (ServerConfigurationManager.Configuration.EnableDebugEncodingLogging)
  735. {
  736. commandLineArgs = "-loglevel debug " + commandLineArgs;
  737. }
  738. var process = new Process
  739. {
  740. StartInfo = new ProcessStartInfo
  741. {
  742. CreateNoWindow = true,
  743. UseShellExecute = false,
  744. // Must consume both stdout and stderr or deadlocks may occur
  745. RedirectStandardOutput = true,
  746. RedirectStandardError = true,
  747. FileName = MediaEncoder.EncoderPath,
  748. WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath),
  749. Arguments = commandLineArgs,
  750. WindowStyle = ProcessWindowStyle.Hidden,
  751. ErrorDialog = false
  752. },
  753. EnableRaisingEvents = true
  754. };
  755. ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.IsInputVideo, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
  756. var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
  757. Logger.Info(commandLineLogMessage);
  758. var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt");
  759. Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
  760. // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
  761. state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
  762. var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
  763. await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length).ConfigureAwait(false);
  764. process.Exited += (sender, args) => OnFfMpegProcessExited(process, state);
  765. try
  766. {
  767. process.Start();
  768. }
  769. catch (Exception ex)
  770. {
  771. Logger.ErrorException("Error starting ffmpeg", ex);
  772. ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType);
  773. throw;
  774. }
  775. // MUST read both stdout and stderr asynchronously or a deadlock may occurr
  776. process.BeginOutputReadLine();
  777. #pragma warning disable 4014
  778. // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
  779. process.StandardError.BaseStream.CopyToAsync(state.LogFileStream);
  780. #pragma warning restore 4014
  781. // Wait for the file to exist before proceeeding
  782. while (!File.Exists(outputPath))
  783. {
  784. await Task.Delay(100).ConfigureAwait(false);
  785. }
  786. // Allow a small amount of time to buffer a little
  787. if (state.IsInputVideo)
  788. {
  789. await Task.Delay(500).ConfigureAwait(false);
  790. }
  791. // This is arbitrary, but add a little buffer time when internet streaming
  792. if (state.IsRemote)
  793. {
  794. await Task.Delay(3000).ConfigureAwait(false);
  795. }
  796. }
  797. private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream)
  798. {
  799. var bitrate = request.VideoBitRate;
  800. if (videoStream != null)
  801. {
  802. var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue &&
  803. request.Height.Value > videoStream.Height.Value;
  804. if (request.Width.HasValue && videoStream.Width.HasValue &&
  805. request.Width.Value > videoStream.Width.Value)
  806. {
  807. isUpscaling = true;
  808. }
  809. // Don't allow bitrate increases unless upscaling
  810. if (!isUpscaling)
  811. {
  812. if (bitrate.HasValue && videoStream.BitRate.HasValue)
  813. {
  814. bitrate = Math.Min(bitrate.Value, videoStream.BitRate.Value);
  815. }
  816. }
  817. }
  818. return bitrate;
  819. }
  820. protected string GetVideoBitrateParam(StreamState state, string videoCodec, bool isHls)
  821. {
  822. var bitrate = state.OutputVideoBitrate;
  823. if (bitrate.HasValue)
  824. {
  825. var hasFixedResolution = state.VideoRequest.HasFixedResolution;
  826. if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
  827. {
  828. if (hasFixedResolution)
  829. {
  830. return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
  831. }
  832. // With vpx when crf is used, b:v becomes a max rate
  833. // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
  834. return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
  835. }
  836. if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
  837. {
  838. return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
  839. }
  840. // H264
  841. if (hasFixedResolution)
  842. {
  843. if (isHls)
  844. {
  845. return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
  846. }
  847. return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
  848. }
  849. return string.Format(" -maxrate {0} -bufsize {1}",
  850. bitrate.Value.ToString(UsCulture),
  851. (bitrate.Value * 2).ToString(UsCulture));
  852. }
  853. return string.Empty;
  854. }
  855. private int? GetAudioBitrateParam(StreamRequest request, MediaStream audioStream)
  856. {
  857. if (request.AudioBitRate.HasValue)
  858. {
  859. // Make sure we don't request a bitrate higher than the source
  860. var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value;
  861. return Math.Min(currentBitrate, request.AudioBitRate.Value);
  862. }
  863. return null;
  864. }
  865. /// <summary>
  866. /// Gets the user agent param.
  867. /// </summary>
  868. /// <param name="path">The path.</param>
  869. /// <returns>System.String.</returns>
  870. private string GetUserAgentParam(string path)
  871. {
  872. var useragent = GetUserAgent(path);
  873. if (!string.IsNullOrEmpty(useragent))
  874. {
  875. return "-user-agent \"" + useragent + "\"";
  876. }
  877. return string.Empty;
  878. }
  879. /// <summary>
  880. /// Gets the user agent.
  881. /// </summary>
  882. /// <param name="path">The path.</param>
  883. /// <returns>System.String.</returns>
  884. protected string GetUserAgent(string path)
  885. {
  886. if (string.IsNullOrEmpty(path))
  887. {
  888. throw new ArgumentNullException("path");
  889. }
  890. if (path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1)
  891. {
  892. return "QuickTime/7.7.4";
  893. }
  894. return string.Empty;
  895. }
  896. /// <summary>
  897. /// Processes the exited.
  898. /// </summary>
  899. /// <param name="process">The process.</param>
  900. /// <param name="state">The state.</param>
  901. protected void OnFfMpegProcessExited(Process process, StreamState state)
  902. {
  903. state.Dispose();
  904. var outputFilePath = GetOutputFilePath(state);
  905. try
  906. {
  907. Logger.Info("FFMpeg exited with code {0} for {1}", process.ExitCode, outputFilePath);
  908. }
  909. catch
  910. {
  911. Logger.Info("FFMpeg exited with an error for {0}", outputFilePath);
  912. }
  913. }
  914. protected double? GetFramerateParam(StreamState state)
  915. {
  916. if (state.VideoRequest != null)
  917. {
  918. if (state.VideoRequest.Framerate.HasValue)
  919. {
  920. return state.VideoRequest.Framerate.Value;
  921. }
  922. var maxrate = state.VideoRequest.MaxFramerate ?? 23.97602;
  923. if (state.VideoStream != null)
  924. {
  925. var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
  926. if (contentRate.HasValue && contentRate.Value > maxrate)
  927. {
  928. return maxrate;
  929. }
  930. }
  931. }
  932. return null;
  933. }
  934. /// <summary>
  935. /// Parses the parameters.
  936. /// </summary>
  937. /// <param name="request">The request.</param>
  938. private void ParseParams(StreamRequest request)
  939. {
  940. var vals = request.Params.Split(';');
  941. var videoRequest = request as VideoStreamRequest;
  942. for (var i = 0; i < vals.Length; i++)
  943. {
  944. var val = vals[i];
  945. if (string.IsNullOrWhiteSpace(val))
  946. {
  947. continue;
  948. }
  949. if (i == 0)
  950. {
  951. request.DeviceProfileId = val;
  952. }
  953. else if (i == 1)
  954. {
  955. request.DeviceId = val;
  956. }
  957. else if (i == 2)
  958. {
  959. request.MediaSourceId = val;
  960. }
  961. else if (i == 3)
  962. {
  963. request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  964. }
  965. else if (i == 4)
  966. {
  967. if (videoRequest != null)
  968. {
  969. videoRequest.VideoCodec = val;
  970. }
  971. }
  972. else if (i == 5)
  973. {
  974. request.AudioCodec = val;
  975. }
  976. else if (i == 6)
  977. {
  978. if (videoRequest != null)
  979. {
  980. videoRequest.AudioStreamIndex = int.Parse(val, UsCulture);
  981. }
  982. }
  983. else if (i == 7)
  984. {
  985. if (videoRequest != null)
  986. {
  987. videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture);
  988. }
  989. }
  990. else if (i == 8)
  991. {
  992. if (videoRequest != null)
  993. {
  994. videoRequest.VideoBitRate = int.Parse(val, UsCulture);
  995. }
  996. }
  997. else if (i == 9)
  998. {
  999. request.AudioBitRate = int.Parse(val, UsCulture);
  1000. }
  1001. else if (i == 10)
  1002. {
  1003. request.MaxAudioChannels = int.Parse(val, UsCulture);
  1004. }
  1005. else if (i == 11)
  1006. {
  1007. if (videoRequest != null)
  1008. {
  1009. videoRequest.MaxFramerate = double.Parse(val, UsCulture);
  1010. }
  1011. }
  1012. else if (i == 12)
  1013. {
  1014. if (videoRequest != null)
  1015. {
  1016. videoRequest.MaxWidth = int.Parse(val, UsCulture);
  1017. }
  1018. }
  1019. else if (i == 13)
  1020. {
  1021. if (videoRequest != null)
  1022. {
  1023. videoRequest.MaxHeight = int.Parse(val, UsCulture);
  1024. }
  1025. }
  1026. else if (i == 14)
  1027. {
  1028. request.StartTimeTicks = long.Parse(val, UsCulture);
  1029. }
  1030. else if (i == 15)
  1031. {
  1032. if (videoRequest != null)
  1033. {
  1034. videoRequest.Level = val;
  1035. }
  1036. }
  1037. }
  1038. }
  1039. /// <summary>
  1040. /// Parses the dlna headers.
  1041. /// </summary>
  1042. /// <param name="request">The request.</param>
  1043. private void ParseDlnaHeaders(StreamRequest request)
  1044. {
  1045. if (!request.StartTimeTicks.HasValue)
  1046. {
  1047. var timeSeek = GetHeader("TimeSeekRange.dlna.org");
  1048. request.StartTimeTicks = ParseTimeSeekHeader(timeSeek);
  1049. }
  1050. }
  1051. /// <summary>
  1052. /// Parses the time seek header.
  1053. /// </summary>
  1054. private long? ParseTimeSeekHeader(string value)
  1055. {
  1056. if (string.IsNullOrWhiteSpace(value))
  1057. {
  1058. return null;
  1059. }
  1060. if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0)
  1061. {
  1062. throw new ArgumentException("Invalid timeseek header");
  1063. }
  1064. value = value.Substring(4).Split(new[] { '-' }, 2)[0];
  1065. if (value.IndexOf(':') == -1)
  1066. {
  1067. // Parses npt times in the format of '417.33'
  1068. double seconds;
  1069. if (double.TryParse(value, NumberStyles.Any, UsCulture, out seconds))
  1070. {
  1071. return TimeSpan.FromSeconds(seconds).Ticks;
  1072. }
  1073. throw new ArgumentException("Invalid timeseek header");
  1074. }
  1075. // Parses npt times in the format of '10:19:25.7'
  1076. var tokens = value.Split(new[] { ':' }, 3);
  1077. double secondsSum = 0;
  1078. var timeFactor = 3600;
  1079. foreach (var time in tokens)
  1080. {
  1081. double digit;
  1082. if (double.TryParse(time, NumberStyles.Any, UsCulture, out digit))
  1083. {
  1084. secondsSum += (digit * timeFactor);
  1085. }
  1086. else
  1087. {
  1088. throw new ArgumentException("Invalid timeseek header");
  1089. }
  1090. timeFactor /= 60;
  1091. }
  1092. return TimeSpan.FromSeconds(secondsSum).Ticks;
  1093. }
  1094. /// <summary>
  1095. /// Gets the state.
  1096. /// </summary>
  1097. /// <param name="request">The request.</param>
  1098. /// <param name="cancellationToken">The cancellation token.</param>
  1099. /// <returns>StreamState.</returns>
  1100. protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken)
  1101. {
  1102. ParseDlnaHeaders(request);
  1103. if (!string.IsNullOrWhiteSpace(request.Params))
  1104. {
  1105. ParseParams(request);
  1106. }
  1107. var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager);
  1108. var url = Request.PathInfo;
  1109. if (string.IsNullOrEmpty(request.AudioCodec))
  1110. {
  1111. request.AudioCodec = InferAudioCodec(url);
  1112. }
  1113. var state = new StreamState(LiveTvManager, Logger)
  1114. {
  1115. Request = request,
  1116. RequestedUrl = url
  1117. };
  1118. if (!string.IsNullOrWhiteSpace(request.AudioCodec))
  1119. {
  1120. state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
  1121. state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
  1122. }
  1123. var item = string.IsNullOrEmpty(request.MediaSourceId) ?
  1124. DtoService.GetItemByDtoId(request.Id) :
  1125. DtoService.GetItemByDtoId(request.MediaSourceId);
  1126. if (user != null && item.GetPlayAccess(user) != PlayAccess.Full)
  1127. {
  1128. throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name));
  1129. }
  1130. if (item is ILiveTvRecording)
  1131. {
  1132. var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
  1133. state.VideoType = VideoType.VideoFile;
  1134. state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
  1135. state.PlayableStreamFileNames = new List<string>();
  1136. var path = recording.RecordingInfo.Path;
  1137. var mediaUrl = recording.RecordingInfo.Url;
  1138. if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl))
  1139. {
  1140. var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false);
  1141. state.LiveTvStreamId = streamInfo.Id;
  1142. path = streamInfo.Path;
  1143. mediaUrl = streamInfo.Url;
  1144. }
  1145. if (!string.IsNullOrEmpty(path))
  1146. {
  1147. state.MediaPath = path;
  1148. state.IsRemote = false;
  1149. }
  1150. else if (!string.IsNullOrEmpty(mediaUrl))
  1151. {
  1152. state.MediaPath = mediaUrl;
  1153. state.IsRemote = true;
  1154. }
  1155. state.RunTimeTicks = recording.RunTimeTicks;
  1156. if (recording.RecordingInfo.Status == RecordingStatus.InProgress)
  1157. {
  1158. await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
  1159. }
  1160. state.ReadInputAtNativeFramerate = recording.RecordingInfo.Status == RecordingStatus.InProgress;
  1161. state.OutputAudioSync = "1000";
  1162. state.DeInterlace = true;
  1163. state.InputVideoSync = "-1";
  1164. state.InputAudioSync = "1";
  1165. }
  1166. else if (item is LiveTvChannel)
  1167. {
  1168. var channel = LiveTvManager.GetInternalChannel(request.Id);
  1169. state.VideoType = VideoType.VideoFile;
  1170. state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
  1171. state.PlayableStreamFileNames = new List<string>();
  1172. var streamInfo = await LiveTvManager.GetChannelStream(request.Id, cancellationToken).ConfigureAwait(false);
  1173. state.LiveTvStreamId = streamInfo.Id;
  1174. if (!string.IsNullOrEmpty(streamInfo.Path))
  1175. {
  1176. state.MediaPath = streamInfo.Path;
  1177. state.IsRemote = false;
  1178. await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
  1179. }
  1180. else if (!string.IsNullOrEmpty(streamInfo.Url))
  1181. {
  1182. state.MediaPath = streamInfo.Url;
  1183. state.IsRemote = true;
  1184. }
  1185. state.ReadInputAtNativeFramerate = true;
  1186. state.OutputAudioSync = "1000";
  1187. state.DeInterlace = true;
  1188. state.InputVideoSync = "-1";
  1189. state.InputAudioSync = "1";
  1190. }
  1191. else
  1192. {
  1193. state.MediaPath = item.Path;
  1194. state.IsRemote = item.LocationType == LocationType.Remote;
  1195. var video = item as Video;
  1196. if (video != null)
  1197. {
  1198. state.IsInputVideo = true;
  1199. state.VideoType = video.VideoType;
  1200. state.IsoType = video.IsoType;
  1201. state.PlayableStreamFileNames = video.PlayableStreamFileNames == null
  1202. ? new List<string>()
  1203. : video.PlayableStreamFileNames.ToList();
  1204. }
  1205. state.RunTimeTicks = item.RunTimeTicks;
  1206. }
  1207. var videoRequest = request as VideoStreamRequest;
  1208. var mediaStreams = ItemRepository.GetMediaStreams(new MediaStreamQuery
  1209. {
  1210. ItemId = item.Id
  1211. }).ToList();
  1212. if (videoRequest != null)
  1213. {
  1214. if (string.IsNullOrEmpty(videoRequest.VideoCodec))
  1215. {
  1216. videoRequest.VideoCodec = InferVideoCodec(url);
  1217. }
  1218. state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
  1219. state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
  1220. state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
  1221. if (state.VideoStream != null && state.VideoStream.IsInterlaced)
  1222. {
  1223. state.DeInterlace = true;
  1224. }
  1225. EnforceResolutionLimit(state, videoRequest);
  1226. }
  1227. else
  1228. {
  1229. state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
  1230. }
  1231. state.HasMediaStreams = mediaStreams.Count > 0;
  1232. state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10;
  1233. state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440;
  1234. var container = Path.GetExtension(state.RequestedUrl);
  1235. if (string.IsNullOrEmpty(container))
  1236. {
  1237. container = Path.GetExtension(GetOutputFilePath(state));
  1238. }
  1239. state.OutputContainer = (container ?? string.Empty).TrimStart('.');
  1240. ApplyDeviceProfileSettings(state);
  1241. state.OutputContainer = GetOutputFileExtension(state).TrimStart('.');
  1242. state.OutputAudioBitrate = GetAudioBitrateParam(state.Request, state.AudioStream);
  1243. state.OutputAudioSampleRate = request.AudioSampleRate;
  1244. state.OutputAudioChannels = GetNumAudioChannelsParam(state.Request, state.AudioStream);
  1245. if (videoRequest != null)
  1246. {
  1247. state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream);
  1248. if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
  1249. {
  1250. videoRequest.VideoCodec = "copy";
  1251. }
  1252. if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
  1253. {
  1254. request.AudioCodec = "copy";
  1255. }
  1256. }
  1257. return state;
  1258. }
  1259. private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
  1260. {
  1261. if (videoStream.IsInterlaced)
  1262. {
  1263. return false;
  1264. }
  1265. // Source and target codecs must match
  1266. if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
  1267. {
  1268. return false;
  1269. }
  1270. // If client is requesting a specific video profile, it must match the source
  1271. if (!string.IsNullOrEmpty(request.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
  1272. {
  1273. return false;
  1274. }
  1275. // Video width must fall within requested value
  1276. if (request.MaxWidth.HasValue)
  1277. {
  1278. if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)
  1279. {
  1280. return false;
  1281. }
  1282. }
  1283. // Video height must fall within requested value
  1284. if (request.MaxHeight.HasValue)
  1285. {
  1286. if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value)
  1287. {
  1288. return false;
  1289. }
  1290. }
  1291. // Video framerate must fall within requested value
  1292. var requestedFramerate = request.MaxFramerate ?? request.Framerate;
  1293. if (requestedFramerate.HasValue)
  1294. {
  1295. var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate;
  1296. if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
  1297. {
  1298. return false;
  1299. }
  1300. }
  1301. // Video bitrate must fall within requested value
  1302. if (request.VideoBitRate.HasValue)
  1303. {
  1304. if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)
  1305. {
  1306. return false;
  1307. }
  1308. }
  1309. // If a specific level was requested, the source must match or be less than
  1310. if (!string.IsNullOrEmpty(request.Level))
  1311. {
  1312. double requestLevel;
  1313. if (double.TryParse(request.Level, NumberStyles.Any, UsCulture, out requestLevel))
  1314. {
  1315. if (!videoStream.Level.HasValue)
  1316. {
  1317. return false;
  1318. }
  1319. if (videoStream.Level.Value > requestLevel)
  1320. {
  1321. return false;
  1322. }
  1323. }
  1324. }
  1325. return request.EnableAutoStreamCopy;
  1326. }
  1327. private bool CanStreamCopyAudio(StreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
  1328. {
  1329. // Source and target codecs must match
  1330. if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
  1331. {
  1332. return false;
  1333. }
  1334. // Video bitrate must fall within requested value
  1335. if (request.AudioBitRate.HasValue)
  1336. {
  1337. if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value > request.AudioBitRate.Value)
  1338. {
  1339. return false;
  1340. }
  1341. }
  1342. // Channels must fall within requested value
  1343. var channels = request.AudioChannels ?? request.MaxAudioChannels;
  1344. if (channels.HasValue)
  1345. {
  1346. if (!audioStream.Channels.HasValue || audioStream.Channels.Value > channels.Value)
  1347. {
  1348. return false;
  1349. }
  1350. }
  1351. // Sample rate must fall within requested value
  1352. if (request.AudioSampleRate.HasValue)
  1353. {
  1354. if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value > request.AudioSampleRate.Value)
  1355. {
  1356. return false;
  1357. }
  1358. }
  1359. return true;
  1360. }
  1361. private void ApplyDeviceProfileSettings(StreamState state)
  1362. {
  1363. var headers = new Dictionary<string, string>();
  1364. foreach (var key in Request.Headers.AllKeys)
  1365. {
  1366. headers[key] = Request.Headers[key];
  1367. }
  1368. var profile = string.IsNullOrWhiteSpace(state.Request.DeviceProfileId) ?
  1369. DlnaManager.GetProfile(headers) :
  1370. DlnaManager.GetProfile(state.Request.DeviceProfileId);
  1371. if (profile == null)
  1372. {
  1373. // Don't use settings from the default profile.
  1374. // Only use a specific profile if it was requested.
  1375. return;
  1376. }
  1377. var audioCodec = state.Request.AudioCodec;
  1378. if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
  1379. {
  1380. audioCodec = state.AudioStream.Codec;
  1381. }
  1382. var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
  1383. if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
  1384. {
  1385. videoCodec = state.VideoStream.Codec;
  1386. }
  1387. var mediaProfile = state.VideoRequest == null ?
  1388. profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.AudioStream) :
  1389. profile.GetVideoMediaProfile(state.OutputContainer, audioCodec, videoCodec, state.AudioStream, state.VideoStream);
  1390. if (mediaProfile != null)
  1391. {
  1392. state.MimeType = mediaProfile.MimeType;
  1393. state.OrgPn = mediaProfile.OrgPn;
  1394. }
  1395. var transcodingProfile = state.VideoRequest == null ?
  1396. profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) :
  1397. profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec);
  1398. if (transcodingProfile != null)
  1399. {
  1400. state.EstimateContentLength = transcodingProfile.EstimateContentLength;
  1401. state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
  1402. state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
  1403. if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.Profile))
  1404. {
  1405. state.VideoRequest.Profile = transcodingProfile.VideoProfile;
  1406. }
  1407. }
  1408. }
  1409. /// <summary>
  1410. /// Adds the dlna headers.
  1411. /// </summary>
  1412. /// <param name="state">The state.</param>
  1413. /// <param name="responseHeaders">The response headers.</param>
  1414. /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param>
  1415. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  1416. protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed)
  1417. {
  1418. var transferMode = GetHeader("transferMode.dlna.org");
  1419. responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
  1420. responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
  1421. // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
  1422. var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(state.RunTimeTicks.HasValue, isStaticallyStreamed, state.TranscodeSeekInfo);
  1423. if (state.RunTimeTicks.HasValue && !isStaticallyStreamed)
  1424. {
  1425. AddTimeSeekResponseHeaders(state, responseHeaders);
  1426. }
  1427. // 0 = native, 1 = transcoded
  1428. var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
  1429. var flagValue = DlnaFlags.StreamingTransferMode |
  1430. DlnaFlags.BackgroundTransferMode |
  1431. DlnaFlags.DlnaV15;
  1432. if (isStaticallyStreamed)
  1433. {
  1434. //flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_BYTE_BASED_SEEK;
  1435. }
  1436. else if (state.RunTimeTicks.HasValue)
  1437. {
  1438. //flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_TIME_BASED_SEEK;
  1439. }
  1440. var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}000000000000000000000000",
  1441. Enum.Format(typeof(DlnaFlags), flagValue, "x"));
  1442. var orgPn = GetOrgPnValue(state);
  1443. var contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty :
  1444. "DLNA.ORG_PN=" + orgPn;
  1445. if (!string.IsNullOrEmpty(contentFeatures))
  1446. {
  1447. responseHeaders["contentFeatures.dlna.org"] = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
  1448. }
  1449. foreach (var item in responseHeaders)
  1450. {
  1451. Request.Response.AddHeader(item.Key, item.Value);
  1452. }
  1453. }
  1454. private string GetOrgPnValue(StreamState state)
  1455. {
  1456. if (!string.IsNullOrWhiteSpace(state.OrgPn))
  1457. {
  1458. return state.OrgPn;
  1459. }
  1460. if (state.VideoRequest == null)
  1461. {
  1462. var format = new MediaFormatProfileResolver()
  1463. .ResolveAudioFormat(state.OutputContainer,
  1464. state.OutputAudioBitrate,
  1465. state.OutputAudioSampleRate,
  1466. state.OutputAudioChannels);
  1467. return format.HasValue ? format.Value.ToString() : null;
  1468. }
  1469. var audioCodec = state.Request.AudioCodec;
  1470. if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
  1471. {
  1472. audioCodec = state.AudioStream.Codec;
  1473. }
  1474. var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
  1475. if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
  1476. {
  1477. videoCodec = state.VideoStream.Codec;
  1478. }
  1479. var videoFormat = new MediaFormatProfileResolver()
  1480. .ResolveVideoFormat(state.OutputContainer,
  1481. videoCodec,
  1482. audioCodec,
  1483. state.OutputWidth,
  1484. state.OutputHeight,
  1485. state.TotalOutputBitrate,
  1486. TransportStreamTimestamp.VALID);
  1487. return videoFormat.HasValue ? videoFormat.Value.ToString() : null;
  1488. }
  1489. private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
  1490. {
  1491. var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
  1492. var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture);
  1493. responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
  1494. responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);
  1495. }
  1496. /// <summary>
  1497. /// Enforces the resolution limit.
  1498. /// </summary>
  1499. /// <param name="state">The state.</param>
  1500. /// <param name="videoRequest">The video request.</param>
  1501. private void EnforceResolutionLimit(StreamState state, VideoStreamRequest videoRequest)
  1502. {
  1503. // If enabled, allow whatever the client asks for
  1504. if (ServerConfigurationManager.Configuration.AllowVideoUpscaling)
  1505. {
  1506. return;
  1507. }
  1508. // Switch the incoming params to be ceilings rather than fixed values
  1509. videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
  1510. videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
  1511. videoRequest.Width = null;
  1512. videoRequest.Height = null;
  1513. }
  1514. protected string GetInputModifier(StreamState state)
  1515. {
  1516. var inputModifier = string.Empty;
  1517. var probeSize = GetProbeSizeArgument(state.MediaPath, state.IsInputVideo, state.VideoType, state.IsoType);
  1518. inputModifier += " " + probeSize;
  1519. inputModifier = inputModifier.Trim();
  1520. inputModifier += " " + GetUserAgentParam(state.MediaPath);
  1521. inputModifier = inputModifier.Trim();
  1522. inputModifier += " " + GetFastSeekCommandLineParameter(state.Request);
  1523. inputModifier = inputModifier.Trim();
  1524. if (state.VideoRequest != null)
  1525. {
  1526. inputModifier += " -fflags genpts";
  1527. }
  1528. if (!string.IsNullOrEmpty(state.InputFormat))
  1529. {
  1530. inputModifier += " -f " + state.InputFormat;
  1531. }
  1532. if (!string.IsNullOrEmpty(state.InputVideoCodec))
  1533. {
  1534. inputModifier += " -vcodec " + state.InputVideoCodec;
  1535. }
  1536. if (!string.IsNullOrEmpty(state.InputAudioCodec))
  1537. {
  1538. inputModifier += " -acodec " + state.InputAudioCodec;
  1539. }
  1540. if (!string.IsNullOrEmpty(state.InputAudioSync))
  1541. {
  1542. inputModifier += " -async " + state.InputAudioSync;
  1543. }
  1544. if (!string.IsNullOrEmpty(state.InputVideoSync))
  1545. {
  1546. inputModifier += " -vsync " + state.InputVideoSync;
  1547. }
  1548. if (state.ReadInputAtNativeFramerate)
  1549. {
  1550. inputModifier += " -re";
  1551. }
  1552. return inputModifier;
  1553. }
  1554. /// <summary>
  1555. /// Infers the audio codec based on the url
  1556. /// </summary>
  1557. /// <param name="url">The URL.</param>
  1558. /// <returns>System.Nullable{AudioCodecs}.</returns>
  1559. private string InferAudioCodec(string url)
  1560. {
  1561. var ext = Path.GetExtension(url);
  1562. if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
  1563. {
  1564. return "mp3";
  1565. }
  1566. if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
  1567. {
  1568. return "aac";
  1569. }
  1570. if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
  1571. {
  1572. return "wma";
  1573. }
  1574. if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
  1575. {
  1576. return "vorbis";
  1577. }
  1578. if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
  1579. {
  1580. return "vorbis";
  1581. }
  1582. if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
  1583. {
  1584. return "vorbis";
  1585. }
  1586. if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
  1587. {
  1588. return "vorbis";
  1589. }
  1590. if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
  1591. {
  1592. return "vorbis";
  1593. }
  1594. return "copy";
  1595. }
  1596. /// <summary>
  1597. /// Infers the video codec.
  1598. /// </summary>
  1599. /// <param name="url">The URL.</param>
  1600. /// <returns>System.Nullable{VideoCodecs}.</returns>
  1601. private string InferVideoCodec(string url)
  1602. {
  1603. var ext = Path.GetExtension(url);
  1604. if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
  1605. {
  1606. return "wmv";
  1607. }
  1608. if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
  1609. {
  1610. return "vpx";
  1611. }
  1612. if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
  1613. {
  1614. return "theora";
  1615. }
  1616. if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
  1617. {
  1618. return "h264";
  1619. }
  1620. return "copy";
  1621. }
  1622. }
  1623. }