最近项目要求,一键生成房子的推荐视频,选几张图,加上联系人的方式就是一个简单的视频,因为有web端、小程序端,为了多端口用,决定放在服务器端生成。
目前用的是react中的nextjs来开发项目。
nextjs中怎样用ffcreator上一章有讲到过,这里不再详细说了,考虑多端口用,并发和处理视频合成等一系列对服务器压力过大的情况,这时候队列就有必要了。
一通了解后FFCreatorCenter能实现队列。官方有koa实现队列的例子。看过后怎样在nextjs中实现?
找准思路:
1.制作预设视频动画模板,意思是合成视频的模板提前预设好的,比如静态内容都提前准备好
2.用户选择想要的视频模板,然后根据视频模板添加动态内容,排队生成视频
3.查询视频生成情况
按照上述流程,我们开干:
1。制作预设视频模板,做一个模板比如相册视频,放到模板详情接口中调用AddTPL(id)
import { FFAlbum, FFScene, FFImage, FFCreator, FFRect, FFText, FFCreatorCenter } from 'ffcreator'
const path = require('path');
import colors from 'colors'
export default function AddTPL({ id }) {
FFCreatorCenter.createTemplate(id, async ({ }) => {
//用官方图片例子
const ROOT_PATH = process.cwd();
const bg1 = path.join(ROOT_PATH, '/assets/imgs/bg/05.jpg');
console.log(bg1)
const bg2 = path.join(ROOT_PATH, '/assets/imgs/bg/04.jpeg');
const logo2 = path.join(ROOT_PATH, '/assets/imgs/logo/logo2.png');
const cloud = path.join(ROOT_PATH, '/assets/imgs/cloud.png');
const mars = path.join(ROOT_PATH, '/assets/imgs/mars.png');
const rock = path.join(ROOT_PATH, '/assets/imgs/rock.png');
const title = path.join(ROOT_PATH, '/assets/imgs/title.png');
const audio = path.join(ROOT_PATH, '/assets/audio/05.wav');
const outputDir = path.join(ROOT_PATH, '/video/');
const cacheDir = path.join(ROOT_PATH, '/cache/');
FFCreator.setFFmpegPath('D:/nextAppV2/H5/ffmpeg-6.0-full_build/bin/ffmpeg.exe');
// create creator instance
const width = 576;
const height = 1024;
const creator = new FFCreator({
cacheDir,
outputDir,
width,
height,
//log: true,
highWaterMark: '3mb',
parallel: 8,
fps: 30,
audio,
});
// create FFScene
const scene1 = new FFScene();
const scene2 = new FFScene();
scene1.setBgColor('#0b0be6');
scene2.setBgColor('#b33771');
// add scene1 background
const fbg1 = new FFImage({ path: bg1, x: width / 2, y: height / 2 });
scene1.addChild(fbg1);
// add bottom cloud
const fcloud = new FFImage({ path: cloud, x: width });
fcloud.setAnchor(1);
fcloud.addAnimate({
from: { y: height + 180 },
to: { y: height },
time: 0.8,
ease: 'Back.Out',
});
scene1.addChild(fcloud);
// add mars ball
const fmars = new FFImage({ path: mars, x: width / 2, y: height / 2 });
fmars.addEffect(['rollIn', 'zoomIn'], 1.8, 0.8);
scene1.addChild(fmars);
// add rock image
const frock = new FFImage({ path: rock, x: width / 2 + 100 });
frock.addAnimate({
from: { y: height / 2 + 720 },
to: { y: height / 2 + 80 },
time: 1,
delay: 2.3,
ease: 'Cubic.InOut',
});
scene1.addChild(frock);
// add rock image
const ftitle = new FFImage({ path: title, x: width / 2, y: height / 2 - 300 });
ftitle.addEffect('fadeInUp', 1, 4);
scene1.addChild(ftitle);
// add logo
const flogo1 = new FFImage({ path: logo2, x: width / 2, y: 50 });
flogo1.setScale(0.5);
scene1.addChild(flogo1);
scene1.setDuration(8);
scene1.setTransition('InvertedPageCurl', 1.5);
creator.addChild(scene1);
// add scene2 background
const fbg2 = new FFImage({ path: bg2, x: width / 2, y: height / 2 });
scene2.addChild(fbg2);
// add logo
const flogo2 = new FFImage({ path: logo2, x: width / 2, y: height / 2 - 80 });
flogo2.setScale(0.9);
flogo2.addEffect('fadeInDown', 1, 1.2);
// 9s remove
flogo2.remove(9);
scene2.addChild(flogo2);
scene2.setDuration(5);
creator.addChild(scene2);
creator.start();
//creator.openLog();
creator.on('start', () => {
console.log(`FFCreator start`);
});
creator.on('error', e => {
console.log(`FFCreator error: ${e.error}`);
});
creator.on('progress', e => {
console.log(colors.yellow(`FFCreator progress: ${(e.percent * 100) >> 0}%`));
});
creator.on('complete', e => {
console.log(
colors.magenta(`FFCreator completed: \n USEAGE: ${e.useage} \n PATH: ${e.output} `),
);
console.log(colors.green(`\n --- You can press the s key or the w key to restart! --- \n`));
});
return creator;
})
}
2.添加队列,
//添加队列
export function AddTask({ Templateid }) {
const Taskid = FFCreatorCenter.addTaskByTemplate(Templateid, { });
res.statusCode = 200
res.json({
code: 0,
data: {
taskid: Taskid
}
})
}
3.查询成功接口,并下载mp4
//下载
import { FFAlbum, FFScene, FFImage, FFCreator, FFRect, FFText, FFCreatorCenter } from 'ffcreator'
export default async (req, res) => {
const taskid = req.query.taskid
var fs = require('fs');
FFCreatorCenter.onTaskError(taskid, function (err) {
console.log("onTaskError", err)
})
FFCreatorCenter.onTaskComplete(taskid, function (e) {
console.log("onTaskComplete", e)
let { file, taskObj } = e
let output = file ? file.replace(/\/\//g, "/") : ""
fs.readFile(output, function (isErr, data) {
if (isErr) {
} else {
let file_name = new Date().getTime()
let Disposition = "inline;";
if (scene == "pwa") {
Disposition = "attchment;";
}
if (scene == "mini") {
Disposition = "inline;";
}
res.setHeader('Content-Type', 'video/mp4; charset=utf-8');
res.setHeader('Content-Disposition', `${Disposition}filename=${encodeURIComponent(file_name)}.mp4`);
res.status(200)
res.end(data)
}
})
})
}
另附上视频转gif的方法:
let cp = require('child_process');
let flie_gif = `${cacheDir}${taskid}.gif`
const bb = `ffmpeg -ss 00:00:00 -to 00:00:${videoConfig.duration} -i ${output} -r 10 -vf scale=1050:-1 ${flie_gif}`
cp.exec(bb,
function (error, stdout, stderr) {
if (error) {
console.log('执行的结果error:', error)
} else {
fs.readFile(flie_gif, function (isErr, data) {
if (isErr) {
console.log('执行的结果error:', isErr)
} else {
let file_name = new Date().getTime()
let Disposition = "inline;";
if (scene == "pwa") {
Disposition = "attchment;";
}
if (scene == "mini") {
Disposition = "inline;";
}
res.setHeader('Content-Type', 'image/gif; charset=utf-8');
res.setHeader('Content-Disposition', `${Disposition}filename=${encodeURIComponent(file_name)}.gif`);
res.status(200)
res.end(data)
}
})
}
// console.log('执行的结果stdout:', stdout)
// console.log('执行的结果stderr:', stderr)
})