tobitti0
Blog
About

UbuntuのEPGStation_v2でCMカットエンコードする。

2021-01-23

はじめに

EPGStation v2が先日公開された。
EPGSstationのおかげでTVをいつでもどこからでも視聴できる上、気が向いたときに録画予約を入れる事ができ大変便利である。
私はEPGSstation v2が公開された日にアップグレードした。
すると、エンコード進捗を表示できる機能が新たに実装されており、感動した。
そこで私の移植したLinuxで動くJoinLogoScpTrialSetLinuxを用いてエンコード進捗を表示できるようにした。下記のように表示される。

そこで、環境構築情報をここに記しておく。
(というか自分用のメモだが。)
もし、Linux環境にてEPGStation v2でCMカットしたいという人がいれば参考にしていただければ幸いである。
ただし、私の利用しているのはDockerを用いた環境であるため、直接導入している人は異なると思われるので、参考に頑張ってもらいたい。

EPGStationのDockerにJoinLogoScpTrialSetLinuxの導入

どうでもいい前置きが長くなったが、導入手順を記載していく。
とりあえず、docker-mirakurun-epgstationを動作できるようにしておく。

動作できたことが確認できたら、一度downしておく。
docker-mirakurun-epgstation/epgstation/Dockerfileを次の内容に置き換える。

FROM ghcr.io/tobitti0/docker-avisynthplus:4.3.1-ubuntu2004 as build
ENV DEBIAN_FRONTEND=noninteractive
ENV NODE_VERSION=14
ENV EPGSTATION_VERSION=v2.0.8
RUN set -xe && \
apt-get update && \
apt-get install --no-install-recommends -y \
curl git make gcc g++ cmake libboost-all-dev
# join_logo_scp_trial build
RUN cd /tmp/ && \
git clone --recursive https://github.com/tobitti0/JoinLogoScpTrialSetLinux.git && \
cd /tmp/JoinLogoScpTrialSetLinux && \
git submodule foreach git pull origin master && \
cd /tmp/JoinLogoScpTrialSetLinux/modules/chapter_exe/src && \
make && \
mv chapter_exe /tmp/JoinLogoScpTrialSetLinux/modules/join_logo_scp_trial/bin/ && \
cd /tmp/JoinLogoScpTrialSetLinux/modules/logoframe/src && \
make && \
mv logoframe /tmp/JoinLogoScpTrialSetLinux/modules/join_logo_scp_trial/bin/ && \
cd /tmp/JoinLogoScpTrialSetLinux/modules/join_logo_scp/src && \
make && \
mv join_logo_scp /tmp/JoinLogoScpTrialSetLinux/modules/join_logo_scp_trial/bin/ && \
cd /tmp/JoinLogoScpTrialSetLinux/modules/tsdivider/ && \
mkdir build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Release .. && \
make && \
mv tsdivider /tmp/JoinLogoScpTrialSetLinux/modules/join_logo_scp_trial/bin/ && \
mv /tmp/JoinLogoScpTrialSetLinux/modules/join_logo_scp_trial /join_logo_scp_trial && \
cd /join_logo_scp_trial
# delogo
RUN set -xe && \
git clone https://github.com/tobitti0/delogo-AviSynthPlus-Linux && \
cd delogo-AviSynthPlus-Linux/src && \
make && \
cp libdelogo.so /join_logo_scp_trial
# node setup tool
RUN set -xe && \
curl -O -sL https://deb.nodesource.com/setup_${NODE_VERSION}.x && \
mv setup_${NODE_VERSION}.x /join_logo_scp_trial/setup_node.x
# EPGStation clone
RUN set -xe && \
cd /tmp && \
git clone https://github.com/l3tnun/EPGStation.git -b ${EPGSTATION_VERSION}
FROM ghcr.io/tobitti0/docker-avisynthplus:4.3.1-ubuntu2004 as release
ENV DEBIAN_FRONTEND=noninteractive
MAINTAINER Tobitti <mail@tobitti.net>
COPY --from=build /join_logo_scp_trial /join_logo_scp_trial
COPY --from=build /tmp/EPGStation /app
WORKDIR /join_logo_scp_trial
RUN bash setup_node.x && \
apt-get update && \
apt-get install --no-install-recommends -y nodejs libboost-filesystem-dev libboost-program-options-dev libboost-system-dev && \
node -v && \
npm --version &&\
mv libdelogo.so /usr/local/lib/avisynth && \
ls /usr/local/lib/avisynth && \
npm install && \
npm link && \
jlse --help
# install EPGStation
RUN cd /app && \
npm install && \
npm install async && \
npm run all-install && \
npm run build
WORKDIR /app
ENTRYPOINT ["npm"]
CMD ["start"]
view raw Dockerfile hosted with ❤ by GitHub

