StreamInfo.cs 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  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 (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  745. {
  746. return result;
  747. }
  748. return null;
  749. }
  750. public int? GetTargetAudioBitDepth(string codec)
  751. {
  752. var value = GetOption(codec, "audiobitdepth");
  753. if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  754. {
  755. return result;
  756. }
  757. return null;
  758. }
  759. public double? GetTargetVideoLevel(string codec)
  760. {
  761. var value = GetOption(codec, "level");
  762. if (double.TryParse(value, CultureInfo.InvariantCulture, out var result))
  763. {
  764. return result;
  765. }
  766. return null;
  767. }
  768. public int? GetTargetRefFrames(string codec)
  769. {
  770. var value = GetOption(codec, "maxrefframes");
  771. if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
  772. {
  773. return result;
  774. }
  775. return null;
  776. }
  777. public int? GetTargetAudioChannels(string codec)
  778. {
  779. var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
  780. var value = GetOption(codec, "audiochannels");
  781. if (string.IsNullOrEmpty(value))
  782. {
  783. return defaultValue;
  784. }
  785. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  786. {
  787. return Math.Min(result, defaultValue ?? result);
  788. }
  789. return defaultValue;
  790. }
  791. private int? GetMediaStreamCount(MediaStreamType type, int limit)
  792. {
  793. var count = MediaSource.GetStreamCount(type);
  794. if (count.HasValue)
  795. {
  796. count = Math.Min(count.Value, limit);
  797. }
  798. return count;
  799. }
  800. }
  801. }