FFCreator icon indicating copy to clipboard operation
FFCreator copied to clipboard

Maximum call stack size exceeded

Open Shijiuwei opened this issue 3 years ago • 6 comments

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。

Shijiuwei avatar Nov 14 '21 04:11 Shijiuwei

How many scenes

drawcall avatar Nov 15 '21 03:11 drawcall

How many scenes

30 scenes, 3-8 pictures per scene

Shijiuwei avatar Nov 15 '21 03:11 Shijiuwei

Oh no problems were found, please upload your project or paste the code

drawcall avatar Nov 15 '21 07:11 drawcall

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
}

Shijiuwei avatar Nov 18 '21 15:11 Shijiuwei

我也遇到了,当照片超过一定数量,就会出现Maximum call stack size exceeded错误,跟new FFImage()数量有关系

z411500976 avatar Feb 23 '22 09:02 z411500976

加1,我也遇到了

huruji avatar Jun 06 '23 17:06 huruji