StreamInfo.cs 34 KB

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