続:ラズパイで動画作成。node.jsで楽する

前回の記事はこちら。

シェルコマンドばっかなんですけど、できればnode.jsでまとめたいなぁ、という要望が出てきたのでまとめてみたいと思います。

この記事って、誰かの役に立つことあるんだろうか。
でも、記事にしないと勉強のモチベーションが保てないし、忘れやすいんですよね。

ということで、これをみている奇特なかた、お付き合いください。

Nodeからffmpegを使ってみる。


この辺から使っていけばいいと思うのですが、最初に仕組み等もろもろを整頓しておこうと思います。

事前に用意しておくもの

  • スライド画像数枚
  • それぞれに対応したテキスト

意外と用意しておくものは少なくてすみますね。

  1. テキストから音声ファイルを作成する(複数)
  2. スライド画像を指定秒数の動画にする(複数)
  3. 動画と音声をくっつける(複数)
  4. 全部の動画をくっつける

このような流れになります。
テキストと音声ファイルをどう整理して関連付けていくかが問題ですね。

ファイル名を決める

とりあえず結合ファイル名と、結合前のリストのルールなど決めます。

読み上げテキストファイル名
voice_01.txt , voice_02.txt・・・
音声ファイル名
voice_01.wav , voice_02.wav・・・
画像名
frame_01.png , frame_02.png・・・・
動画名(音無し)
frame_01.mp4 , frame_01.mp4・・・
動画名(音有り)
video_01.mp4 , video_01.mp4・・・

mylist.txt

file video_01.mp4
file video_02.mp4
file video_03.mp4
file video_04.mp4

最終的なファイル名は
output.mp4
にしたいと思います。

こうなりました

voice.js

var OpenJTalk = require('openjtalk');
var fs = require('fs');
var mei = new OpenJTalk();
var list = [
    "voice_01",
    "voice_02",
    "voice_03"
]
var i = 0;

setInterval(loop,10000);

function loop(){
    if(i < list.length){
    var text = fs.readFileSync("./" + list[i] + ".txt","utf-8");
    mei.talk(text,200);
    //process.exit(1);
    readdir("./",list[i]);
    i++
    }else{
        process.exit(1);
    }
  }

function readdir(dir,file) {
    setTimeout(function(){
    fs.readdir(dir, function (err, files) {
      if (err) {
          throw err;
      }
      for(let i = 0; i < files.length; i++) {
        var result = files[i].match( /.wav$/ );
        if(result != null ){
            console.log(result.input);
            fs.copyFile(result.input, './tmp/' + file + '.wav', (err) => {
                if (err) {
                    console.log(err.stack);
                }
                else {
                    console.log('Done.');
                }
            });
        }
      }
    });
},700);
  }

makevideo.js

const exec = require('child_process').exec;
const fs = require('fs');
var f_voice_txt = [
    'voice_01.txt',
    'voice_02.txt',
    'voice_03.txt'
];
var time = "00:00:10";
var f_voice_wav = [
    './tmp/voice_01.wav',
    './tmp/voice_02.wav',
    './tmp/voice_03.wav'
];
var f_png = [
    'frame_01.png',
    'frame_02.png',
    'frame_03.png'
];
var f_movie = [
    './tmp/frame_01.mp4',
    './tmp/frame_02.mp4',
    './tmp/frame_03.mp4'
];
var f_movie_v = [
    'video_01.mp4',
    'video_02.mp4',
    'video_03.mp4'
];
var output = "output.mp4";

require('date-utils');
var dt = new Date();
var dtformatted = dt.toFormat("YYYYMMDDHH24MISS");

//画像を動画にする
for (let i = 0; i < f_png.length; i++) {
    exec('ffmpeg -f image2 -r 1 -loop 1 -t ' + time + ' -i ' + f_png[i] + ' ' + f_movie[i], (err, stdout, stderr) => {
    if (err) { console.log(err); }
    console.log(stdout);
    });
};

//動画と音をくっつける
for (let i = 0; i < f_movie.length; i++) {
    exec('ffmpeg -i ' + f_movie[i] + ' -i ' + f_voice_wav[i] + '  -c:a aac -map 0:v:0 -map 1:a:0 ' + f_movie_v[i], (err, stdout, stderr) => {
    if (err) { console.log(err); }
    console.log(stdout);
    });
};

//動画の結合
for (let i = 0; i < f_movie.length; i++) {
    f_movie_v[i] = "file " + f_movie_v[i];
};
var f_movie_v_j = f_movie_v.join("\n");
fs.writeFileSync("./tmp/movie_list.txt", f_movie_v_j);
console.log(f_movie_v_j);

//動画の結合
fs.mkdir(dtformatted, function (err) {});
exec('ffmpeg -f concat -i ./tmp/movie_list.txt -c:v copy ' + "./" + dtformatted + "/" + output, (err, stdout, stderr) => {
//process.exit(1);
if (err) { console.log(err); }
console.log(stdout);
});

音声は10秒ごとに発音、発音してる間にwavファイルをコピーするやり方にしました。
あんまり美しくないけど、とりあえず動くからいいや。
動画作成の方はシェルコマンドをファイル分やるように加工。
ニッチすぎるけど公開しておきます。

videoを作る方は、一発でちゃんと動いてるかよくわからない。
もしかしたらちゃんとタイミングを設定してあげないとダメかも。