Open37

Raspberry Pi 4でKonomiTV用にtsreplaceを使いたい+ハードウェアエンコもやりたい

しちみしちみ

タイトル通り。KonomiTVのβ0.12で録画視聴機能が実装されたので、tsreplaceでメタデータを保持したままmp4に圧縮して保存したい。

しちみしちみ

が、エラー

$ sudo apt install ./tsreplace_0.12_Ubuntu20.04_arm64.deb
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています... 完了
状態情報を読み取っています... 完了
注意、'./tsreplace_0.12_Ubuntu20.04_arm64.deb' の代わりに 'tsreplace' を選択します
インストールすることができないパッケージがありました。おそらく、あり得
ない状況を要求したか、(不安定版ディストリビューションを使用しているの
であれば) 必要なパッケージがまだ作成されていなかったり Incoming から移
動されていないことが考えられます。
以下の情報がこの問題を解決するために役立つかもしれません:

以下のパッケージには満たせない依存関係があります:
 libavformat58 : 依存: libavcodec58 (= 8:4.3.6-0+deb11u1+rpt5)
                 依存: libsrt1.4-gnutls (>= 1.4.2) しかし、インストールすることができません
 tsreplace : 依存: libavcodec58
             依存: libavfilter7
E: 問題を解決することができません。壊れた変更禁止パッケージがあります。
しちみしちみ

とりあえず足りないものをインストールしてビルド
ここはChatGPTに頼ってたりする

sudo apt update
sudo apt install build-essential libavcodec-dev libavformat-dev libavfilter-dev

リポジトリクローンしてmake

git clone https://github.com/rigaya/tsreplace.git
cd tsreplace

make
sudo make install
しちみしちみ

入ったっぽい

xxxx@raspi:~/tsreplace $ tsreplace
ERROR: input file not set.
しちみしちみ

手動で依存関係解決したからビルドしなくてもdebファイルでインストールできたかも?

しちみしちみ

1分くらいのtsファイルを投げ込んでみる
このコマンドは公式リポジトリの使用例のまま

tsreplace -i input.m2ts -o output.m2ts -e ffmpeg -y -f mpegts -i - -copyts -start_at_zero -vf yadif -an -c:v libx264 -preset slow -crf 23 -g 90 -f mpegts -
しちみしちみ

ファイルサイズが小さくなっていることは確認できる

xxxx@raspi:~/tstest $ du -a
25444   ./output.m2ts
114492  ./input.m2ts
しちみしちみ

再生したいのでクライアントのWin11にVLCインストール中

しちみしちみ

今回はffmpegでソフトウェアエンコードしたが、ハードウェアエンコードが可能なら試したい

しちみしちみ

KonomiTVのインストーラー削除してたけどもう一回ダウンロードしてきたら行けた。
インストールされてるディレクトリを指定したら自動でやってくれる。

xxxx@raspi:~ $ ./KonomiTV-Installer-ARM.elf

  ────────────────────────────────────────────────────────── KonomiTV version 0.12.0 Installer ───────────────────────────────────────────────────────────

  KonomiTV のインストール/アップデート/アンインストールを行うインストーラーです。

  ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ 01. KonomiTV をインストールするときは 1 を、アップデートするときは 2 を、                                                                            │
  │     アンインストールするときは 3 を入力してください。                                                                                                │
  └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

  インストール(1) / アップデート(2) / アンインストール(3) [1/2/3] (1): 2

  ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ 注意: このアップデーターは現時点では動作しない可能性があります。                                                                                     │
  │ KonomiTV は鋭意開発中のため、現在破壊的な構成変更が頻繁に行われています。                                                                            │
  │ 破壊的変更が続く中アップデーターの機能を維持することは難しいため、                                                                                   │
  │ 安定版リリースまでの当面の間、アップデーターは最低限のメンテナンスのみ行っています。                                                                 │
  │ もしアップデーターが動作しない場合は、適宜 DB や設定ファイルなどをバックアップの上で                                                                 │
  │ 一旦アンインストールし、新規でインストールし直していただきますようお願いいたします。                                                                 │
  └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

  ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ 02. アップデート対象の KonomiTV のフォルダのパスを入力してください。                                                                                 │
  ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 例: /opt/KonomiTV                                                                                                                                    │
  └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

  アップデート対象の KonomiTV のフォルダのパス: /opt/KonomiTV

