Esempio n. 1
0
        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);
            }
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
        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;
                    }
                }
            }
        }