video_util.dart
5.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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;
}
}
}