• 【ffmpeg】解决fluentffmpeg使用ffprobe无效问题


    前言

      在使用fluent-ffmpeg时,ffprobe方法无论添加什么选项都只返回视频的元信息。

       如下图:下图是获取视频信息的函数

      

       如下图:下图为调用并打印出视频信息

      

       

       

      使用ffprobe不管添加什么参数,第一张图添加了 ['-v', 'quiet', '-select_streams', 'v', '-show_entries', 'frame=pkt_pts_time,pict_type'] (获取IBP帧时间点), 但是获取到的结果还是一些视频的元信息。

    查看源代码

      在 fluent-ffmpeg/lib/ffprobe.js 中:

    1 var ffprobe = spawn(path, ['-show_streams', '-show_format'].concat(options, src));

      这段代码可以看出,是在使用ffprobe执行命令,并且默认添加了 '-show_streams' 和 '-show_format' 选项,而options则是调用者传进来的,两者进行合并。这里既然合并并执行了,为何没效呢,说明问题不在这,继续看。

      

     1     ffprobe.stdout.on('data', function(data) {
     2         console.log(data.toString())
     3         stdout += data;
     4       });
     5 
     6       ffprobe.stdout.on('close', function() {
     7         stdoutClosed = true;
     8         handleExit();
     9       });
    10 
    11       ffprobe.stderr.on('data', function(data) {
    12         stderr += data;
    13       });
    14 
    15       ffprobe.stderr.on('close', function() {
    16         stderrClosed = true;
    17         handleExit();
    18       });

      这些代码则是使用 spawn 执行的事件监听,可以直接看看执行的结果

      

      可以看到,确实是有结果的。说明在处理这些数据的时候没有处理这些数据,只处理了他原本默认选项的数据。

      再看 spawn 的 close事件,调用了 handleExit 

     1 function handleExit(err) {
     2         if (err) {
     3           exitError = err;
     4         }
     5 
     6         if (processExited && stdoutClosed && stderrClosed) {
     7           if (exitError) {
     8             if (stderr) {
     9               exitError.message += '\n' + stderr;
    10             }
    11 
    12             return handleCallback(exitError);
    13           }
    14 
    15           // Process output
    16           var data = parseFfprobeOutput(stdout);
    17           
    18           // Handle legacy output with "TAG:x" and "DISPOSITION:x" keys
    19           [data.format].concat(data.streams).forEach(function(target) {
    20             if (target) {
    21               var legacyTagKeys = Object.keys(target).filter(legacyTag);
    22 
    23               if (legacyTagKeys.length) {
    24                 target.tags = target.tags || {};
    25 
    26                 legacyTagKeys.forEach(function(tagKey) {
    27                   target.tags[tagKey.substr(4)] = target[tagKey];
    28                   delete target[tagKey];
    29                 });
    30               }
    31 
    32               var legacyDispositionKeys = Object.keys(target).filter(legacyDisposition);
    33 
    34               if (legacyDispositionKeys.length) {
    35                 target.disposition = target.disposition || {};
    36 
    37                 legacyDispositionKeys.forEach(function(dispositionKey) {
    38                   target.disposition[dispositionKey.substr(12)] = target[dispositionKey];
    39                   delete target[dispositionKey];
    40                 });
    41               }
    42             }
    43           });
    44 
    45           handleCallback(null, data);
    46         }
    47       }

      具体关键代码为,上面代码片段的 16 行,parseFfprobeOutput, 解析ffprobe的输出

     1 function parseFfprobeOutput(out) {
     2   var lines = out.split(/\r\n|\r|\n/);
     3 
     4   lines = lines.filter(function (line) {
     5     return line.length > 0;
     6   });
     7 
     8   var data = {
     9     streams: [],
    10     format: {},
    11     chapters: []
    12 13   };
    14 
    15   function parseBlock(name) {
    16     var data = {};
    17 
    18     var line = lines.shift();
    19     while (typeof line !== 'undefined') {
    20       if (line.toLowerCase() == '[/'+name+']') {
    21         return data;
    22       } else if (line.match(/^\[/)) {
    23         line = lines.shift();
    24         continue;
    25       }
    26 
    27       var kv = line.match(/^([^=]+)=(.*)$/);
    28       if (kv) {
    29         if (!(kv[1].match(/^TAG:/)) && kv[2].match(/^[0-9]+(\.[0-9]+)?$/)) {
    30           data[kv[1]] = Number(kv[2]);
    31         } else {
    32           data[kv[1]] = kv[2];
    33         }
    34       }
    35 
    36       line = lines.shift();
    37     }
    38 
    39     return data;
    40   }
    41 
    42   var line = lines.shift();
    43   while (typeof line !== 'undefined') {
    44     if (line.match(/^\[stream/i)) {
    45       var stream = parseBlock('stream');
    46       data.streams.push(stream);
    47     } else if (line.match(/^\[chapter/i)) {
    48       var chapter = parseBlock('chapter');
    49       data.chapters.push(chapter);
    50     51      52 53     } else if (line.toLowerCase() === '[format]') {
    54       data.format = parseBlock('format');
    55     }
    56 
    57     line = lines.shift();
    58   }
    59 
    60   return data;
    61 }

      这里代码就是具体解析ffprobe执行命令后输出的字符串的函数,不管有什么结果,都只解析了 stream、chapter、format 三个字段的值。

    修改代码

    • 手动修改

      只需将 while 循环里的代码修改即可。

      修改前:

     1 while (typeof line !== 'undefined') {
     2      if (line.match(/^\[stream/i)) {
     3        var stream = parseBlock('stream');
     4        data.streams.push(stream);
     5      } else if (line.match(/^\[chapter/i)) {
     6        var chapter = parseBlock('chapter');
     7        data.chapters.push(chapter);
     8      
     9       
    10       
    11      } else if (line.toLowerCase() === '[format]') {
    12        data.format = parseBlock('format');
    13      }
    14  
    15      line = lines.shift();
    16    }

      修改后:

     1 while (typeof line !== 'undefined') {
     2 
     3     if (line.match(/^\[stream/i)) {
     4       var stream = parseBlock('stream');
     5       data.streams.push(stream);
     6     } else if (line.match(/^\[chapter/i)) {
     7       var chapter = parseBlock('chapter');
     8       data.chapters.push(chapter);
     9     } else if (line.toLowerCase() === '[format]') {
    10       data.format = parseBlock('format');
    11     } else if (line.match(/^\[[^\/].*?/i)) {
    12 
    13       let name = line.slice(1,-1).toLowerCase()
    14       if(!data[name] || !(data[name] instanceof Array)) data[name] = []
    15       var res = parseBlock(name)
    16       data[name].push(res)
    17     }
    18 
    19     line = lines.shift();
    20   }

      上面是手动的修改 fluent-ffmpeg内的源代码,每次安装 fluent-ffmpeg 都要重新修改非常麻烦。

    •  自动修改

      原理是读取 fluent-ffmpeg/lib/ffprobe.js 文件的代码字符串,将代码字符串转换为 AST,再修改 AST,最后将 AST 转换为代码,再将代码写到 ffprobe.js 文件中。

     1 require('fluent-ffmpeg/lib/ffprobe.js')  // 导入 ffprobe
     2 const esprima = require('esprima')
     3 const escodegen = require('escodegen')
     4 const estraverse = require('estraverse')
     5 const fs = require('fs')
     6 
     7 const sourcePath = module.children[0].id  // 用 module 获取到 ffprobe 的路径(得先导入ffprobe)
     8 
     9 
    10 // 修改后的代码的字符串
    11 const newParseCode = `
    12     while (typeof line !== 'undefined') {
    13  
    14      if (line.match(/^\\[stream/i)) {
    15       var stream = parseBlock('stream');
    16        data.streams.push(stream);
    17     } else if (line.match(/^\\[chapter/i)) {
    18       var chapter = parseBlock('chapter');
    19       data.chapters.push(chapter);
    20      } else if (line.toLowerCase() === '[format]') {
    21        data.format = parseBlock('format');
    22     } else if (line.match(/^\\[[^\\/].*?/i)) {
    23 
    24        let name = line.slice(1,-1).toLowerCase()
    25        if(!data[name] || !(data[name] instanceof Array)) data[name] = []
    26       var res = parseBlock(name)
    27        data[name].push(res)
    28     }
    29 
    30     line = lines.shift();
    31    }
    32 `
    33 
    34 // 读取 ffprobe 的源代码为字符串
    35 const oldParseCode = fs.readFileSync(sourcePath).toString()
    36 
    37 // 将修改后的代码字符串转换为 AST
    38 const newParseAST = esprima.parseScript(newParseCode)
    39 
    40 // 将 ffprobe 源代码字符串转换为 AST
    41 var oldParseAST = esprima.parseScript(oldParseCode)
    42 
    43 // 用 estraverse 找到 parseFfprobeOutput 函数的位置
    44 estraverse.traverse(oldParseAST, {
    45     enter: (node) => {
    46 
    47         if (node.type == 'FunctionDeclaration' && node.id.name == 'parseFfprobeOutput') {
    48 
    49             // 再找到 while 循环的位置
    50             estraverse.replace(node, {
    51                 enter: (node, parent) => {
    52                     if (node.type == 'WhileStatement' && parent.body.length > 4) {
    53 
    54                         // 直接将 while 循环位置的 AST 进行替换为修改后代码字符串的 AST
    55                         return newParseAST
    56                     }
    57                 }
    58 
    59             })
    60 
    61 
    62             return
    63         }
    64     }
    65 })
    66 
    67 // 用 escodegen 将 AST 转换为代码
    68 const code = escodegen.generate(oldParseAST)
    69
    70 // 将代码字符串写到文件中
    72 fs.writeFileSync(sourcePath, code)

      这样只需引入写好的这段代码即可。

    Welcome to my blog!
  • 相关阅读:
    WebGoat之Injection Flaws
    WebGoat之Web Services
    XPath语法
    用Maven在Eclipse中配置Selenium WebDriver——方法1
    用Maven在Eclipse中配置Selenium WebDriver——方法2
    Enum与enum名字相互转换
    数据库数据类型varchar(n)与nvarchar(n)比较
    网页切图工具
    Html标签
    在VS的Solution Explorer中高亮当前编辑的文件
  • 原文地址:https://www.cnblogs.com/blogCblog/p/14165664.html
Copyright © 2020-2023  润新知