video_util.dart 5.96 KB
import 'dart:io';
import 'dart:async';

import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
import 'package:ffmpeg_kit_flutter_new/statistics.dart';

class VideoUtil {
  ///
  /// 将视频格式转换为mp4
  /// 转码的同时,进行压缩
  /// [onProgress] 进度回调,值范围 0.0 ~ 1.0
  ///
  static Future<bool> convertToMp4(
    String inputPath,
    String outputPath, {
    void Function(int progress)? onProgress,
  }) async {
    // 先获取视频总时长(微秒)
    final duration = await _getVideoDuration(inputPath);

    String cmd;
    if (Platform.isIOS) {
      cmd = '-i "$inputPath" '
          '-c:v h264_videotoolbox ' // 启用 iOS 硬件加速
          '-b:v 1500k ' // 限制视频码率为 1.5Mbps (体积小,手机看足够)
          '-vf scale=1280:-2 ' // 缩放到 720p (保持比例)
          '-c:a aac ' // 音频转为 AAC (兼容性最好)
          '-b:a 128k ' // 音频码率
          '"$outputPath"';
    } else {
      cmd = '-i "$inputPath" ' // 指定输入文件路径
          '-c:v libx264 ' // 设置视频编码器为libx264(H.264)
          '-crf 28 ' // 设置恒定速率因子CRF为28(中等压缩质量)
          '-c:a aac ' // 设置音频编码器为AAC
          '-b:a 128k ' // 设置音频比特率为128kbps
          '-preset fast '
          '-threads 0 '
          '-strict experimental ' // 允许使用实验性编解码器功能
          '-movflags faststart ' // 优化MP4文件结构,使视频可以快速启动播放
          '-f mp4 ' // 指定输出格式为MP4
          '"$outputPath"'; // 指定输出文件路径
    }

    final completer = Completer<bool>();

    FFmpegKit.executeAsync(
      cmd,
      (session) async {
        final returnCode = await session.getReturnCode();
        completer.complete(ReturnCode.isSuccess(returnCode));
      },
      null,
      (Statistics statistics) {
        if (onProgress != null && duration > 0) {
          final currentTime = statistics.getTime();
          final progress = (currentTime / duration).clamp(0.0, 1.0);
          onProgress((progress * 100).floor());
        }
      },
    );

    return completer.future;
  }

  ///
  /// 通过 ffmpeg 压缩视频
  /// [onProgress] 进度回调,值范围 0.0 ~ 1.0
  ///
  static Future<bool> compressVideo(
    String inputPath,
    String outputPath,
    String quality, {
    void Function(int progress)? onProgress,
  }) async {
    final duration = await _getVideoDuration(inputPath);

    // 使用CRF模式进行压缩,值范围0-51,建议值18-28
    // 高质量: CRF 18-20
    // 中等质量: CRF 23-26
    // 低质量: CRF 28-32
    int crf;
    switch (quality) {
      case 'low':
        crf = 32;
        break;
      case 'middle':
        crf = 26;
        break;
      case 'high':
        crf = 20;
        break;
      default:
        throw Exception('参数错误');
    }
    String cmd = '-i "$inputPath" ' // 输入文件
        '-c:v libx264 ' // 视频编码器
        '-crf $crf ' // 恒定速率因子(质量控制)
        '-c:a aac ' // 音频编码器
        '-b:a 128k ' // 音频比特率
        // '-preset medium ' // 编码预设
        '-preset fast ' // 编码预设,编码速度显著提升,体积损失很小
        '-threads 0 ' // 让 libx264 自动使用所有可用 CPU 核心,默认行为可能只用单核
        '-movflags faststart ' // 优化MP4文件结构
        '"$outputPath"'; // 输出文件

    final completer = Completer<bool>();

    FFmpegKit.executeAsync(
      cmd,
      (session) async {
        final returnCode = await session.getReturnCode();
        completer.complete(ReturnCode.isSuccess(returnCode));
      },
      null,
      (Statistics statistics) {
        if (onProgress != null && duration > 0) {
          final currentTime = statistics.getTime();
          final progress = (currentTime / duration).clamp(0.0, 1.0);
          onProgress((progress * 100).floor());
        }
      },
    );

    return completer.future;
  }

  /// 获取视频总时长,返回毫秒
  static Future<int> _getVideoDuration(String videoPath) async {
    final session = await FFmpegKit.execute(
      '-i "$videoPath"',
    );
    final output = await session.getOutput();
    // 从 ffmpeg 输出中解析时长,格式如: Duration: 00:01:23.45
    final regex = RegExp(r'Duration:\s*(\d+):(\d+):(\d+)\.(\d+)');
    final match = regex.firstMatch(output ?? '');
    if (match != null) {
      final hours = int.parse(match.group(1)!);
      final minutes = int.parse(match.group(2)!);
      final seconds = int.parse(match.group(3)!);
      final ms = int.parse(match.group(4)!);
      return (hours * 3600 + minutes * 60 + seconds) * 1000 + ms * 10;
    }
    return 0;
  }

  /// 为视频文件生成缩略图
  /// [videoPath] 要生成缩略图的视频文件路径
  /// [thumbnailPath] 要生成的缩略图文件路径
  /// 返回缩略图路径
  static Future<String?> genVideoThumbnail(String videoPath, String thumbnailPath) async {
    try {
      // final thumbnailPath = '${dir.path}/video_thumb_${DateTime.now().millisecondsSinceEpoch}.jpg';

      // 使用 ffmpeg_kit_flutter_new 生成视频缩略图
      // 构建FFmpeg命令行参数
      String cmd = '-i "$videoPath" ' // 指定输入文件路径
          '-ss 1 ' // 从视频第1秒处截取画面
          '-vframes 1 ' // 只截取一帧画面
          '-vf scale=128:-1 ' // 设置缩略图宽度为128像素,高度按比例缩放
          '-y ' // 覆盖已存在的输出文件
          '"$thumbnailPath"'; // 指定输出文件路径

      final session = await FFmpegKit.execute(cmd);
      final returnCode = await session.getReturnCode();

      if (ReturnCode.isSuccess(returnCode)) {
        return thumbnailPath;
      } else {
        print('生成视频缩略图失败: ${await session.getFailStackTrace()}');
        return null;
      }
    } catch (e) {
      print('生成视频缩略图出错: $e');
      return null;
    }
  }
}