拼接后,并非视频的所有部分都能正常播放

时间:2018-09-23 10:26:48

标签: javascript node.js ffmpeg fluent-ffmpeg

Node.JS 8.11.4,fluent-ffmpeg 2.1.2

我需要在一个视频文件中连接不同长度的相同长度的随机部分。进行的连接没有错误。但是,当我播放最终的串联文件时,我看到某些部分的声音播放良好,另一些部分的视频却“冻结”了,但声音却在播放。

出什么问题了?我希望所有部分在最终的串联文件中都能正常播放。

串联配置:

trex@cave:/media/trex/safe1/Development/app$ head concat_config.txt
file /media/trex/safe1/Development/app/videos/test/417912400.mp4
inpoint 145
outpoint 155
file /media/trex/safe1/Development/app/videos/test/440386842.mp4
inpoint 59
outpoint 69
file /media/trex/safe1/Development/app/videos/test/417912400.mp4
inpoint 144
outpoint 154
    ...

我总共有2个视频的16个部分。一部分持续时间为10秒。将来,视频文件和视频片段的数量将会更多。

trex@cave:/media/trex/safe1/Development/app$ ls -lh videos/test/
total 344M
-rw-r--r-- 1 trex trex  90M set 23 12:19 417912400.mp4
-rw-r--r-- 1 trex trex 254M set 23 12:19 440386842.mp4

配置的JavaScript代码:

const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
const ffprobePath = require('@ffprobe-installer/ffprobe').path;
const ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(ffmpegPath);
ffmpeg.setFfprobePath(ffprobePath);


function getMetadata(absPathToFile) {
  return new Promise(function (resolve, reject) {
    ffmpeg.ffprobe(absPathToFile, function(err, metadata) {
      if (err) {
        reject('get video meta: ' + err.toString());
      }
      resolve(metadata);
    });
  });
}

async function getFormat(files) {
  const pArray = files.map(async f => {
    const meta = await getMetadata(f);
    meta.format.short_filename = meta.format.filename.split('/').pop();
    return meta.format;
  });
  return await Promise.all(pArray);
}

function getSliceValues(duration, max = 10) {
  max = duration < max ? duration * 0.5 : max; // sec
  const start = _.random(0, duration * 0.9);
  const end = start + max > duration ? duration : start + max;
  return `inpoint ${Math.floor(start)}\noutpoint ${Math.floor(end)}\n`;
}

function addPath(arr, aPath) {
  return arr.map(e => path.join(aPath, e));
}

function createConfig(meta) {
  return meta.map(video => `file ${video.filename}\n${getSliceValues(video.duration)}`).join('');
}

function duplicateMeta(meta) {
  for (let i = 0; i < 3; i++) {
    meta.push(...meta);
  }
  return _.shuffle(meta);
}

const videoFolder = path.join(__dirname, 'videos/test');
const finalVideo = 'final_video.mp4';
const configFile = 'concat_config.txt'; 

// main
(async () => {
  let videos = addPath(fs.readdirSync(videoFolder), videoFolder);

  let meta = await getFormat(videos);
  meta = duplicateMeta(meta); // get multiple portions of videos

  fs.writeFileSync(configFile, createConfig(meta));

  const mpeg = ffmpeg();
  mpeg.input(configFile)
    .inputOptions(['-f concat', '-safe 0'])
    .outputOptions('-c copy')
    .save(finalVideo);
})();

视频文件格式:

{ streams: 
   [ { index: 0,
       codec_name: 'h264',
       codec_long_name: 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10',
       profile: 'High',
       codec_type: 'video',
       codec_time_base: '1001/60000',
       codec_tag_string: 'avc1',
       codec_tag: '0x31637661',
       width: 1920,
       height: 1080,
       coded_width: 1920,
       coded_height: 1088,
       has_b_frames: 2,
       sample_aspect_ratio: '1:1',
       display_aspect_ratio: '16:9',
       pix_fmt: 'yuv420p',
       level: 40,
       color_range: 'tv',
       color_space: 'bt709',
       color_transfer: 'bt709',
       color_primaries: 'bt709',
       chroma_location: 'left',
       field_order: 'unknown',
       timecode: 'N/A',
       refs: 1,
       is_avc: 'true',
       nal_length_size: 4,
       id: 'N/A',
       r_frame_rate: '30000/1001',
       avg_frame_rate: '30000/1001',
       time_base: '1/30000',
       start_pts: 0,
       start_time: 0,
       duration_ts: 4936900,
       duration: 164.563333,
       bit_rate: 4323409,
       max_bit_rate: 'N/A',
       bits_per_raw_sample: 8,
       nb_frames: 4932,
       nb_read_frames: 'N/A',
       nb_read_packets: 'N/A',
       tags: [Object],
       disposition: [Object] },
     { index: 1,
       codec_name: 'aac',
       codec_long_name: 'AAC (Advanced Audio Coding)',
       profile: 'LC',
       codec_type: 'audio',
       codec_time_base: '1/48000',
       codec_tag_string: 'mp4a',
       codec_tag: '0x6134706d',
       sample_fmt: 'fltp',
       sample_rate: 48000,
       channels: 2,
       channel_layout: 'stereo',
       bits_per_sample: 0,
       id: 'N/A',
       r_frame_rate: '0/0',
       avg_frame_rate: '0/0',
       time_base: '1/48000',
       start_pts: 0,
       start_time: 0,
       duration_ts: 7899120,
       duration: 164.565,
       bit_rate: 256000,
       max_bit_rate: 263232,
       bits_per_raw_sample: 'N/A',
       nb_frames: 7714,
       nb_read_frames: 'N/A',
       nb_read_packets: 'N/A',
       tags: [Object],
       disposition: [Object] } ],
  format: 
   { filename: '/media/trex/safe1/Development/app/videos/test/417912400.mp4',
     nb_streams: 2,
     nb_programs: 0,
     format_name: 'mov,mp4,m4a,3gp,3g2,mj2',
     format_long_name: 'QuickTime / MOV',
     start_time: 0,
     duration: 164.565,
     size: 94298844,
     bit_rate: 4584150,
     probe_score: 100,
     tags: 
      { major_brand: 'mp42',
        minor_version: '0',
        compatible_brands: 'mp42mp41isomavc1',
        creation_time: '2015-09-21T19:11:21.000000Z' } },
  chapters: [] }
{ streams: 
   [ { index: 0,
       codec_name: 'h264',
       codec_long_name: 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10',
       profile: 'High',
       codec_type: 'video',
       codec_time_base: '1001/48000',
       codec_tag_string: 'avc1',
       codec_tag: '0x31637661',
       width: 2560,
       height: 1440,
       coded_width: 2560,
       coded_height: 1440,
       has_b_frames: 2,
       sample_aspect_ratio: '1:1',
       display_aspect_ratio: '16:9',
       pix_fmt: 'yuv420p',
       level: 51,
       color_range: 'tv',
       color_space: 'bt709',
       color_transfer: 'bt709',
       color_primaries: 'bt709',
       chroma_location: 'left',
       field_order: 'unknown',
       timecode: 'N/A',
       refs: 1,
       is_avc: 'true',
       nal_length_size: 4,
       id: 'N/A',
       r_frame_rate: '24000/1001',
       avg_frame_rate: '24000/1001',
       time_base: '1/24000',
       start_pts: 0,
       start_time: 0,
       duration_ts: 4206200,
       duration: 175.258333,
       bit_rate: 11891834,
       max_bit_rate: 'N/A',
       bits_per_raw_sample: 8,
       nb_frames: 4202,
       nb_read_frames: 'N/A',
       nb_read_packets: 'N/A',
       tags: [Object],
       disposition: [Object] },
     { index: 1,
       codec_name: 'aac',
       codec_long_name: 'AAC (Advanced Audio Coding)',
       profile: 'LC',
       codec_type: 'audio',
       codec_time_base: '1/48000',
       codec_tag_string: 'mp4a',
       codec_tag: '0x6134706d',
       sample_fmt: 'fltp',
       sample_rate: 48000,
       channels: 2,
       channel_layout: 'stereo',
       bits_per_sample: 0,
       id: 'N/A',
       r_frame_rate: '0/0',
       avg_frame_rate: '0/0',
       time_base: '1/48000',
       start_pts: 0,
       start_time: 0,
       duration_ts: 8414160,
       duration: 175.295,
       bit_rate: 256000,
       max_bit_rate: 262152,
       bits_per_raw_sample: 'N/A',
       nb_frames: 8217,
       nb_read_frames: 'N/A',
       nb_read_packets: 'N/A',
       tags: [Object],
       disposition: [Object] } ],
  format: 
   { filename: '/media/trex/safe1/Development/app/videos/test/440386842.mp4',
     nb_streams: 2,
     nb_programs: 0,
     format_name: 'mov,mp4,m4a,3gp,3g2,mj2',
     format_long_name: 'QuickTime / MOV',
     start_time: 0,
     duration: 175.295,
     size: 266214940,
     bit_rate: 12149345,
     probe_score: 100,
     tags: 
      { major_brand: 'mp42',
        minor_version: '0',
        compatible_brands: 'mp42mp41isomavc1',
        creation_time: '2015-11-15T19:30:49.000000Z' } },
  chapters: [] }

0 个答案:

没有答案