原来用的是直接在手机端用Canvas每秒输出25张图片 然后把图片合成视频 但是由于受手机端的性能和兼容问题换成了在服务器上用无头浏览器模拟手机端 可以实现稳定截取 添加队列和多线程实现并发保障
Node 端 需要安装依赖 bull任务队列 cluster集群多线程 redis数据服务 request访问页面操作
const url = require('url'); //导入url
const http = require('http'); //导入http
const request = require('request');//访问页面
const fs= require("fs");
const path = require("path");// 创建文件夹
const Bull = require('bull');//队列
const cluster = require('cluster');//集群操作
var numWorkers = 8;//最多开启8个线程处理
const port = 19999; //端口号
var redis = require('redis'),
RDS_PORT = '9918', //端口号
RDS_HOST = '127.0.0.1', //服务器IP
RDS_PWD = 'GhYNv+1j7ZJl0oSvGy57igA', //密码
RDS_OPTS = {auth_pass: RDS_PWD};
// 1. 创建队列 ImageToVideo图片转视频
const ImageToVideo = new Bull('ImageToVideo', {redis: {port: RDS_PORT, host: RDS_HOST, password: RDS_PWD}});
if(cluster.isMaster){
for (var i = 0; i < numWorkers; i++) {
cluster.fork();
}
cluster.on('online', function(worker) {
});
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
}else{
//开启redis
client = redis.createClient(RDS_PORT, RDS_HOST, RDS_OPTS);
client.on('ready',function(res){
// console.log('ready');
});
client.on('end',function(err){
console.log('end');
});
client.on('error', function (err) {
console.log(err);
});
client.on('connect',function(){
// console.log('redis connect success!');
});
const server = http.createServer((req, res) => { //创建一个server
res.statusCode = 200;
res.setHeader('Content-type','application/json;charset = utf-8');
var parseObj = url.parse(req.url, true);
var HandleType=parseObj.query.HandleType;
if(HandleType=="ImageToVideo"){
var RedisKey=parseObj.query.RedisKey;
client.get(RedisKey,function (err, data) {
res.write(JSON.stringify({error:data}));
res.end()
})
}else{
var member_id=parseInt(parseObj.query.member_id);
var scene_id=parseInt(parseObj.query.scene_id);
var VideoTime=parseObj.query.VideoTime;
var audiourl=parseObj.query.audiourl;
console.log("开始截图了时间:"+getFormatDate()+"数据:"+member_id+"="+scene_id);
var jieurl='http://browsershot2.kuleiman.com/VideoDivision.php?member_id='+member_id+'&scene_id='+scene_id+'&VideoTime='+VideoTime+"&audiourl="+audiourl;
if(member_id&scene_id){
fs.exists("RecordingScreen/"+member_id+"/"+scene_id+"/video.mp4", function (exists) {
//文件存在的话直接操作 不存在的话 判断当前时间是否有人在生成 没有人创建的话直接生成
if(exists){
res.write(JSON.stringify({type:1}));
res.end()
}else{
fs.exists("RecordingScreen/"+member_id+"/"+scene_id+"/0001.jpg", function (exists) {
if(!exists){
var RedisKey=member_id+"_"+new Date().getTime();
client.set(RedisKey, 0);
ImageToVideo.add({member_id:member_id,scene_id:scene_id,RedisKey:RedisKey});
res.write(JSON.stringify({type:3,RedisKey: RedisKey}));
res.end()
}else{
res.write(JSON.stringify({type:2}));
res.end()
}
})
}
})
}else{
return false;
}
}
});
server.setTimeout(300*1000);
server.on('timeout', function(){
console.log('执行超时啦')
})
server.listen(port, () => { //监听server
console.log(`服务器运行在http://${port}/`);
});
//绑定队列执行事件 cluster.worker.id 线程id job.id 队列id job.data .add()传过来的参数
ImageToVideo.process(function(job, jobDone){
var member_id=job.data.member_id,scene_id=job.data.scene_id,RedisKey=job.data.RedisKey;
var recurl='http://browsershot2.kuleiman.com/RecordingScreenVideo.php?member_id='+member_id+'&scene_id='+scene_id;
mkdirsSync("RecordingScreen/"+member_id+"/"+scene_id);
var startime=getFormatDate();
videoimg(member_id,scene_id,function(){
// 开始生成视频文件
request(recurl, function (error, response, data) {
client.set(RedisKey, data);
jobDone();
});
})
});
function getFormatDate(){
var nowDate = new Date();
var year = nowDate.getFullYear();
var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1) : nowDate.getMonth() + 1;
var date = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate.getDate();
var hour = nowDate.getHours()< 10 ? "0" + nowDate.getHours() : nowDate.getHours();
var minute = nowDate.getMinutes()< 10 ? "0" + nowDate.getMinutes() : nowDate.getMinutes();
var second = nowDate.getSeconds()< 10 ? "0" + nowDate.getSeconds() : nowDate.getSeconds();
return year + "-" + month + "-" + date+" "+hour+":"+minute+":"+second;
}
// 递归创建目录 同步方法
function mkdirsSync(dirname) {
if (fs.existsSync(dirname)) {
return true;
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname,0777);
fs.chmod(dirname,0777,function () {
})
return true;
}
}
}
//截图方法
const puppeteer = require('puppeteer');
var page;
const videoimg=(async (member_id,scene_id,callback) => {
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
page = await browser.newPage();
await page.setViewport({
width: 360,
height: 640
});
//要生成视频的页面
await page.goto('http://klm0307.kuleiman.com/Public/panonn/plugins/ShortVideoShare/ShortVideoShare.html?scene_id='+scene_id+'&title=&tileserver=//imj.kuleiman.com/'+member_id);
console.log('http://klm0307.kuleiman.com/Public/panonn/plugins/ShortVideoShare/ShortVideoShare.html?scene_id='+scene_id+'&title=&tileserver=//imj.kuleiman.com/'+member_id)
await page.waitFor(300);
var ii=1;
var videotime=await setInterval(function (args) {
var picname=ii;
if(ii<10){
var picname="000"+ii;
}else{
if(ii<100){
var picname="00"+ii;
}else{
if(ii<1000){
var picname="0"+ii;
}
}
}
imgdom(member_id,scene_id,picname);
ii++;
},40);
await page.waitFor(60000);
clearInterval(videotime);
console.log("线程:"+cluster.worker.id+' 保存图片,共'+ii+'张')
callback();
})
var imgdom=(async (member_id,scene_id,picname)=>{
const base64Img = await page.evaluate(async () => {
return aaal();
})
var base64Data = base64Img.replace(/^data:image\/\w+;base64,/, "");
var decodeImg = Buffer.from(base64Data, 'base64');
fs.writeFile('./RecordingScreen/'+member_id+'/'+scene_id+'/'+picname+'.jpg', decodeImg, function(err) {
if(err) {console.log(err)}
});
})
}
PHP页面 生成视频页面
2,'msg'=>'目录错误'));}
$path=$_SERVER["DOCUMENT_ROOT"]."/RecordingScreen/".$Filename;
$num=txtnum($path);
$imgurl="./RecordingScreen/".$Filename."/%04d.jpg";
$videourl="./RecordingScreen/".$Filename."/video.mp4";
// $videourl="/home/www/klm0307/Public/RecordingScreen/".$Filename."/00000.mp4";
//$jiestop=ceil($num/25);
//if($jiestop<60){
// $jiestop="0:00:".$jiestop;
//}else{
// $jiestop="00:00:59";
//}
$jiestop="00:00:59";
//$eevvaal="ffmpeg -f image2 -r 25 -i $imgurl -ss 00:00:00 -t ".$jiestop." -i http://klm0307.kuleiman.com/Public/RecordingScreen/shwj.mp3 -vcodec mpeg4 $videourl 2>&1";
$eevvaal='ffmpeg -r 25 -i '.$imgurl.' -ss 00:00:00 -t '.$jiestop.' -c:v libx264 -vf "fps=25,format=yuv420p" '.$videourl.' 2>&1';
// echo $eevvaal;die();
$re=exec($eevvaal,$array,$error);
// print_r($array);
deldir($path);
if($error==0){
echo 1;
}else{
echo 2;
}
die();
/*读取目录文件数量*/
function txtnum($dir){
$handle=opendir($dir);
$i=0;
while(false!==($file=readdir($handle))){
if($file!='.' && $file!='..'){
$i++;
}
}
closedir($handle);
return $i;
}
/*删除目录下面所有的图片*/
function deldir($dir) {
//先删除目录下的文件:
$dh = opendir($dir);
while ($file = readdir($dh)) {
if($file != "." && $file!="..") {
$fullpath = $dir."/".$file;
if(!is_dir($fullpath)&&strstr($file,"jpg")) {
unlink($fullpath);
} else {
deldir($fullpath);
}
}
}
closedir($dh);
}
PHP截取并添加背景音乐页面 需安装七牛SDK
'http://audio.kuleiman.com/member_107369/music/B31z55uZwc8084FVG0j402.mp3',
2=>'http://audio.kuleiman.com/member_107369/music/1wi55b8N0m8yIeV4r4M013.mp3',
3=>'http://audio.kuleiman.com/member_107369/music/C12Xx55LM5M8084D4xQP01.mp3',
4=>'http://audio.kuleiman.com/member_107369/music/p15goIK58084hn4019gx92.mp3',
);
$member_id=intval($_GET['member_id']);
$scene_id=intval($_GET['scene_id']);
$VideoTime=intval($_GET['VideoTime']);
$VideoType=intval($_GET['VideoType']);
$audiourl=intval($_GET['audiourl']);
$audio=$audioarray[$audiourl];
$Filename=$member_id."/".$scene_id;
$videoname=time().".mp4";
$path=$_SERVER["DOCUMENT_ROOT"]."/RecordingScreen/".$Filename."/video.mp4";
$copypath=$_SERVER["DOCUMENT_ROOT"]."/RecordingScreen/".$Filename."/".$videoname;
/*判断主视频是否生成*/
if(!is_file($path)){
echo json_encode(array('error'=>1));die();
}
if($VideoTime<60){
$VideoTime="00:00:".$VideoTime;
}else{
$VideoTime="00:01:00";
}
//
//$ffemgsql='ffmpeg -ss 00:00:00 -t '.$VideoTime.' -i '.$path.' -vcodec copy -acodec copy '.$copypath;
//$re=exec($ffemgsql,$array,$error);
//添加音频
if($audio!=""){
$audiosql=' -ss 00:00:00 -t '.$VideoTime.' -i '.$audio;
$audiosql2=' -acodec aac -strict -2 ';
}
$ffemgsql='ffmpeg -ss 00:00:00 -t '.$VideoTime.' -i '.$path.$audiosql.' -vcodec copy '.$audiosql2.$copypath;
$re=exec($ffemgsql,$array,$error);
//echo $ffemgsql;die();
// 需要填写你的 Access Key 和 Secret Key
$accessKey = '65bK-g7RdpksKTbK6z8vld66BavIo4fCe7B9Yh3o';
$secretKey = '2sBlJBpNNOrfjV7WDHq751blIVO_KZpsy6kl6nxq';
$bucket = '16cskuleimanvideo';
// 构建鉴权对象
$auth = new Auth($accessKey, $secretKey);
// 生成上传 Token
$token = $auth->uploadToken($bucket);
// 要上传文件的本地路径
$filePath = $copypath;
// 上传到七牛后保存的文件名
$key = "RecordingScreen/".$Filename."/".$videoname;
// 初始化 UploadManager 对象并进行文件的上传。
$uploadMgr = new UploadManager();
// 调用 UploadManager 的 putFile 方法进行文件的上传。
list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath);
if ($err !== null) {
} else {
// 上传完成以后删除文件夹
// RemoveDirFiles($path);
}
unlink($copypath);
echo json_encode(array('error'=>0,'url'=>$videoname));die();