buildSegmentJoin static method

RenderPlan buildSegmentJoin({
  1. required String concatListPath,
  2. required Duration duration,
  3. required List<VideoEditorAudioTrack> audioTracks,
  4. required List<VideoEffectOverlay> effectOverlays,
  5. required String outputPath,
  6. required List<String> videoCodecArgs,
  7. int fps = 30,
  8. String audioBitrate = '96k',
})

Joins cached segments listed in concatListPath (FFmpeg concat demuxer format) into the preview MP4. The video is copied as is unless timed effects need it re-encoded; the audio is re-timed, mixed with the audio tracks and encoded to AAC.

Implementation

static RenderPlan buildSegmentJoin({
  required String concatListPath,
  required Duration duration,
  required List<VideoEditorAudioTrack> audioTracks,
  required List<VideoEffectOverlay> effectOverlays,
  required String outputPath,
  required List<String> videoCodecArgs,
  int fps = 30,
  String audioBitrate = '96k',
}) {
  final inputs = <String>['-f', 'concat', '-safe', '0', '-i', concatListPath];
  final filters = <String>[
    // Segments are whole frames long, so audio and video already line up;
    // any tiny gap left is filled at once (not slowly corrected), so the
    // voice can never drift from the lips across many cuts.
    '[0:a]aresample=async=1000:min_hard_comp=0.01:first_pts=0,$_audioFormat[acat]',
  ];
  final hasEffects = effectOverlays
      .any((o) => RenderFilterBuilder.effectFilterChain(o.effect).isNotEmpty);
  if (hasEffects) {
    final last = _addEffects(filters, '0:v', effectOverlays);
    filters.add('[$last]format=yuv420p[vout]');
  }
  _addAudioMix(
    inputs: inputs,
    filters: filters,
    audioTracks: audioTracks,
    projectMs: duration.inMilliseconds,
    firstInput: 1,
    base: 'acat',
  );
  return RenderPlan(
    arguments: [
      '-y',
      ...inputs,
      '-filter_complex', filters.join(';'),
      '-map', hasEffects ? '[vout]' : '0:v',
      '-map', '[aout]',
      if (hasEffects) ...[
        '-r', '$fps',
        ...videoCodecArgs,
        if (!videoCodecArgs.contains('-pix_fmt')) ...['-pix_fmt', 'yuv420p'],
      ] else ...['-c:v', 'copy'],
      '-c:a', 'aac',
      '-b:a', audioBitrate,
      '-ar', '44100',
      '-movflags', '+faststart',
      '-t', _sec(duration.inMilliseconds / 1000),
      outputPath,
    ],
    duration: duration,
    canvas: Size.zero,
  );
}