MediaEncoderHelpers.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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. }
  126. return info;
  127. }
  128. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  129. /// <summary>
  130. /// Converts ffprobe stream info to our MediaStream class
  131. /// </summary>
  132. /// <param name="streamInfo">The stream info.</param>
  133. /// <param name="formatInfo">The format info.</param>
  134. /// <returns>MediaStream.</returns>
  135. private static MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
  136. {
  137. var stream = new MediaStream
  138. {
  139. Codec = streamInfo.codec_name,
  140. Profile = streamInfo.profile,
  141. Level = streamInfo.level,
  142. Index = streamInfo.index,
  143. PixelFormat = streamInfo.pix_fmt
  144. };
  145. if (streamInfo.tags != null)
  146. {
  147. stream.Language = GetDictionaryValue(streamInfo.tags, "language");
  148. }
  149. if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase))
  150. {
  151. stream.Type = MediaStreamType.Audio;
  152. stream.Channels = streamInfo.channels;
  153. if (!string.IsNullOrEmpty(streamInfo.sample_rate))
  154. {
  155. stream.SampleRate = int.Parse(streamInfo.sample_rate, UsCulture);
  156. }
  157. stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout);
  158. }
  159. else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase))
  160. {
  161. stream.Type = MediaStreamType.Subtitle;
  162. }
  163. else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
  164. {
  165. stream.Type = MediaStreamType.Video;
  166. stream.Width = streamInfo.width;
  167. stream.Height = streamInfo.height;
  168. stream.AspectRatio = GetAspectRatio(streamInfo);
  169. stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
  170. stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
  171. }
  172. else
  173. {
  174. return null;
  175. }
  176. // Get stream bitrate
  177. var bitrate = 0;
  178. if (!string.IsNullOrEmpty(streamInfo.bit_rate))
  179. {
  180. bitrate = int.Parse(streamInfo.bit_rate, UsCulture);
  181. }
  182. else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video)
  183. {
  184. // If the stream info doesn't have a bitrate get the value from the media format info
  185. bitrate = int.Parse(formatInfo.bit_rate, UsCulture);
  186. }
  187. if (bitrate > 0)
  188. {
  189. stream.BitRate = bitrate;
  190. }
  191. if (streamInfo.disposition != null)
  192. {
  193. var isDefault = GetDictionaryValue(streamInfo.disposition, "default");
  194. var isForced = GetDictionaryValue(streamInfo.disposition, "forced");
  195. stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase);
  196. stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase);
  197. }
  198. return stream;
  199. }
  200. /// <summary>
  201. /// Gets a string from an FFProbeResult tags dictionary
  202. /// </summary>
  203. /// <param name="tags">The tags.</param>
  204. /// <param name="key">The key.</param>
  205. /// <returns>System.String.</returns>
  206. private static string GetDictionaryValue(Dictionary<string, string> tags, string key)
  207. {
  208. if (tags == null)
  209. {
  210. return null;
  211. }
  212. string val;
  213. tags.TryGetValue(key, out val);
  214. return val;
  215. }
  216. private static string ParseChannelLayout(string input)
  217. {
  218. if (string.IsNullOrEmpty(input))
  219. {
  220. return input;
  221. }
  222. return input.Split('(').FirstOrDefault();
  223. }
  224. private static string GetAspectRatio(MediaStreamInfo info)
  225. {
  226. var original = info.display_aspect_ratio;
  227. int height;
  228. int width;
  229. var parts = (original ?? string.Empty).Split(':');
  230. if (!(parts.Length == 2 &&
  231. int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) &&
  232. int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) &&
  233. width > 0 &&
  234. height > 0))
  235. {
  236. width = info.width;
  237. height = info.height;
  238. }
  239. if (width > 0 && height > 0)
  240. {
  241. double ratio = width;
  242. ratio /= height;
  243. if (IsClose(ratio, 1.777777778, .03))
  244. {
  245. return "16:9";
  246. }
  247. if (IsClose(ratio, 1.3333333333, .05))
  248. {
  249. return "4:3";
  250. }
  251. if (IsClose(ratio, 1.41))
  252. {
  253. return "1.41:1";
  254. }
  255. if (IsClose(ratio, 1.5))
  256. {
  257. return "1.5:1";
  258. }
  259. if (IsClose(ratio, 1.6))
  260. {
  261. return "1.6:1";
  262. }
  263. if (IsClose(ratio, 1.66666666667))
  264. {
  265. return "5:3";
  266. }
  267. if (IsClose(ratio, 1.85, .02))
  268. {
  269. return "1.85:1";
  270. }
  271. if (IsClose(ratio, 2.35, .025))
  272. {
  273. return "2.35:1";
  274. }
  275. if (IsClose(ratio, 2.4, .025))
  276. {
  277. return "2.40:1";
  278. }
  279. }
  280. return original;
  281. }
  282. private static bool IsClose(double d1, double d2, double variance = .005)
  283. {
  284. return Math.Abs(d1 - d2) <= variance;
  285. }
  286. /// <summary>
  287. /// Gets a frame rate from a string value in ffprobe output
  288. /// This could be a number or in the format of 2997/125.
  289. /// </summary>
  290. /// <param name="value">The value.</param>
  291. /// <returns>System.Nullable{System.Single}.</returns>
  292. private static float? GetFrameRate(string value)
  293. {
  294. if (!string.IsNullOrEmpty(value))
  295. {
  296. var parts = value.Split('/');
  297. float result;
  298. if (parts.Length == 2)
  299. {
  300. result = float.Parse(parts[0], UsCulture) / float.Parse(parts[1], UsCulture);
  301. }
  302. else
  303. {
  304. result = float.Parse(parts[0], UsCulture);
  305. }
  306. return float.IsNaN(result) ? (float?)null : result;
  307. }
  308. return null;
  309. }
  310. }
  311. }