Ejemplo n.º 1
0
        private static readonly byte[] IndexKeyword = { 0x69, 0x6E, 0x64, 0x65, 0x78 };             // = index

        /// <summary>
        /// Parses FFprobe JSON output and returns a new <see cref="MediaInfo"/>
        /// </summary>
        /// <param name="data">JSON output</param>
        /// <param name="file">The file the JSON output format is about</param>
        /// <returns><see cref="MediaInfo"/> containing information from FFprobe output</returns>
        public static MediaInfo Read(byte[] data, string file)
        {
            var json = new Utf8JsonReader(data, isFinalBlock: false, state: default);

            var streams = new List <Dictionary <string, object> >();
            var format  = new Dictionary <string, object>();

            var currentStream = -1;

            var    currentObject = JsonObjects.None;
            string lastKey       = null;

            while (json.Read())
            {
                JsonTokenType       tokenType = json.TokenType;
                ReadOnlySpan <byte> valueSpan = json.ValueSpan;
                switch (tokenType)
                {
                case JsonTokenType.StartObject:
                case JsonTokenType.EndObject:
                case JsonTokenType.Null:
                case JsonTokenType.StartArray:
                case JsonTokenType.EndArray:
                    break;

                case JsonTokenType.PropertyName:
                    if (valueSpan.SequenceEqual(StreamsKeyword.AsSpan()))
                    {
                        currentObject = JsonObjects.Streams;
                        break;
                    }
                    if (valueSpan.SequenceEqual(FormatKeyword.AsSpan()))
                    {
                        currentObject = JsonObjects.Format;
                        break;
                    }

                    if (valueSpan.SequenceEqual(IndexKeyword.AsSpan()))
                    {
                        streams.Add(new Dictionary <string, object>());
                        currentStream++;
                    }

                    if (currentObject == JsonObjects.Streams)
                    {
                        lastKey = json.GetString();
                        streams[currentStream].Add(lastKey, null);
                    }
                    else if (currentObject == JsonObjects.Format)
                    {
                        lastKey = json.GetString();
                        format.Add(lastKey, null);
                    }
                    break;

                case JsonTokenType.String:
                    if (currentObject == JsonObjects.Streams)
                    {
                        streams[currentStream][lastKey] = json.GetString();
                    }
                    else if (currentObject == JsonObjects.Format)
                    {
                        format[lastKey] = json.GetString();
                    }
                    break;

                case JsonTokenType.Number:
                    if (!json.TryGetInt32(out int valueInteger))
                    {
#if DEBUG
                        System.Diagnostics.Trace.TraceWarning($"JSON number parse error: \"{lastKey}\" = {System.Text.Encoding.UTF8.GetString(valueSpan.ToArray())}, file = {file}");
#endif
                        break;
                    }

                    if (currentObject == JsonObjects.Streams)
                    {
                        streams[currentStream][lastKey] = valueInteger;
                    }
                    else if (currentObject == JsonObjects.Format)
                    {
                        format[lastKey] = valueInteger;
                    }
                    break;

                case JsonTokenType.True:
                case JsonTokenType.False:
                    bool valueBool = json.GetBoolean();
                    if (currentObject == JsonObjects.Streams)
                    {
                        streams[currentStream][lastKey] = valueBool;
                    }
                    else if (currentObject == JsonObjects.Format)
                    {
                        format[lastKey] = valueBool;
                    }
                    break;

                default:
                    throw new ArgumentException();
                }
            }

            var info = new MediaInfo {
                Streams = new MediaInfo.StreamInfo[streams.Count]
            };

            if (format.ContainsKey("duration") && TimeSpan.TryParse((string)format["duration"], out var duration))
            {
                info.Duration = duration;
            }

            for (int i = 0; i < streams.Count; i++)
            {
                info.Streams[i] = new MediaInfo.StreamInfo();
                if (streams[i].ContainsKey("bit_rate") && long.TryParse((string)streams[i]["bit_rate"], out var bitrate))
                {
                    info.Streams[i].BitRate = bitrate;
                }
                if (streams[i].ContainsKey("width"))
                {
                    info.Streams[i].Width = (int)streams[i]["width"];
                }
                if (streams[i].ContainsKey("height"))
                {
                    info.Streams[i].Height = (int)streams[i]["height"];
                }
                if (streams[i].ContainsKey("codec_name"))
                {
                    info.Streams[i].CodecName = (string)streams[i]["codec_name"];
                }
                if (streams[i].ContainsKey("codec_long_name"))
                {
                    info.Streams[i].CodecLongName = (string)streams[i]["codec_long_name"];
                }
                if (streams[i].ContainsKey("codec_type"))
                {
                    info.Streams[i].CodecType = (string)streams[i]["codec_type"];
                }
                if (streams[i].ContainsKey("channel_layout"))
                {
                    info.Streams[i].ChannelLayout = (string)streams[i]["channel_layout"];
                }

                if (streams[i].ContainsKey("pix_fmt"))
                {
                    info.Streams[i].PixelFormat = (string)streams[i]["pix_fmt"];
                }
                if (streams[i].ContainsKey("sample_rate") && int.TryParse((string)streams[i]["sample_rate"], out var sample_rate))
                {
                    info.Streams[i].SampleRate = sample_rate;
                }
                if (streams[i].ContainsKey("index"))
                {
                    info.Streams[i].Index = ((int)streams[i]["index"]).ToString();
                }

                if (streams[i].ContainsKey("r_frame_rate"))
                {
                    var stringFrameRate = (string)streams[i]["r_frame_rate"];
                    if (stringFrameRate.Contains('/'))
                    {
                        var split = stringFrameRate.Split('/');
                        if (split.Length == 2 && int.TryParse(split[0], out var firstRate) && int.TryParse(split[1], out var secondRate))
                        {
                            info.Streams[i].FrameRate = (firstRate > 0 && secondRate > 0) ? firstRate / (float)secondRate : -1f;
                        }
                    }
                }
            }

            return(info);
        }
        static ReadOnlySpan <byte> IndexKeyword => new byte[] { 0x69, 0x6E, 0x64, 0x65, 0x78 };               // = index

        /// <summary>
        /// Parses FFprobe JSON output and returns a new <see cref="MediaInfo"/>
        /// </summary>
        /// <param name="data">JSON output</param>
        /// <param name="file">The file the JSON output format is about</param>
        /// <returns><see cref="MediaInfo"/> containing information from FFprobe output</returns>
        public static MediaInfo Read(byte[] data, string file)
        {
            var json = new Utf8JsonReader(data, isFinalBlock: false, state: default);


            var streams = new List <Dictionary <string, object> >();
            var format  = new Dictionary <string, object>();

            var currentStream = -1;

            var    currentObject = JsonObjects.None;
            string?lastKey       = null;

            while (json.Read())
            {
                JsonTokenType       tokenType = json.TokenType;
                ReadOnlySpan <byte> valueSpan = json.ValueSpan;
                switch (tokenType)
                {
                case JsonTokenType.StartObject:
                case JsonTokenType.EndObject:
                case JsonTokenType.Null:
                case JsonTokenType.StartArray:
                case JsonTokenType.EndArray:
                    break;

                case JsonTokenType.PropertyName:
                    if (valueSpan.SequenceEqual(StreamsKeyword))
                    {
                        currentObject = JsonObjects.Streams;
                        break;
                    }
                    if (valueSpan.SequenceEqual(FormatKeyword))
                    {
                        currentObject = JsonObjects.Format;
                        break;
                    }

                    if (valueSpan.SequenceEqual(IndexKeyword))
                    {
                        streams.Add(new Dictionary <string, object>());
                        currentStream++;
                    }

                    if (currentObject == JsonObjects.Streams)
                    {
                        lastKey = json.GetString();
                        streams[currentStream].TryAdd(lastKey, new object());
                    }
                    else if (currentObject == JsonObjects.Format)
                    {
                        lastKey = json.GetString();
                        format.TryAdd(lastKey, new object());
                    }
                    break;

                case JsonTokenType.String:
                    if (currentObject == JsonObjects.Streams && lastKey != null)
                    {
                        streams[currentStream][lastKey] = json.GetString();
                    }
                    else if (currentObject == JsonObjects.Format && lastKey != null)
                    {
                        format[lastKey] = json.GetString();
                    }
                    break;

                case JsonTokenType.Number:
                    if (!json.TryGetInt32(out int valueInteger))
                    {
#if DEBUG
                        System.Diagnostics.Trace.TraceWarning($"JSON number parse error: \"{lastKey}\" = {System.Text.Encoding.UTF8.GetString(valueSpan.ToArray())}, file = {file}");
#endif
                        break;
                    }

                    if (currentObject == JsonObjects.Streams && lastKey != null)
                    {
                        streams[currentStream][lastKey] = valueInteger;
                    }
                    else if (currentObject == JsonObjects.Format && lastKey != null)
                    {
                        format[lastKey] = valueInteger;
                    }
                    break;

                case JsonTokenType.True:
                case JsonTokenType.False:
                    bool valueBool = json.GetBoolean();
                    if (currentObject == JsonObjects.Streams && lastKey != null)
                    {
                        streams[currentStream][lastKey] = valueBool;
                    }
                    else if (currentObject == JsonObjects.Format && lastKey != null)
                    {
                        format[lastKey] = valueBool;
                    }
                    break;

                default:
                    throw new ArgumentException();
                }
            }

            var info = new MediaInfo {
                Streams = new MediaInfo.StreamInfo[streams.Count]
            };

            if (format.ContainsKey("duration") && TimeSpan.TryParse((string)format["duration"], out var duration))
            {
                /*
                 * Trim miliseconds here as we would have done it later anyway.
                 * Reasons are:
                 * - More user friendly
                 * - Allows an improved check against equality
                 * Cons are:
                 * - Not 100% accurate if you consider a difference of e.g. 2 miliseconds makes a duplicate no longer a duplicate
                 * - Breaking change at the moment of implementation as it doesn't apply to already scanned files
                 */
                info.Duration = duration.TrimMiliseconds();
            }

            var foundBitRate = false;
            for (int i = 0; i < streams.Count; i++)
            {
                info.Streams[i] = new MediaInfo.StreamInfo();
                if (streams[i].ContainsKey("bit_rate") && long.TryParse((string)streams[i]["bit_rate"], out var bitrate))
                {
                    foundBitRate            = true;
                    info.Streams[i].BitRate = bitrate;
                }
                if (streams[i].ContainsKey("width"))
                {
                    info.Streams[i].Width = (int)streams[i]["width"];
                }
                if (streams[i].ContainsKey("height"))
                {
                    info.Streams[i].Height = (int)streams[i]["height"];
                }
                if (streams[i].ContainsKey("codec_name"))
                {
                    info.Streams[i].CodecName = (string)streams[i]["codec_name"];
                }
                if (streams[i].ContainsKey("codec_long_name"))
                {
                    info.Streams[i].CodecLongName = (string)streams[i]["codec_long_name"];
                }
                if (streams[i].ContainsKey("codec_type"))
                {
                    info.Streams[i].CodecType = (string)streams[i]["codec_type"];
                }
                if (streams[i].ContainsKey("channel_layout"))
                {
                    info.Streams[i].ChannelLayout = (string)streams[i]["channel_layout"];
                }

                if (streams[i].ContainsKey("pix_fmt"))
                {
                    info.Streams[i].PixelFormat = (string)streams[i]["pix_fmt"];
                }
                if (streams[i].ContainsKey("sample_rate") && int.TryParse((string)streams[i]["sample_rate"], out var sample_rate))
                {
                    info.Streams[i].SampleRate = sample_rate;
                }
                if (streams[i].ContainsKey("index"))
                {
                    info.Streams[i].Index = ((int)streams[i]["index"]).ToString();
                }

                if (streams[i].ContainsKey("r_frame_rate"))
                {
                    var stringFrameRate = (string)streams[i]["r_frame_rate"];
                    if (stringFrameRate.Contains('/'))
                    {
                        var split = stringFrameRate.Split('/');
                        if (split.Length == 2 && int.TryParse(split[0], out var firstRate) && int.TryParse(split[1], out var secondRate))
                        {
                            info.Streams[i].FrameRate = (firstRate > 0 && secondRate > 0) ? firstRate / (float)secondRate : -1f;
                        }
                    }
                }
            }
            //Workaround if video stream bitrate is not set but in format
            if (!foundBitRate && info.Streams.Length > 0 && format.ContainsKey("bit_rate") && long.TryParse((string)format["bit_rate"], out var formatBitrate))
            {
                info.Streams[0].BitRate = formatBitrate;
            }

            return(info);
        }