FFCreator
FFCreator copied to clipboard
Maximum call stack size exceeded
hi, 各位大神,当场景超过一定数量,出现Maximum call stack size exceeded
错误。错误信息如下:
FFCreator start
FFCreator error: {"type":"error","pos":"preProcessing","error":"Maximum call stack size exceeded"}
[FF] Creator production error. FFEvent {
type: 'error',
pos: 'preProcessing',
error: 'Maximum call stack size exceeded'
}
(node:7076) UnhandledPromiseRejectionWarning: Error
at new GLError (D:\Projects\video-ffcreator\node_modules\gl-shader\lib\GLError.js:8:19)
at Shader.proto.update (D:\Projects\video-ffcreator\node_modules\gl-shader\index.js:131:13)
at createShader (D:\Projects\video-ffcreator\node_modules\gl-shader\index.js:255:10)
at exports.default (D:\Projects\video-ffcreator\node_modules\gl-transition\lib\index.js:43:39)
at FFTransition.createTransition (D:\Projects\video-ffcreator\lib\animate\transition.js:62:23)
at FFTransition.bindGL (D:\Projects\video-ffcreator\lib\animate\transition.js:40:10)
at D:\Projects\video-ffcreator\lib\core\renderer.js:119:57
at arrayEach (D:\Projects\video-ffcreator\node_modules\lodash\_arrayEach.js:15:9)
at forEach (D:\Projects\video-ffcreator\node_modules\lodash\forEach.js:38:10)
at Renderer.transBindGL (D:\Projects\video-ffcreator\lib\core\renderer.js:119:5)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:7076) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:7076) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
D:\Projects\video-ffcreator\node_modules\canvas\lib\image.js:91
SetSource.call(img, src);
^
RangeError: Maximum call stack size exceeded
at setSource (D:\Projects\video-ffcreator\node_modules\canvas\lib\image.js:91:13)
at PsImage.set (D:\Projects\video-ffcreator\node_modules\canvas\lib\image.js:62:9)
at Resource._loadImage (D:\Projects\video-ffcreator\node_modules\inkpaint\lib\resource\Resource.js:229:19)
at Resource.load (D:\Projects\video-ffcreator\node_modules\inkpaint\lib\resource\Resource.js:102:14)
at D:\Projects\video-ffcreator\node_modules\inkpaint\lib\resource\Loader.js:287:18
at next (D:\Projects\video-ffcreator\node_modules\inkpaint\lib\resource\async.js:18:9)
at Object.eachSeries (D:\Projects\video-ffcreator\node_modules\inkpaint\lib\resource\async.js:31:5)
at Loader._loadResource (D:\Projects\video-ffcreator\node_modules\inkpaint\lib\resource\Loader.js:278:11)
at ResourceLoader._boundLoadResource (D:\Projects\video-ffcreator\node_modules\inkpaint\lib\resource\Loader.js:43:46)
at Object.process (D:\Projects\video-ffcreator\node_modules\inkpaint\lib\resource\async.js:95:9)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
终端进程“C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -Command yarn run vision-dwnews”已终止,退出代码: 1。
How many scenes
How many scenes
30 scenes, 3-8 pictures per scene
Oh no problems were found, please upload your project or paste the code
Oh no problems were found, please upload your project or paste the code
thank you so mush, here is my code:
const path = require('path');
const colors = require('colors');
const shuffle = require('lodash/shuffle');
const startAndListen = require('./listen');
const fs = require('fs-extra');
const {
FFCreatorCenter,
FFScene,
FFImage,
FFText,
FFCreator,
FFSubtitle,
FFVideo,
} = require('../lib');
const { textColors, backgroundColor, trans } = require('../common/const');
const { StrCut2Arr, getTTSTime } = require('../common/utils');
const newsDir = '2021中国县域旅游竞争力30强,会有你的家乡吗';
const isbgm = true; // 背景音乐
const isSubtitle = true; // 字幕
const isTTS = true; // 语音
const firstSceneTTS = false; // 场景1是否播放语音
const setAudio = false; // 是否播放视频中的语音
const logo = 'MyVlog';
let mp3 = '秋风片片-历史类纪录片《广府春秋》变奏.mp3';
const sceneDurationType = 'auto'; // TTS: 场景持续时间等于语音时长, AV:场景持续时间= Album + FFVideo 时间; auto
const width = 1280;
const height = 720;
const randomInt = (min, max) => Math.floor(Math.random() * (max - min) + min);
const assetsPath = path.join(__dirname, './assets/' + newsDir);
const assetsDirSync = fs.readdirSync(assetsPath).sort((a, b) => {
return a.replace(/scene/g, '') - b.replace(/scene/g, '');
});
console.log('assetsDirSync:', assetsDirSync);
const outputDir = path.join(__dirname, './output/');
const cacheDir = path.join(__dirname, './cache/');
const bgmDir = path.join(__dirname, '../static/bgm/');
const fontDir = path.join(__dirname, '../static/font/');
const { ttsDurations, totalTtsTime } = getTTSTime(assetsDirSync, assetsPath);
const font1 = path.join(fontDir, 'RuiZiYunZiKuShuiZhuTiGBK-1.ttf');
const font2 = path.join(fontDir, '苏新诗古印宋简.ttf');
const fontfamily = '"Microsoft YaHei UI", Helvetica, Arial, sans-serif';
const createFFTask = () => {
console.log('tts Durations', ttsDurations);
console.log('total tts time:', totalTtsTime + "\n\n");
// create creator instance
const creator = new FFCreator({
cacheDir,
outputDir,
width,
height,
fps: 30,
log: true,
norAudio: true,
// highWaterMark: '',
inCenter: true,
debug: false,
threads: 4,
});
const textClip = ({
scene,
text,
x = 48,
y = 39,
fontSize = 42,
font = font1,
color = '#9A8F8C',
bgcolor = '',
effect = ['fadeInLeft', 1, 1],
alignCenter = false,
setStyle = {},
outTime = 0,
outTimeSpeed = 1,
}) => {
const textTitle = new FFText({
text,
x,
y,
fontSize,
});
textTitle.setFont(font);
textTitle.setColor(color);
if (alignCenter) {
textTitle.alignCenter();
}
if (bgcolor) {
textTitle.setBackgroundColor(bgcolor);
}
textTitle.setStyle(setStyle);
textTitle.addEffect(effect[0], effect[1], effect[2]);
if (outTime > 0) {
textTitle.addEffect('fadeOut', outTimeSpeed, outTime);
}
const sceneTextTitle = scene.addChild(textTitle);
return sceneTextTitle;
};
let videoFileName = null;
assetsDirSync.forEach((item, index) => {
/**
* begin:读取json文件相关
*/
const jsonPath = path.join(assetsPath, `/${item}/json`);
fs.ensureDirSync(jsonPath);
const textJsonArray = fs.readdirSync(jsonPath);
let textJson = null;
if (textJsonArray.length > 0) {
textJson = fs.readJSONSync(path.join(jsonPath, `/${textJsonArray[0]}`));
if (!videoFileName) {
videoFileName = textJson.videoTitle;
}
}
// console.log(`scene${index + 1} Json文件: `, textJson.contentSplit);
/**
* end:读取json文件相关
*/
/**
* begin:读取语音文件相关
*/
const audioPath = path.join(assetsPath, `/${item}/audio/tts`);
fs.ensureDirSync(audioPath);
const tts = fs.readdirSync(audioPath);
const ttsPath = path.join(audioPath, `/${tts[0]}`);
console.log(`scene${index + 1} 语音文件: `, ttsPath);
/**
* end:读取语音文件相关
*/
/**
* begin:分离字幕内容
*/
const subtitleInfo = textJson.content.replace(/\(|)|(|)|[|]|{|}|\\n/g, '') || '无';
console.log(`scene${index + 1}字幕内容: `, subtitleInfo);
/**
* end:分离字幕内容
*/
/**
* begin:create FFScene
*/
const scene = new FFScene();
scene.setBgColor(backgroundColor[0]);
/**
* end:create FFScene
*/
/**
* begin:add image album and video to scene
*/
let albumTotalDuration = 0;
let albumSplitDuration = 0;
let videoTotalDuration = 0;
let videoSplitDuration = 0;
textJson.contentSplit.forEach((contentSplit, contentSplitIndex) => {
const albumSplitPath = path.join(assetsPath, `/${item}/image/album/${contentSplitIndex + 1}`);
fs.ensureDirSync(albumSplitPath);
const albumSplitPictures = fs.readdirSync(albumSplitPath);
const timelineCount = contentSplit.timeline.length
contentSplit.timeline.forEach((timelineList, timelineIndex) => {
if (timelineList.type === 'img') {
/**
* begin:add image album
*/
albumSplitPictures.forEach(pic => {
if (pic === timelineList.imgPath) {
const picpath = path.join(albumSplitPath, `/${pic}`);
const image = new FFImage({
path: picpath,
x: width / 2,
y: height / 2,
width: width,
height: height,
});
creator.createEffect('customEffect', {
from: { scale: 1 },
to: { scale: 1.1 },
ease: 'Linear.None',
});
creator.createEffect('customEffect2', {
from: { scale: 1.1 },
to: { scale: 1 },
ease: 'Linear.None',
});
// const albumTransition = ['customEffect', 'fadeIn', 'customEffect2'];
const albumTransition = ['customEffect', 'customEffect2'];
const transition = shuffle(albumTransition)[0];
const transTime = timelineList.duration * 1 || 3; // TODO: 图片与tts内容的显示匹配
const DurationTime = timelineList.duration * 1 || 3; // todo: 图片与tts内容的显示匹配
const delaySplitTime = albumSplitDuration + videoSplitDuration;
image.addEffect('fadeIn', 0, delaySplitTime);
image.addEffect(
transition,
transition.indexOf('customEffect') > -1 ? transTime : 1,
delaySplitTime,
);
if (timelineCount !== timelineIndex + 1) {
// 最后一个图片或视频不用淡出场景
image.addEffect('fadeOut', 0, delaySplitTime + DurationTime);
image.setDuration(DurationTime); // 相册里每张图片显示时长,包括动画时长与完成动画后显示时长
}
scene.addChild(image);
}
});
albumSplitDuration += timelineList.duration * 1 || 3;
console.log('FFImage Split Duration:', albumSplitDuration);
/**
* end:add image album
*/
} else if (timelineList.type === 'video') {
/**
* begin:add video to scene
*/
if (timelineList.videoDateInterval.length) {
const fvideo = new FFVideo({
path: timelineList.videoPath,
width: width,
height: height,
x: width / 2,
y: height / 2,
loop: false,
});
fvideo.setAudio(setAudio); // todo 设置是否播放视频中的音频
fvideo.setDuration(
timelineList.videoDateInterval[0].ss,
timelineList.videoDateInterval[0].to,
);
const thisVideoDuration = Math.floor(
timelineList.videoDateInterval[0].eto - timelineList.videoDateInterval[0].sto,
);
const delaySplitTime = albumSplitDuration + videoSplitDuration;
fvideo.addEffect('fadeIn', 0, delaySplitTime);
if (timelineCount !== timelineIndex + 1) {
// 最后一个图片或视频不用淡出场景
fvideo.addEffect('fadeOut', 0, delaySplitTime + thisVideoDuration);
}
scene.addChild(fvideo);
videoSplitDuration += thisVideoDuration;
}
/**
* end:add video to scene
*/
}
});
});
albumTotalDuration += albumSplitDuration;
videoTotalDuration += videoSplitDuration;
console.log('albumTotalDuration:', albumTotalDuration);
console.log('videoTotalDuration:', videoTotalDuration);
/**
* end:add image album and video to scene
*/
/**
* begin:add logo title
*/
textClip({
scene,
text: logo,
x: 13,
y: 10,
font: font2,
fontSize: 28,
effect: ['fadeIn', 1, 1],
color: '#ffffff',
bgcolor: '#394F8A',
setStyle: {
padding: 3,
},
});
/**
* end:add logo title
*/
/**
* begin 文字效果
*/
const title = textJson.content;
// let textArray = title.split(/[!\?',;\s*$\s*\、\,\。' ]+/);
let textArray = title.split(/[!\?';\、\,\。']+/);
if (title.length > 17 && textArray.length < 2) {
textArray = StrCut2Arr(title);
}
let tempTextArray = textArray;
tempTextArray.forEach((element, index) => {
if (element.length > 24) {
textArray.splice(index, 1);
textArray.splice(index, 0, element.substring(0, 19));
textArray.splice(index + 1, 0, element.substring(19));
}
});
let myTextArray = [];
textArray.forEach(itemText => {
if (itemText) {
myTextArray.push(itemText);
}
});
let textArrayLength = myTextArray.length;
console.log(`scene${index + 1}标题分行:`, myTextArray);
if (textArrayLength > 0 && (!isSubtitle || index < 1)) {
let k = 0;
// base 文字显示Y轴基坐标
let base =
textArrayLength === 1
? height - (index === 0 ? 88 : 68)
: textArrayLength === 2
? height - (index === 0 ? 88 : 68) * 2
: height - (index === 0 ? 88 : 68) * 3;
console.log('Y base:', base);
const coloretext = textColors[randomInt(0, 18)];
if (index === 0) {
textClip({
scene,
text: '|',
x: 23,
y: base + 6,
font: fontfamily,
fontSize: myTextArray.length > 1 ? 120 : 80,
effect: ['fadeInLeft', 0.5, 0.5],
color: coloretext,
bgcolor: coloretext,
setStyle: {
padding: 5,
},
});
}
for (let i = 0; i < textArrayLength; i++) {
let isOutTime = textArrayLength - i > 3 ? 1 : 0; // 倒数3个字幕不用消失
console.log(`Y base${k}:${base + (index === 0 ? 88 : 68) * k}`);
textClip({
scene,
text: myTextArray[i],
// x: index === 0 ? width / 2 : 43, // 居中
x: index === 0 ? 80 : 43,
// Y轴在中间位置
y: base + (index === 0 ? 88 : 68) * k,
effect: [
index === 0 ? 'fadeInRight' : 'fadeIn',
index === 0 ? 0.5 : 1,
index === 0 ? 1 + i * 0.5 : 1 + i * 2,
],
color: textColors[randomInt(0, 18)],
setStyle: {
stroke: '#ffffff',
strokeThickness: index === 0 ? 5 : 3,
},
// alignCenter: index === 0 ? true : false,
alignCenter: false,
font: font2,
fontSize: index === 0 ? 68 : 50,
outTime: (5 + i * 2 + 1) * isOutTime,
});
if (k < 2) {
k += 1;
} else {
k = 0;
}
if (index > 0 && isSubtitle) {
break;
}
}
}
/**
* end 文字效果
*/
/**
* begin:add TTS to scene
*/
if (isTTS && tts.length > 0 && (index > 0 || firstSceneTTS)) {
scene.addAudio({
path: ttsPath,
start: 1.5,
volume: 3,
});
}
/**
* end:add TTS to scene
*/
/**
* begin:subtitle 字幕
*/
if (isSubtitle && subtitleInfo && index !== 0) {
const subtitle = new FFSubtitle({
comma: true, // 是否逗号分割
color: '#ffffff',
fontSize: 38,
x: width / 2,
y: height / 2 + 297,
});
subtitle.setText(subtitleInfo);
if (tts.length > 0) {
subtitle.setSpeech(path.join(audioPath, `/${tts[0]}`)); // 语音配音-tts
}
subtitle.setBackgroundColor('');
subtitle.setStyle({
stroke: '#333333',
strokeThickness: 3,
})
subtitle.setFont(font2);
subtitle.frameBuffer = 26; // todo: 可配置 ,默认 24
subtitle.setStartTime(2.0);
scene.addChild(subtitle);
}
/**
* end:subtitle 字幕
*/
const albumVideoAlbumDuration = albumTotalDuration + videoTotalDuration;
const ttsDuration = ttsDurations[index];
console.log('scene album duration : ', Math.ceil(albumVideoAlbumDuration));
console.log('scene tts duration: ', Math.ceil(ttsDuration));
let sceneDuration =
albumVideoAlbumDuration > ttsDuration ? albumVideoAlbumDuration : ttsDuration;
if (sceneDurationType === 'TTS') {
sceneDuration = ttsDuration;
} else if (sceneDurationType === 'AV') {
sceneDuration = albumVideoAlbumDuration;
}
if (index === 0 && textJson) {
mp3 = textJson.backgroundMusic.url ? textJson.backgroundMusic.url.split("/bgm/")[1] : mp3
}
scene.setDuration(Math.ceil(sceneDuration) + 0.5);
scene.setTransition(shuffle(trans)[0], 1);
creator.addChild(scene);
if (isbgm && index === 0) {
creator.addAudio({
path: path.join(bgmDir, mp3),
volume: isTTS ? textJson.backgroundMusic.volume || 0.3 : 1,
});
}
});
creator.setOutput(videoFileName);
creator.start();
creator.closeLog();
creator.on('start', () => {
console.log(`FFCreator start`);
});
creator.on('error', e => {
console.log(`FFCreator error: ${JSON.stringify(e)}`);
});
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;
};
module.exports = () =>
startAndListen(() => setTimeout(() => FFCreatorCenter.addTask(createFFTask), 1000));
每个场景的json文件类似这样:
{
"source": "https://www.dwnews.com/视觉/60267633/2021中国县域旅游竞争力最强30县市浙江竟占8席图集",
"videoTitle": "2021中国县域旅游竞争力30强,会有你的家乡吗",
"content": "第二名是四川省县级市峨眉山市。峨眉山市因山得名,景区面积154平方公里,最高峰万佛顶海拔3099米,佛教圣地华藏寺所在地金顶海拔3077米、成为了峨眉山旅游的最高点。",
"pics": [
"https://media.dwnews.net/dw/g3K3tuSMOYyn_n7hkpQc5i1KmAA%3D/640*1080/media/images/dw/20180913/15368162055b99f44d957d2.jpg"
],
"keywords": ["中国", "峨眉山", "地质博物馆", "县级市"],
"logo": null,
"isSubtitle": true,
"firstSceneTTS": false,
"setAudio": false,
"isbgm": true,
"isTTS": true,
"backgroundMusic": {
"name": "秋风片片-历史类纪录片《广府春秋》变奏.mp3",
"url": "//127.0.0.1:8088/bgm/秋风片片-历史类纪录片《广府春秋》变奏.mp3",
"volume": 0.3
},
"tts": {
"voice": "zh-CN-YunxiNeural",
"voicestyle": "cheerful",
"roleplay": "Default",
"rate": "0%",
"pitch": "0%"
},
"contentSplit": [
{
"text": "第二名是四川省县级市峨眉山市",
"keywords": ["县级市", "峨眉山市", "四川省"],
"entities": [],
"keywordsEn": ["County-level city", "Emeishan city", "Sichuan province"],
"album": [],
"cover": [],
"videos": [],
"timeline": [
{
"type": "img",
"match": [{ "type": "googlePictureSearch", "value": "" }],
"imgPath": "6865303121158119424.jpg"
},
{
"type": "img",
"match": [{ "type": "googlePictureSearch", "value": " 峨眉山" }],
"imgPath": "2.6865463776351789056.jpg"
},
{
"type": "img",
"match": [{ "type": "googlePictureSearch", "value": " 峨眉山" }],
"imgPath": "4.6865463791572918272.jpg"
},
{
"type": "img",
"match": [{ "type": "googlePictureSearch", "value": " 峨眉山" }],
"imgPath": "7.6865463823147638784.jpg"
},
{
"type": "img",
"match": [{ "type": "googlePictureSearch", "value": " 峨眉山市" }],
"imgPath": "1.6865464720867106816.jpg"
},
{
"type": "img",
"match": [{ "type": "googlePictureSearch", "value": " 峨眉山市" }],
"imgPath": "6.6865464781546102784.jpg"
},
{
"type": "img",
"match": [{ "type": "googlePictureSearch", "value": " 峨眉山市" }],
"imgPath": "7.6865464793550200832.jpg"
}
]
},
{
"text": "峨眉山市因山得名,景区面积154平方公里,最高峰万佛顶海拔3099米,佛教圣地华藏寺所在地金顶海拔3077米、成为了峨眉山旅游的最高点。",
"keywords": ["山", "王国", "植物"],
"entities": [],
"keywordsEn": ["Mountain", "Kingdom", "Plant"],
"album": [],
"cover": [],
"videos": [],
"timeline": []
}
],
"id": 3,
"pid": null
}
我也遇到了,当照片超过一定数量,就会出现Maximum call stack size exceeded错误,跟new FFImage()数量有关系
加1,我也遇到了