using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; namespace MediaBrowser.Controller.MediaEncoding { /// /// Class MediaEncoderHelpers /// public static class MediaEncoderHelpers { /// /// Gets the input argument. /// /// The video path. /// The protocol. /// The iso mount. /// The playable stream file names. /// System.String[][]. public static string[] GetInputArgument(string videoPath, MediaProtocol protocol, IIsoMount isoMount, List playableStreamFileNames) { if (playableStreamFileNames.Count > 0) { if (isoMount == null) { return GetPlayableStreamFiles(videoPath, playableStreamFileNames).ToArray(); } return GetPlayableStreamFiles(isoMount.MountedPath, playableStreamFileNames).ToArray(); } return new[] {videoPath}; } public static List GetPlayableStreamFiles(string rootPath, IEnumerable filenames) { var allFiles = Directory .EnumerateFiles(rootPath, "*", SearchOption.AllDirectories) .ToList(); return filenames.Select(name => allFiles.FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase))) .Where(f => !string.IsNullOrEmpty(f)) .ToList(); } public static MediaInfo GetMediaInfo(InternalMediaInfoResult data) { var internalStreams = data.streams ?? new MediaStreamInfo[] { }; var info = new MediaInfo { MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format)) .Where(i => i != null) .ToList() }; if (data.format != null) { info.Format = data.format.format_name; if (!string.IsNullOrEmpty(data.format.bit_rate)) { info.TotalBitrate = int.Parse(data.format.bit_rate, UsCulture); } } return info; } private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// /// Converts ffprobe stream info to our MediaStream class /// /// The stream info. /// The format info. /// MediaStream. private static MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo) { var stream = new MediaStream { Codec = streamInfo.codec_name, Profile = streamInfo.profile, Level = streamInfo.level, Index = streamInfo.index, PixelFormat = streamInfo.pix_fmt }; if (streamInfo.tags != null) { stream.Language = GetDictionaryValue(streamInfo.tags, "language"); } if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) { stream.Type = MediaStreamType.Audio; stream.Channels = streamInfo.channels; if (!string.IsNullOrEmpty(streamInfo.sample_rate)) { stream.SampleRate = int.Parse(streamInfo.sample_rate, UsCulture); } stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout); } else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) { stream.Type = MediaStreamType.Subtitle; } else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) { stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1 ? MediaStreamType.EmbeddedImage : MediaStreamType.Video; stream.Width = streamInfo.width; stream.Height = streamInfo.height; stream.AspectRatio = GetAspectRatio(streamInfo); stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); stream.BitDepth = GetBitDepth(stream.PixelFormat); } else { return null; } // Get stream bitrate var bitrate = 0; if (!string.IsNullOrEmpty(streamInfo.bit_rate)) { bitrate = int.Parse(streamInfo.bit_rate, UsCulture); } else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video) { // If the stream info doesn't have a bitrate get the value from the media format info bitrate = int.Parse(formatInfo.bit_rate, UsCulture); } if (bitrate > 0) { stream.BitRate = bitrate; } if (streamInfo.disposition != null) { var isDefault = GetDictionaryValue(streamInfo.disposition, "default"); var isForced = GetDictionaryValue(streamInfo.disposition, "forced"); stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase); stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); } return stream; } private static int? GetBitDepth(string pixelFormat) { var eightBit = new List { "yuv420p", "yuv411p", "yuvj420p", "uyyvyy411", "nv12", "nv21", "rgb444le", "rgb444be", "bgr444le", "bgr444be", "yuvj411p" }; if (!string.IsNullOrEmpty(pixelFormat)) { if (eightBit.Contains(pixelFormat, StringComparer.OrdinalIgnoreCase)) { return 8; } } return null; } /// /// Gets a string from an FFProbeResult tags dictionary /// /// The tags. /// The key. /// System.String. private static string GetDictionaryValue(Dictionary tags, string key) { if (tags == null) { return null; } string val; tags.TryGetValue(key, out val); return val; } private static string ParseChannelLayout(string input) { if (string.IsNullOrEmpty(input)) { return input; } return input.Split('(').FirstOrDefault(); } private static string GetAspectRatio(MediaStreamInfo info) { var original = info.display_aspect_ratio; int height; int width; var parts = (original ?? string.Empty).Split(':'); if (!(parts.Length == 2 && int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) && int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) && width > 0 && height > 0)) { width = info.width; height = info.height; } if (width > 0 && height > 0) { double ratio = width; ratio /= height; if (IsClose(ratio, 1.777777778, .03)) { return "16:9"; } if (IsClose(ratio, 1.3333333333, .05)) { return "4:3"; } if (IsClose(ratio, 1.41)) { return "1.41:1"; } if (IsClose(ratio, 1.5)) { return "1.5:1"; } if (IsClose(ratio, 1.6)) { return "1.6:1"; } if (IsClose(ratio, 1.66666666667)) { return "5:3"; } if (IsClose(ratio, 1.85, .02)) { return "1.85:1"; } if (IsClose(ratio, 2.35, .025)) { return "2.35:1"; } if (IsClose(ratio, 2.4, .025)) { return "2.40:1"; } } return original; } private static bool IsClose(double d1, double d2, double variance = .005) { return Math.Abs(d1 - d2) <= variance; } /// /// Gets a frame rate from a string value in ffprobe output /// This could be a number or in the format of 2997/125. /// /// The value. /// System.Nullable{System.Single}. private static float? GetFrameRate(string value) { if (!string.IsNullOrEmpty(value)) { var parts = value.Split('/'); float result; if (parts.Length == 2) { result = float.Parse(parts[0], UsCulture) / float.Parse(parts[1], UsCulture); } else { result = float.Parse(parts[0], UsCulture); } return float.IsNaN(result) ? (float?)null : result; } return null; } } }