Dockerfileの中で使っている移植したJoin_logo_scp_trial_setとdelogoはGitHubにおいてある。

次に、ホスト側のファイルを用意する。
以前からJoinLogoScpTrialSetLinuxを使っている方はlogoやJLやSettingを一旦避難させて、docker-mirakurun-epgstation/join_logo_scp_trialを削除する。
つかってない人や、過去のものを削除したらdocker-mirakurun-epgstationの中でgit clone https://github.com/tobitti0/join_logo_scp_trialを実行する。
以前から使っていた人は実行したら避難させたものを戻す。

次にdocker-composeを修正する。 docker-mirakurun-epgstation/docker-compose.yml内のepgstationのvolumesへ次の内容を追記する。

- ./join_logo_scp_trial/JL:/join_logo_scp_trial/JL
- ./join_logo_scp_trial/logo:/join_logo_scp_trial/logo
- ./join_logo_scp_trial/result:/join_logo_scp_trial/result
- ./join_logo_scp_trial/setting:/join_logo_scp_trial/setting
- ./join_logo_scp_trial/src:/join_logo_scp_trial/src 

ここまでできたらdocker-compose up -d --buildでビルドする。
以前の解説と違い、FFmpegやl-smashなどを含んだdocker-avisynthplusというPackageをGithubにて公開したのでFFmpegなどのビルド時間がかからずに構築できるようになった。

nvencのためのタグもあるのでGPUエンコードにしたい人はベースイメージのタグをそれに変更し、nvidia-container-runtime等を入れて構築することもできる。 必要であれば解説を追加したいと思うが、とりあえずは書かないでおく。

ビルドできたらjoin_logo_scp_trialのlogoフォルダにロゴデータを入れておく。

エンコードスクリプトの設定

次にdocker-mirakurun-epgstation/epgstation/configにjlse.jsを作成する。中身は次の通り。

