StreamInfo.cs 37 KB

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