compress method

  1. @override
Future<VideoCompressResult> compress(
  1. String id,
  2. String path,
  3. VideoCompressConfig config,
  4. String? outputDir,
  5. String? outputName,
)
override

Compress path using config. id identifies the job (for progress correlation and cancellation). When outputPath is non-null the encoded file is written there; otherwise it lands in the plugin cache.

Implementation

@override
Future<VideoCompressResult> compress(
  String id,
  String path,
  VideoCompressConfig config,
  String? outputDir,
  String? outputName,
) async {
  if (_preCancelledId == id) {
    _preCancelledId = null;
    throw VideoCompressCancelledException();
  }
  try {
    await FfmpegBinaries.ensureAvailable();
    final info = await FfmpegVideo.probe(path);
    final originalSize = await File(path).length();
    final durationMs = _clampDuration(info.durationMs, config);
    final dims = SizeMath.targetDimensions(info.width, info.height, config);
    final videoBps = SizeMath.videoBitrateBps(
      config: config,
      durationMs: durationMs,
      sourceBitrateKbps: info.bitrateKbps,
      targetHeight: dims.$2,
    );
    final outFile = PluginCache.resolveOutput(
      outputDir: outputDir,
      outputName: outputName,
      sourcePath: path,
      ext: 'mp4',
    );

    final result = await FfmpegVideo.compress(
      id: id,
      inputPath: path,
      outputPath: outFile.path,
      config: config,
      info: info,
      targetWidth: dims.$1,
      targetHeight: dims.$2,
      videoBitrateBps: videoBps,
      durationMs: durationMs,
      onProgress: (progress) {
        if (!_progressController.isClosed) {
          _progressController.add(
            CompressionProgress(id: id, progress: progress),
          );
        }
      },
      onProcess: (proc) {
        _activeProcess = proc;
        _activeId = id;
      },
      isCancelled: () => _preCancelledId == id,
    );

    _activeProcess = null;
    _activeId = null;

    if (result.cancelled) {
      throw VideoCompressCancelledException();
    }

    final usedCodec = result.usedCodec;
    final compressedSize = await File(outFile.path).length();
    final minSave = config.minSavingsPercent;
    final savedPct = originalSize == 0
        ? 0.0
        : (1 - compressedSize / originalSize) * 100;
    final shouldSkip =
        config.keepOriginalIfLarger &&
        (compressedSize >= originalSize || savedPct < minSave);
    if (shouldSkip) {
      try {
        await File(outFile.path).delete();
      } catch (_) {}
      return VideoCompressResult(
        id: id,
        outputPath: path,
        originalSizeBytes: originalSize,
        compressedSizeBytes: originalSize,
        width: info.width,
        height: info.height,
        durationMs: info.durationMs,
        codec: info.codec ?? usedCodec,
        skipped: true,
        frameRate: info.frameRate,
        hasAudio: !config.removeAudio,
      );
    }

    return VideoCompressResult(
      id: id,
      outputPath: outFile.path,
      originalSizeBytes: originalSize,
      compressedSizeBytes: compressedSize,
      width: dims.$1,
      height: dims.$2,
      durationMs: durationMs,
      codec: usedCodec,
      skipped: false,
      frameRate: config.frameRate ?? info.frameRate,
      hasAudio: !config.removeAudio,
    );
  } on CompressException {
    rethrow;
  } catch (e) {
    throw VideoCompressException(CompressErrorCode.compressFailed, '$e');
  } finally {
    _activeProcess = null;
    if (_activeId == id) _activeId = null;
  }
}