StreamInfo.cs 36 KB

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