From 5c610d71f61f4f834a07102e9947d847f6a4efbf Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 19 Dec 2015 11:46:32 -0500 Subject: [PATCH 1/2] remove call from probe result normalizer --- .../Encoder/MediaEncoder.cs | 77 +++++++++++++++++++ .../Probing/ProbeResultNormalizer.cs | 22 ------ 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index c8361ea04c..d4a626da02 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -262,6 +262,8 @@ namespace MediaBrowser.MediaEncoding.Encoder var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); + await DetectInterlaced(mediaInfo, inputPath, probeSizeArgument).ConfigureAwait(false); + if (extractKeyFrameInterval && mediaInfo.RunTimeTicks.HasValue) { if (ConfigurationManager.Configuration.EnableVideoFrameByFrameAnalysis && mediaInfo.Size.HasValue) @@ -304,6 +306,81 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ApplicationException(string.Format("FFProbe failed for {0}", inputPath)); } + private async Task DetectInterlaced(MediaSourceInfo video, string inputPath, string probeSizeArgument) + { + var videoStream = video.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + + if (video.Protocol != MediaProtocol.File || videoStream == null) + { + return; + } + + // Take a shortcut and limit this to containers that are likely to have interlaced content + if (!string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase) && + !string.Equals(video.Container, "wtv", StringComparison.OrdinalIgnoreCase)) + { + //return; + } + + var args = "{0} -i {1} -map 0:v:{2} -filter:v idet -frames:v 500 -an -f null /dev/null"; + + var process = new Process + { + StartInfo = new ProcessStartInfo + { + CreateNoWindow = true, + UseShellExecute = false, + + // Must consume both or ffmpeg may hang due to deadlocks. See comments below. + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + FileName = FFMpegPath, + Arguments = string.Format(args, probeSizeArgument, inputPath, videoStream.Index.ToString(CultureInfo.InvariantCulture)).Trim(), + + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false + }, + + EnableRaisingEvents = true + }; + + _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); + + using (var processWrapper = new ProcessWrapper(process, this, _logger)) + { + try + { + StartProcess(processWrapper); + } + catch (Exception ex) + { + _logger.ErrorException("Error starting ffprobe", ex); + + throw; + } + + try + { + process.BeginOutputReadLine(); + + using (var reader = new StreamReader(process.StandardError.BaseStream)) + { + var result = await reader.ReadToEndAsync().ConfigureAwait(false); + + File.WriteAllText("D:\\\\1.txt", result); + } + + } + catch + { + StopProcess(processWrapper, 100, true); + + throw; + } + } + } + private bool EnableKeyframeExtraction(MediaSourceInfo mediaSource, MediaStream videoStream) { if (videoStream.Type == MediaStreamType.Video && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase) && diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 55b3398bb1..d9fda220df 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -103,13 +103,6 @@ namespace MediaBrowser.MediaEncoding.Probing } ExtractTimestamp(info); - - var videoStream = info.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - - if (videoStream != null && videoType == VideoType.VideoFile) - { - DetectInterlaced(info, videoStream); - } } return info; @@ -932,20 +925,5 @@ namespace MediaBrowser.MediaEncoding.Probing return TransportStreamTimestamp.None; } - - private void DetectInterlaced(MediaSourceInfo video, MediaStream videoStream) - { - if (video.Protocol != MediaProtocol.File || videoStream == null) - { - return; - } - - // Take a shortcut and limit this to containers that are likely to have interlaced content - if (!string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase) && - !string.Equals(video.Container, "wtv", StringComparison.OrdinalIgnoreCase)) - { - return; - } - } } } From 5fffd38963e83aec92bd74a172be8dd38bdaba35 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 19 Dec 2015 12:44:38 -0500 Subject: [PATCH 2/2] detect interlaced --- .../Encoder/MediaEncoder.cs | 136 ++++++++++++++++-- 1 file changed, 124 insertions(+), 12 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index d4a626da02..1dc1a42150 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -262,7 +262,17 @@ namespace MediaBrowser.MediaEncoding.Encoder var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); - await DetectInterlaced(mediaInfo, inputPath, probeSizeArgument).ConfigureAwait(false); + var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + + if (videoStream != null) + { + var isInterlaced = await DetectInterlaced(mediaInfo, videoStream, inputPath, probeSizeArgument).ConfigureAwait(false); + + if (isInterlaced) + { + videoStream.IsInterlaced = true; + } + } if (extractKeyFrameInterval && mediaInfo.RunTimeTicks.HasValue) { @@ -306,20 +316,21 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ApplicationException(string.Format("FFProbe failed for {0}", inputPath)); } - private async Task DetectInterlaced(MediaSourceInfo video, string inputPath, string probeSizeArgument) + private async Task DetectInterlaced(MediaSourceInfo video, MediaStream videoStream, string inputPath, string probeSizeArgument) { - var videoStream = video.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - - if (video.Protocol != MediaProtocol.File || videoStream == null) + if (video.Protocol != MediaProtocol.File) { - return; + return false; } + var formats = (video.Container ?? string.Empty).Split(',').ToList(); + // Take a shortcut and limit this to containers that are likely to have interlaced content - if (!string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase) && - !string.Equals(video.Container, "wtv", StringComparison.OrdinalIgnoreCase)) + if (!formats.Contains("ts", StringComparer.OrdinalIgnoreCase) && + !formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) && + !formats.Contains("wtv", StringComparer.OrdinalIgnoreCase)) { - //return; + return false; } var args = "{0} -i {1} -map 0:v:{2} -filter:v idet -frames:v 500 -an -f null /dev/null"; @@ -346,6 +357,7 @@ namespace MediaBrowser.MediaEncoding.Encoder }; _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); + var idetFoundInterlaced = false; using (var processWrapper = new ProcessWrapper(process, this, _logger)) { @@ -366,11 +378,27 @@ namespace MediaBrowser.MediaEncoding.Encoder using (var reader = new StreamReader(process.StandardError.BaseStream)) { - var result = await reader.ReadToEndAsync().ConfigureAwait(false); + while (!reader.EndOfStream) + { + var line = await reader.ReadLineAsync().ConfigureAwait(false); + + if (line.StartsWith("[Parsed_idet", StringComparison.OrdinalIgnoreCase)) + { + var idetResult = AnalyzeIdetResult(line); + + if (idetResult.HasValue) + { + if (!idetResult.Value) + { + return false; + } - File.WriteAllText("D:\\\\1.txt", result); + idetFoundInterlaced = true; + } + } + } } - + } catch { @@ -379,6 +407,90 @@ namespace MediaBrowser.MediaEncoding.Encoder throw; } } + + return idetFoundInterlaced; + } + + private bool? AnalyzeIdetResult(string line) + { + // As you can see, the filter only guessed one frame as progressive. + // Results like this are pretty typical. So if less than 30% of the detections are in the "Undetermined" category, then I only consider the video to be interlaced if at least 65% of the identified frames are in either the TFF or BFF category. + // In this case (310 + 311)/(622) = 99.8% which is well over the 65% metric. I may refine that number with more testing but I honestly do not believe I will need to. + // http://awel.domblogger.net/videoTranscode/interlace.html + var index = line.IndexOf("detection:", StringComparison.OrdinalIgnoreCase); + + if (index == -1) + { + return null; + } + + line = line.Substring(index).Trim(); + var parts = line.Split(' ').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => i.Trim()).ToList(); + + if (parts.Count < 2) + { + return null; + } + double tff = 0; + double bff = 0; + double progressive = 0; + double undetermined = 0; + double total = 0; + + for (var i = 0; i < parts.Count - 1; i++) + { + var part = parts[i]; + + if (string.Equals(part, "tff:", StringComparison.OrdinalIgnoreCase)) + { + tff = GetNextPart(parts, i); + total += tff; + } + else if (string.Equals(part, "bff:", StringComparison.OrdinalIgnoreCase)) + { + bff = GetNextPart(parts, i); + total += tff; + } + else if (string.Equals(part, "progressive:", StringComparison.OrdinalIgnoreCase)) + { + progressive = GetNextPart(parts, i); + total += progressive; + } + else if (string.Equals(part, "undetermined:", StringComparison.OrdinalIgnoreCase)) + { + undetermined = GetNextPart(parts, i); + total += undetermined; + } + } + + if (total == 0) + { + return null; + } + + if ((undetermined / total) >= .3) + { + return false; + } + + if (((tff + bff) / total) >= .65) + { + return true; + } + + return false; + } + + private int GetNextPart(List parts, int index) + { + var next = parts[index + 1]; + + int value; + if (int.TryParse(next, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + return value; + } + return 0; } private bool EnableKeyframeExtraction(MediaSourceInfo mediaSource, MediaStream videoStream)