StreamInfo.cs 36 KB

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