(以下略)
しちみしちみ

クライアントはアップデート後に更新されるまでちょっと待つかもしれない

しちみしちみ

データ解析とかサムネイル作成とかでとんでもなく重い処理が動いてるのか、SSHもpingも通らなくなった

しちみしちみ

ハードウェアエンコードは思ってたより簡単
libx264をh264_v4l2m2mに書き換えるだけ

tsreplace -i input.m2ts -o output_he.m2ts -e ffmpeg -y -f mpegts -i - -copyts -start_at_zero -an -c:v h264_v4l2m2m -flags +ildct+ilme -preset slow -crf 23 -g 90 -f mpegts -
しちみしちみ

アス比は -vf で指定すると治った

tsreplace -i input.m2ts -o output_he_1280.m2ts -e ffmpeg -y -f mpegts -i - -copyts -start_at_zero -vf scale=1920:1080 -an -c:v h264_v4l2m2m -flags +ildct+ilme -preset slow -crf 23 -g 90 -f mpegts -
しちみしちみ

EPGStationからのエンコードをv4l2とかtsreplaceにしようとしてるけど全くうまくいかない

しちみしちみ

docker-mirakurun-epgstationで動かしてるのでコンテナ内に関係ファイルを入れないと動かない

しちみしちみ

できねえ
コンテナにtsreplaceのバイナリマウントとaptで依存関係のインストールはした

しちみしちみ

うーんdocker execからtsreplaceは動くんだけどな

しちみしちみ

bashファイル作ってそれを実行する形にしてみる

enc-tsreplace.sh
#!/bin/bash

# エラーがあれば即終了
set -e

# ログ出力(任意)
echo "[enc-tsreplace.sh] Start encoding"
echo "INPUT: $INPUT"
echo "OUTPUT: $OUTPUT"
echo "FFMPEG: $FFMPEG"

# tsreplace + ffmpeg でエンコード
/usr/local/bin/tsreplace \
  -i "$INPUT" \
  -o "$OUTPUT" \
  -e "$FFMPEG" \
  -y -f mpegts -i - \
  -copyts -start_at_zero \
  -vf yadif -an \
  -c:v libx264 -preset slow -crf 23 -g 90 \
  -f mpegts -

# 完了ログ
echo "[enc-tsreplace.sh] Done encoding"
config.yml
- name: H.264(tsreplace)
  cmd: '/bin/bash %ROOT%/config/enc-tsreplace.sh'
  suffix: .m2ts
  rate: 4.0
しちみしちみ

結論としてはbashファイル経由ならtsreplaceがEPGStationの自動エンコードで使えそう
h264_v4l2m2mはまだ時間が必要

しちみしちみ

mirakurunのアップデートのついでにDockerfileを修正して、一発でtsrepalceとh264_v4l2m2mが使えるようにした。DockerfileはGPT-4oとの合作。
参考
https://nosubject.io/raspberry-pi4-ubuntu-tv-recorder-pxq1ud/#toc7

/epgstation/debian.Dockerfile
FROM l3tnun/epgstation:master-debian

ENV DEV="make gcc git g++ automake wget autoconf build-essential libass-dev libfreetype6-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo zlib1g-dev"
ENV FFMPEG_VERSION=7.0

RUN apt-get update && \
    apt-get -y install curl && \
    apt-get -y install $DEV && \
    apt-get -y install yasm libx264-dev libmp3lame-dev libopus-dev libvpx-dev && \
    apt-get -y install libx265-dev libnuma-dev && \
    apt-get -y install libasound2 libass9 libvdpau1 libva-x11-2 libva-drm2 libxcb-shm0 libxcb-xfixes0 libxcb-shape0 libvorbisenc2 libtheora0 libaribb24-dev && \
    apt-get -y install libavcodec-dev libavformat-dev libavfilter-dev && \
