StreamInfo.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  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(CultureInfo.InvariantCulture).ToLowerInvariant()));
  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?.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(CultureInfo.InvariantCulture).ToLowerInvariant()));
  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(CultureInfo.InvariantCulture).ToLowerInvariant()));
  209. }
  210. if (item.EnableMpegtsM2TsMode)
  211. {
  212. list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  213. }
  214. if (item.EstimateContentLength)
  215. {
  216. list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  217. }
  218. if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
  219. {
  220. list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
  221. }
  222. if (item.CopyTimestamps)
  223. {
  224. list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  225. }
  226. list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
  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(CultureInfo.InvariantCulture)));
  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()))));
  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. ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
  788. size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
  789. return size.Width;
  790. }
  791. return MaxWidth;
  792. }
  793. }
  794. public int? TargetHeight
  795. {
  796. get
  797. {
  798. var videoStream = TargetVideoStream;
  799. if (videoStream != null && videoStream.Width.HasValue && videoStream.Height.HasValue)
  800. {
  801. ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
  802. size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
  803. return size.Height;
  804. }
  805. return MaxHeight;
  806. }
  807. }
  808. public int? TargetVideoStreamCount
  809. {
  810. get
  811. {
  812. if (IsDirectStream)
  813. {
  814. return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
  815. }
  816. return GetMediaStreamCount(MediaStreamType.Video, 1);
  817. }
  818. }
  819. public int? TargetAudioStreamCount
  820. {
  821. get
  822. {
  823. if (IsDirectStream)
  824. {
  825. return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
  826. }
  827. return GetMediaStreamCount(MediaStreamType.Audio, 1);
  828. }
  829. }
  830. private int? GetMediaStreamCount(MediaStreamType type, int limit)
  831. {
  832. var count = MediaSource.GetStreamCount(type);
  833. if (count.HasValue)
  834. {
  835. count = Math.Min(count.Value, limit);
  836. }
  837. return count;
  838. }
  839. public List<MediaStream> GetSelectableAudioStreams()
  840. {
  841. return GetSelectableStreams(MediaStreamType.Audio);
  842. }
  843. public List<MediaStream> GetSelectableSubtitleStreams()
  844. {
  845. return GetSelectableStreams(MediaStreamType.Subtitle);
  846. }
  847. public List<MediaStream> GetSelectableStreams(MediaStreamType type)
  848. {
  849. var list = new List<MediaStream>();
  850. foreach (var stream in MediaSource.MediaStreams)
  851. {
  852. if (type == stream.Type)
  853. {
  854. list.Add(stream);
  855. }
  856. }
  857. return list;
  858. }
  859. }
  860. }