コード例 #1
0
ファイル: FFProbeHelpers.cs プロジェクト: rezafouladian/Emby
        /// <summary>
        /// Normalizes the FF probe result.
        /// </summary>
        /// <param name="result">The result.</param>
        public static void NormalizeFFProbeResult(InternalMediaInfoResult result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            if (result.format != null && result.format.tags != null)
            {
                result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags);
            }

            if (result.streams != null)
            {
                // Convert all dictionaries to case insensitive
                foreach (var stream in result.streams)
                {
                    if (stream.tags != null)
                    {
                        stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags);
                    }

                    if (stream.disposition != null)
                    {
                        stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition);
                    }
                }
            }
        }
コード例 #2
0
        /// <summary>
        /// Fetches the specified audio.
        /// </summary>
        /// <param name="audio">The audio.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="data">The data.</param>
        /// <returns>Task.</returns>
        protected Task Fetch(Audio audio, CancellationToken cancellationToken, InternalMediaInfoResult data)
        {
            var mediaInfo    = MediaEncoderHelpers.GetMediaInfo(data);
            var mediaStreams = mediaInfo.MediaStreams;

            audio.FormatName       = mediaInfo.Format;
            audio.TotalBitrate     = mediaInfo.TotalBitrate;
            audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.EmbeddedImage);

            if (data.streams != null)
            {
                // Get the first audio stream
                var stream = data.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));

                if (stream != null)
                {
                    // Get duration from stream properties
                    var duration = stream.duration;

                    // If it's not there go into format properties
                    if (string.IsNullOrEmpty(duration))
                    {
                        duration = data.format.duration;
                    }

                    // If we got something, parse it
                    if (!string.IsNullOrEmpty(duration))
                    {
                        audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
                    }
                }
            }

            if (data.format != null)
            {
                var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');

                audio.Container = extension;

                if (!string.IsNullOrEmpty(data.format.size))
                {
                    audio.Size = long.Parse(data.format.size, _usCulture);
                }
                else
                {
                    audio.Size = null;
                }

                if (data.format.tags != null)
                {
                    FetchDataFromTags(audio, data.format.tags);
                }
            }

            return(_itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken));
        }
コード例 #3
0
        /// <summary>
        /// Adds the chapters.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="standardError">The standard error.</param>
        private void AddChapters(InternalMediaInfoResult result, string standardError)
        {
            var lines = standardError.Split('\n').Select(l => l.TrimStart());

            var chapters = new List <ChapterInfo>();

            ChapterInfo lastChapter = null;

            foreach (var line in lines)
            {
                if (line.StartsWith("Chapter", StringComparison.OrdinalIgnoreCase))
                {
                    // Example:
                    // Chapter #0.2: start 400.534, end 4565.435
                    const string srch  = "start ";
                    var          start = line.IndexOf(srch, StringComparison.OrdinalIgnoreCase);

                    if (start == -1)
                    {
                        continue;
                    }

                    var subString = line.Substring(start + srch.Length);
                    subString = subString.Substring(0, subString.IndexOf(','));

                    double seconds;

                    if (double.TryParse(subString, NumberStyles.Any, UsCulture, out seconds))
                    {
                        lastChapter = new ChapterInfo
                        {
                            StartPositionTicks = TimeSpan.FromSeconds(seconds).Ticks
                        };

                        chapters.Add(lastChapter);
                    }
                }

                else if (line.StartsWith("title", StringComparison.OrdinalIgnoreCase))
                {
                    if (lastChapter != null && string.IsNullOrEmpty(lastChapter.Name))
                    {
                        var index = line.IndexOf(':');

                        if (index != -1)
                        {
                            lastChapter.Name = line.Substring(index + 1).Trim().TrimEnd('\r');
                        }
                    }
                }
            }

            result.Chapters = chapters;
        }