\
    mkdir /tmp/ffmpeg_sources && \
    cd /tmp/ffmpeg_sources && \
    curl -fsSL http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 | tar -xj --strip-components=1 && \
    ./configure \
      --prefix=/usr/local \
      --disable-shared \
      --pkg-config-flags=--static \
      --enable-gpl \
      --enable-libass \
      --enable-libfreetype \
      --enable-libmp3lame \
      --enable-libopus \
      --enable-libtheora \
      --enable-libvorbis \
      --enable-libvpx \
      --enable-libx264 \
      --enable-libx265 \
      --enable-version3 \
      --enable-libaribb24 \
      --enable-nonfree \
      --disable-debug \
      --disable-doc \
    && \
    make -j$(nproc) && \
    make install && \
\
    # tsreplace ソースからビルド
    cd /opt && \
    git clone https://github.com/rigaya/tsreplace.git && \
    cd tsreplace && \
    make -j$(nproc) && \
    make install && \
\
    # 後始末
    apt-get -y remove $DEV && \
    apt-get autoremove -y && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    rm -rf /tmp/* && \
    rm -rf /opt/tsreplace
しちみしちみ

tsreplaceは例によってコンテナ内でビルドしてる

しちみしちみ

あとはm2ts録画ファイルの(1)をどうにか直す

しちみしちみ

ハードウェア縁故なんとかうまくいかないかなーストリームだけでも

しちみしちみ

各エンコードファイルもまとめておこうかな
ソフトウェアエンコ用はjsファイル、ワードウェアエンコ用はシェルスクリプト?で書いた
プログレスバーが表示されないのも直したい

ソフトウェアエンコ
・enc.js(ffmpeg/libx264)
・enc-tsreplace.sh(tsreplace/libx264)
ハードウェアエンコ
・enc-hwe.js(ffmpeg/h264_v4l2m2m)
・enc-hwe-tsreplace.sh(tsreplace/h264_v4l2m2m)

全部どっちかのファイル形式にまとめたい。あとハードウェアエンコは現時点では映像が壊れる。

これはほぼデフォルト

enc.js(ffmpeg/libx264)
const spawn = require('child_process').spawn;
const execFile = require('child_process').execFile;
const ffmpeg = process.env.FFMPEG;
const ffprobe = process.env.FFPROBE;

const input = process.env.INPUT;
const output = process.env.OUTPUT;

const isDualMono = parseInt(process.env.AUDIOCOMPONENTTYPE, 10) == 2;
const args = ['-y'];

/**
 * 動画長取得関数
 * @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);
            }
        });
    });
};

// 字幕用
Array.prototype.push.apply(args, ['-fix_sub_duration']);
// input 設定
Array.prototype.push.apply(args, ['-i', input]);
// ビデオストリーム設定
Array.prototype.push.apply(args, ['-map', '0:v', '-c:v', 'libx264']);
// インターレス解除
Array.prototype.push.apply(args, ['-vf', 'yadif']);
// オーディオストリーム設定
if (isDualMono) {
    Array.prototype.push.apply(args, [
        '-filter_complex',
        'channelsplit[FL][FR]',
        '-map', '[FL]',
        '-map', '[FR]',
        '-metadata:s:a:0', 'language=jpn',
        '-metadata:s:a:1', 'language=eng',
        '-ac', '1',
    ]);
} else {
    Array.prototype.push.apply(args, ['-map', '0:a']);
}
Array.prototype.push.apply(args, ['-c:a', 'aac']);
// 字幕ストリーム設定
Array.prototype.push.apply(args, ['-map', '0:s?', '-c:s', 'mov_text']);
// 品質設定
Array.prototype.push.apply(args, ['-preset', 'veryfast', '-crf', '26']);
// 出力ファイル
Array.prototype.push.apply(args, [output]);

(async () => {
    // 進捗計算のために動画の長さを取得
    const duration = await getDuration(input);
    const child = spawn(ffmpeg, args);
    child.stderr.on('data', data => {
        let strbyline = String(data).split('\n');
        for (let i = 0; i < strbyline.length; i++) {
            let str = strbyline[i];
            if (str.startsWith('frame')) {
                const progress = {};
                const ffmpeg_reg = /frame=\s*(?<frame>\d+)\sfps=\s*(?<fps>\d+(?:\.\d+)?)\sq=\s*(?<q>[+-]?\d+(?:\.\d+)?)\sL?size=\s*(?<size>\d+(?:\.\d+)?)(kB|KiB)\stime=\s*(?<time>\d+[:\.\d+]*)\sbitrate=\s*(?<bitrate>\d+(?:\.\d+)?)kbits\/s(?:\sdup=\s*(?<dup>\d+))?(?:\sdrop=\s*(?<drop>\d+))?\sspeed=\s*(?<speed>\d+(?:\.\d+)?)x/;
                let ffmatch =str.match(ffmpeg_reg);
                if (ffmatch === null) continue;

                progress['frame'] = parseInt(ffmatch.groups.frame);
                progress['fps'] = parseFloat(ffmatch.groups.fps);
                progress['q'] = parseFloat(ffmatch.groups.q);
                progress['size'] = parseInt(ffmatch.groups.size);
                progress['time'] = ffmatch.groups.time;
                progress['bitrate'] = parseFloat(ffmatch.groups.bitrate);
                progress['dup'] = ffmatch.groups.dup == null ? 0 : parseInt(ffmatch.groups.dup);
                progress['drop'] = ffmatch.groups.drop == null ? 0 : parseInt(ffmatch.groups.drop);
                progress['speed'] = parseFloat(ffmatch.groups.speed);

                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.0100%
                const percent = current / duration;
                const log =
                    'frame= ' +
                    progress.frame +
                    ' fps=' +
                    progress.fps +
                    ' size=' +
                    progress.size +
                    ' time=' +
                    progress.time +
                    ' bitrate=' +
                    progress.bitrate +
                    ' drop=' +
                    progress.drop +
                    ' speed=' +
                    progress.speed;

                console.log(JSON.stringify({ type: 'progress', percent: percent, log: log }));
            }
        }
    });

    child.on('error', err => {
        console.error(err);
        throw new Error(err);
    });

    child.on('close', (code) => {
        process.exitCode = code;
    });

    process.on('SIGINT', () => {
        child.kill('SIGINT');
    });
})();
しちみしちみ

tsreplace経由のソフトウェアエンコ
エンコードはできるけどtmpのあたりは修正するかも

enc-tsreplace.sh
#!/bin/bash

set -e

echo "[enc-tsreplace.sh] Start encoding"
echo "INPUT: $INPUT"
echo "OUTPUT: $OUTPUT"
echo "FFMPEG: $FFMPEG"

# 一時ファイル名を作成(例: 元のファイル名の末尾に .tmp)
TEMP_OUTPUT="${OUTPUT}.tmp"

# tsreplace + ffmpeg でエンコードして一時ファイルに出力
/usr/local/bin/tsreplace \
  -i "$INPUT" \
  -o "$TEMP_OUTPUT" \
  -e "$FFMPEG" \
  -y -f mpegts -i - \
  -copyts -start_at_zero \
  -vf yadif -an \
  -c:v libx264 -preset ultrafast -crf 23 -g 90 \
  -f mpegts -

# 成功したら一時ファイルを元のファイルに上書き
mv "$TEMP_OUTPUT" "$OUTPUT"

echo "[enc-tsreplace.sh] Done encoding"
しちみしちみ

ffmpegのみ用とtsreplace経由用のハードウェアエンコファイルは動作しないから直ったら貼る

しちみしちみ

次はKonomiTVのストリーム用にHWE使ってみたい