internal static void ParseInputLine(IResourceAccessor file, string inputLine, ref MetadataContainer info) { inputLine = inputLine.Trim(); int inputPos = inputLine.IndexOf("Input #0", StringComparison.InvariantCultureIgnoreCase); string ffmContainer = inputLine.Substring(inputPos + 10, inputLine.IndexOf(",", inputPos + 11) - 10).Trim(); if (info.IsAudio(Editions.DEFAULT_EDITION)) { info.Metadata[Editions.DEFAULT_EDITION].AudioContainerType = FFMpegParseAudioContainer.ParseAudioContainer(ffmContainer, file); } else if (info.IsVideo(Editions.DEFAULT_EDITION)) { info.Metadata[Editions.DEFAULT_EDITION].VideoContainerType = FFMpegParseVideoContainer.ParseVideoContainer(ffmContainer, file); } else if (info.IsImage(Editions.DEFAULT_EDITION)) { info.Metadata[Editions.DEFAULT_EDITION].ImageContainerType = FFMpegParseImageContainer.ParseImageContainer(ffmContainer, file); } else { info.Metadata[Editions.DEFAULT_EDITION].VideoContainerType = FFMpegParseVideoContainer.ParseVideoContainer(ffmContainer, file); info.Metadata[Editions.DEFAULT_EDITION].AudioContainerType = FFMpegParseAudioContainer.ParseAudioContainer(ffmContainer, file); info.Metadata[Editions.DEFAULT_EDITION].ImageContainerType = FFMpegParseImageContainer.ParseImageContainer(ffmContainer, file); } }
public override async Task <MetadataContainer> ParseMediaStreamAsync(IEnumerable <IResourceAccessor> mediaResources) { bool isImage = true; bool isFileSystem = false; bool isNetwork = false; bool isUnsupported = false; //Check all files if (!mediaResources.Any()) { throw new ArgumentException($"FFMpegMediaAnalyzer: Resource list is empty", "mediaResources"); } foreach (var res in mediaResources) { if (res is IFileSystemResourceAccessor fileRes) { isFileSystem = true; if (!fileRes.IsFile) { throw new ArgumentException($"FFMpegMediaAnalyzer: Resource '{res.ResourceName}' is not a file", "mediaResources"); } } else if (res is INetworkResourceAccessor urlRes) { isNetwork = true; } else { isUnsupported = true; } } if (isFileSystem && isNetwork) { throw new ArgumentException($"FFMpegMediaAnalyzer: Resources are of mixed media formats", "mediaResources"); } if (isUnsupported) { throw new ArgumentException($"FFMpegMediaAnalyzer: Resources are of unsupported media formats", "mediaResources"); } ProcessExecutionResult executionResult = null; string logFileName = "?"; if (isFileSystem) { List <LocalFsResourceAccessorHelper> helpers = new List <LocalFsResourceAccessorHelper>(); List <IDisposable> accessors = new List <IDisposable>(); try { MetadataContainer info = null; logFileName = mediaResources.First().ResourceName; //Initialize and check file system resources foreach (var res in mediaResources) { string resName = res.ResourceName; if (!(HasImageExtension(resName) || HasVideoExtension(resName) || HasAudioExtension(resName) || HasOpticalDiscFileExtension(resName))) { throw new ArgumentException($"FFMpegMediaAnalyzer: Resource '{res.ResourceName}' has unsupported file extension", "mediaResources"); } if (!(HasImageExtension(resName))) { isImage = false; } //Ensure availability var rah = new LocalFsResourceAccessorHelper(res); helpers.Add(rah); var accessor = rah.LocalFsResourceAccessor.EnsureLocalFileSystemAccess(); if (accessor != null) { accessors.Add(accessor); } } string fileName; if (helpers.Count > 1) //Check if concatenation needed { fileName = $"concat:\"{string.Join("|", helpers.Select(h => h.LocalFsResourceAccessor.LocalFileSystemPath))}\""; } else { fileName = $"\"{helpers.First().LocalFsResourceAccessor.LocalFileSystemPath}\""; } string arguments = ""; if (isImage) { //Default image decoder (image2) fails if file name contains å, ø, ö etc., so force format to image2pipe arguments = string.Format("-threads {0} -f image2pipe -i {1}", _analyzerMaximumThreads, fileName); } else { arguments = string.Format("-threads {0} -i {1}", _analyzerMaximumThreads, fileName); } //Use first file for parsing. The other files are expected to be of same encoding and same location var firstFile = helpers.First().LocalFsResourceAccessor; executionResult = await ParseFileAsync(helpers.First().LocalFsResourceAccessor, arguments); if (executionResult != null && executionResult.Success && executionResult.ExitCode == 0 && !string.IsNullOrEmpty(executionResult.StandardError)) { //_logger.Debug("MediaAnalyzer: Successfully ran FFProbe:\n {0}", executionResult.StandardError); info = new MetadataContainer(); info.AddEdition(Editions.DEFAULT_EDITION); info.Metadata[Editions.DEFAULT_EDITION].Size = helpers.Sum(h => h.LocalFsResourceAccessor.Size); FFMpegParseFFMpegOutput.ParseFFMpegOutput(firstFile, executionResult.StandardError, ref info, _countryCodesMapping); // Special handling for files like OGG which will be falsely identified as videos if (info.Metadata[0].VideoContainerType != VideoContainer.Unknown && info.Video[0].Codec == VideoCodec.Unknown) { info.Metadata[0].VideoContainerType = VideoContainer.Unknown; } if (info.IsImage(Editions.DEFAULT_EDITION) || HasImageExtension(fileName)) { info.Metadata[Editions.DEFAULT_EDITION].Mime = MimeDetector.GetFileMime(firstFile, "image/unknown"); } else if (info.IsVideo(Editions.DEFAULT_EDITION) || HasVideoExtension(fileName)) { info.Metadata[Editions.DEFAULT_EDITION].Mime = MimeDetector.GetFileMime(firstFile, "video/unknown"); await _probeLock.WaitAsync(); try { FFMpegParseH264Info.ParseH264Info(firstFile, info, _h264MaxDpbMbs, H264_TIMEOUT_MS); } finally { _probeLock.Release(); } FFMpegParseMPEG2TSInfo.ParseMPEG2TSInfo(firstFile, info); } else if (info.IsAudio(Editions.DEFAULT_EDITION) || HasAudioExtension(fileName)) { info.Metadata[Editions.DEFAULT_EDITION].Mime = MimeDetector.GetFileMime(firstFile, "audio/unknown"); } else { return(null); } return(info); } } finally { foreach (var accessor in accessors) { accessor?.Dispose(); } foreach (var helper in helpers) { helper?.Dispose(); } } if (executionResult != null) { _logger.Error("FFMpegMediaAnalyzer: Failed to extract media type information for resource '{0}', Result: {1}, ExitCode: {2}, Success: {3}", logFileName, executionResult.StandardError, executionResult.ExitCode, executionResult.Success); } else { _logger.Error("FFMpegMediaAnalyzer: Failed to extract media type information for resource '{0}', Execution result empty", logFileName); } } else if (isNetwork) { //We can only read one network resource so take the first var urlRes = mediaResources.First() as INetworkResourceAccessor; string url = urlRes.URL; string arguments = ""; if (url.StartsWith("rtsp://", StringComparison.InvariantCultureIgnoreCase) == true) { arguments += "-rtsp_transport tcp "; } arguments += "-analyzeduration " + _analyzerStreamTimeout + " "; //Resolve host first because ffprobe can hang when resolving host var resolvedUrl = UrlHelper.ResolveHostToIPv4Url(url); if (string.IsNullOrEmpty(resolvedUrl)) { throw new InvalidOperationException($"FFMpegMediaAnalyzer: Failed to resolve host for resource '{url}'"); } arguments += string.Format("-i \"{0}\"", resolvedUrl); executionResult = await ParseUrlAsync(url, arguments); if (executionResult != null && executionResult.Success && executionResult.ExitCode == 0 && !string.IsNullOrEmpty(executionResult.StandardError)) { //_logger.Debug("MediaAnalyzer: Successfully ran FFProbe:\n {0}", executionResult.StandardError); MetadataContainer info = new MetadataContainer(); info.AddEdition(Editions.DEFAULT_EDITION); info.Metadata[Editions.DEFAULT_EDITION].Size = 0; FFMpegParseFFMpegOutput.ParseFFMpegOutput(urlRes, executionResult.StandardError, ref info, _countryCodesMapping); // Special handling for files like OGG which will be falsely identified as videos if (info.Metadata[Editions.DEFAULT_EDITION].VideoContainerType != VideoContainer.Unknown && info.Video[Editions.DEFAULT_EDITION].Codec == VideoCodec.Unknown) { info.Metadata[Editions.DEFAULT_EDITION].VideoContainerType = VideoContainer.Unknown; } if (info.IsImage(Editions.DEFAULT_EDITION)) { info.Metadata[Editions.DEFAULT_EDITION].Mime = MimeDetector.GetUrlMime(url, "image/unknown"); } else if (info.IsVideo(Editions.DEFAULT_EDITION)) { info.Metadata[Editions.DEFAULT_EDITION].Mime = MimeDetector.GetUrlMime(url, "video/unknown"); await _probeLock.WaitAsync(); try { FFMpegParseH264Info.ParseH264Info(urlRes, info, _h264MaxDpbMbs, H264_TIMEOUT_MS); } finally { _probeLock.Release(); } FFMpegParseMPEG2TSInfo.ParseMPEG2TSInfo(urlRes, info); } else if (info.IsAudio(Editions.DEFAULT_EDITION)) { info.Metadata[Editions.DEFAULT_EDITION].Mime = MimeDetector.GetUrlMime(url, "audio/unknown"); } else { return(null); } return(info); } if (executionResult != null) { _logger.Error("FFMpegMediaAnalyzer: Failed to extract media type information for resource '{0}', Result: {1}, ExitCode: {2}, Success: {3}", url, executionResult.StandardError, executionResult.ExitCode, executionResult.Success); } else { _logger.Error("FFMpegMediaAnalyzer: Failed to extract media type information for resource '{0}', Execution result empty", url); } } return(null); }
internal static void ParseStreamVideoLine(IResourceAccessor file, string streamVideoLine, ref MetadataContainer info, Dictionary <string, CultureInfo> countryCodesMapping) { if (info.Video[Editions.DEFAULT_EDITION].Codec != VideoCodec.Unknown) { return; } streamVideoLine = streamVideoLine.Trim(); string beforeVideo = streamVideoLine.Substring(0, streamVideoLine.IndexOf("Video:", StringComparison.InvariantCultureIgnoreCase)); string afterVideo = streamVideoLine.Substring(beforeVideo.Length); string[] beforeVideoTokens = beforeVideo.Split(','); string[] afterVideoTokens = afterVideo.Split(','); foreach (string mediaToken in beforeVideoTokens) { string token = mediaToken.Trim(); if (token.StartsWith("Stream", StringComparison.InvariantCultureIgnoreCase)) { Match match = Regex.Match(token, @"#[\d][\.:](?<stream>[\d]{1,2}).*\((?<lang>(\w+))\)[\.:]", RegexOptions.IgnoreCase); if (match.Success) { info.Video[Editions.DEFAULT_EDITION].StreamIndex = Convert.ToInt32(match.Groups["stream"].Value.Trim()); if (match.Groups.Count == 4) { string lang = match.Groups["lang"].Value.Trim().ToUpperInvariant(); if (countryCodesMapping.ContainsKey(lang)) { info.Video[Editions.DEFAULT_EDITION].Language = countryCodesMapping[lang].TwoLetterISOLanguageName.ToUpperInvariant(); } } } else { match = Regex.Match(token, @"#[\d][\.:](?<stream>[\d]{1,2}).*[\.:]", RegexOptions.IgnoreCase); if (match.Success) { info.Video[Editions.DEFAULT_EDITION].StreamIndex = Convert.ToInt32(match.Groups["stream"].Value.Trim()); } } } } bool nextTokenIsPixelFormat = false; foreach (string mediaToken in afterVideoTokens) { string token = mediaToken.Trim(); if (token.StartsWith("Video:", StringComparison.InvariantCultureIgnoreCase)) { string[] parts = token.Substring(token.IndexOf("Video: ", StringComparison.InvariantCultureIgnoreCase) + 7).Split(' '); string codecValue = parts[0]; if ((codecValue != null) && (codecValue.StartsWith("drm", StringComparison.InvariantCultureIgnoreCase))) { throw new Exception($"MediaAnalyzer: {file} is DRM protected"); } string codecDetails = null; if (parts.Length > 1) { string details = string.Join(" ", parts).Trim(); if (details.Contains("(")) { int iIndex = details.IndexOf("("); codecDetails = details.Substring(iIndex + 1, details.IndexOf(")") - iIndex - 1); } } info.Video[Editions.DEFAULT_EDITION].Codec = FFMpegParseVideoCodec.ParseVideoCodec(codecValue); if (info.IsImage(Editions.DEFAULT_EDITION)) { info.Metadata[Editions.DEFAULT_EDITION].ImageContainerType = FFMpegParseImageContainer.ParseImageContainer(codecValue, file as ILocalFsResourceAccessor); } if (info.Video[Editions.DEFAULT_EDITION].Codec == VideoCodec.H264 || info.Video[Editions.DEFAULT_EDITION].Codec == VideoCodec.H265) { info.Video[Editions.DEFAULT_EDITION].ProfileType = FFMpegParseProfile.ParseProfile(codecDetails); } string fourCC = token.Trim(); if (token.Contains("(")) { string fourCCBlock = fourCC.Substring(fourCC.LastIndexOf("(") + 1, fourCC.LastIndexOf(")") - fourCC.LastIndexOf("(") - 1); if (fourCCBlock.IndexOf("/") > -1) { fourCC = (fourCCBlock.Split('/')[0].Trim()).ToLowerInvariant(); if (fourCC.IndexOf("[") == -1) { info.Video[Editions.DEFAULT_EDITION].FourCC = fourCC; } } } nextTokenIsPixelFormat = true; } else if (nextTokenIsPixelFormat) { nextTokenIsPixelFormat = false; if (info.IsImage(Editions.DEFAULT_EDITION)) { info.Image[Editions.DEFAULT_EDITION].PixelFormatType = FFMpegParsePixelFormat.ParsePixelFormat(token.Trim()); } else { info.Video[Editions.DEFAULT_EDITION].PixelFormatType = FFMpegParsePixelFormat.ParsePixelFormat(token.Trim()); } } else if (token.IndexOf("x", StringComparison.InvariantCultureIgnoreCase) > -1 && token.Contains("max") == false) { string resolution = token.Trim(); int aspectStart = resolution.IndexOf(" ["); if (aspectStart > -1) { info.Video[Editions.DEFAULT_EDITION].PixelAspectRatio = 1.0F; string aspectDef = resolution.Substring(aspectStart + 2, resolution.IndexOf("]") - aspectStart - 2); int sarIndex = aspectDef.IndexOf("SAR"); //Sample AR if (sarIndex < 0) { sarIndex = aspectDef.IndexOf("PAR"); //Pixel AR } if (sarIndex > -1) { aspectDef = aspectDef.Substring(sarIndex + 4); string sar = aspectDef.Substring(0, aspectDef.IndexOf(" ")).Trim(); string[] sarRatio = sar.Split(':'); if (sarRatio.Length == 2) { try { info.Video[Editions.DEFAULT_EDITION].PixelAspectRatio = Convert.ToSingle(sarRatio[0], CultureInfo.InvariantCulture) / Convert.ToSingle(sarRatio[1], CultureInfo.InvariantCulture); } catch { } } } resolution = resolution.Substring(0, aspectStart); } string[] res = resolution.Split('x'); if (res.Length == 2 && res[0].All(Char.IsDigit) && res[1].All(Char.IsDigit)) { try { if (info.IsImage(Editions.DEFAULT_EDITION)) { info.Image[Editions.DEFAULT_EDITION].Width = Convert.ToInt32(res[0], CultureInfo.InvariantCulture); info.Image[Editions.DEFAULT_EDITION].Height = Convert.ToInt32(res[1], CultureInfo.InvariantCulture); } else { info.Video[Editions.DEFAULT_EDITION].Width = Convert.ToInt32(res[0], CultureInfo.InvariantCulture); info.Video[Editions.DEFAULT_EDITION].Height = Convert.ToInt32(res[1], CultureInfo.InvariantCulture); } } catch { } if (info.Video[Editions.DEFAULT_EDITION].Height > 0) { info.Video[Editions.DEFAULT_EDITION].AspectRatio = (float)info.Video[Editions.DEFAULT_EDITION].Width / (float)info.Video[Editions.DEFAULT_EDITION].Height; } } } else if (token.IndexOf("SAR", StringComparison.InvariantCultureIgnoreCase) > -1 || token.IndexOf("PAR", StringComparison.InvariantCultureIgnoreCase) > -1) { info.Video[Editions.DEFAULT_EDITION].PixelAspectRatio = 1.0F; string aspectDef = token.Trim(); int sarIndex = aspectDef.IndexOf("SAR", StringComparison.InvariantCultureIgnoreCase); //Sample AR if (sarIndex < 0) { sarIndex = aspectDef.IndexOf("PAR", StringComparison.InvariantCultureIgnoreCase); //Pixel AR } if (sarIndex > -1) { aspectDef = aspectDef.Substring(sarIndex + 4); string sar = aspectDef.Substring(0, aspectDef.IndexOf(" ")).Trim(); string[] sarRatio = sar.Split(':'); if (sarRatio.Length == 2) { try { info.Video[Editions.DEFAULT_EDITION].PixelAspectRatio = Convert.ToSingle(sarRatio[0], CultureInfo.InvariantCulture) / Convert.ToSingle(sarRatio[1], CultureInfo.InvariantCulture); } catch { } } } } else if (token.IndexOf("kb/s", StringComparison.InvariantCultureIgnoreCase) > -1) { string[] parts = token.Split(' '); //if (parts.Length == 3 && parts[1].All(Char.IsDigit)) // info.Video.Bitrate = long.Parse(parts[1].Trim(), CultureInfo.InvariantCulture); if (parts.Length == 2 && parts[0].All(Char.IsDigit)) { info.Video[Editions.DEFAULT_EDITION].Bitrate = long.Parse(parts[0].Trim(), CultureInfo.InvariantCulture); } } else if (token.IndexOf("mb/s", StringComparison.InvariantCultureIgnoreCase) > -1) { string[] parts = token.Split(' '); //if (parts.Length == 3 && parts[1].All(Char.IsDigit)) // info.Video.Bitrate = long.Parse(parts[1].Trim(), CultureInfo.InvariantCulture) * 1024; if (parts.Length == 2 && parts[0].All(Char.IsDigit)) { info.Video[Editions.DEFAULT_EDITION].Bitrate = long.Parse(parts[0].Trim(), CultureInfo.InvariantCulture) * 1024; } } else if (token.IndexOf("tbr", StringComparison.InvariantCultureIgnoreCase) > -1 || token.IndexOf("fps", StringComparison.InvariantCultureIgnoreCase) > -1) { if (info.Video[Editions.DEFAULT_EDITION].Framerate == 0) { string fpsValue = "23.976"; if (token.IndexOf("tbr", StringComparison.InvariantCultureIgnoreCase) > -1) { fpsValue = token.Substring(0, token.IndexOf("tbr", StringComparison.InvariantCultureIgnoreCase)).Trim(); } else if (token.IndexOf("fps", StringComparison.InvariantCultureIgnoreCase) > -1) { fpsValue = token.Substring(0, token.IndexOf("fps", StringComparison.InvariantCultureIgnoreCase)).Trim(); } if (fpsValue.Contains("k")) { fpsValue = fpsValue.Replace("k", "000"); } double fr = 0; float validFrameRate = 23.976F; if (double.TryParse(fpsValue, out fr) == true) { if (fr > 23.899999999999999D && fr < 23.989999999999998D) { validFrameRate = 23.976F; } else if (fr > 23.989999999999998D && fr < 24.100000000000001D) { validFrameRate = 24; } else if (fr >= 24.989999999999998D && fr < 25.100000000000001D) { validFrameRate = 25; } else if (fr > 29.899999999999999D && fr < 29.989999999999998D) { validFrameRate = 29.97F; } else if (fr >= 29.989999999999998D && fr < 30.100000000000001D) { validFrameRate = 30; } else if (fr > 49.899999999999999D && fr < 50.100000000000001D) { validFrameRate = 50; } else if (fr > 59.899999999999999D && fr < 59.990000000000002D) { validFrameRate = 59.94F; } else if (fr >= 59.990000000000002D && fr < 60.100000000000001D) { validFrameRate = 60; } } info.Video[Editions.DEFAULT_EDITION].Framerate = validFrameRate; } } } }