コード例 #4
0
        protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService)
        {
            if (data.format != null)
            {
                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

                if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
                {
                    video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
                }
            }

            var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;

            var chapters = data.Chapters ?? new List <ChapterInfo>();

            if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
            {
                FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
            }

            AddExternalSubtitles(video, mediaStreams, directoryService);

            FetchWtvInfo(video, data);

            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);

            if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
            {
                AddDummyChapters(video, chapters);
            }

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.VideoBitRate            = videoStream == null ? null : videoStream.BitRate;
            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);

            await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
            {
                Chapters      = chapters,
                Video         = video,
                ExtractImages = false,
                SaveChapters  = false
            }, cancellationToken).ConfigureAwait(false);

            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);

            await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
        }
コード例 #5
0
        /// <summary>
        /// Fetches the specified audio.
        /// </summary>
        /// <param name="audio">The audio.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="data">The data.</param>
        /// <returns>Task.</returns>
        protected Task Fetch(Audio audio, CancellationToken cancellationToken, InternalMediaInfoResult data)
        {
            var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;

            audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.Video);

            if (data.streams != null)
            {
                // Get the first audio stream
                var stream = data.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));

                if (stream != null)
                {
                    // Get duration from stream properties
                    var duration = stream.duration;

                    // If it's not there go into format properties
                    if (string.IsNullOrEmpty(duration))
                    {
                        duration = data.format.duration;
                    }

                    // If we got something, parse it
                    if (!string.IsNullOrEmpty(duration))
                    {
                        audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
                    }
                }
            }

            if (data.format.tags != null)
            {
                FetchDataFromTags(audio, data.format.tags);
            }

            return(_itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken));
        }
コード例 #6
0
        /// <summary>
        /// Normalizes the FF probe result.
        /// </summary>
        /// <param name="result">The result.</param>
        public static void NormalizeFFProbeResult(InternalMediaInfoResult result)
        {
            if (result.format != null && result.format.tags != null)
            {
                result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags);
            }

            if (result.streams != null)
            {
                // Convert all dictionaries to case insensitive
                foreach (var stream in result.streams)
                {
                    if (stream.tags != null)
                    {
                        stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags);
                    }

                    if (stream.disposition != null)
                    {
                        stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition);
                    }
                }
            }
        }
コード例 #7
0
        public const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)

        private void FetchWtvInfo(Video video, InternalMediaInfoResult data)
        {
            if (data.format == null || data.format.tags == null)
            {
                return;
            }

            if (!video.LockedFields.Contains(MetadataFields.Genres))
            {
                var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre");

                if (!string.IsNullOrWhiteSpace(genres))
                {
                    //genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
                }

                if (!string.IsNullOrWhiteSpace(genres))
                {
                    video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
                                   .Where(i => !string.IsNullOrWhiteSpace(i))
                                   .Select(i => i.Trim())
                                   .ToList();
                }
            }

            if (!video.LockedFields.Contains(MetadataFields.OfficialRating))
            {
                var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");

                if (!string.IsNullOrWhiteSpace(officialRating))
                {
                    video.OfficialRating = officialRating;
                }
            }

            if (!video.LockedFields.Contains(MetadataFields.Cast))
            {
                var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");

                if (!string.IsNullOrEmpty(people))
                {
                    video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
                                   .Where(i => !string.IsNullOrWhiteSpace(i))
                                   .Select(i => new PersonInfo {
                        Name = i.Trim(), Type = PersonType.Actor
                    })
                                   .ToList();
                }
            }

            var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");

            if (!string.IsNullOrWhiteSpace(year))
            {
                int val;

                if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val))
                {
                    video.ProductionYear = val;
                }
            }

            var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime");

            if (!string.IsNullOrWhiteSpace(premiereDateString))
            {
                DateTime val;

                // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
                // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None)
                if (DateTime.TryParse(year, null, DateTimeStyles.None, out val))
                {
                    video.PremiereDate = val.ToUniversalTime();
                }
            }

            var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");

            var episode = video as Episode;

            if (episode != null)
            {
                var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle");

                // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/

                // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910
                // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION
                // OR -> COMMENT. SUBTITLE: DESCRIPTION
                // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S]
                // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S]
                if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
                {
                    string[] parts = description.Split(':');
                    if (parts.Length > 0)
                    {
                        string subtitle = parts[0];
                        try
                        {
                            if (subtitle.Contains("/")) // It contains a episode number and season number
                            {
                                string[] numbers = subtitle.Split(' ');
                                episode.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
                                int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]);

                                description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
                            }
                            else
                            {
                                throw new Exception(); // Switch to default parsing
                            }
                        }
                        catch                                                                                                  // Default parsing
                        {
                            if (subtitle.Contains("."))                                                                        // skip the comment, keep the subtitle
                            {
                                description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
                            }
                            else
                            {
                                description = subtitle.Trim(); // Clean up whitespaces and save it
                            }
                        }
                    }
                }
            }

            if (!video.LockedFields.Contains(MetadataFields.Overview))
            {
                if (!string.IsNullOrWhiteSpace(description))
                {
                    video.Overview = description;
                }
            }
        }
