StreamInfo.cs 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. #nullable disable
  2. #pragma warning disable CS1591
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using MediaBrowser.Model.Drawing;
  7. using MediaBrowser.Model.Dto;
  8. using MediaBrowser.Model.Entities;
  9. using MediaBrowser.Model.MediaInfo;
  10. using MediaBrowser.Model.Session;
  11. namespace MediaBrowser.Model.Dlna
  12. {
  13. /// <summary>
  14. /// Class StreamInfo.
  15. /// </summary>
  16. public class StreamInfo
  17. {
  18. public StreamInfo()
  19. {
  20. AudioCodecs = Array.Empty<string>();
  21. VideoCodecs = Array.Empty<string>();
  22. SubtitleCodecs = Array.Empty<string>();
  23. StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  24. }
  25. public Guid ItemId { get; set; }
  26. public PlayMethod PlayMethod { get; set; }
  27. public EncodingContext Context { get; set; }
  28. public DlnaProfileType MediaType { get; set; }
  29. public string Container { get; set; }
  30. public string SubProtocol { get; set; }
  31. public long StartPositionTicks { get; set; }
  32. public int? SegmentLength { get; set; }
  33. public int? MinSegments { get; set; }
  34. public bool BreakOnNonKeyFrames { get; set; }
  35. public bool RequireAvc { get; set; }
  36. public bool RequireNonAnamorphic { get; set; }
  37. public bool CopyTimestamps { get; set; }
  38. public bool EnableMpegtsM2TsMode { get; set; }
  39. public bool EnableSubtitlesInManifest { get; set; }
  40. public string[] AudioCodecs { get; set; }
  41. public string[] VideoCodecs { get; set; }
  42. public int? AudioStreamIndex { get; set; }
  43. public int? SubtitleStreamIndex { get; set; }
  44. public int? TranscodingMaxAudioChannels { get; set; }
  45. public int? GlobalMaxAudioChannels { get; set; }
  46. public int? AudioBitrate { get; set; }
  47. public int? AudioSampleRate { get; set; }
  48. public int? VideoBitrate { get; set; }
  49. public int? MaxWidth { get; set; }
  50. public int? MaxHeight { get; set; }
  51. public float? MaxFramerate { get; set; }
  52. public DeviceProfile DeviceProfile { get; set; }
  53. public string DeviceProfileId { get; set; }
  54. public string DeviceId { get; set; }
  55. public long? RunTimeTicks { get; set; }
  56. public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
  57. public bool EstimateContentLength { get; set; }
  58. public MediaSourceInfo MediaSource { get; set; }
  59. public string[] SubtitleCodecs { get; set; }
  60. public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
  61. public string SubtitleFormat { get; set; }
  62. public string PlaySessionId { get; set; }
  63. public TranscodeReason TranscodeReasons { get; set; }
  64. public Dictionary<string, string> StreamOptions { get; private set; }
  65. public string MediaSourceId => MediaSource?.Id;
  66. public bool IsDirectStream =>
  67. PlayMethod == PlayMethod.DirectStream ||
  68. PlayMethod == PlayMethod.DirectPlay;
  69. /// <summary>
  70. /// Gets the audio stream that will be used.
  71. /// </summary>
  72. public MediaStream TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex);
  73. /// <summary>
  74. /// Gets the video stream that will be used.
  75. /// </summary>
  76. public MediaStream TargetVideoStream => MediaSource?.VideoStream;
  77. /// <summary>
  78. /// Gets the audio sample rate that will be in the output stream.
  79. /// </summary>
  80. public int? TargetAudioSampleRate
  81. {
  82. get
  83. {
  84. var stream = TargetAudioStream;
  85. return AudioSampleRate.HasValue && !IsDirectStream
  86. ? AudioSampleRate
  87. : stream?.SampleRate;
  88. }
  89. }
  90. /// <summary>
  91. /// Gets the audio sample rate that will be in the output stream.
  92. /// </summary>
  93. public int? TargetAudioBitDepth
  94. {
  95. get
  96. {
  97. if (IsDirectStream)
  98. {
  99. return TargetAudioStream?.BitDepth;
  100. }
  101. var targetAudioCodecs = TargetAudioCodec;
  102. var audioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
  103. if (!string.IsNullOrEmpty(audioCodec))
  104. {
  105. return GetTargetAudioBitDepth(audioCodec);
  106. }
  107. return TargetAudioStream?.BitDepth;
  108. }
  109. }
  110. /// <summary>
  111. /// Gets the audio sample rate that will be in the output stream.
  112. /// </summary>
  113. public int? TargetVideoBitDepth
  114. {
  115. get
  116. {
  117. if (IsDirectStream)
  118. {
  119. return TargetVideoStream?.BitDepth;
  120. }
  121. var targetVideoCodecs = TargetVideoCodec;
  122. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  123. if (!string.IsNullOrEmpty(videoCodec))
  124. {
  125. return GetTargetVideoBitDepth(videoCodec);
  126. }
  127. return TargetVideoStream?.BitDepth;
  128. }
  129. }
  130. /// <summary>
  131. /// Gets the target reference frames.
  132. /// </summary>
  133. /// <value>The target reference frames.</value>
  134. public int? TargetRefFrames
  135. {
  136. get
  137. {
  138. if (IsDirectStream)
  139. {
  140. return TargetVideoStream?.RefFrames;
  141. }
  142. var targetVideoCodecs = TargetVideoCodec;
  143. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  144. if (!string.IsNullOrEmpty(videoCodec))
  145. {
  146. return GetTargetRefFrames(videoCodec);
  147. }
  148. return TargetVideoStream?.RefFrames;
  149. }
  150. }
  151. /// <summary>
  152. /// Gets the audio sample rate that will be in the output stream.
  153. /// </summary>
  154. public float? TargetFramerate
  155. {
  156. get
  157. {
  158. var stream = TargetVideoStream;
  159. return MaxFramerate.HasValue && !IsDirectStream
  160. ? MaxFramerate
  161. : stream is null ? null : stream.AverageFrameRate ?? stream.RealFrameRate;
  162. }
  163. }
  164. /// <summary>
  165. /// Gets the audio sample rate that will be in the output stream.
  166. /// </summary>
  167. public double? TargetVideoLevel
  168. {
  169. get
  170. {
  171. if (IsDirectStream)
  172. {
  173. return TargetVideoStream?.Level;
  174. }
  175. var targetVideoCodecs = TargetVideoCodec;
  176. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  177. if (!string.IsNullOrEmpty(videoCodec))
  178. {
  179. return GetTargetVideoLevel(videoCodec);
  180. }
  181. return TargetVideoStream?.Level;
  182. }
  183. }
  184. /// <summary>
  185. /// Gets the audio sample rate that will be in the output stream.
  186. /// </summary>
  187. public int? TargetPacketLength
  188. {
  189. get
  190. {
  191. var stream = TargetVideoStream;
  192. return !IsDirectStream
  193. ? null
  194. : stream?.PacketLength;
  195. }
  196. }
  197. /// <summary>
  198. /// Gets the audio sample rate that will be in the output stream.
  199. /// </summary>
  200. public string TargetVideoProfile
  201. {
  202. get
  203. {
  204. if (IsDirectStream)
  205. {
  206. return TargetVideoStream?.Profile;
  207. }
  208. var targetVideoCodecs = TargetVideoCodec;
  209. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  210. if (!string.IsNullOrEmpty(videoCodec))
  211. {
  212. return GetOption(videoCodec, "profile");
  213. }
  214. return TargetVideoStream?.Profile;
  215. }
  216. }
  217. /// <summary>
  218. /// Gets the target video range type that will be in the output stream.
  219. /// </summary>
  220. public string TargetVideoRangeType
  221. {
  222. get
  223. {
  224. if (IsDirectStream)
  225. {
  226. return TargetVideoStream?.VideoRangeType;
  227. }
  228. var targetVideoCodecs = TargetVideoCodec;
  229. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  230. if (!string.IsNullOrEmpty(videoCodec))
  231. {
  232. return GetOption(videoCodec, "rangetype");
  233. }
  234. return TargetVideoStream?.VideoRangeType;
  235. }
  236. }
  237. /// <summary>
  238. /// Gets the target video codec tag.
  239. /// </summary>
  240. /// <value>The target video codec tag.</value>
  241. public string TargetVideoCodecTag
  242. {
  243. get
  244. {
  245. var stream = TargetVideoStream;
  246. return !IsDirectStream
  247. ? null
  248. : stream?.CodecTag;
  249. }
  250. }
  251. /// <summary>
  252. /// Gets the audio bitrate that will be in the output stream.
  253. /// </summary>
  254. public int? TargetAudioBitrate
  255. {
  256. get
  257. {
  258. var stream = TargetAudioStream;
  259. return AudioBitrate.HasValue && !IsDirectStream
  260. ? AudioBitrate
  261. : stream?.BitRate;
  262. }
  263. }
  264. /// <summary>
  265. /// Gets the audio channels that will be in the output stream.
  266. /// </summary>
  267. public int? TargetAudioChannels
  268. {
  269. get
  270. {
  271. if (IsDirectStream)
  272. {
  273. return TargetAudioStream?.Channels;
  274. }
  275. var targetAudioCodecs = TargetAudioCodec;
  276. var codec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
  277. if (!string.IsNullOrEmpty(codec))
  278. {
  279. return GetTargetRefFrames(codec);
  280. }
  281. return TargetAudioStream?.Channels;
  282. }
  283. }
  284. /// <summary>
  285. /// Gets the audio codec that will be in the output stream.
  286. /// </summary>
  287. public string[] TargetAudioCodec
  288. {
  289. get
  290. {
  291. var stream = TargetAudioStream;
  292. string inputCodec = stream?.Codec;
  293. if (IsDirectStream)
  294. {
  295. return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
  296. }
  297. foreach (string codec in AudioCodecs)
  298. {
  299. if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
  300. {
  301. return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec };
  302. }
  303. }
  304. return AudioCodecs;
  305. }
  306. }
  307. public string[] TargetVideoCodec
  308. {
  309. get
  310. {
  311. var stream = TargetVideoStream;
  312. string inputCodec = stream?.Codec;
  313. if (IsDirectStream)
  314. {
  315. return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
  316. }
  317. foreach (string codec in VideoCodecs)
  318. {
  319. if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
  320. {
  321. return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec };
  322. }
  323. }
  324. return VideoCodecs;
  325. }
  326. }
  327. /// <summary>
  328. /// Gets the audio channels that will be in the output stream.
  329. /// </summary>
  330. public long? TargetSize
  331. {
  332. get
  333. {
  334. if (IsDirectStream)
  335. {
  336. return MediaSource.Size;
  337. }
  338. if (RunTimeTicks.HasValue)
  339. {
  340. int? totalBitrate = TargetTotalBitrate;
  341. double totalSeconds = RunTimeTicks.Value;
  342. // Convert to ms
  343. totalSeconds /= 10000;
  344. // Convert to seconds
  345. totalSeconds /= 1000;
  346. return totalBitrate.HasValue ?
  347. Convert.ToInt64(totalBitrate.Value * totalSeconds) :
  348. (long?)null;
  349. }
  350. return null;
  351. }
  352. }
  353. public int? TargetVideoBitrate
  354. {
  355. get
  356. {
  357. var stream = TargetVideoStream;
  358. return VideoBitrate.HasValue && !IsDirectStream
  359. ? VideoBitrate
  360. : stream?.BitRate;
  361. }
  362. }
  363. public TransportStreamTimestamp TargetTimestamp
  364. {
  365. get
  366. {
  367. var defaultValue = string.Equals(Container, "m2ts", StringComparison.OrdinalIgnoreCase)
  368. ? TransportStreamTimestamp.Valid
  369. : TransportStreamTimestamp.None;
  370. return !IsDirectStream
  371. ? defaultValue
  372. : MediaSource is null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None;
  373. }
  374. }
  375. public int? TargetTotalBitrate => (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0);
  376. public bool? IsTargetAnamorphic
  377. {
  378. get
  379. {
  380. if (IsDirectStream)
  381. {
  382. return TargetVideoStream?.IsAnamorphic;
  383. }
  384. return false;
  385. }
  386. }
  387. public bool? IsTargetInterlaced
  388. {
  389. get
  390. {
  391. if (IsDirectStream)
  392. {
  393. return TargetVideoStream?.IsInterlaced;
  394. }
  395. var targetVideoCodecs = TargetVideoCodec;
  396. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  397. if (!string.IsNullOrEmpty(videoCodec))
  398. {
  399. if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
  400. {
  401. return false;
  402. }
  403. }
  404. return TargetVideoStream?.IsInterlaced;
  405. }
  406. }
  407. public bool? IsTargetAVC
  408. {
  409. get
  410. {
  411. if (IsDirectStream)
  412. {
  413. return TargetVideoStream?.IsAVC;
  414. }
  415. return true;
  416. }
  417. }
  418. public int? TargetWidth
  419. {
  420. get
  421. {
  422. var videoStream = TargetVideoStream;
  423. if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue)
  424. {
  425. ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
  426. size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
  427. return size.Width;
  428. }
  429. return MaxWidth;
  430. }
  431. }
  432. public int? TargetHeight
  433. {
  434. get
  435. {
  436. var videoStream = TargetVideoStream;
  437. if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue)
  438. {
  439. ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
  440. size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
  441. return size.Height;
  442. }
  443. return MaxHeight;
  444. }
  445. }
  446. public int? TargetVideoStreamCount
  447. {
  448. get
  449. {
  450. if (IsDirectStream)
  451. {
  452. return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
  453. }
  454. return GetMediaStreamCount(MediaStreamType.Video, 1);
  455. }
  456. }
  457. public int? TargetAudioStreamCount
  458. {
  459. get
  460. {
  461. if (IsDirectStream)
  462. {
  463. return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
  464. }
  465. return GetMediaStreamCount(MediaStreamType.Audio, 1);
  466. }
  467. }
  468. public void SetOption(string qualifier, string name, string value)
  469. {
  470. if (string.IsNullOrEmpty(qualifier))
  471. {
  472. SetOption(name, value);
  473. }
  474. else
  475. {
  476. SetOption(qualifier + "-" + name, value);
  477. }
  478. }
  479. public void SetOption(string name, string value)
  480. {
  481. StreamOptions[name] = value;
  482. }
  483. public string GetOption(string qualifier, string name)
  484. {
  485. var value = GetOption(qualifier + "-" + name);
  486. if (string.IsNullOrEmpty(value))
  487. {
  488. value = GetOption(name);
  489. }
  490. return value;
  491. }
  492. public string GetOption(string name)
  493. {
  494. if (StreamOptions.TryGetValue(name, out var value))
  495. {
  496. return value;
  497. }
  498. return null;
  499. }
  500. public string ToUrl(string baseUrl, string accessToken)
  501. {
  502. ArgumentException.ThrowIfNullOrEmpty(baseUrl);
  503. var list = new List<string>();
  504. foreach (NameValuePair pair in BuildParams(this, accessToken))
  505. {
  506. if (string.IsNullOrEmpty(pair.Value))
  507. {
  508. continue;
  509. }
  510. // Try to keep the url clean by omitting defaults
  511. if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase)
  512. && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
  513. {
  514. continue;
  515. }
  516. if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase)
  517. && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
  518. {
  519. continue;
  520. }
  521. if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase)
  522. && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
  523. {
  524. continue;
  525. }
  526. var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
  527. list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
  528. }
  529. string queryString = string.Join('&', list);
  530. return GetUrl(baseUrl, queryString);
  531. }
  532. private string GetUrl(string baseUrl, string queryString)
  533. {
  534. ArgumentException.ThrowIfNullOrEmpty(baseUrl);
  535. string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
  536. baseUrl = baseUrl.TrimEnd('/');
  537. if (MediaType == DlnaProfileType.Audio)
  538. {
  539. if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
  540. {
  541. return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
  542. }
  543. return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
  544. }
  545. if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
  546. {
  547. return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
  548. }
  549. return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
  550. }
  551. private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string accessToken)
  552. {
  553. var list = new List<NameValuePair>();
  554. string audioCodecs = item.AudioCodecs.Length == 0 ?
  555. string.Empty :
  556. string.Join(',', item.AudioCodecs);
  557. string videoCodecs = item.VideoCodecs.Length == 0 ?
  558. string.Empty :
  559. string.Join(',', item.VideoCodecs);
  560. list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
  561. list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
  562. list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
  563. list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  564. list.Add(new NameValuePair("VideoCodec", videoCodecs));
  565. list.Add(new NameValuePair("AudioCodec", audioCodecs));
  566. list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  567. list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  568. list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  569. list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  570. list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  571. list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  572. list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  573. list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  574. long startPositionTicks = item.StartPositionTicks;
  575. var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase);
  576. if (isHls)
  577. {
  578. list.Add(new NameValuePair("StartTimeTicks", string.Empty));
  579. }
  580. else
  581. {
  582. list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
  583. }
  584. list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
  585. list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
  586. string liveStreamId = item.MediaSource?.LiveStreamId;
  587. list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
  588. list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
  589. if (!item.IsDirectStream)
  590. {
  591. if (item.RequireNonAnamorphic)
  592. {
  593. list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  594. }
  595. list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  596. if (item.EnableSubtitlesInManifest)
  597. {
  598. list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  599. }
  600. if (item.EnableMpegtsM2TsMode)
  601. {
  602. list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  603. }
  604. if (item.EstimateContentLength)
  605. {
  606. list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  607. }
  608. if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
  609. {
  610. list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
  611. }
  612. if (item.CopyTimestamps)
  613. {
  614. list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  615. }
  616. list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  617. }
  618. list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty));
  619. string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
  620. string.Empty :
  621. string.Join(",", item.SubtitleCodecs);
  622. list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
  623. if (isHls)
  624. {
  625. list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
  626. if (item.SegmentLength.HasValue)
  627. {
  628. list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
  629. }
  630. if (item.MinSegments.HasValue)
  631. {
  632. list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
  633. }
  634. list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
  635. }
  636. foreach (var pair in item.StreamOptions)
  637. {
  638. if (string.IsNullOrEmpty(pair.Value))
  639. {
  640. continue;
  641. }
  642. // strip spaces to avoid having to encode h264 profile names
  643. list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
  644. }
  645. if (!item.IsDirectStream)
  646. {
  647. list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
  648. }
  649. return list;
  650. }
  651. public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken)
  652. {
  653. return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
  654. }
  655. public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
  656. {
  657. var list = new List<SubtitleStreamInfo>();
  658. // HLS will preserve timestamps so we can just grab the full subtitle stream
  659. long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)
  660. ? 0
  661. : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
  662. // First add the selected track
  663. if (SubtitleStreamIndex.HasValue)
  664. {
  665. foreach (var stream in MediaSource.MediaStreams)
  666. {
  667. if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
  668. {
  669. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  670. }
  671. }
  672. }
  673. if (!includeSelectedTrackOnly)
  674. {
  675. foreach (var stream in MediaSource.MediaStreams)
  676. {
  677. if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
  678. {
  679. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  680. }
  681. }
  682. }
  683. return list;
  684. }
  685. private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
  686. {
  687. if (enableAllProfiles)
  688. {
  689. foreach (var profile in DeviceProfile.SubtitleProfiles)
  690. {
  691. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
  692. list.Add(info);
  693. }
  694. }
  695. else
  696. {
  697. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
  698. list.Add(info);
  699. }
  700. }
  701. private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
  702. {
  703. var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
  704. var info = new SubtitleStreamInfo
  705. {
  706. IsForced = stream.IsForced,
  707. Language = stream.Language,
  708. Name = stream.Language ?? "Unknown",
  709. Format = subtitleProfile.Format,
  710. Index = stream.Index,
  711. DeliveryMethod = subtitleProfile.Method,
  712. DisplayTitle = stream.DisplayTitle
  713. };
  714. if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
  715. {
  716. if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
  717. {
  718. info.Url = string.Format(
  719. CultureInfo.InvariantCulture,
  720. "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
  721. baseUrl,
  722. ItemId,
  723. MediaSourceId,
  724. stream.Index.ToString(CultureInfo.InvariantCulture),
  725. startPositionTicks.ToString(CultureInfo.InvariantCulture),
  726. subtitleProfile.Format);
  727. if (!string.IsNullOrEmpty(accessToken))
  728. {
  729. info.Url += "?api_key=" + accessToken;
  730. }
  731. info.IsExternalUrl = false;
  732. }
  733. else
  734. {
  735. info.Url = stream.Path;
  736. info.IsExternalUrl = true;
  737. }
  738. }
  739. return info;
  740. }
  741. public int? GetTargetVideoBitDepth(string codec)
  742. {
  743. var value = GetOption(codec, "videobitdepth");
  744. if (string.IsNullOrEmpty(value))
  745. {
  746. return null;
  747. }
  748. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  749. {
  750. return result;
  751. }
  752. return null;
  753. }
  754. public int? GetTargetAudioBitDepth(string codec)
  755. {
  756. var value = GetOption(codec, "audiobitdepth");
  757. if (string.IsNullOrEmpty(value))
  758. {
  759. return null;
  760. }
  761. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  762. {
  763. return result;
  764. }
  765. return null;
  766. }
  767. public double? GetTargetVideoLevel(string codec)
  768. {
  769. var value = GetOption(codec, "level");
  770. if (string.IsNullOrEmpty(value))
  771. {
  772. return null;
  773. }
  774. if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  775. {
  776. return result;
  777. }
  778. return null;
  779. }
  780. public int? GetTargetRefFrames(string codec)
  781. {
  782. var value = GetOption(codec, "maxrefframes");
  783. if (string.IsNullOrEmpty(value))
  784. {
  785. return null;
  786. }
  787. if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  788. {
  789. return result;
  790. }
  791. return null;
  792. }
  793. public int? GetTargetAudioChannels(string codec)
  794. {
  795. var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
  796. var value = GetOption(codec, "audiochannels");
  797. if (string.IsNullOrEmpty(value))
  798. {
  799. return defaultValue;
  800. }
  801. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  802. {
  803. return Math.Min(result, defaultValue ?? result);
  804. }
  805. return defaultValue;
  806. }
  807. private int? GetMediaStreamCount(MediaStreamType type, int limit)
  808. {
  809. var count = MediaSource.GetStreamCount(type);
  810. if (count.HasValue)
  811. {
  812. count = Math.Min(count.Value, limit);
  813. }
  814. return count;
  815. }
  816. }
  817. }