StreamInfo.cs 34 KB

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