StreamInfo.cs 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. #nullable disable
  2. #pragma warning disable CS1591
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using 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 (string.IsNullOrEmpty(baseUrl))
  484. {
  485. throw new ArgumentNullException(nameof(baseUrl));
  486. }
  487. var list = new List<string>();
  488. foreach (NameValuePair pair in BuildParams(this, accessToken))
  489. {
  490. if (string.IsNullOrEmpty(pair.Value))
  491. {
  492. continue;
  493. }
  494. // Try to keep the url clean by omitting defaults
  495. if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase)
  496. && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
  497. {
  498. continue;
  499. }
  500. if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase)
  501. && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
  502. {
  503. continue;
  504. }
  505. if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase)
  506. && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
  507. {
  508. continue;
  509. }
  510. var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
  511. list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
  512. }
  513. string queryString = string.Join('&', list);
  514. return GetUrl(baseUrl, queryString);
  515. }
  516. private string GetUrl(string baseUrl, string queryString)
  517. {
  518. if (string.IsNullOrEmpty(baseUrl))
  519. {
  520. throw new ArgumentNullException(nameof(baseUrl));
  521. }
  522. string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
  523. baseUrl = baseUrl.TrimEnd('/');
  524. if (MediaType == DlnaProfileType.Audio)
  525. {
  526. if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
  527. {
  528. return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
  529. }
  530. return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
  531. }
  532. if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
  533. {
  534. return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
  535. }
  536. return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
  537. }
  538. private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string accessToken)
  539. {
  540. var list = new List<NameValuePair>();
  541. string audioCodecs = item.AudioCodecs.Length == 0 ?
  542. string.Empty :
  543. string.Join(',', item.AudioCodecs);
  544. string videoCodecs = item.VideoCodecs.Length == 0 ?
  545. string.Empty :
  546. string.Join(',', item.VideoCodecs);
  547. list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
  548. list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
  549. list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
  550. list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  551. list.Add(new NameValuePair("VideoCodec", videoCodecs));
  552. list.Add(new NameValuePair("AudioCodec", audioCodecs));
  553. list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  554. list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  555. list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  556. list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  557. list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  558. list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  559. list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  560. list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  561. long startPositionTicks = item.StartPositionTicks;
  562. var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase);
  563. if (isHls)
  564. {
  565. list.Add(new NameValuePair("StartTimeTicks", string.Empty));
  566. }
  567. else
  568. {
  569. list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
  570. }
  571. list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
  572. list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
  573. string liveStreamId = item.MediaSource?.LiveStreamId;
  574. list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
  575. list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
  576. if (!item.IsDirectStream)
  577. {
  578. if (item.RequireNonAnamorphic)
  579. {
  580. list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  581. }
  582. list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
  583. if (item.EnableSubtitlesInManifest)
  584. {
  585. list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  586. }
  587. if (item.EnableMpegtsM2TsMode)
  588. {
  589. list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  590. }
  591. if (item.EstimateContentLength)
  592. {
  593. list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  594. }
  595. if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
  596. {
  597. list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
  598. }
  599. if (item.CopyTimestamps)
  600. {
  601. list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  602. }
  603. list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  604. }
  605. list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty));
  606. string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
  607. string.Empty :
  608. string.Join(",", item.SubtitleCodecs);
  609. list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
  610. if (isHls)
  611. {
  612. list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
  613. if (item.SegmentLength.HasValue)
  614. {
  615. list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
  616. }
  617. if (item.MinSegments.HasValue)
  618. {
  619. list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
  620. }
  621. list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
  622. }
  623. foreach (var pair in item.StreamOptions)
  624. {
  625. if (string.IsNullOrEmpty(pair.Value))
  626. {
  627. continue;
  628. }
  629. // strip spaces to avoid having to encode h264 profile names
  630. list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
  631. }
  632. if (!item.IsDirectStream)
  633. {
  634. list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
  635. }
  636. return list;
  637. }
  638. public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken)
  639. {
  640. return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
  641. }
  642. public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
  643. {
  644. var list = new List<SubtitleStreamInfo>();
  645. // HLS will preserve timestamps so we can just grab the full subtitle stream
  646. long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)
  647. ? 0
  648. : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
  649. // First add the selected track
  650. if (SubtitleStreamIndex.HasValue)
  651. {
  652. foreach (var stream in MediaSource.MediaStreams)
  653. {
  654. if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
  655. {
  656. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  657. }
  658. }
  659. }
  660. if (!includeSelectedTrackOnly)
  661. {
  662. foreach (var stream in MediaSource.MediaStreams)
  663. {
  664. if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
  665. {
  666. AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
  667. }
  668. }
  669. }
  670. return list;
  671. }
  672. private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
  673. {
  674. if (enableAllProfiles)
  675. {
  676. foreach (var profile in DeviceProfile.SubtitleProfiles)
  677. {
  678. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
  679. list.Add(info);
  680. }
  681. }
  682. else
  683. {
  684. var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
  685. list.Add(info);
  686. }
  687. }
  688. private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
  689. {
  690. var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
  691. var info = new SubtitleStreamInfo
  692. {
  693. IsForced = stream.IsForced,
  694. Language = stream.Language,
  695. Name = stream.Language ?? "Unknown",
  696. Format = subtitleProfile.Format,
  697. Index = stream.Index,
  698. DeliveryMethod = subtitleProfile.Method,
  699. DisplayTitle = stream.DisplayTitle
  700. };
  701. if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
  702. {
  703. if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
  704. {
  705. info.Url = string.Format(
  706. CultureInfo.InvariantCulture,
  707. "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
  708. baseUrl,
  709. ItemId,
  710. MediaSourceId,
  711. stream.Index.ToString(CultureInfo.InvariantCulture),
  712. startPositionTicks.ToString(CultureInfo.InvariantCulture),
  713. subtitleProfile.Format);
  714. if (!string.IsNullOrEmpty(accessToken))
  715. {
  716. info.Url += "?api_key=" + accessToken;
  717. }
  718. info.IsExternalUrl = false;
  719. }
  720. else
  721. {
  722. info.Url = stream.Path;
  723. info.IsExternalUrl = true;
  724. }
  725. }
  726. return info;
  727. }
  728. public int? GetTargetVideoBitDepth(string codec)
  729. {
  730. var value = GetOption(codec, "videobitdepth");
  731. if (string.IsNullOrEmpty(value))
  732. {
  733. return null;
  734. }
  735. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  736. {
  737. return result;
  738. }
  739. return null;
  740. }
  741. public int? GetTargetAudioBitDepth(string codec)
  742. {
  743. var value = GetOption(codec, "audiobitdepth");
  744. if (string.IsNullOrEmpty(value))
  745. {
  746. return null;
  747. }
  748. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  749. {
  750. return result;
  751. }
  752. return null;
  753. }
  754. public double? GetTargetVideoLevel(string codec)
  755. {
  756. var value = GetOption(codec, "level");
  757. if (string.IsNullOrEmpty(value))
  758. {
  759. return null;
  760. }
  761. if (double.TryParse(value, NumberStyles.Any, 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 (string.IsNullOrEmpty(value))
  771. {
  772. return null;
  773. }
  774. if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  775. {
  776. return result;
  777. }
  778. return null;
  779. }
  780. public int? GetTargetAudioChannels(string codec)
  781. {
  782. var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
  783. var value = GetOption(codec, "audiochannels");
  784. if (string.IsNullOrEmpty(value))
  785. {
  786. return defaultValue;
  787. }
  788. if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
  789. {
  790. return Math.Min(result, defaultValue ?? result);
  791. }
  792. return defaultValue;
  793. }
  794. private int? GetMediaStreamCount(MediaStreamType type, int limit)
  795. {
  796. var count = MediaSource.GetStreamCount(type);
  797. if (count.HasValue)
  798. {
  799. count = Math.Min(count.Value, limit);
  800. }
  801. return count;
  802. }
  803. }
  804. }