const spawn = require('child_process').spawn;
const execFile = require('child_process').execFile;
const ffmpeg = process.env.FFMPEG;
const ffprobe = process.env.FFPROBE;
const path = require('path');
const input = process.env.INPUT;
const output = process.env.OUTPUT;
const videoHeight = parseInt(process.env.VIDEORESOLUTION, 10);
const isDualMono = parseInt(process.env.AUDIOCOMPONENTTYPE, 10) == 2;
const output_name = path.basename(output, path.extname(output));
const output_dir = path.dirname(output);
//FFmpegオプション生成 ここから
const args = ['-y'];
const preset = 'medium';
const codec = 'libx264'; //libx264でエンコード
const crf = 23;
const videoFilter = 'yadif';
if (isDualMono) {
Array.prototype.push.apply(args, [
'-filter_complex',
'channelsplit[FL][FR]',
'-map', '0:v',
'-map', '[FL]',
'-map', '[FR]',
'-metadata:s:a:0', 'language=jpn',
'-metadata:s:a:1', 'language=eng',
]);
Array.prototype.push.apply(args, ['-c:a ac3', '-ar 48000', '-ab 256k']);
} else {
// audio dataをコピー
Array.prototype.push.apply(args, ['-c:a', 'aac']);
}
Array.prototype.push.apply(args, ['-ignore_unknown']);
// その他設定
Array.prototype.push.apply(args,[
'-vf', videoFilter,
'-preset', preset,
'-aspect', '16:9',
'-c:v', codec,
'-crf', crf,
'-f', 'mp4',
]);
let str = '';
for (let i of args) {
str += ` ${i}`;
}
// console.error(str); //オプションを確認するときコメントアウトを外す
//FFmpegオプション生成 ここまで
/**
* 動画長取得関数
* @param {string} filePath ファイルパス
* @return number 動画長を返す (秒)
*/
const getDuration = filePath => {
return new Promise((resolve, reject) => {
execFile(ffprobe, ['-v', '0', '-show_format', '-of', 'json', filePath], (err, stdout) => {
if (err) {
reject(err);
return;
}
try {
const result = JSON.parse(stdout);
resolve(parseFloat(result.format.duration));
} catch (err) {
reject(err);
}
});
});
};
//メインの処理 ここから
(async () => {
// 進捗計算のために動画の長さを取得
const duration = await getDuration(input);
//必要な変数
let total_num = 0;
let now_num = 0;
let avisynth_flag = false;
let percent = 0;
let update_log_flag = false;
let log = '';
const jlse_args = ['-i', input, '-e', '-o', str,'-r','-d', output_dir, '-n', output_name];
console.error(jlse_args);
var env = Object.create( process.env );
env.HOME = '/root';
console.error(env);
const child = spawn('jlse', jlse_args, {env:env});
let inputfileinfo = false;
let outputfileinfo = false;
let fileinfolog = '';
/**
* エンコード進捗表示用に標準出力に進捗情報を吐き出す
* 出力する JSON
* {"type":"progress","percent": 0.8, "log": "view log" }
*/
child.stderr.on('data', data => {
let strbyline = String(data).split('\n');
for (let i = 0; i < strbyline.length; i++) {
let str = strbyline[i];
switch(str){
case str.startsWith('AviSynth') && str :{ //AviSynth+
const raw_avisynth_data = str.replace(/AviSynth\s/,'');
if(raw_avisynth_data.startsWith('Creating')){
const avisynth_reg = /Creating\slwi\sindex\sfile\s(\d+)%/;
total_num = 200;
now_num = Number(raw_avisynth_data.match(avisynth_reg)[1]);
now_num += avisynth_flag ? 100 : 0;
avisynth_flag = avisynth_flag ? true : now_num == 100 ? true : false ;
}
update_log_flag = true;
log = `(1/4) AviSynth:Creating lwi index files`;
break;
}
case str.startsWith('chapter_exe') && str :{ //chapter_exe
const raw_chapter_exe_data = str.replace(/chapter_exe\s/,'');
switch(raw_chapter_exe_data){
case raw_chapter_exe_data.startsWith('\tVideo Frames') && raw_chapter_exe_data :{
//chapter_exeでの総フレーム数取得
const movie_frame_reg = /\tVideo\sFrames:\s(\d+)\s\[\d+\.\d+fps\]/;
total_num = Number(raw_chapter_exe_data.match(movie_frame_reg)[1]);
update_log_flag = true;
break;
}
case raw_chapter_exe_data.startsWith('mute') && raw_chapter_exe_data :{
//現在のフレーム数取得
const chapter_reg = /mute\s?\d+:\s(\d+)\s\-\s\d+フレーム/;
now_num = Number(raw_chapter_exe_data.match(chapter_reg)[1]);
update_log_flag = true;
break;
}
case raw_chapter_exe_data.startsWith('end') && raw_chapter_exe_data :{
//chapter_exeの終了検知
now_num = total_num;
update_log_flag = true;
break;
}
default :{
break;
}
}
log = `(2/4) Chapter_exe: ${now_num}/${total_num}`;
break;
}
case str.startsWith('logoframe') && str:{ //logoframe
const raw_logoframe_data = str.replace(/logoframe\s/,'');
switch (raw_logoframe_data){
case raw_logoframe_data.startsWith('checking') && raw_logoframe_data :{
const logoframe_reg = /checking\s*(\d+)\/(\d+)\sended./;
const logoframe = raw_logoframe_data.match(logoframe_reg);
now_num = Number(logoframe[1]);
total_num = Number(logoframe[2]);
update_log_flag = true;
}
default :{
break;
}
}
log = `(3/4) logoframe: ${now_num}/${total_num}`;
break;
}
case str.startsWith('frame') && str:{ //FFmpeg
// frame= 2847 fps=0.0 q=-1.0 Lsize= 216432kB time=00:01:35.64 bitrate=18537.1kbits/s speed= 222x
const progress = {};
let tmp = (str + ' ').match(/[A-z]*=[A-z,0-9,\s,.,\/,:,-]* /g);
if (tmp === null) continue;
for (let j = 0; j < tmp.length; j++) {
progress[tmp[j].split('=')[0]] = tmp[j].split('=')[1].replace(/\r/g, '').trim();
}
progress['frame'] = parseInt(progress['frame']);
progress['fps'] = parseFloat(progress['fps']);
progress['q'] = parseFloat(progress['q']);
let current = 0;
const times = progress.time.split(':');
for (let i = 0; i < times.length; i++) {
if (i == 0) {
current += parseFloat(times[i]) * 3600;
} else if (i == 1) {
current += parseFloat(times[i]) * 60;
} else if (i == 2) {
current += parseFloat(times[i]);
}
}
// 進捗率 1.0 で 100%
now_num = current;
total_num = duration;
log =
'(4/4) FFmpeg: ' +
//'frame= ' +
//progress.frame +
//' fps=' +
//progress.fps +
//' size=' +
//progress.size +
' time=' +
progress.time +
//' bitrate=' +
//progress.bitrate +
' speed=' +
progress.speed;
update_log_flag = true;
break;
}
default:{ //進捗表示に必要ない出力データを流す
console.log(strbyline[i]);
break;
}
}
percent = now_num / total_num;
if(update_log_flag) console.log(JSON.stringify({ type: 'progress', percent: percent, log: log }));
update_log_flag = false;
}
});
child.on('error', err => {
console.error(err);
throw new Error(err);
});
process.on('SIGINT', () => {
child.kill('SIGINT');
});
child.on('close', code => {
//終了後にしたい処理があれば書く
});
})();
//メインの処理 ここまで
view raw jls.js hosted with ❤ by GitHub
FFmpeのオプションなどは各自好きなように設定してほしい。

docker-mirakurun-epgstation/epgstation/config/config.jsonのencodeには次のような感じで記載する。

encode:
    - name: jlse
      cmd: '%NODE% %ROOT%/config/jlse.js'
      suffix: .mp4
      rate: 4.0

これでエンコードのところにjlsの選択が現れるので、選択しエンコードすれば問題なく利用できると思う。
進捗も表示されるはずだ。

注意としては以前からJoinLogoScpTrialSetLinuxを利用していた場合はdocker-mirakurun-epgstation/join_logo_scp_trialの中身を最新のものにしなければ、進捗は表示されない。
jlsの設定などはwindowsとかわらないのでwindowsの記事を参考に設定すれば問題ないはず。 jlsの設定ファイルなどはdocker-mirakurun-epgstation/join_logo_scp_trial/の中をいじればよい。

おわりに

雑ですがとりあえず一通り導入方法を記載した。
導入方法に万一問題があればTwitterまで。
移植版に問題があればGitHubのissueに。
現状私が使用できているので、問題なく使用できるはずですが、暇があれば問題には対応します。たぶん。。。

謝辞

EPGStationやJoinLogoScpTrialなどを制作された様々な偉大なる先人達に感謝いたします。