コード例 #8
0
        protected async Task Fetch(Video video,
                                   CancellationToken cancellationToken,
                                   InternalMediaInfoResult data,
                                   IIsoMount isoMount,
                                   BlurayDiscInfo blurayInfo,
                                   MetadataRefreshOptions options)
        {
            var mediaInfo    = MediaEncoderHelpers.GetMediaInfo(data);
            var mediaStreams = mediaInfo.MediaStreams;

            video.TotalBitrate = mediaInfo.TotalBitrate;
            video.FormatName   = (mediaInfo.Format ?? string.Empty)
                                 .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);

            if (data.format != null)
            {
                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

                if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
                {
                    video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
                }

                if (video.VideoType == VideoType.VideoFile)
                {
                    var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');

                    video.Container = extension;
                }
                else
                {
                    video.Container = null;
                }

                if (!string.IsNullOrEmpty(data.format.size))
                {
                    video.Size = long.Parse(data.format.size, _usCulture);
                }
                else
                {
                    video.Size = null;
                }
            }

            var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList();
            var chapters      = mediaChapters.Select(GetChapterInfo).ToList();

            if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
            {
                FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
            }

            await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);

            FetchWtvInfo(video, data);

            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.VideoBitRate            = videoStream == null ? null : videoStream.BitRate;
            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);

            ExtractTimestamp(video);
            UpdateFromMediaInfo(video, videoStream);

            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);

            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
                options.MetadataRefreshMode == MetadataRefreshMode.Default)
            {
                var chapterOptions = _chapterManager.GetConfiguration();

                try
                {
                    var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false);

                    if (remoteChapters.Count > 0)
                    {
                        chapters = remoteChapters;
                    }
                }
                catch (Exception ex)
                {
                    _logger.ErrorException("Error downloading chapters", ex);
                }

                if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
                {
                    AddDummyChapters(video, chapters);
                }

                NormalizeChapterNames(chapters);

                await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
                {
                    Chapters      = chapters,
                    Video         = video,
                    ExtractImages = chapterOptions.ExtractDuringLibraryScan,
                    SaveChapters  = false
                }, cancellationToken).ConfigureAwait(false);

                await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false);
            }
        }
コード例 #9
0
        private void FetchWtvInfo(Video video, InternalMediaInfoResult data)
        {
            if (data.format == null || data.format.tags == null)
            {
                return;
            }

            if (video.Genres.Count == 0)
            {
                if (!video.LockedFields.Contains(MetadataFields.Genres))
                {
                    var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");

                    if (!string.IsNullOrEmpty(genres))
                    {
                        video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
                                       .Where(i => !string.IsNullOrWhiteSpace(i))
                                       .Select(i => i.Trim())
                                       .ToList();
                    }
                }
            }

            if (string.IsNullOrEmpty(video.Overview))
            {
                if (!video.LockedFields.Contains(MetadataFields.Overview))
                {
                    var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");

                    if (!string.IsNullOrWhiteSpace(overview))
                    {
                        video.Overview = overview;
                    }
                }
            }

            if (string.IsNullOrEmpty(video.OfficialRating))
            {
                var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");

                if (!string.IsNullOrWhiteSpace(officialRating))
                {
                    if (!video.LockedFields.Contains(MetadataFields.OfficialRating))
                    {
                        video.OfficialRating = officialRating;
                    }
                }
            }

            if (video.People.Count == 0)
            {
                if (!video.LockedFields.Contains(MetadataFields.Cast))
                {
                    var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");

                    if (!string.IsNullOrEmpty(people))
                    {
                        video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
                                       .Where(i => !string.IsNullOrWhiteSpace(i))
                                       .Select(i => new PersonInfo {
                            Name = i.Trim(), Type = PersonType.Actor
                        })
                                       .ToList();
                    }
                }
            }

            if (!video.ProductionYear.HasValue)
            {
                var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");

                if (!string.IsNullOrWhiteSpace(year))
                {
                    int val;

                    if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val))
                    {
                        video.ProductionYear = val;
                    }
                }
            }
        }
