EncodingJobInfo.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using MediaBrowser.Controller.Entities;
  6. using MediaBrowser.Controller.Library;
  7. using MediaBrowser.Model.Dlna;
  8. using MediaBrowser.Model.Drawing;
  9. using MediaBrowser.Model.Dto;
  10. using MediaBrowser.Model.Entities;
  11. using MediaBrowser.Model.IO;
  12. using MediaBrowser.Model.MediaInfo;
  13. using MediaBrowser.Model.Session;
  14. using Microsoft.Extensions.Logging;
  15. namespace MediaBrowser.Controller.MediaEncoding
  16. {
  17. // For now, a common base class until the API and MediaEncoding classes are unified
  18. public abstract class EncodingJobInfo
  19. {
  20. private readonly ILogger _logger;
  21. public MediaStream VideoStream { get; set; }
  22. public VideoType VideoType { get; set; }
  23. public Dictionary<string, string> RemoteHttpHeaders { get; set; }
  24. public string OutputVideoCodec { get; set; }
  25. public MediaProtocol InputProtocol { get; set; }
  26. public string MediaPath { get; set; }
  27. public bool IsInputVideo { get; set; }
  28. public IIsoMount IsoMount { get; set; }
  29. public string[] PlayableStreamFileNames { get; set; }
  30. public string OutputAudioCodec { get; set; }
  31. public int? OutputVideoBitrate { get; set; }
  32. public MediaStream SubtitleStream { get; set; }
  33. public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
  34. public string[] SupportedSubtitleCodecs { get; set; }
  35. public int InternalSubtitleStreamOffset { get; set; }
  36. public MediaSourceInfo MediaSource { get; set; }
  37. public User User { get; set; }
  38. public long? RunTimeTicks { get; set; }
  39. public bool ReadInputAtNativeFramerate { get; set; }
  40. private TranscodeReason[] _transcodeReasons = null;
  41. public TranscodeReason[] TranscodeReasons
  42. {
  43. get
  44. {
  45. if (_transcodeReasons == null)
  46. {
  47. _transcodeReasons = (BaseRequest.TranscodeReasons ?? string.Empty)
  48. .Split(',')
  49. .Where(i => !string.IsNullOrEmpty(i))
  50. .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
  51. .ToArray();
  52. }
  53. return _transcodeReasons;
  54. }
  55. }
  56. public bool IgnoreInputDts => MediaSource.IgnoreDts;
  57. public bool IgnoreInputIndex => MediaSource.IgnoreIndex;
  58. public bool GenPtsInput => MediaSource.GenPtsInput;
  59. public bool DiscardCorruptFramesInput => false;
  60. public bool EnableFastSeekInput => false;
  61. public bool GenPtsOutput => false;
  62. public string OutputContainer { get; set; }
  63. public string OutputVideoSync
  64. {
  65. get
  66. {
  67. // For live tv + in progress recordings
  68. if (string.Equals(InputContainer, "mpegts", StringComparison.OrdinalIgnoreCase) || string.Equals(InputContainer, "ts", StringComparison.OrdinalIgnoreCase))
  69. {
  70. if (!MediaSource.RunTimeTicks.HasValue)
  71. {
  72. return "cfr";
  73. }
  74. }
  75. return "-1";
  76. }
  77. }
  78. public string AlbumCoverPath { get; set; }
  79. public string InputAudioSync { get; set; }
  80. public string InputVideoSync { get; set; }
  81. public TransportStreamTimestamp InputTimestamp { get; set; }
  82. public MediaStream AudioStream { get; set; }
  83. public string[] SupportedAudioCodecs { get; set; }
  84. public string[] SupportedVideoCodecs { get; set; }
  85. public string InputContainer { get; set; }
  86. public IsoType? IsoType { get; set; }
  87. public BaseEncodingJobOptions BaseRequest { get; set; }
  88. public long? StartTimeTicks => BaseRequest.StartTimeTicks;
  89. public bool CopyTimestamps => BaseRequest.CopyTimestamps;
  90. public int? OutputAudioBitrate;
  91. public int? OutputAudioChannels;
  92. public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
  93. {
  94. var videoStream = VideoStream;
  95. var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
  96. if (!isInputInterlaced)
  97. {
  98. return false;
  99. }
  100. // Support general param
  101. if (BaseRequest.DeInterlace)
  102. {
  103. return true;
  104. }
  105. if (!string.IsNullOrEmpty(videoCodec))
  106. {
  107. if (string.Equals(BaseRequest.GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
  108. {
  109. return true;
  110. }
  111. }
  112. if (forceDeinterlaceIfSourceIsInterlaced)
  113. {
  114. if (isInputInterlaced)
  115. {
  116. return true;
  117. }
  118. }
  119. return false;
  120. }
  121. public string[] GetRequestedProfiles(string codec)
  122. {
  123. if (!string.IsNullOrEmpty(BaseRequest.Profile))
  124. {
  125. return BaseRequest.Profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
  126. }
  127. if (!string.IsNullOrEmpty(codec))
  128. {
  129. var profile = BaseRequest.GetOption(codec, "profile");
  130. if (!string.IsNullOrEmpty(profile))
  131. {
  132. return profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
  133. }
  134. }
  135. return Array.Empty<string>();
  136. }
  137. public string GetRequestedLevel(string codec)
  138. {
  139. if (!string.IsNullOrEmpty(BaseRequest.Level))
  140. {
  141. return BaseRequest.Level;
  142. }
  143. if (!string.IsNullOrEmpty(codec))
  144. {
  145. return BaseRequest.GetOption(codec, "level");
  146. }
  147. return null;
  148. }
  149. public int? GetRequestedMaxRefFrames(string codec)
  150. {
  151. if (BaseRequest.MaxRefFrames.HasValue)
  152. {
  153. return BaseRequest.MaxRefFrames;
  154. }
  155. if (!string.IsNullOrEmpty(codec))
  156. {
  157. var value = BaseRequest.GetOption(codec, "maxrefframes");
  158. if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  159. {
  160. return result;
  161. }
  162. }
  163. return null;
  164. }
  165. public int? GetRequestedVideoBitDepth(string codec)
  166. {
  167. if (BaseRequest.MaxVideoBitDepth.HasValue)
  168. {
  169. return BaseRequest.MaxVideoBitDepth;
  170. }
  171. if (!string.IsNullOrEmpty(codec))
  172. {
  173. var value = BaseRequest.GetOption(codec, "videobitdepth");
  174. if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  175. {
  176. return result;
  177. }
  178. }
  179. return null;
  180. }
  181. public int? GetRequestedAudioBitDepth(string codec)
  182. {
  183. if (BaseRequest.MaxAudioBitDepth.HasValue)
  184. {
  185. return BaseRequest.MaxAudioBitDepth;
  186. }
  187. if (!string.IsNullOrEmpty(codec))
  188. {
  189. var value = BaseRequest.GetOption(codec, "audiobitdepth");
  190. if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  191. {
  192. return result;
  193. }
  194. }
  195. return null;
  196. }
  197. public int? GetRequestedAudioChannels(string codec)
  198. {
  199. if (BaseRequest.MaxAudioChannels.HasValue)
  200. {
  201. return BaseRequest.MaxAudioChannels;
  202. }
  203. if (BaseRequest.AudioChannels.HasValue)
  204. {
  205. return BaseRequest.AudioChannels;
  206. }
  207. if (!string.IsNullOrEmpty(codec))
  208. {
  209. var value = BaseRequest.GetOption(codec, "audiochannels");
  210. if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  211. {
  212. return result;
  213. }
  214. }
  215. return null;
  216. }
  217. public bool IsVideoRequest { get; set; }
  218. public TranscodingJobType TranscodingType { get; set; }
  219. public EncodingJobInfo(ILogger logger, IMediaSourceManager unused, TranscodingJobType jobType)
  220. {
  221. _logger = logger;
  222. TranscodingType = jobType;
  223. RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  224. PlayableStreamFileNames = Array.Empty<string>();
  225. SupportedAudioCodecs = Array.Empty<string>();
  226. SupportedVideoCodecs = Array.Empty<string>();
  227. SupportedSubtitleCodecs = Array.Empty<string>();
  228. }
  229. public bool IsSegmentedLiveStream => TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue;
  230. public bool EnableBreakOnNonKeyFrames(string videoCodec)
  231. {
  232. if (TranscodingType != TranscodingJobType.Progressive)
  233. {
  234. if (IsSegmentedLiveStream)
  235. {
  236. return false;
  237. }
  238. return BaseRequest.BreakOnNonKeyFrames && string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase);
  239. }
  240. return false;
  241. }
  242. public int? TotalOutputBitrate => (OutputAudioBitrate ?? 0) + (OutputVideoBitrate ?? 0);
  243. public int? OutputWidth
  244. {
  245. get
  246. {
  247. if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
  248. {
  249. var size = new ImageSize
  250. {
  251. Width = VideoStream.Width.Value,
  252. Height = VideoStream.Height.Value
  253. };
  254. var newSize = DrawingUtils.Resize(size,
  255. BaseRequest.Width ?? 0,
  256. BaseRequest.Height ?? 0,
  257. BaseRequest.MaxWidth ?? 0,
  258. BaseRequest.MaxHeight ?? 0);
  259. return Convert.ToInt32(newSize.Width);
  260. }
  261. if (!IsVideoRequest)
  262. {
  263. return null;
  264. }
  265. return BaseRequest.MaxWidth ?? BaseRequest.Width;
  266. }
  267. }
  268. public int? OutputHeight
  269. {
  270. get
  271. {
  272. if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
  273. {
  274. var size = new ImageSize
  275. {
  276. Width = VideoStream.Width.Value,
  277. Height = VideoStream.Height.Value
  278. };
  279. var newSize = DrawingUtils.Resize(size,
  280. BaseRequest.Width ?? 0,
  281. BaseRequest.Height ?? 0,
  282. BaseRequest.MaxWidth ?? 0,
  283. BaseRequest.MaxHeight ?? 0);
  284. return Convert.ToInt32(newSize.Height);
  285. }
  286. if (!IsVideoRequest)
  287. {
  288. return null;
  289. }
  290. return BaseRequest.MaxHeight ?? BaseRequest.Height;
  291. }
  292. }
  293. public int? OutputAudioSampleRate
  294. {
  295. get
  296. {
  297. if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
  298. {
  299. if (AudioStream != null)
  300. {
  301. return AudioStream.SampleRate;
  302. }
  303. }
  304. else if (BaseRequest.AudioSampleRate.HasValue)
  305. {
  306. // Don't exceed what the encoder supports
  307. // Seeing issues of attempting to encode to 88200
  308. return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
  309. }
  310. return null;
  311. }
  312. }
  313. public int? OutputAudioBitDepth
  314. {
  315. get
  316. {
  317. if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
  318. {
  319. if (AudioStream != null)
  320. {
  321. return AudioStream.BitDepth;
  322. }
  323. }
  324. //else if (BaseRequest.AudioSampleRate.HasValue)
  325. //{
  326. // // Don't exceed what the encoder supports
  327. // // Seeing issues of attempting to encode to 88200
  328. // return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
  329. //}
  330. return null;
  331. }
  332. }
  333. /// <summary>
  334. /// Predicts the audio sample rate that will be in the output stream
  335. /// </summary>
  336. public double? TargetVideoLevel
  337. {
  338. get
  339. {
  340. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  341. {
  342. return VideoStream == null ? null : VideoStream.Level;
  343. }
  344. var level = GetRequestedLevel(ActualOutputVideoCodec);
  345. if (!string.IsNullOrEmpty(level) && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
  346. {
  347. return result;
  348. }
  349. return null;
  350. }
  351. }
  352. /// <summary>
  353. /// Predicts the audio sample rate that will be in the output stream
  354. /// </summary>
  355. public int? TargetVideoBitDepth
  356. {
  357. get
  358. {
  359. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  360. {
  361. return VideoStream == null ? null : VideoStream.BitDepth;
  362. }
  363. return null;
  364. }
  365. }
  366. /// <summary>
  367. /// Gets the target reference frames.
  368. /// </summary>
  369. /// <value>The target reference frames.</value>
  370. public int? TargetRefFrames
  371. {
  372. get
  373. {
  374. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  375. {
  376. return VideoStream == null ? null : VideoStream.RefFrames;
  377. }
  378. return null;
  379. }
  380. }
  381. /// <summary>
  382. /// Predicts the audio sample rate that will be in the output stream
  383. /// </summary>
  384. public float? TargetFramerate
  385. {
  386. get
  387. {
  388. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  389. {
  390. return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate);
  391. }
  392. return BaseRequest.MaxFramerate ?? BaseRequest.Framerate;
  393. }
  394. }
  395. public TransportStreamTimestamp TargetTimestamp
  396. {
  397. get
  398. {
  399. var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
  400. TransportStreamTimestamp.Valid :
  401. TransportStreamTimestamp.None;
  402. return !BaseRequest.Static
  403. ? defaultValue
  404. : InputTimestamp;
  405. }
  406. }
  407. /// <summary>
  408. /// Predicts the audio sample rate that will be in the output stream
  409. /// </summary>
  410. public int? TargetPacketLength
  411. {
  412. get
  413. {
  414. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  415. {
  416. return VideoStream == null ? null : VideoStream.PacketLength;
  417. }
  418. return null;
  419. }
  420. }
  421. /// <summary>
  422. /// Predicts the audio sample rate that will be in the output stream
  423. /// </summary>
  424. public string TargetVideoProfile
  425. {
  426. get
  427. {
  428. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  429. {
  430. return VideoStream == null ? null : VideoStream.Profile;
  431. }
  432. var requestedProfile = GetRequestedProfiles(ActualOutputVideoCodec).FirstOrDefault();
  433. if (!string.IsNullOrEmpty(requestedProfile))
  434. {
  435. return requestedProfile;
  436. }
  437. return null;
  438. }
  439. }
  440. /// <summary>
  441. /// Predicts the audio sample rate that will be in the output stream
  442. /// </summary>
  443. public string TargetVideoRange
  444. {
  445. get
  446. {
  447. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  448. {
  449. return VideoStream == null ? null : VideoStream.VideoRange;
  450. }
  451. return "SDR";
  452. }
  453. }
  454. public string TargetAudioProfile
  455. {
  456. get
  457. {
  458. if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
  459. {
  460. return AudioStream == null ? null : AudioStream.Profile;
  461. }
  462. return null;
  463. }
  464. }
  465. public string TargetVideoCodecTag
  466. {
  467. get
  468. {
  469. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  470. {
  471. return VideoStream == null ? null : VideoStream.CodecTag;
  472. }
  473. return null;
  474. }
  475. }
  476. public bool? IsTargetAnamorphic
  477. {
  478. get
  479. {
  480. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  481. {
  482. return VideoStream == null ? null : VideoStream.IsAnamorphic;
  483. }
  484. return false;
  485. }
  486. }
  487. public string ActualOutputVideoCodec
  488. {
  489. get
  490. {
  491. var codec = OutputVideoCodec;
  492. if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
  493. {
  494. var stream = VideoStream;
  495. if (stream != null)
  496. {
  497. return stream.Codec;
  498. }
  499. return null;
  500. }
  501. return codec;
  502. }
  503. }
  504. public bool? IsTargetInterlaced
  505. {
  506. get
  507. {
  508. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  509. {
  510. return VideoStream == null ? (bool?)null : VideoStream.IsInterlaced;
  511. }
  512. if (DeInterlace(ActualOutputVideoCodec, true))
  513. {
  514. return false;
  515. }
  516. return VideoStream == null ? (bool?)null : VideoStream.IsInterlaced;
  517. }
  518. }
  519. public bool? IsTargetAVC
  520. {
  521. get
  522. {
  523. if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  524. {
  525. return VideoStream == null ? null : VideoStream.IsAVC;
  526. }
  527. return false;
  528. }
  529. }
  530. public int? TargetVideoStreamCount
  531. {
  532. get
  533. {
  534. if (BaseRequest.Static)
  535. {
  536. return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
  537. }
  538. return GetMediaStreamCount(MediaStreamType.Video, 1);
  539. }
  540. }
  541. public int? TargetAudioStreamCount
  542. {
  543. get
  544. {
  545. if (BaseRequest.Static)
  546. {
  547. return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
  548. }
  549. return GetMediaStreamCount(MediaStreamType.Audio, 1);
  550. }
  551. }
  552. private int? GetMediaStreamCount(MediaStreamType type, int limit)
  553. {
  554. var count = MediaSource.GetStreamCount(type);
  555. if (count.HasValue)
  556. {
  557. count = Math.Min(count.Value, limit);
  558. }
  559. return count;
  560. }
  561. protected void DisposeIsoMount()
  562. {
  563. if (IsoMount != null)
  564. {
  565. try
  566. {
  567. IsoMount.Dispose();
  568. }
  569. catch (Exception ex)
  570. {
  571. _logger.LogError(ex, "Error disposing iso mount");
  572. }
  573. IsoMount = null;
  574. }
  575. }
  576. public IProgress<double> Progress { get; set; }
  577. public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
  578. {
  579. Progress.Report(percentComplete.Value);
  580. }
  581. public virtual void Dispose()
  582. {
  583. }
  584. }
  585. /// <summary>
  586. /// Enum TranscodingJobType
  587. /// </summary>
  588. public enum TranscodingJobType
  589. {
  590. /// <summary>
  591. /// The progressive
  592. /// </summary>
  593. Progressive,
  594. /// <summary>
  595. /// The HLS
  596. /// </summary>
  597. Hls,
  598. /// <summary>
  599. /// The dash
  600. /// </summary>
  601. Dash
  602. }
  603. }