StreamInfo.cs 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.Linq;
  6. using Jellyfin.Data.Enums;
  7. using MediaBrowser.Model.Drawing;
  8. using MediaBrowser.Model.Dto;
  9. using MediaBrowser.Model.Entities;
  10. using MediaBrowser.Model.MediaInfo;
  11. using MediaBrowser.Model.Session;
  12. namespace MediaBrowser.Model.Dlna
  13. {
  14. /// <summary>
  15. /// Class StreamInfo.
  16. /// </summary>
  17. public class StreamInfo
  18. {
  19. public StreamInfo()
  20. {
  21. AudioCodecs = Array.Empty<string>();
  22. VideoCodecs = Array.Empty<string>();
  23. SubtitleCodecs = Array.Empty<string>();
  24. StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  25. }
  26. public Guid ItemId { get; set; }
  27. public PlayMethod PlayMethod { get; set; }
  28. public EncodingContext Context { get; set; }
  29. public DlnaProfileType MediaType { get; set; }
  30. public string? Container { get; set; }
  31. public MediaStreamProtocol SubProtocol { get; set; }
  32. public long StartPositionTicks { get; set; }
  33. public int? SegmentLength { get; set; }
  34. public int? MinSegments { get; set; }
  35. public bool BreakOnNonKeyFrames { get; set; }
  36. public bool RequireAvc { get; set; }
  37. public bool RequireNonAnamorphic { get; set; }
  38. public bool CopyTimestamps { get; set; }
  39. public bool EnableMpegtsM2TsMode { get; set; }
  40. public bool EnableSubtitlesInManifest { get; set; }
  41. public string[] AudioCodecs { get; set; }
  42. public string[] VideoCodecs { get; set; }
  43. public int? AudioStreamIndex { get; set; }
  44. public int? SubtitleStreamIndex { get; set; }
  45. public int? TranscodingMaxAudioChannels { get; set; }
  46. public int? GlobalMaxAudioChannels { get; set; }
  47. public int? AudioBitrate { get; set; }
  48. public int? AudioSampleRate { get; set; }
  49. public int? VideoBitrate { get; set; }
  50. public int? MaxWidth { get; set; }
  51. public int? MaxHeight { get; set; }
  52. public float? MaxFramerate { get; set; }
  53. public required DeviceProfile DeviceProfile { get; set; }
  54. public string? DeviceProfileId { get; set; }
  55. public string? DeviceId { get; set; }
  56. public long? RunTimeTicks { get; set; }
  57. public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
  58. public bool EstimateContentLength { get; set; }
  59. public MediaSourceInfo? MediaSource { get; set; }
  60. public string[] SubtitleCodecs { get; set; }
  61. public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
  62. public string? SubtitleFormat { get; set; }
  63. public string? PlaySessionId { get; set; }
  64. public TranscodeReason TranscodeReasons { get; set; }
  65. public Dictionary<string, string> StreamOptions { get; private set; }
  66. public string? MediaSourceId => MediaSource?.Id;
  67. public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay)
  68. && PlayMethod is PlayMethod.DirectStream or 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 VideoRangeType TargetVideoRangeType
  221. {
  222. get
  223. {
  224. if (IsDirectStream)
  225. {
  226. return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
  227. }
  228. var targetVideoCodecs = TargetVideoCodec;
  229. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  230. if (!string.IsNullOrEmpty(videoCodec)
  231. && Enum.TryParse(GetOption(videoCodec, "rangetype"), true, out VideoRangeType videoRangeType))
  232. {
  233. return videoRangeType;
  234. }
  235. return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
  236. }
  237. }
  238. /// <summary>
  239. /// Gets the target video codec tag.
  240. /// </summary>
  241. /// <value>The target video codec tag.</value>
  242. public string? TargetVideoCodecTag
  243. {
  244. get
  245. {
  246. var stream = TargetVideoStream;
  247. return !IsDirectStream
  248. ? null
  249. : stream?.CodecTag;
  250. }
  251. }
  252. /// <summary>
  253. /// Gets the audio bitrate that will be in the output stream.
  254. /// </summary>
  255. public int? TargetAudioBitrate
  256. {
  257. get
  258. {
  259. var stream = TargetAudioStream;
  260. return AudioBitrate.HasValue && !IsDirectStream
  261. ? AudioBitrate
  262. : stream?.BitRate;
  263. }
  264. }
  265. /// <summary>
  266. /// Gets the audio channels that will be in the output stream.
  267. /// </summary>
  268. public int? TargetAudioChannels
  269. {
  270. get
  271. {
  272. if (IsDirectStream)
  273. {
  274. return TargetAudioStream?.Channels;
  275. }
  276. var targetAudioCodecs = TargetAudioCodec;
  277. var codec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
  278. if (!string.IsNullOrEmpty(codec))
  279. {
  280. return GetTargetRefFrames(codec);
  281. }
  282. return TargetAudioStream?.Channels;
  283. }
  284. }
  285. /// <summary>
  286. /// Gets the audio codec that will be in the output stream.
  287. /// </summary>
  288. public string[] TargetAudioCodec
  289. {
  290. get
  291. {
  292. var stream = TargetAudioStream;
  293. string? inputCodec = stream?.Codec;
  294. if (IsDirectStream)
  295. {
  296. return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
  297. }
  298. foreach (string codec in AudioCodecs)
  299. {
  300. if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
  301. {
  302. return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec };
  303. }
  304. }
  305. return AudioCodecs;
  306. }
  307. }
  308. public string[] TargetVideoCodec
  309. {
  310. get
  311. {
  312. var stream = TargetVideoStream;
  313. string? inputCodec = stream?.Codec;
  314. if (IsDirectStream)
  315. {
  316. return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
  317. }
  318. foreach (string codec in VideoCodecs)
  319. {
  320. if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
  321. {
  322. return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec };
  323. }
  324. }
  325. return VideoCodecs;
  326. }
  327. }
  328. /// <summary>
  329. /// Gets the audio channels that will be in the output stream.
  330. /// </summary>
  331. public long? TargetSize
  332. {
  333. get
  334. {
  335. if (IsDirectStream)
  336. {
  337. return MediaSource?.Size;
  338. }
  339. if (RunTimeTicks.HasValue)
  340. {
  341. int? totalBitrate = TargetTotalBitrate;
  342. double totalSeconds = RunTimeTicks.Value;
  343. // Convert to ms
  344. totalSeconds /= 10000;
  345. // Convert to seconds
  346. totalSeconds /= 1000;
  347. return totalBitrate.HasValue ?
  348. Convert.ToInt64(totalBitrate.Value * totalSeconds) :
  349. null;
  350. }
  351. return null;
  352. }
  353. }
  354. public int? TargetVideoBitrate
  355. {
  356. get
  357. {
  358. var stream = TargetVideoStream;
  359. return VideoBitrate.HasValue && !IsDirectStream
  360. ? VideoBitrate
  361. : stream?.BitRate;
  362. }
  363. }
  364. public TransportStreamTimestamp TargetTimestamp
  365. {
  366. get
  367. {
  368. var defaultValue = string.Equals(Container, "m2ts", StringComparison.OrdinalIgnoreCase)
  369. ? TransportStreamTimestamp.Valid
  370. : TransportStreamTimestamp.None;
  371. return !IsDirectStream
  372. ? defaultValue
  373. : MediaSource is null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None;
  374. }
  375. }
  376. public int? TargetTotalBitrate => (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0);
  377. public bool? IsTargetAnamorphic
  378. {
  379. get
  380. {
  381. if (IsDirectStream)
  382. {
  383. return TargetVideoStream?.IsAnamorphic;
  384. }
  385. return false;
  386. }
  387. }
  388. public bool? IsTargetInterlaced
  389. {
  390. get
  391. {
  392. if (IsDirectStream)
  393. {
  394. return TargetVideoStream?.IsInterlaced;
  395. }
  396. var targetVideoCodecs = TargetVideoCodec;
  397. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  398. if (!string.IsNullOrEmpty(videoCodec))
  399. {
  400. if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
  401. {
  402. return false;
  403. }
  404. }
  405. return TargetVideoStream?.IsInterlaced;
  406. }
  407. }
  408. public bool? IsTargetAVC
  409. {
  410. get
  411. {
  412. if (IsDirectStream)
  413. {
  414. return TargetVideoStream?.IsAVC;
  415. }
  416. return true;
  417. }
  418. }
  419. public int? TargetWidth
  420. {
  421. get
  422. {
  423. var videoStream = TargetVideoStream;
  424. if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue)
  425. {
  426. ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
  427. size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
  428. return size.Width;
  429. }
  430. return MaxWidth;
  431. }
  432. }
  433. public int? TargetHeight
  434. {
  435. get
  436. {
  437. var videoStream = TargetVideoStream;
  438. if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue)
  439. {
  440. ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
  441. size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
  442. return size.Height;
  443. }
  444. return MaxHeight;
  445. }
  446. }
  447. public int? TargetVideoStreamCount
  448. {
  449. get
  450. {
  451. if (IsDirectStream)
  452. {
  453. return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
  454. }
  455. return GetMediaStreamCount(MediaStreamType.Video, 1);
  456. }
  457. }
  458. public int? TargetAudioStreamCount
  459. {
  460. get
  461. {
  462. if (IsDirectStream)
  463. {
  464. return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
  465. }
  466. return GetMediaStreamCount(MediaStreamType.Audio, 1);
  467. }
  468. }
  469. public void SetOption(string? qualifier, string name, string value)
  470. {
  471. if (string.IsNullOrEmpty(qualifier))
  472. {
  473. SetOption(name, value);
  474. }
  475. else
  476. {
  477. SetOption(qualifier + "-" + name, value);
  478. }
  479. }
  480. public void SetOption(string name, string value)
  481. {
  482. StreamOptions[name] = value;
  483. }
  484. public string? GetOption(string? qualifier, string name)
  485. {
  486. var value = GetOption(qualifier + "-" + name);
  487. if (string.IsNullOrEmpty(value))
  488. {
  489. value = GetOption(name);
  490. }
  491. return value;
  492. }
  493. public string? GetOption(string name)
  494. {
  495. if (StreamOptions.TryGetValue(name, out var value))
  496. {
  497. return value;
  498. }
  499. return null;
  500. }
  501. public string ToUrl(string baseUrl, string? accessToken)
  502. {
  503. ArgumentException.ThrowIfNullOrEmpty(baseUrl);
  504. var list = new List<string>();
  505. foreach (NameValuePair pair in BuildParams(this, accessToken))
  506. {
  507. if (string.IsNullOrEmpty(pair.Value))
  508. {
  509. continue;
  510. }
  511. // Try to keep the url clean by omitting defaults
  512. if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase)
  513. && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
  514. {
  515. continue;
  516. }
  517. if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase)
  518. && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
  519. {
  520. continue;
  521. }
  522. if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase)
  523. && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
  524. {
  525. continue;
  526. }
  527. var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
  528. list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
  529. }
  530. string queryString = string.Join('&', list);
  531. return GetUrl(baseUrl, queryString);
  532. }
  533. private string GetUrl(string baseUrl, string queryString)
  534. {
  535. ArgumentException.ThrowIfNullOrEmpty(baseUrl);
  536. string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
  537. baseUrl = baseUrl.TrimEnd('/');
  538. if (MediaType == DlnaProfileType.Audio)
  539. {
  540. if (SubProtocol == MediaStreamProtocol.hls)
  541. {
  542. return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
  543. }
  544. return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
  545. }
  546. if (SubProtocol == MediaStreamProtocol.hls)
  547. {
  548. return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
  549. }
  550. return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
  551. }
  552. private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string? accessToken)
  553. {
  554. var list = new List<NameValuePair>();
  555. string audioCodecs = item.AudioCodecs.Length == 0 ?
  556. string.Empty :
  557. string.Join(',', item.AudioCodecs);
  558. string videoCodecs = item.VideoCodecs.Length == 0 ?
  559. string.Empty :
  560. string.Join(',', item.VideoCodecs);
  561. list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
  562. list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
  563. list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
  564. list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  565. list.Add(new NameValuePair("VideoCodec", videoCodecs));
  566. list.Add(new NameValuePair("AudioCodec", audioCodecs));
  567. list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  568. list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  569. list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  570. list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  571. list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  572. list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  573. list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  574. list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  575. long startPositionTicks = item.StartPositionTicks;
  576. if (item.SubProtocol == MediaStreamProtocol.hls)
  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 (item.SubProtocol == MediaStreamProtocol.hls)
  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. if (MediaSource is null)
  658. {
  659. return Enumerable.Empty<SubtitleStreamInfo>();
  660. }
  661. var list = new List<SubtitleStreamInfo>();
  662. // HLS will preserve timestamps so we can just grab the full subtitle stream
  663. long startPositionTicks = SubProtocol == MediaStreamProtocol.hls
  664. ? 0
  665. : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
  666. // First add the selected track
  667. if (SubtitleStreamIndex.HasValue)
  668. {
  669. foreach (var stream in MediaSource.MediaStreams)
  670. {
  671. if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
  672. {
  673. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  674. }
  675. }
  676. }
  677. if (!includeSelectedTrackOnly)
  678. {
  679. foreach (var stream in MediaSource.MediaStreams)
  680. {
  681. if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
  682. {
  683. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  684. }
  685. }
  686. }
  687. return list;
  688. }
  689. private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string? accessToken, long startPositionTicks)
  690. {
  691. if (enableAllProfiles)
  692. {
  693. foreach (var profile in DeviceProfile.SubtitleProfiles)
  694. {
  695. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
  696. if (info is not null)
  697. {
  698. list.Add(info);
  699. }
  700. }
  701. }
  702. else
  703. {
  704. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
  705. if (info is not null)
  706. {
  707. list.Add(info);
  708. }
  709. }
  710. }
  711. private SubtitleStreamInfo? GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string? accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
  712. {
  713. if (MediaSource is null)
  714. {
  715. return null;
  716. }
  717. var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
  718. var info = new SubtitleStreamInfo
  719. {
  720. IsForced = stream.IsForced,
  721. Language = stream.Language,
  722. Name = stream.Language ?? "Unknown",
  723. Format = subtitleProfile.Format,
  724. Index = stream.Index,
  725. DeliveryMethod = subtitleProfile.Method,
  726. DisplayTitle = stream.DisplayTitle
  727. };
  728. if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
  729. {
  730. if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
  731. {
  732. info.Url = string.Format(
  733. CultureInfo.InvariantCulture,
  734. "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
  735. baseUrl,
  736. ItemId,
  737. MediaSourceId,
  738. stream.Index.ToString(CultureInfo.InvariantCulture),
  739. startPositionTicks.ToString(CultureInfo.InvariantCulture),
  740. subtitleProfile.Format);
  741. if (!string.IsNullOrEmpty(accessToken))
  742. {
  743. info.Url += "?api_key=" + accessToken;
  744. }
  745. info.IsExternalUrl = false;
  746. }
  747. else
  748. {
  749. info.Url = stream.Path;
  750. info.IsExternalUrl = true;
  751. }
  752. }
  753. return info;
  754. }
  755. public int? GetTargetVideoBitDepth(string? codec)
  756. {
  757. var value = GetOption(codec, "videobitdepth");
  758. if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  759. {
  760. return result;
  761. }
  762. return null;
  763. }
  764. public int? GetTargetAudioBitDepth(string? codec)
  765. {
  766. var value = GetOption(codec, "audiobitdepth");
  767. if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  768. {
  769. return result;
  770. }
  771. return null;
  772. }
  773. public double? GetTargetVideoLevel(string? codec)
  774. {
  775. var value = GetOption(codec, "level");
  776. if (double.TryParse(value, CultureInfo.InvariantCulture, out var result))
  777. {
  778. return result;
  779. }
  780. return null;
  781. }
  782. public int? GetTargetRefFrames(string? codec)
  783. {
  784. var value = GetOption(codec, "maxrefframes");
  785. if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  786. {
  787. return result;
  788. }
  789. return null;
  790. }
  791. public int? GetTargetAudioChannels(string? codec)
  792. {
  793. var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
  794. var value = GetOption(codec, "audiochannels");
  795. if (string.IsNullOrEmpty(value))
  796. {
  797. return defaultValue;
  798. }
  799. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  800. {
  801. return Math.Min(result, defaultValue ?? result);
  802. }
  803. return defaultValue;
  804. }
  805. private int? GetMediaStreamCount(MediaStreamType type, int limit)
  806. {
  807. var count = MediaSource?.GetStreamCount(type);
  808. if (count.HasValue)
  809. {
  810. count = Math.Min(count.Value, limit);
  811. }
  812. return count;
  813. }
  814. }
  815. }