2
0

StreamInfo.cs 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  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 string? 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 (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
  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 (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
  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. var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase);
  577. if (isHls)
  578. {
  579. list.Add(new NameValuePair("StartTimeTicks", string.Empty));
  580. }
  581. else
  582. {
  583. list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
  584. }
  585. list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
  586. list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
  587. string? liveStreamId = item.MediaSource?.LiveStreamId;
  588. list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
  589. list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
  590. if (!item.IsDirectStream)
  591. {
  592. if (item.RequireNonAnamorphic)
  593. {
  594. list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  595. }
  596. list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  597. if (item.EnableSubtitlesInManifest)
  598. {
  599. list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  600. }
  601. if (item.EnableMpegtsM2TsMode)
  602. {
  603. list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  604. }
  605. if (item.EstimateContentLength)
  606. {
  607. list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  608. }
  609. if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
  610. {
  611. list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
  612. }
  613. if (item.CopyTimestamps)
  614. {
  615. list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  616. }
  617. list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  618. }
  619. list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty));
  620. string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
  621. string.Empty :
  622. string.Join(",", item.SubtitleCodecs);
  623. list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
  624. if (isHls)
  625. {
  626. list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
  627. if (item.SegmentLength.HasValue)
  628. {
  629. list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
  630. }
  631. if (item.MinSegments.HasValue)
  632. {
  633. list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
  634. }
  635. list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
  636. }
  637. foreach (var pair in item.StreamOptions)
  638. {
  639. if (string.IsNullOrEmpty(pair.Value))
  640. {
  641. continue;
  642. }
  643. // strip spaces to avoid having to encode h264 profile names
  644. list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
  645. }
  646. if (!item.IsDirectStream)
  647. {
  648. list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
  649. }
  650. return list;
  651. }
  652. public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string? accessToken)
  653. {
  654. return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
  655. }
  656. public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string? accessToken)
  657. {
  658. if (MediaSource is null)
  659. {
  660. return Enumerable.Empty<SubtitleStreamInfo>();
  661. }
  662. var list = new List<SubtitleStreamInfo>();
  663. // HLS will preserve timestamps so we can just grab the full subtitle stream
  664. long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)
  665. ? 0
  666. : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
  667. // First add the selected track
  668. if (SubtitleStreamIndex.HasValue)
  669. {
  670. foreach (var stream in MediaSource.MediaStreams)
  671. {
  672. if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
  673. {
  674. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  675. }
  676. }
  677. }
  678. if (!includeSelectedTrackOnly)
  679. {
  680. foreach (var stream in MediaSource.MediaStreams)
  681. {
  682. if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
  683. {
  684. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  685. }
  686. }
  687. }
  688. return list;
  689. }
  690. private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string? accessToken, long startPositionTicks)
  691. {
  692. if (enableAllProfiles)
  693. {
  694. foreach (var profile in DeviceProfile.SubtitleProfiles)
  695. {
  696. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
  697. if (info is not null)
  698. {
  699. list.Add(info);
  700. }
  701. }
  702. }
  703. else
  704. {
  705. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
  706. if (info is not null)
  707. {
  708. list.Add(info);
  709. }
  710. }
  711. }
  712. private SubtitleStreamInfo? GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string? accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
  713. {
  714. if (MediaSource is null)
  715. {
  716. return null;
  717. }
  718. var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
  719. var info = new SubtitleStreamInfo
  720. {
  721. IsForced = stream.IsForced,
  722. Language = stream.Language,
  723. Name = stream.Language ?? "Unknown",
  724. Format = subtitleProfile.Format,
  725. Index = stream.Index,
  726. DeliveryMethod = subtitleProfile.Method,
  727. DisplayTitle = stream.DisplayTitle
  728. };
  729. if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
  730. {
  731. if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
  732. {
  733. info.Url = string.Format(
  734. CultureInfo.InvariantCulture,
  735. "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
  736. baseUrl,
  737. ItemId,
  738. MediaSourceId,
  739. stream.Index.ToString(CultureInfo.InvariantCulture),
  740. startPositionTicks.ToString(CultureInfo.InvariantCulture),
  741. subtitleProfile.Format);
  742. if (!string.IsNullOrEmpty(accessToken))
  743. {
  744. info.Url += "?api_key=" + accessToken;
  745. }
  746. info.IsExternalUrl = false;
  747. }
  748. else
  749. {
  750. info.Url = stream.Path;
  751. info.IsExternalUrl = true;
  752. }
  753. }
  754. return info;
  755. }
  756. public int? GetTargetVideoBitDepth(string? codec)
  757. {
  758. var value = GetOption(codec, "videobitdepth");
  759. if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  760. {
  761. return result;
  762. }
  763. return null;
  764. }
  765. public int? GetTargetAudioBitDepth(string? codec)
  766. {
  767. var value = GetOption(codec, "audiobitdepth");
  768. if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  769. {
  770. return result;
  771. }
  772. return null;
  773. }
  774. public double? GetTargetVideoLevel(string? codec)
  775. {
  776. var value = GetOption(codec, "level");
  777. if (double.TryParse(value, CultureInfo.InvariantCulture, out var result))
  778. {
  779. return result;
  780. }
  781. return null;
  782. }
  783. public int? GetTargetRefFrames(string? codec)
  784. {
  785. var value = GetOption(codec, "maxrefframes");
  786. if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  787. {
  788. return result;
  789. }
  790. return null;
  791. }
  792. public int? GetTargetAudioChannels(string? codec)
  793. {
  794. var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
  795. var value = GetOption(codec, "audiochannels");
  796. if (string.IsNullOrEmpty(value))
  797. {
  798. return defaultValue;
  799. }
  800. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  801. {
  802. return Math.Min(result, defaultValue ?? result);
  803. }
  804. return defaultValue;
  805. }
  806. private int? GetMediaStreamCount(MediaStreamType type, int limit)
  807. {
  808. var count = MediaSource?.GetStreamCount(type);
  809. if (count.HasValue)
  810. {
  811. count = Math.Min(count.Value, limit);
  812. }
  813. return count;
  814. }
  815. }
  816. }