コード例 #10
0
ファイル: Program.cs プロジェクト: EraYaN/MediaInfoTest
        static void Main(string[] args)
        {
            if (args.Length != 2)
            {
                Console.WriteLine("Usage (this one is bugged for now): MediaInfoTest -images <directory>");
                Console.WriteLine("Benchmark grabbing media info for all image files in this directory and subdirectories.");
                Console.WriteLine("Usage: MediaInfoTest -av <directory>");
                Console.WriteLine("Benchmark grabbing media info for all audio and video files in this directory and subdirectories.");
                Console.WriteLine("Note: For both ffprobe executable must be in PATH");
                Environment.Exit(1);
            }

            string mode = "av";

            if (args[0] == "-images")
            {
                Console.WriteLine("Filtering for image files.");
                mode = "img";
            }
            else if (args[0] == "-av")
            {
                Console.WriteLine("Filtering for audio and video files.");
                mode = "av";
            }
            else
            {
                Console.WriteLine("Did not understand first argument");
                Environment.Exit(2);
            }

            List <string> extensions = new List <string>();

            using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(string.Format("MediaInfoTest.extensions-{0}.txt", mode)))
                using (StreamReader reader = new StreamReader(stream))
                {
                    while (!reader.EndOfStream)
                    {
                        var extension = "." + reader.ReadLine();
                        if (!extensions.Contains(extension))
                        {
                            extensions.Add(extension);
                        }
                    }
                }

            var           _logger = new ConsoleLogger();
            DirectoryInfo dir     = new DirectoryInfo(args[1]);

            if (!dir.Exists)
            {
                Console.WriteLine("Directory does not exist.");
                Environment.Exit(3);
            }

            Stopwatch sw       = Stopwatch.StartNew();
            Stopwatch inner_sw = new Stopwatch();
            int       count    = 0;
            Dictionary <string, Stats> stats = new Dictionary <string, Stats>();
            var files              = dir.GetFiles("*.*", SearchOption.AllDirectories);
            var filteredFiles      = files.Where(x => extensions.Contains(x.Extension.ToLowerInvariant())).ToList();
            var skipped_extensions = files.Where(x => !extensions.Contains(x.Extension.ToLowerInvariant())).Select(x => x.Extension).Distinct().ToList();
            var failures           = new List <Failure>();
            int total_files        = filteredFiles.Count;

            foreach (FileInfo file in filteredFiles)
            {
                if (!stats.ContainsKey(file.Extension.ToLowerInvariant()))
                {
                    stats.Add(file.Extension.ToLowerInvariant(), new Stats());
                }
                if (file.Exists)
                {
                    Console.Write("{1} of {2}: {0}", file.Name, count + 1, total_files);
                    inner_sw.Restart();
                    MediaInfoWrapper info = new MediaInfoWrapper(file.FullName /*, _logger*/);
                    //foreach (VideoStream stream in info.VideoStreams)
                    //{
                    //    Console.WriteLine("Title: {0} {1}", stream.Resolution, stream.CodecName);
                    //    Console.WriteLine("Codec: {0}", stream.CodecName);
                    //    Console.WriteLine("AVC: {0}", stream.Format);
                    //    Console.WriteLine("Profile: {0}", stream.CodecProfile);
                    //    Console.WriteLine("Resolution: {0}x{1}", stream.Size.Width, stream.Size.Height);
                    //}
                    //foreach (AudioStream stream in info.AudioStreams)
                    //{

                    //}
                    //foreach (SubtitleStream stream in info.Subtitles)
                    //{

                    //}
                    inner_sw.Stop();
                    stats[file.Extension.ToLowerInvariant()].TimeMediaInfo += inner_sw.Elapsed;
                    count++;
                    stats[file.Extension.ToLowerInvariant()].Count++;
                    Console.Write(" -> MediaInfo Done (v{0}:a{1}:s{2});", info.VideoStreams.Count, info.AudioStreams.Count, info.Subtitles.Count);
                    inner_sw.Restart();
                    var process = new ProcessOptions();
                    // Configure the process using the StartInfo properties.
                    process.FileName               = @"ffprobe";
                    process.UseShellExecute        = false;
                    process.RedirectStandardOutput = true;
                    process.Arguments              = string.Format("-analyzeduration 3000000 -i \"{0}\" -threads 0 -v warning -print_format json -show_streams -show_chapters -show_format", file.FullName.Replace("\"", "\\\""));
                    process.IsHidden               = true;
                    process.ErrorDialog            = false;
                    process.EnableRaisingEvents    = true;
                    var commonProcess = new CommonProcess(process);
                    commonProcess.Start();
                    InternalMediaInfoResult info_ff = new InternalMediaInfoResult();
                    try
                    {
                        info_ff = JsonSerializer.DeserializeFromStream <InternalMediaInfoResult>(commonProcess.StandardOutput.BaseStream);
                    }
                    catch
                    {
                        commonProcess.Kill();
                        Console.Write(" -> Failed...");
                    }
                    commonProcess.WaitForExit(1000);
                    inner_sw.Stop();
                    stats[file.Extension.ToLowerInvariant()].TimeFFProbe += inner_sw.Elapsed;
                    int videostreams = info_ff.streams.Where(x => x.codec_type == "video" && x.disposition["attached_pic"] == "0").Count();
                    int audiostreams = info_ff.streams.Where(x => x.codec_type == "audio").Count();
                    int substreams   = info_ff.streams.Where(x => x.codec_type == "subtitle").Count();
                    Console.WriteLine(" -> FFProbe Done (v{0}:a{1}:s{2}).", videostreams, audiostreams, substreams);


                    if (videostreams != info.VideoStreams.Count || audiostreams != info.AudioStreams.Count || substreams != info.Subtitles.Count)
                    {
                        Console.WriteLine("FFProbe and MediaInfo do not agree on the number of streams!", videostreams, audiostreams, substreams);
                        failures.Add(new Failure()
                        {
                            file = file, mediainfo = info, ffprobe = info_ff
                        });
                    }
                }
                else
                {
                    Console.WriteLine(string.Format("File {0} not found!", file.Name));
                }
            }
            sw.Stop();
            Console.WriteLine(string.Format("Took {0} for {1} * 2 items ({2:f2} i/s).", sw.Elapsed, count, (count * 2) / sw.Elapsed.TotalSeconds));
            foreach (var kvp in stats)
            {
                Console.WriteLine(string.Format("Extension: {0}, Count: {1}, Time MI: {2}, Speed MI: {4:f2} i/s, Time FF: {3}, Speed FF: {5:f2} i/s.", kvp.Key, kvp.Value.Count,
                                                kvp.Value.TimeMediaInfo, kvp.Value.TimeFFProbe,
                                                kvp.Value.Count / kvp.Value.TimeMediaInfo.TotalSeconds, kvp.Value.Count / kvp.Value.TimeFFProbe.TotalSeconds));
            }
            foreach (var me in skipped_extensions)
            {
                Console.WriteLine(string.Format("Skipped Extension: {0}", me));
            }
            foreach (var failure in failures)
            {
                Console.WriteLine(string.Format("Failure: Name: {0}; Streams MI: v{1}:a{2}:s{3}; Streams FF: v{4}:a{5}:s{6};", failure.file.FullName,
                                                failure.mediainfo.VideoStreams.Count, failure.mediainfo.AudioStreams.Count, failure.mediainfo.Subtitles.Count,
                                                failure.ffprobe.streams.Where(x => x.codec_type == "video" && x.disposition["attached_pic"] == "0").Count(),
                                                failure.ffprobe.streams.Where(x => x.codec_type == "audio").Count(),
                                                failure.ffprobe.streams.Where(x => x.codec_type == "subtitle").Count()
                                                ));
            }
        }