run method

Future<AutoEditReport> run(
  1. EditPlan plan, {
  2. ValueChanged<String>? onStatus,
  3. String captionLanguage = 'en',
  4. bool useSmartAi = true,
})

useSmartAi lets the on-device model pick highlights and write the post (when it is downloaded); otherwise the offline rules do it.

Implementation

Future<AutoEditReport> run(
  EditPlan plan, {
  ValueChanged<String>? onStatus,
  String captionLanguage = 'en',
  bool useSmartAi = true,
}) async {
  final report = AutoEditReport();
  if (!controller.hasProject) {
    throw StateError('Add clips before running an AI edit.');
  }

  // Removals first, so "remove the filters and add vintage" ends vintage.
  _removals(plan, report);

  if (plan.ratio != null && plan.ratio != controller.aspectRatio) {
    controller.setAspectRatio(plan.ratio!);
    report.done.add('Ratio set to ${plan.ratio!.label}');
  }

  if (plan.fillMode != null) {
    controller.setFillModeForAll(plan.fillMode!);
    report.done.add('Every clip set to ${plan.fillMode!.label.toLowerCase()}');
  }
  if (plan.highlightLength != null) {
    await _highlights(plan.highlightLength!, captionLanguage, report, onStatus, useSmartAi);
  }

  if (plan.reframe) {
    onStatus?.call('Finding faces to crop around…');
    await _reframe(report);
  }

  if (plan.removeSilence) {
    onStatus?.call('Finding the talking parts…');
    await _removeSilence(report);
  }

  if (plan.speed != null) {
    final speed = plan.speed!;
    controller.applyClipEdits((c) {
      for (var i = 0; i < c.clips.length; i++) {
        if (c.clips[i].isVideo) c.updateClipSpeed(i, speed);
      }
    });
    report.done.add('Speed set to ${speed}x');
  }

  if (plan.targetLength != null) {
    final removed = controller.trimProjectTo(plan.targetLength!);
    if (removed > Duration.zero) {
      report.done.add('Shortened to ${plan.targetLength!.inSeconds}s');
    } else {
      report.skipped.add('The video is already ${plan.targetLength!.inSeconds}s or shorter');
    }
  }

  if (plan.clipVolume != null) {
    final volume = plan.clipVolume!;
    controller.applyClipEdits((c) {
      for (var i = 0; i < c.clips.length; i++) {
        if (c.clips[i].isVideo && c.clips[i].hasAudio) c.updateClipVolume(i, volume);
      }
    });
    report.done.add(volume == 0 ? 'Original sound muted' : 'Original sound on');
  }
  if (plan.musicPrompt != null && !plan.noMusic && controller.audioTracks.isEmpty) {
    onStatus?.call('Composing music…');
    await _addMusic(plan.musicPrompt!, hasVoice: _hasVoice(), report: report);
  }

  if (plan.musicVolume != null) {
    if (controller.audioTracks.isEmpty) {
      report.skipped.add('There is no music to make louder or quieter');
    } else {
      controller.scaleAudioTrackVolumes(plan.musicVolume!);
      report.done.add(plan.musicVolume! > 1 ? 'Music louder' : 'Music quieter');
    }
  }
  if (plan.beatEvery != null) {
    onStatus?.call('Cutting on the beat…');
    await _beatSync(plan.beatEvery!, report);
  }

  if (plan.effect != null || plan.zoomCuts || plan.transition != null || plan.transitionMix != null) {
    onStatus?.call('Styling clips…');
    _style(plan);
    if (plan.effect != null) report.done.add('${plan.effect!.label} look applied');
    if (plan.zoomCuts) report.done.add('Zoom cuts added');
    if (plan.transition != null && plan.transition != ClipTransition.none) {
      report.done.add('${plan.transition!.label} transitions added');
    }
    if (plan.transition == ClipTransition.none) report.done.add('Transitions removed');
    if (plan.transitionMix != null) report.done.add('A different transition on every cut');
  }

  for (final (effect, span) in plan.timedEffects) {
    _addTimedEffect(effect, span);
    report.done.add('${effect.label} effect added ${span.label}');
  }

  if (plan.audioCleanup != null) {
    final n = controller.setAudioCleanupForAll(plan.audioCleanup!);
    report.done.add(n == 0
        ? 'No clip has sound to clean up'
        : '${plan.audioCleanup!.label} on $n clip${n == 1 ? '' : 's'}');
  }
  if (plan.captions) {
    onStatus?.call('Writing captions…');
    await _captions(plan, captionLanguage, report, onStatus);
  }

  if (plan.hookText != null) {
    _hook(plan.hookText!);
    report.done.add('Title "${plan.hookText}" added');
  }

  if (plan.stickers.isNotEmpty) {
    _addStickers(plan.stickers);
    report.done.add('${plan.stickers.join(' ')} added');
  }

  // A post is only worth writing for a finished short / styled edit.
  if (plan.template != null || plan.highlightLength != null || plan.captions || plan.hookText != null) {
    onStatus?.call('Writing your post caption…');
    report.postText = await agent.writePost(
      title: plan.hookText,
      transcript: _transcriptText(),
      template: plan.template,
      useSmartAi: useSmartAi,
    );
  }
  return report;
}