MediaEncoderHelpers.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. using MediaBrowser.Model.Entities;
  2. using MediaBrowser.Model.IO;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Linq;
  8. namespace MediaBrowser.Controller.MediaEncoding
  9. {
  10. /// <summary>
  11. /// Class MediaEncoderHelpers
  12. /// </summary>
  13. public static class MediaEncoderHelpers
  14. {
  15. /// <summary>
  16. /// Gets the input argument.
  17. /// </summary>
  18. /// <param name="videoPath">The video path.</param>
  19. /// <param name="isRemote">if set to <c>true</c> [is remote].</param>
  20. /// <param name="videoType">Type of the video.</param>
  21. /// <param name="isoType">Type of the iso.</param>
  22. /// <param name="isoMount">The iso mount.</param>
  23. /// <param name="playableStreamFileNames">The playable stream file names.</param>
  24. /// <param name="type">The type.</param>
  25. /// <returns>System.String[][].</returns>
  26. public static string[] GetInputArgument(string videoPath, bool isRemote, VideoType videoType, IsoType? isoType, IIsoMount isoMount, IEnumerable<string> playableStreamFileNames, out InputType type)
  27. {
  28. var inputPath = isoMount == null ? new[] { videoPath } : new[] { isoMount.MountedPath };
  29. type = InputType.File;
  30. switch (videoType)
  31. {
  32. case VideoType.BluRay:
  33. type = InputType.Bluray;
  34. inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray();
  35. break;
  36. case VideoType.Dvd:
  37. type = InputType.Dvd;
  38. inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray();
  39. break;
  40. case VideoType.Iso:
  41. if (isoType.HasValue)
  42. {
  43. switch (isoType.Value)
  44. {
  45. case IsoType.BluRay:
  46. type = InputType.Bluray;
  47. inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray();
  48. break;
  49. case IsoType.Dvd:
  50. type = InputType.Dvd;
  51. inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray();
  52. break;
  53. }
  54. }
  55. break;
  56. case VideoType.VideoFile:
  57. {
  58. if (isRemote)
  59. {
  60. type = InputType.Url;
  61. }
  62. break;
  63. }
  64. }
  65. return inputPath;
  66. }
  67. public static List<string> GetPlayableStreamFiles(string rootPath, IEnumerable<string> filenames)
  68. {
  69. var allFiles = Directory
  70. .EnumerateFiles(rootPath, "*", SearchOption.AllDirectories)
  71. .ToList();
  72. return filenames.Select(name => allFiles.FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase)))
  73. .Where(f => !string.IsNullOrEmpty(f))
  74. .ToList();
  75. }
  76. /// <summary>
  77. /// Gets the type of the input.
  78. /// </summary>
  79. /// <param name="videoType">Type of the video.</param>
  80. /// <param name="isoType">Type of the iso.</param>
  81. /// <returns>InputType.</returns>
  82. public static InputType GetInputType(VideoType? videoType, IsoType? isoType)
  83. {
  84. var type = InputType.File;
  85. if (videoType.HasValue)
  86. {
  87. switch (videoType.Value)
  88. {
  89. case VideoType.BluRay:
  90. type = InputType.Bluray;
  91. break;
  92. case VideoType.Dvd:
  93. type = InputType.Dvd;
  94. break;
  95. case VideoType.Iso:
  96. if (isoType.HasValue)
  97. {
  98. switch (isoType.Value)
  99. {
  100. case IsoType.BluRay:
  101. type = InputType.Bluray;
  102. break;
  103. case IsoType.Dvd:
  104. type = InputType.Dvd;
  105. break;
  106. }
  107. }
  108. break;
  109. }
  110. }
  111. return type;
  112. }
  113. public static MediaInfo GetMediaInfo(InternalMediaInfoResult data)
  114. {
  115. var internalStreams = data.streams ?? new MediaStreamInfo[] { };
  116. var info = new MediaInfo
  117. {
  118. MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format))
  119. .Where(i => i != null)
  120. .ToList()
  121. };
  122. if (data.format != null)
  123. {
  124. info.Format = data.format.format_name;
  125. if (!string.IsNullOrEmpty(data.format.bit_rate))
  126. {
  127. info.TotalBitrate = int.Parse(data.format.bit_rate, UsCulture);
  128. }
  129. }
  130. return info;
  131. }
  132. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  133. /// <summary>
  134. /// Converts ffprobe stream info to our MediaStream class
  135. /// </summary>
  136. /// <param name="streamInfo">The stream info.</param>
  137. /// <param name="formatInfo">The format info.</param>
  138. /// <returns>MediaStream.</returns>
  139. private static MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
  140. {
  141. var stream = new MediaStream
  142. {
  143. Codec = streamInfo.codec_name,
  144. Profile = streamInfo.profile,
  145. Level = streamInfo.level,
  146. Index = streamInfo.index,
  147. PixelFormat = streamInfo.pix_fmt
  148. };
  149. if (streamInfo.tags != null)
  150. {
  151. stream.Language = GetDictionaryValue(streamInfo.tags, "language");
  152. }
  153. if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase))
  154. {
  155. stream.Type = MediaStreamType.Audio;
  156. stream.Channels = streamInfo.channels;
  157. if (!string.IsNullOrEmpty(streamInfo.sample_rate))
  158. {
  159. stream.SampleRate = int.Parse(streamInfo.sample_rate, UsCulture);
  160. }
  161. stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout);
  162. }
  163. else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase))
  164. {
  165. stream.Type = MediaStreamType.Subtitle;
  166. }
  167. else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
  168. {
  169. stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1
  170. ? MediaStreamType.EmbeddedImage
  171. : MediaStreamType.Video;
  172. stream.Width = streamInfo.width;
  173. stream.Height = streamInfo.height;
  174. stream.AspectRatio = GetAspectRatio(streamInfo);
  175. stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
  176. stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
  177. stream.BitDepth = GetBitDepth(stream.PixelFormat);
  178. }
  179. else
  180. {
  181. return null;
  182. }
  183. // Get stream bitrate
  184. var bitrate = 0;
  185. if (!string.IsNullOrEmpty(streamInfo.bit_rate))
  186. {
  187. bitrate = int.Parse(streamInfo.bit_rate, UsCulture);
  188. }
  189. else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video)
  190. {
  191. // If the stream info doesn't have a bitrate get the value from the media format info
  192. bitrate = int.Parse(formatInfo.bit_rate, UsCulture);
  193. }
  194. if (bitrate > 0)
  195. {
  196. stream.BitRate = bitrate;
  197. }
  198. if (streamInfo.disposition != null)
  199. {
  200. var isDefault = GetDictionaryValue(streamInfo.disposition, "default");
  201. var isForced = GetDictionaryValue(streamInfo.disposition, "forced");
  202. stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase);
  203. stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase);
  204. }
  205. return stream;
  206. }
  207. private static int? GetBitDepth(string pixelFormat)
  208. {
  209. var eightBit = new List<string>
  210. {
  211. "yuv420p",
  212. "yuv411p",
  213. "yuvj420p",
  214. "uyyvyy411",
  215. "nv12",
  216. "nv21",
  217. "rgb444le",
  218. "rgb444be",
  219. "bgr444le",
  220. "bgr444be",
  221. "yuvj411p"
  222. };
  223. if (!string.IsNullOrEmpty(pixelFormat))
  224. {
  225. if (eightBit.Contains(pixelFormat, StringComparer.OrdinalIgnoreCase))
  226. {
  227. return 8;
  228. }
  229. }
  230. return null;
  231. }
  232. /// <summary>
  233. /// Gets a string from an FFProbeResult tags dictionary
  234. /// </summary>
  235. /// <param name="tags">The tags.</param>
  236. /// <param name="key">The key.</param>
  237. /// <returns>System.String.</returns>
  238. private static string GetDictionaryValue(Dictionary<string, string> tags, string key)
  239. {
  240. if (tags == null)
  241. {
  242. return null;
  243. }
  244. string val;
  245. tags.TryGetValue(key, out val);
  246. return val;
  247. }
  248. private static string ParseChannelLayout(string input)
  249. {
  250. if (string.IsNullOrEmpty(input))
  251. {
  252. return input;
  253. }
  254. return input.Split('(').FirstOrDefault();
  255. }
  256. private static string GetAspectRatio(MediaStreamInfo info)
  257. {
  258. var original = info.display_aspect_ratio;
  259. int height;
  260. int width;
  261. var parts = (original ?? string.Empty).Split(':');
  262. if (!(parts.Length == 2 &&
  263. int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) &&
  264. int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) &&
  265. width > 0 &&
  266. height > 0))
  267. {
  268. width = info.width;
  269. height = info.height;
  270. }
  271. if (width > 0 && height > 0)
  272. {
  273. double ratio = width;
  274. ratio /= height;
  275. if (IsClose(ratio, 1.777777778, .03))
  276. {
  277. return "16:9";
  278. }
  279. if (IsClose(ratio, 1.3333333333, .05))
  280. {
  281. return "4:3";
  282. }
  283. if (IsClose(ratio, 1.41))
  284. {
  285. return "1.41:1";
  286. }
  287. if (IsClose(ratio, 1.5))
  288. {
  289. return "1.5:1";
  290. }
  291. if (IsClose(ratio, 1.6))
  292. {
  293. return "1.6:1";
  294. }
  295. if (IsClose(ratio, 1.66666666667))
  296. {
  297. return "5:3";
  298. }
  299. if (IsClose(ratio, 1.85, .02))
  300. {
  301. return "1.85:1";
  302. }
  303. if (IsClose(ratio, 2.35, .025))
  304. {
  305. return "2.35:1";
  306. }
  307. if (IsClose(ratio, 2.4, .025))
  308. {
  309. return "2.40:1";
  310. }
  311. }
  312. return original;
  313. }
  314. private static bool IsClose(double d1, double d2, double variance = .005)
  315. {
  316. return Math.Abs(d1 - d2) <= variance;
  317. }
  318. /// <summary>
  319. /// Gets a frame rate from a string value in ffprobe output
  320. /// This could be a number or in the format of 2997/125.
  321. /// </summary>
  322. /// <param name="value">The value.</param>
  323. /// <returns>System.Nullable{System.Single}.</returns>
  324. private static float? GetFrameRate(string value)
  325. {
  326. if (!string.IsNullOrEmpty(value))
  327. {
  328. var parts = value.Split('/');
  329. float result;
  330. if (parts.Length == 2)
  331. {
  332. result = float.Parse(parts[0], UsCulture) / float.Parse(parts[1], UsCulture);
  333. }
  334. else
  335. {
  336. result = float.Parse(parts[0], UsCulture);
  337. }
  338. return float.IsNaN(result) ? (float?)null : result;
  339. }
  340. return null;
  341. }
  342. }
  343. }