StreamInfo.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. #nullable disable
  2. #pragma warning disable CS1591
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using System.Linq;
  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 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 =>
  68. PlayMethod == PlayMethod.DirectStream ||
  69. PlayMethod == PlayMethod.DirectPlay;
  70. /// <summary>
  71. /// Gets the audio stream that will be used.
  72. /// </summary>
  73. public MediaStream TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex);
  74. /// <summary>
  75. /// Gets the video stream that will be used.
  76. /// </summary>
  77. public MediaStream TargetVideoStream => MediaSource?.VideoStream;
  78. /// <summary>
  79. /// Gets the audio sample rate that will be in the output stream.
  80. /// </summary>
  81. public int? TargetAudioSampleRate
  82. {
  83. get
  84. {
  85. var stream = TargetAudioStream;
  86. return AudioSampleRate.HasValue && !IsDirectStream
  87. ? AudioSampleRate
  88. : stream?.SampleRate;
  89. }
  90. }
  91. /// <summary>
  92. /// Gets the audio sample rate that will be in the output stream.
  93. /// </summary>
  94. public int? TargetAudioBitDepth
  95. {
  96. get
  97. {
  98. if (IsDirectStream)
  99. {
  100. return TargetAudioStream?.BitDepth;
  101. }
  102. var targetAudioCodecs = TargetAudioCodec;
  103. var audioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
  104. if (!string.IsNullOrEmpty(audioCodec))
  105. {
  106. return GetTargetAudioBitDepth(audioCodec);
  107. }
  108. return TargetAudioStream?.BitDepth;
  109. }
  110. }
  111. /// <summary>
  112. /// Gets the audio sample rate that will be in the output stream.
  113. /// </summary>
  114. public int? TargetVideoBitDepth
  115. {
  116. get
  117. {
  118. if (IsDirectStream)
  119. {
  120. return TargetVideoStream?.BitDepth;
  121. }
  122. var targetVideoCodecs = TargetVideoCodec;
  123. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  124. if (!string.IsNullOrEmpty(videoCodec))
  125. {
  126. return GetTargetVideoBitDepth(videoCodec);
  127. }
  128. return TargetVideoStream?.BitDepth;
  129. }
  130. }
  131. /// <summary>
  132. /// Gets the target reference frames.
  133. /// </summary>
  134. /// <value>The target reference frames.</value>
  135. public int? TargetRefFrames
  136. {
  137. get
  138. {
  139. if (IsDirectStream)
  140. {
  141. return TargetVideoStream?.RefFrames;
  142. }
  143. var targetVideoCodecs = TargetVideoCodec;
  144. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  145. if (!string.IsNullOrEmpty(videoCodec))
  146. {
  147. return GetTargetRefFrames(videoCodec);
  148. }
  149. return TargetVideoStream?.RefFrames;
  150. }
  151. }
  152. /// <summary>
  153. /// Gets the audio sample rate that will be in the output stream.
  154. /// </summary>
  155. public float? TargetFramerate
  156. {
  157. get
  158. {
  159. var stream = TargetVideoStream;
  160. return MaxFramerate.HasValue && !IsDirectStream
  161. ? MaxFramerate
  162. : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate;
  163. }
  164. }
  165. /// <summary>
  166. /// Gets the audio sample rate that will be in the output stream.
  167. /// </summary>
  168. public double? TargetVideoLevel
  169. {
  170. get
  171. {
  172. if (IsDirectStream)
  173. {
  174. return TargetVideoStream?.Level;
  175. }
  176. var targetVideoCodecs = TargetVideoCodec;
  177. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  178. if (!string.IsNullOrEmpty(videoCodec))
  179. {
  180. return GetTargetVideoLevel(videoCodec);
  181. }
  182. return TargetVideoStream?.Level;
  183. }
  184. }
  185. /// <summary>
  186. /// Gets the audio sample rate that will be in the output stream.
  187. /// </summary>
  188. public int? TargetPacketLength
  189. {
  190. get
  191. {
  192. var stream = TargetVideoStream;
  193. return !IsDirectStream
  194. ? null
  195. : stream?.PacketLength;
  196. }
  197. }
  198. /// <summary>
  199. /// Gets the audio sample rate that will be in the output stream.
  200. /// </summary>
  201. public string TargetVideoProfile
  202. {
  203. get
  204. {
  205. if (IsDirectStream)
  206. {
  207. return TargetVideoStream?.Profile;
  208. }
  209. var targetVideoCodecs = TargetVideoCodec;
  210. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  211. if (!string.IsNullOrEmpty(videoCodec))
  212. {
  213. return GetOption(videoCodec, "profile");
  214. }
  215. return TargetVideoStream?.Profile;
  216. }
  217. }
  218. /// <summary>
  219. /// Gets the target video range type that will be in the output stream.
  220. /// </summary>
  221. public string TargetVideoRangeType
  222. {
  223. get
  224. {
  225. if (IsDirectStream)
  226. {
  227. return TargetVideoStream?.VideoRangeType;
  228. }
  229. var targetVideoCodecs = TargetVideoCodec;
  230. var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
  231. if (!string.IsNullOrEmpty(videoCodec))
  232. {
  233. return GetOption(videoCodec, "rangetype");
  234. }
  235. return TargetVideoStream?.VideoRangeType;
  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. (long?)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 == 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 != 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 != 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. if (string.IsNullOrEmpty(baseUrl))
  504. {
  505. throw new ArgumentNullException(nameof(baseUrl));
  506. }
  507. var list = new List<string>();
  508. foreach (NameValuePair pair in BuildParams(this, accessToken))
  509. {
  510. if (string.IsNullOrEmpty(pair.Value))
  511. {
  512. continue;
  513. }
  514. // Try to keep the url clean by omitting defaults
  515. if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase)
  516. && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
  517. {
  518. continue;
  519. }
  520. if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase)
  521. && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
  522. {
  523. continue;
  524. }
  525. if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase)
  526. && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
  527. {
  528. continue;
  529. }
  530. var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
  531. list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
  532. }
  533. string queryString = string.Join('&', list);
  534. return GetUrl(baseUrl, queryString);
  535. }
  536. private string GetUrl(string baseUrl, string queryString)
  537. {
  538. if (string.IsNullOrEmpty(baseUrl))
  539. {
  540. throw new ArgumentNullException(nameof(baseUrl));
  541. }
  542. string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
  543. baseUrl = baseUrl.TrimEnd('/');
  544. if (MediaType == DlnaProfileType.Audio)
  545. {
  546. if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
  547. {
  548. return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
  549. }
  550. return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
  551. }
  552. if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
  553. {
  554. return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
  555. }
  556. return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
  557. }
  558. private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string accessToken)
  559. {
  560. var list = new List<NameValuePair>();
  561. string audioCodecs = item.AudioCodecs.Length == 0 ?
  562. string.Empty :
  563. string.Join(',', item.AudioCodecs);
  564. string videoCodecs = item.VideoCodecs.Length == 0 ?
  565. string.Empty :
  566. string.Join(',', item.VideoCodecs);
  567. list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
  568. list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
  569. list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
  570. list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  571. list.Add(new NameValuePair("VideoCodec", videoCodecs));
  572. list.Add(new NameValuePair("AudioCodec", audioCodecs));
  573. list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  574. list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  575. list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  576. list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  577. list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  578. list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  579. list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  580. list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  581. long startPositionTicks = item.StartPositionTicks;
  582. var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase);
  583. if (isHls)
  584. {
  585. list.Add(new NameValuePair("StartTimeTicks", string.Empty));
  586. }
  587. else
  588. {
  589. list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
  590. }
  591. list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
  592. list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
  593. string liveStreamId = item.MediaSource?.LiveStreamId;
  594. list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
  595. list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
  596. if (!item.IsDirectStream)
  597. {
  598. if (item.RequireNonAnamorphic)
  599. {
  600. list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  601. }
  602. list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  603. if (item.EnableSubtitlesInManifest)
  604. {
  605. list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  606. }
  607. if (item.EnableMpegtsM2TsMode)
  608. {
  609. list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  610. }
  611. if (item.EstimateContentLength)
  612. {
  613. list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  614. }
  615. if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
  616. {
  617. list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
  618. }
  619. if (item.CopyTimestamps)
  620. {
  621. list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  622. }
  623. list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  624. }
  625. list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty));
  626. string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
  627. string.Empty :
  628. string.Join(",", item.SubtitleCodecs);
  629. list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
  630. if (isHls)
  631. {
  632. list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
  633. if (item.SegmentLength.HasValue)
  634. {
  635. list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
  636. }
  637. if (item.MinSegments.HasValue)
  638. {
  639. list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
  640. }
  641. list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
  642. }
  643. foreach (var pair in item.StreamOptions)
  644. {
  645. if (string.IsNullOrEmpty(pair.Value))
  646. {
  647. continue;
  648. }
  649. // strip spaces to avoid having to encode h264 profile names
  650. list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
  651. }
  652. if (!item.IsDirectStream)
  653. {
  654. list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
  655. }
  656. return list;
  657. }
  658. public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken)
  659. {
  660. return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
  661. }
  662. public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
  663. {
  664. var list = new List<SubtitleStreamInfo>();
  665. // HLS will preserve timestamps so we can just grab the full subtitle stream
  666. long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)
  667. ? 0
  668. : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
  669. // First add the selected track
  670. if (SubtitleStreamIndex.HasValue)
  671. {
  672. foreach (var stream in MediaSource.MediaStreams)
  673. {
  674. if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
  675. {
  676. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  677. }
  678. }
  679. }
  680. if (!includeSelectedTrackOnly)
  681. {
  682. foreach (var stream in MediaSource.MediaStreams)
  683. {
  684. if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
  685. {
  686. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  687. }
  688. }
  689. }
  690. return list;
  691. }
  692. private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
  693. {
  694. if (enableAllProfiles)
  695. {
  696. foreach (var profile in DeviceProfile.SubtitleProfiles)
  697. {
  698. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
  699. list.Add(info);
  700. }
  701. }
  702. else
  703. {
  704. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
  705. list.Add(info);
  706. }
  707. }
  708. private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
  709. {
  710. var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
  711. var info = new SubtitleStreamInfo
  712. {
  713. IsForced = stream.IsForced,
  714. Language = stream.Language,
  715. Name = stream.Language ?? "Unknown",
  716. Format = subtitleProfile.Format,
  717. Index = stream.Index,
  718. DeliveryMethod = subtitleProfile.Method,
  719. DisplayTitle = stream.DisplayTitle
  720. };
  721. if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
  722. {
  723. if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
  724. {
  725. info.Url = string.Format(
  726. CultureInfo.InvariantCulture,
  727. "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
  728. baseUrl,
  729. ItemId,
  730. MediaSourceId,
  731. stream.Index.ToString(CultureInfo.InvariantCulture),
  732. startPositionTicks.ToString(CultureInfo.InvariantCulture),
  733. subtitleProfile.Format);
  734. if (!string.IsNullOrEmpty(accessToken))
  735. {
  736. info.Url += "?api_key=" + accessToken;
  737. }
  738. info.IsExternalUrl = false;
  739. }
  740. else
  741. {
  742. info.Url = stream.Path;
  743. info.IsExternalUrl = true;
  744. }
  745. }
  746. return info;
  747. }
  748. public int? GetTargetVideoBitDepth(string codec)
  749. {
  750. var value = GetOption(codec, "videobitdepth");
  751. if (string.IsNullOrEmpty(value))
  752. {
  753. return null;
  754. }
  755. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  756. {
  757. return result;
  758. }
  759. return null;
  760. }
  761. public int? GetTargetAudioBitDepth(string codec)
  762. {
  763. var value = GetOption(codec, "audiobitdepth");
  764. if (string.IsNullOrEmpty(value))
  765. {
  766. return null;
  767. }
  768. if (int.TryParse(value, NumberStyles.Integer, 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 (string.IsNullOrEmpty(value))
  778. {
  779. return null;
  780. }
  781. if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  782. {
  783. return result;
  784. }
  785. return null;
  786. }
  787. public int? GetTargetRefFrames(string codec)
  788. {
  789. var value = GetOption(codec, "maxrefframes");
  790. if (string.IsNullOrEmpty(value))
  791. {
  792. return null;
  793. }
  794. if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  795. {
  796. return result;
  797. }
  798. return null;
  799. }
  800. public int? GetTargetAudioChannels(string codec)
  801. {
  802. var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
  803. var value = GetOption(codec, "audiochannels");
  804. if (string.IsNullOrEmpty(value))
  805. {
  806. return defaultValue;
  807. }
  808. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  809. {
  810. return Math.Min(result, defaultValue ?? result);
  811. }
  812. return defaultValue;
  813. }
  814. private int? GetMediaStreamCount(MediaStreamType type, int limit)
  815. {
  816. var count = MediaSource.GetStreamCount(type);
  817. if (count.HasValue)
  818. {
  819. count = Math.Min(count.Value, limit);
  820. }
  821. return count;
  822. }
  823. }
  824. }