-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
luohao
committed
Jan 15, 2021
1 parent
bcda1ee
commit 0a220e9
Showing
6 changed files
with
125 additions
and
287 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,5 @@ dist | |
|
||
# TernJS port file | ||
.tern-port | ||
|
||
src/doc/video/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,276 +1,3 @@ | ||
const { PPT2video } = require('./src/ppt2pdf.js'); | ||
const path = require('path'); | ||
const fs = require('fs'); | ||
const { spawnAsync, execAsync } = require('./src/lib.js'); | ||
const { PPT2video} = require('./src/ppt2pdf.js'); | ||
|
||
// const ppt2video = new PPT2video({ | ||
// pptPath: path.resolve('./src/doc/test.pptx') | ||
// }); | ||
// const a = fs.statSync(path.resolve('./src/doc/audio/tip.mp3')); | ||
// console.log(a); | ||
const audioPath = path.resolve('./src/doc/audio/tip1.mp3'); | ||
// getDuration(); | ||
// ffprobe -v quiet -print_format json -show_format E:\work\bnu\code\ppt2video\src\doc\audio\tip1.mp3 | ||
// ffprobe -v quiet -print_format json -show_format=duration E:\work\bnu\code\ppt2video\src\doc\audio\tip1.mp3 | ||
// ffprobe E:\work\bnu\code\ppt2video\src\doc\audio\tip1.mp3 | ||
function getTransitionType(type) { | ||
// 变换效果 | ||
const transitions = [ | ||
"fade" , | ||
"wipeleft" , | ||
"wiperight" , | ||
"wipeup" , | ||
"wipedown" , | ||
"slideleft" , | ||
"slideright" , | ||
"slideup" , | ||
"slidedown" , | ||
"circlecrop" , | ||
"rectcrop" , | ||
"distance" , | ||
"fadeblack" , | ||
"fadewhite" , | ||
"radial" , | ||
"smoothleft" , | ||
"smoothright" , | ||
"smoothup" , | ||
"smoothdown" , | ||
"circleopen" , | ||
"circleclose" , | ||
"vertopen" , | ||
"vertclose" , | ||
"horzopen" , | ||
"horzclose" , | ||
"dissolve" , | ||
"pixelize" , | ||
"diagtl" , | ||
"diagtr" , | ||
"diagbl" , | ||
"diagbr" , | ||
]; | ||
if (type && transitions.includes(type)) return type; | ||
const random = Math.floor(Math.random() * 31); | ||
return transitions[random]; | ||
} | ||
function getSlideVideos() { | ||
return new Promise(async (resolve, reject) => { | ||
try { | ||
const imgFolder = path.resolve('./src/doc/img'); | ||
const audioFolder = path.resolve('./src/doc/audio'); | ||
const imgList = fs.readdirSync(imgFolder); | ||
const audioList = fs.readdirSync(audioFolder); | ||
for (let i = 0; i < imgList.length; i++) { | ||
console.log(`转换第${i}个`); | ||
const imgPath = path.join(imgFolder, imgList[i]); | ||
/**audio TODO//////////////////////////////////////// */ | ||
const audioPath = path.join(audioFolder, audioList[i]); | ||
// const audioPath = audioList[i]; | ||
/**audio TODO//////////////////////////////////////// */ | ||
await img2video(imgPath, audioPath); | ||
} | ||
resolve(); | ||
} catch(err) { | ||
console.log('errrrrrr', err); | ||
reject(err); | ||
} | ||
}); | ||
} | ||
function img2video(imgPath, audioPath) { | ||
return new Promise(async (resolve, reject) => { | ||
// ffmpeg -hide_banner -loglevel quiet -loop 1 -i 1.PNG -i tip1.mp3 -c:v libx264 -c:a copy -t 5 -pix_fmt yuv420p -y 1.mp4 | ||
try { | ||
// tip:拼接命令时加空格,放在前一条的末尾处 | ||
let cmd = `ffmpeg -hide_banner -loop 1 -i ${imgPath} `; | ||
let duration = 0; | ||
// 有对应音频时,添加该音频,视频时长为音频时长 | ||
if (existPath(audioPath)) { | ||
duration = await getDuration(audioPath); | ||
cmd += `-i ${audioPath} -c:a copy `; | ||
} else { | ||
duration = 5; | ||
cmd += '-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 '; | ||
} | ||
cmd += `-c:v libx264 -s 1920x1080 -pix_fmt yuv420p -t ${duration} -y `; | ||
const videoName = path.basename(imgPath, path.extname(imgPath)) + '.mp4'; | ||
cmd += path.join(process.cwd(), videoName); | ||
// 执行ffmpeg命令 | ||
await spawnAsync(cmd); | ||
resolve(); | ||
} catch(err) { | ||
return reject(err); | ||
} | ||
}); | ||
} | ||
function existPath(path) { | ||
try { | ||
return fs.existsSync(path); | ||
} catch(err) { | ||
return false; | ||
} | ||
} | ||
function getDuration(audioPath) { | ||
return new Promise((resolve, reject) => { | ||
// spawnAsync('ffmpeg', ['-hide_banner', '-i', audioPath]).then(data => { | ||
// console.log(data.match(/^Duration: ([\d|:|\.]+),$/)); | ||
// }).catch(err => { | ||
// console.log(err.match(/Duration: ([\d|:|\.]+),/)); | ||
// }); | ||
const cmd = `ffprobe -v quiet -print_format json -show_format ${audioPath}`; | ||
spawnAsync(cmd).then(data => { | ||
const time = JSON.parse(data).format.duration; | ||
resolve(parseFloat(time)); | ||
}).catch(err => { | ||
reject(err); | ||
}); | ||
}); | ||
} | ||
|
||
// 过滤videoFolder中非video文件 | ||
// -filter_complex_script 对应表达式 | ||
function createFilterScript(videoFolder, animate=false, animateDuration=1) { | ||
return new Promise((resolve, reject) => { | ||
let scriptText = ''; | ||
try { | ||
const videoList = fs.readdirSync(videoFolder).filter(v => path.extname(v) === '.mp4'); | ||
if (!animate) { | ||
// 没有转场动画,直接拼接 | ||
scriptText += videoList.map((v, i) => `[${i}:v][${i}:a]`).join(''); | ||
scriptText += `concat=n=${videoList.length}:v=1:a=1[video][audio]`; | ||
return resolve(scriptText); | ||
} | ||
// 创建副本,准备用来制作转场动画的片段 | ||
videoList.forEach((v, i) => { | ||
// 视频流创建2个副本,副本1用来最后的拼接,副本2用来制作转场动画 | ||
scriptText += `[${i}:v]split[v${i}][v${i}copy];`; | ||
// 取副本2的其中animateDuration(即设置的转场动画时间)秒,这里从开头取, | ||
// 如果取其他时间段,需要使用setpts=PTS-STARTPTS矫正时间戳 | ||
scriptText += `[v${i}copy]trim=0:${animateDuration}[v${i}1];`; | ||
if (i > 0 && i < videoList.length - 1) { | ||
// 非首尾的视频,创建animateDuration秒的两个副本,分别和前后制作转场动画 | ||
scriptText += `[v${i}1]split[v${i}10][v${i}11];`; | ||
} | ||
}); | ||
// 制作转场动画 | ||
videoList.forEach((v, i) => { | ||
if (i < videoList.length - 1) { | ||
const v1 = `[v${i}1${i > 0 ? 1 : ''}]`;//[v01],[v111],[v211] | ||
const v2 = `[v${i + 1}1${i + 1 === videoList.length - 1 ? '' : 0}]`;//[v110],[v210],...[v81] | ||
scriptText += `${v1}${v2}xfade=transition=${getTransitionType()}:duration=${animateDuration}:offset=0[vt${i}];`; | ||
} | ||
}); | ||
// 合并video和转场视频 | ||
// scriptText += `[v0][vt0][v1][vt1]...[v7][vt7][v8]concat=n=17[video];`; | ||
scriptText += videoList.map((v, i) => { | ||
if (i < videoList.length - 1) { | ||
return `[v${i}][vt${i}]`; | ||
} else { | ||
return `[v${i}]concat=n=${2 * videoList.length - 1}[video];`; | ||
} | ||
}).join(''); | ||
// 合并audio,给转场动画添加空白音频 | ||
// 最后一个输入流是静音文件(即[videoList.length : a]),可供操作 | ||
// 取animateDuration秒静音,并创建videoList.length-1个副本 | ||
scriptText += `[${videoList.length}:a]atrim=0:1[asilent];`; | ||
// [asilent]asplit=8[asilent0][asilent1]...[asilent8]; | ||
scriptText += `[asilent]asplit=${videoList.length - 1}`; | ||
scriptText += videoList.slice(0, -1).map((v, i) => `[asilent${i}]`).join(''); | ||
scriptText += ';'; | ||
// 合并音频并插入静音音频,转场动画需要对应音频流,否则正常音频会提前播放,导致音视频不同步 | ||
scriptText += videoList.map((v, i) => { | ||
if (i < videoList.length - 1) { | ||
return `[${i}:a][asilent${i}]`; | ||
} else { | ||
return `[${i}:a]concat=n=${2 * videoList.length - 1}:v=0:a=1[audio]`; | ||
} | ||
}).join(''); | ||
resolve(scriptText); | ||
} catch(err) { | ||
reject(err); | ||
} | ||
}); | ||
} | ||
|
||
function concatVideos(videoFolder, resultFolder) { | ||
console.log('===================================================',videoFolder) | ||
return new Promise(async (resolve, reject) => { | ||
// -hide_banner | ||
let cmd = 'ffmpeg '; | ||
const videoList = fs.readdirSync(videoFolder).filter(v => path.extname(v) === '.mp4'); | ||
console.log(videoList) | ||
// 输入文件 | ||
const inputFiles = videoList.map(v => `-i ${path.join(videoFolder, v)}`).join(' '); | ||
cmd += `${inputFiles} `; | ||
// 静音音频 | ||
cmd += '-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 '; | ||
/*******-filter_complex_script "file.txt */ | ||
const scriptText = await createFilterScript(videoFolder, true); | ||
const scriptPath = path.resolve('./src/doc/filter.txt'); | ||
console.log(scriptPath) | ||
// scriptText += videoList.map((v, i) => { | ||
// let char = 0; | ||
// if ( i === videoList.length - 1) { | ||
// char = ''; | ||
// } | ||
// return `[${i}:a]atrim=0:${char}[a${i}];[${i}:v]split[v${i}00][v${i}10];`; | ||
// }).join(''); | ||
fs.writeFileSync(scriptPath, scriptText); | ||
cmd += `-safe 0 -filter_complex_script ${scriptPath} -vsync 0 `; | ||
/*******-filter_complex_script "file.txt */ | ||
// settings -map [audio] -movflags +faststart | ||
cmd += '-map [video] -map [audio] -movflags +faststart -y '; | ||
// cmd += '-profile:v high -level 3.1 -preset:v veryfast -keyint_min 72 -g 72 -sc_threshold 0 '; | ||
// cmd += '-b:v 3000k -minrate 3000k -maxrate 6000k -bufsize 6000k '; | ||
// cmd += '-b:a 128k -avoid_negative_ts make_zero -fflags +genpts -y '; | ||
cmd += path.join(resultFolder, 'out.mp4'); | ||
spawnAsync(cmd).then(data => { | ||
resolve(data); | ||
}).catch(err => { | ||
reject(err); | ||
}); | ||
// fs.writeFileSync(path.resolve('./doc/cmd.txt'), cmd); | ||
}); | ||
} | ||
const videoFolder = path.resolve('./src/doc/video'); | ||
const resultFolder = path.resolve('./src/doc'); | ||
// getSlideVideos().then(data => { | ||
// console.log(data) | ||
// }).catch(err => { | ||
// console.log(err) | ||
// }); | ||
// concatVideos(process.cwd(), resultFolder).then(data => { | ||
// console.log(data); | ||
// }).catch(err => { | ||
// console.log(err); | ||
// }) | ||
// getDuration(path.resolve(videoFolder, '../out.mp4')).then(data => { | ||
// console.log(data); | ||
// }).catch(err => { | ||
// console.log(err); | ||
// }); | ||
// const imgPath = path.resolve('./src/doc/img/1.PNG'); | ||
// const audioPathss = path.resolve('./src/doc/audio/tip2.mp3'); | ||
// img2video(imgPath, 'ddddd').then(data => { | ||
// console.log(data); | ||
// }).catch(err => { | ||
// console.log(err); | ||
// }); | ||
|
||
const ppt2video = new PPT2video({ | ||
pptPath: path.resolve('./src/doc/test.pptx'), | ||
animate: { | ||
use: true, | ||
// type: 'vertclose', | ||
type: 'inturn', | ||
duration: 1 | ||
}, | ||
tmpdir: path.resolve('./temp'), | ||
resultFolder: path.resolve('./result'), | ||
audioFolder: path.resolve('./src/doc/audio'), | ||
slideDuration: 3 | ||
}); | ||
console.log(1) | ||
ppt2video.convert().then(data => { | ||
console.log(data); | ||
}).catch(err => { | ||
console.log(err); | ||
}); | ||
module.exports = PPT2video; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
const { PPT2video } = require('../ppt2pdf.js'); | ||
const path = require('path'); | ||
const ppt2video = new PPT2video({ | ||
pptPath: path.resolve('../doc/test.pptx'), | ||
animate: { | ||
use: true, | ||
// type: 'vertclose', | ||
type: 'inturn', | ||
duration: 1 | ||
}, | ||
tmpdir: path.resolve('./temp'), | ||
resultFolder: path.resolve('./result'), | ||
audioFolder: path.resolve('../doc/audio'), | ||
slideDuration: 3 | ||
}); | ||
|
||
const imgFolder = path.resolve(__dirname, '../doc/img'); | ||
const audioFolder = path.resolve(__dirname, '../doc/audio'); | ||
const videoFolder = path.resolve(__dirname, '../doc/video'); | ||
// ppt2video.getSlideVideos(imgFolder, audioFolder, videoFolder).then(data => { | ||
// console.log(data) | ||
// }).catch(err => { | ||
// console.log(err); | ||
// }); | ||
|
||
ppt2video.concatVideos(videoFolder, path.dirname(videoFolder), { | ||
resultName: 'hhhhhhhresult.mp4' | ||
}, (a, b, c) =>{ | ||
console.log(a, b, c); | ||
}).then(data => { | ||
console.log(data) | ||
}).catch(err => { | ||
console.log(err); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[0:v]split[v0][v0copy];[v0copy]trim=0:1[v01];[1:v]split[v1][v1copy];[v1copy]trim=0:1[v11];[v11]split[v110][v111];[2:v]split[v2][v2copy];[v2copy]trim=0:1[v21];[v21]split[v210][v211];[3:v]split[v3][v3copy];[v3copy]trim=0:1[v31];[v31]split[v310][v311];[4:v]split[v4][v4copy];[v4copy]trim=0:1[v41];[v41]split[v410][v411];[5:v]split[v5][v5copy];[v5copy]trim=0:1[v51];[v51]split[v510][v511];[6:v]split[v6][v6copy];[v6copy]trim=0:1[v61];[v61]split[v610][v611];[7:v]split[v7][v7copy];[v7copy]trim=0:1[v71];[v71]split[v710][v711];[8:v]split[v8][v8copy];[v8copy]trim=0:1[v81];[v01][v110]xfade=transition=fade:duration=1:offset=0[vt0];[v111][v210]xfade=transition=wipeleft:duration=1:offset=0[vt1];[v211][v310]xfade=transition=wiperight:duration=1:offset=0[vt2];[v311][v410]xfade=transition=wipeup:duration=1:offset=0[vt3];[v411][v510]xfade=transition=wipedown:duration=1:offset=0[vt4];[v511][v610]xfade=transition=slideleft:duration=1:offset=0[vt5];[v611][v710]xfade=transition=slideright:duration=1:offset=0[vt6];[v711][v81]xfade=transition=slideup:duration=1:offset=0[vt7];[v0][vt0][v1][vt1][v2][vt2][v3][vt3][v4][vt4][v5][vt5][v6][vt6][v7][vt7][v8]concat=n=17[video];[9:a]atrim=0:1[asilent];[asilent]asplit=8[asilent0][asilent1][asilent2][asilent3][asilent4][asilent5][asilent6][asilent7];[0:a][asilent0][1:a][asilent1][2:a][asilent2][3:a][asilent3][4:a][asilent4][5:a][asilent5][6:a][asilent6][7:a][asilent7][8:a]concat=n=17:v=0:a=1[audio] |
Oops, something went wrong.