BaseStreamingService.cs 74 KB

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