需求
用一堆图片来合成一个视频
基础的数据类
class FFmpegData {
///游标
int index;
int x;
int y;
int w;
int h;
///旋转角度
int angle;
///文件路径
String path;
///缩放别名
String scaleAlias;
///旋转别名
String rotationAlias;
///游标别名
String _indexAlias;
///位置别名
String positionAlias;
///别名
String vAlias;
///构造函数
/// index 用来处理ffmpeg的文件参数,不要做修改。
/// x=x轴的坐标;
/// y=y轴的坐标;
/// w=显示的宽度具体像素大小;
/// h=显示的高度具体像素大小;
/// path=再存储器上分别存储的全路径地址,一定要全路径;
FFmpegData(this.index, this.x, this.y, this.w, this.h, this.angle, this.path) {
_indexAlias = '[$index:v]';
vAlias = '[${index}v]';
rotationAlias = '[rotation$index]';
scaleAlias = '[scale$index]';
positionAlias = '[dkg$index]';
}
///旋转缩放的命令行
/// '[1:v]scale=200:200[scale1];'
String rotationAndScaleString() {
//:ow=hypot(iw,ih):oh=ow
return '${_indexAlias}fade=out:st=30:d=1:alpha=1$vAlias;${vAlias}rotate=\'$angle*PI/180:c=none\'$rotationAlias;${rotationAlias}scale=$w:$h$scaleAlias;';
}
///位置的命令行
String positionString(String lastAlias, bool end) {
return '$lastAlias${scaleAlias}overlay=$x:$y${end ? ' ' : '$positionAlias;'}';
}
String fileString() {
if (path.toLowerCase().endsWith('.gif')) {
return '-ignore_loop 0 -i "$path" '; //gif的输入方式的区别
} else {
return '-i $path ';
}
}
}
合成命令
String dat(String background, String music, List<FFmpegData> datas, String outMp4FilePath) {
File mp4 = File(outMp4FilePath);
String dat = '-threads 4 -y -r 30 ';
if (background.toLowerCase().endsWith('.mp4')) {
dat += '-stream_loop -1 -i "$background" ';
} else {
dat += '-loop 1 -f image2 -i "$background" ';
}
if (datas != null && datas.isNotEmpty) {
//先添加文件
for (int i = 0; i < datas.length; i++) {
dat += datas[i].fileString();
}
if (music != null && music.isNotEmpty) {
dat += '-stream_loop -1 -i "$music" ';
if (background.toLowerCase().endsWith('.mp4')) {
int musicIndex = datas.length + 1;
dat += '-c:v copy -map 0:v:0 -map $musicIndex:a:0 ';
}
}
dat += '-filter_complex ';
//先缩放修改大小
for (int i = 0; i < datas.length; i++) {
dat += datas[i].rotationAndScaleString();
}
//设置位置
for (int i = 0; i < datas.length; i++) {
dat += datas[i].positionString(i == 0 ? '[0:v]' : datas[i - 1].positionAlias, i == datas.length - 1);
}
} else {
if (music != null && music.isNotEmpty) {
dat += '-stream_loop -1 -i "$music" ';
if (background.toLowerCase().endsWith('.mp4')) {
int musicIndex = 1;
dat += '-c:v copy -map 0:v:0 -map $musicIndex:a:0 ';
}
}
}
dat += '-vcodec libx264 ';
dat += '-preset ultrafast ';
dat += '-b:v 16m -bufsize 16m -maxrate 16m ';
dat += '-s 1920x1080 ';
dat += '-t 00:00:15 ';
dat += '${mp4.path}';
print('ffmpeg dat=' + dat);
return dat;
}
| 参数 | 解释 |
|---|---|
| -threads | 线程数 |
| -y | 覆盖文件 |
| -r 30 | 帧率30 |
| -loop | 基础图片组循环 |
| -i | 获取文件 |
| -stream_loop -1 -i "$music" | 音乐和视频以流的方式循环 |
| -c:v copy -map 0:v:0 -map $musicIndex:a:0 | 音频文件覆盖视频的音频通道 |
| -ignore_loop 0 -i ${path} | gif无限循环铺满视频 |
| -filter_complex | 开始设置具体参数 |
| [0:v][1:v]overlay=0:0[dkg1] | 第二个资源放在第一个资源的0,0出,并设置别名dkg1 |
| [1:v]scale=100:100[img1];[2:v]scale=1280:720[img2] | 资源1缩放到100_100,设置别名img1,图片2设置成1280_720,设置别名img2 |
| -vcodec libx264 | 指定编码 |
| -preset ultrafast | 合成速度 极快 |
| -b:v 16m -bufsize 16m -maxrate 16m | 码率等参数 |
| -s 1920x1080 | 视频输出分辨率 |
| -t 00:00:15 | 视频总长度00时00分00秒 |
| ${mp4.path} | 输出路径 |