mirakc(Docker)+EDCB+KonomiTV環境構築

Mirakurun+EDCB(Linux版)+KonomiTV環境は非常に安定しています。チャンネル切り替えもスムーズ。画質違いでの同時視聴なども何も問題なく。録画失敗もなさそうです。なので、なかなか切り替えを考えていないのですが、KonomiTV最新版対応のスクリプトを修正したりしたこの機会に、mirakc+EDCB(Linux版)+KonomiTV環境についても整備してみました。まだ長期運用していないので、これから構築するなら現時点では先ほどのMirakurun+EDCB(Linux版)+KonomiTV環境がおすすめですが。
※Dockerを使用しないバージョンのmirakc+EDCB+KonomiTVも作ってみました。

チューナードライバインストール・LXDコンテナ作成(ホストで実行)

OS:Ubuntu 26.04
TVチューナー:DTV02A-1T1S-U

まずはホスト側にチューナードライバを適用します。過去にダウンロードしたドライバがあれば、/opt/lxd-data/konomitv-backupに入れておけば、それを利用するかの確認も表示されます(サイトを確認して保持しているのが最新かチェックしてもよいかと。スクリプトでは自動で最新版をダウンロードします。保持しているファイルを利用する場合はパーミッションにも注意)。
Secure Boot環境の場合、ドライバインストール時に確認画面が表示されるので、こちらの記事を参考に設定。

Mirakurun版とは違い、mirakc版ではDockerのセットアップとコンテナに対しNestingを有効にする設定も追加しています。

sudo mkdir -p /opt/lxd-data/konomitv-backup
cd /opt/lxd-data/konomitv-backup
sudo nano tuner-lxd-docker.sh
sudo bash tuner-lxd-docker.sh
#!/bin/bash
# =============================================================
# KonomiTV LXD セットアップスクリプト (ホスト側)
# TunerOK スナップショットまで
# =============================================================
set -euo pipefail

MOUNT_PATH="/opt/lxd-data"
DRIVER_DIR="/opt/lxd-data/konomitv-backup"

# ============================================================
# ユーティリティ
# ============================================================
ask_yn() {
    local prompt="$1"
    local default="${2:-Y}"
    local hint
    if [ "$default" = "Y" ]; then
        hint="Y/n"
    else
        hint="y/N"
    fi
    while true; do
        read -rp "${prompt} [${hint}]: " ans
        ans="${ans:-$default}"
        case "${ans,,}" in
            y|yes) return 0 ;;
            n|no)  return 1 ;;
            *) echo "  y または n で答えてください。" ;;
        esac
    done
}

# ============================================================
# 1. チューナードライバのインストール
# ============================================================
echo "=========================================="
echo " KonomiTV LXD セットアップ"
echo "=========================================="
echo ""

if ask_yn "チューナードライバ (px4_drv) をインストールしますか?"; then
    echo ""
    echo "=== チューナードライバのインストール ==="
    mkdir -p "$DRIVER_DIR"

    EXISTING_DEB=$(ls "${DRIVER_DIR}"/px4-drv-dkms_*.deb 2>/dev/null | head -n1 || true)

    if [ -n "$EXISTING_DEB" ]; then
        echo "  ダウンロード済みのドライバが見つかりました: $(basename "$EXISTING_DEB")"
        if ask_yn "ダウンロード済みの$(basename "$EXISTING_DEB")を使用しますか?"; then
            # 既存ファイルのフルパスをそのまま使用
            DRIVER_DEB="$EXISTING_DEB"
        else
            DRIVER_VERSION=$(curl -s https://api.github.com/repos/tsukumijima/px4_drv/releases/latest | grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/')
            DRIVER_DEB="${DRIVER_DIR}/px4-drv-dkms_${DRIVER_VERSION}_all.deb"
            DRIVER_URL="https://github.com/tsukumijima/px4_drv/releases/download/v${DRIVER_VERSION}/px4-drv-dkms_${DRIVER_VERSION}_all.deb"
            curl -L -o "${DRIVER_DEB}" "${DRIVER_URL}"
            chmod 644 "${DRIVER_DEB}"
        fi
    else
        DRIVER_VERSION=$(curl -s https://api.github.com/repos/tsukumijima/px4_drv/releases/latest | grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/')
        DRIVER_DEB="${DRIVER_DIR}/px4-drv-dkms_${DRIVER_VERSION}_all.deb"
        DRIVER_URL="https://github.com/tsukumijima/px4_drv/releases/download/v${DRIVER_VERSION}/px4-drv-dkms_${DRIVER_VERSION}_all.deb"
        curl -L -o "${DRIVER_DEB}" "${DRIVER_URL}"
        chmod 644 "${DRIVER_DEB}"
    fi

    # DRIVER_DEB は常にフルパスなので ./ を付けずにそのまま渡す
    sudo apt install -y "${DRIVER_DEB}"
    sudo modprobe -r px4_drv 2>/dev/null || true
    sudo modprobe px4_drv
    echo "  ドライバ インストール完了"
else
    echo "  ドライバインストールをスキップします"
fi
echo ""

# ============================================================
# 2. コンテナ名の入力
# ============================================================
read -rp "作成するLXDコンテナ名を入力してください [konomitv]: " CONTAINER
CONTAINER="${CONTAINER:-konomitv}"
echo "  コンテナ名: ${CONTAINER}"
echo ""

# ============================================================
# 3. Tailscale authkey の入力
# ============================================================
if ask_yn "Tailscale の authkey がありますか?"; then
    read -rsp "authkey を入力してください(tskeyから入力。入力は非表示): " TS_AUTHKEY
    echo ""
    USE_TS_AUTHKEY=true
else
    USE_TS_AUTHKEY=false
fi
echo ""

# ============================================================
# 4. コンテナ作成・マウント・ID マッピング
# ============================================================
echo "=== コンテナ '${CONTAINER}' を作成 ==="
if lxc info "$CONTAINER" &>/dev/null; then
    echo "  コンテナは既に存在します"
else
    lxc launch ubuntu:26.04 "$CONTAINER"
fi

echo ""
echo "=== ${MOUNT_PATH} の確認・作成 ==="
if [ ! -d "$MOUNT_PATH" ]; then
    sudo mkdir -p "$MOUNT_PATH"
    echo "  ${MOUNT_PATH} を作成しました"
else
    echo "  ${MOUNT_PATH} は既に存在します"
fi

TV_DIR="${MOUNT_PATH}/tv"
if [ ! -d "$TV_DIR" ]; then
    sudo mkdir -p "$TV_DIR"
    echo "  ${TV_DIR} を作成しました"
else
    echo "  ${TV_DIR} は既に存在します"
fi
sudo chown 1000:1000 "$TV_DIR"
sudo chmod 755 "$TV_DIR"
echo "  録画フォルダ所有者: 1000:1000  パーミッション: 755"

echo ""
echo "=== ホストの ${MOUNT_PATH} をコンテナにマウント ==="
if lxc config device show "$CONTAINER" 2>/dev/null | grep -q "opt-lxd-data"; then
    echo "  opt-lxd-data は登録済み"
else
    lxc config device add "$CONTAINER" opt-lxd-data disk source="$MOUNT_PATH" path="$MOUNT_PATH"
fi

echo ""
echo "=== ID マッピング設定 ==="
lxc config set "$CONTAINER" raw.idmap "both 1000 1000"

echo ""
echo "=== Nesting 設定 (Docker 用) ==="
lxc config set "$CONTAINER" security.nesting true

echo ""
echo "=== コンテナを再起動 ==="
lxc restart "$CONTAINER"
sleep 3

# ============================================================
# 5. コンテナ内セットアップ (apt / Docker / Tailscale)
# ============================================================
echo ""
echo "=== コンテナ内セットアップ ==="
lxc exec "${CONTAINER}" -- bash -euo pipefail << 'INNER'
apt update
apt upgrade -y
apt install -y curl

echo "=== Docker インストール ==="
apt install -y ca-certificates curl gnupg

install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
usermod -aG docker ubuntu
echo "Dockerインストール完了"

echo "=== Tailscale インストール ==="
curl -fsSL https://tailscale.com/install.sh | sh
INNER

# ============================================================
# 6. Tailscale 起動
# ============================================================
echo ""
echo "=== Tailscale を起動 ==="
if [ "$USE_TS_AUTHKEY" = true ]; then
    lxc exec "${CONTAINER}" -- tailscale up --authkey="${TS_AUTHKEY}"
else
    echo "  authkeyがないため、手動認証を行ってください。"
    lxc exec "${CONTAINER}" -- tailscale up || true
fi
TS_IP=$(lxc exec "${CONTAINER}" -- tailscale ip -4 2>/dev/null || echo "取得中...")
echo "  Tailscale IP: ${TS_IP}"

# ============================================================
# 7. TailscaleOK スナップショット
# ============================================================
echo ""
if ask_yn "スナップショット 'TailscaleOK' を作成しますか?"; then
    echo "=== コンテナを停止中 ==="
    lxc stop "${CONTAINER}"
    echo "=== スナップショット 'TailscaleOK' を作成中 ==="
    lxc snapshot "${CONTAINER}" TailscaleOK
    echo "  スナップショット 'TailscaleOK' を作成しました"
    echo "=== コンテナを起動中 ==="
    lxc start "${CONTAINER}"
    sleep 3
fi

# ============================================================
# 8. USB チューナーパススルー
# ============================================================
DO_TUNER_PASS=false
if ask_yn "USB チューナーをコンテナにパススルーしますか?"; then
    DO_TUNER_PASS=true

    echo ""
    echo "=== USB チューナーを検出中 ==="
    LSUSB_LINE=$(lsusb | grep -i "ISDBT2056" || true)
    if [ -z "$LSUSB_LINE" ]; then
        echo "  警告: ISDBT2056 デバイスが見つかりません。パススルーをスキップします。"
        DO_TUNER_PASS=false
    else
        echo "  検出: $LSUSB_LINE"
        IDS=$(echo "$LSUSB_LINE" | grep -oP 'ID \K[0-9a-fA-F]{4}:[0-9a-fA-F]{4}')
        VENDOR_ID=$(echo "$IDS" | cut -d: -f1)
        PRODUCT_ID=$(echo "$IDS" | cut -d: -f2)
        echo "  vendorid : $VENDOR_ID"
        echo "  productid: $PRODUCT_ID"

        if lxc config device show "$CONTAINER" 2>/dev/null | grep -q "usb-tuner"; then
            echo "  usb-tuner は登録済みのため上書きします"
            lxc config device remove "$CONTAINER" usb-tuner
        fi
        lxc config device add "$CONTAINER" usb-tuner usb \
            vendorid="$VENDOR_ID" \
            productid="$PRODUCT_ID"
        echo "  usb-tuner を追加しました"

        echo ""
        echo "=== isdb2056video デバイスを検出中 ==="
        ISDB_DEVS=$(ls /dev/isdb2056video* 2>/dev/null || true)
        if [ -z "$ISDB_DEVS" ]; then
            echo "  警告: /dev/isdb2056video* が見つかりません。スキップします。"
        else
            IDX=0
            for DEV in $ISDB_DEVS; do
                NAME="isdb2056-${IDX}"
                echo "  追加: $DEV ($NAME)"
                if lxc config device show "$CONTAINER" 2>/dev/null | grep -q "^${NAME}:"; then
                    lxc config device remove "$CONTAINER" "$NAME"
                fi
                lxc config device add "$CONTAINER" "$NAME" unix-char \
                    source="$DEV" path="$DEV"
                IDX=$(( IDX + 1 ))
            done
        fi
    fi
fi

# ============================================================
# 9. コンテナ内チューナー確認
# ============================================================
if [ "$DO_TUNER_PASS" = true ]; then
    echo ""
    echo "=== コンテナ内チューナー確認 ==="
    lxc exec "${CONTAINER}" -- bash -c '
        ISDB_DEVS=$(ls /dev/isdb2056video* 2>/dev/null || true)
        if [ -z "$ISDB_DEVS" ]; then
            echo "  NG: /dev/isdb2056video* が見つかりません"
        else
            for DEV in $ISDB_DEVS; do
                chmod 666 "$DEV" 2>/dev/null
                echo "  OK: $DEV"
            done
        fi
    '
fi

# ============================================================
# 10. TunerOK スナップショット
# ============================================================
echo ""
if ask_yn "スナップショット 'TunerOK' を作成しますか?"; then
    echo "=== コンテナを停止中 ==="
    lxc stop "${CONTAINER}"
    echo "=== スナップショット 'TunerOK' を作成中 ==="
    lxc snapshot "${CONTAINER}" TunerOK
    echo "  スナップショット 'TunerOK' を作成しました"
    echo "=== コンテナを起動中 ==="
    lxc start "${CONTAINER}"
    sleep 3
fi

# ============================================================
# 11. コンテナ内でシェルを開始
# ============================================================
echo ""
echo "コンテナ内でシェルを開始します。"
exec lxc exec "${CONTAINER}" -- bash

mirakc・EDCB・KonomiTVセットアップ(コンテナで実行)

そのままコンテナ内で下記を貼り付けてソフトをインストールします。mirakcの初回サービススキャンが結構時間がかかるんですけれど多分これは不要な気がします。

#!/bin/bash
set -e

# ============================================================
# mirakc + EDCB + KonomiTV 環境構築スクリプト
#
# 前提条件:
#   - Debian系Linux (Ubuntu/Debian)
#   - ネット接続
#   - TVチューナー (px4_drv対応デバイス) 接続済み
#   - Docker / Docker Compose V2 以降 インストール済み
# ============================================================

REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || id -un)}"
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
DTV_DIR="$REAL_HOME/dtv"

# ============================================================
# 0. ユーザー入力
# ============================================================
echo "=== 1/10: キー設定 ==="
echo "キーの内容を貼り付けてください(入力後、Enterキーを2回押すと確定します):"
BCAS_CONTENT=$(sed '/^$/q')

# ============================================================
# 1. 依存パッケージ
# ============================================================
echo "=== 2/10: 依存パッケージをインストール中 ==="
sudo apt update
sudo apt install -y \
    autoconf automake cmake libtool libpcsclite-dev git build-essential pkg-config \
    curl wget libclang-dev libdvbv5-dev libudev-dev \
    nodejs npm ffmpeg liblua5.2-dev lua-zlib g++ make gcc

# ============================================================
# 2. TVチューナードライバ (px4_drv)
# ============================================================
echo "=== 3/10: PX4 ドライバをセットアップ中 ==="
mkdir -p "$DTV_DIR"
cd "$DTV_DIR"
DRIVER_VERSION="0.5.5"
DRIVER_DEB="px4-drv-dkms_${DRIVER_VERSION}_all.deb"
DRIVER_URL="https://github.com/tsukumijima/px4_drv/releases/download/v${DRIVER_VERSION}/${DRIVER_DEB}"

if [ ! -f "${DRIVER_DEB}" ]; then
    curl -L -o "${DRIVER_DEB}" "${DRIVER_URL}"
fi
sudo apt install -y "./${DRIVER_DEB}" || true
sudo modprobe px4_drv || true

# ============================================================
# 3. libyakisoba / libsobacas のビルド
# ============================================================
echo "=== 4/10: 復号ライブラリのビルド ==="
cd "$DTV_DIR"
for repo in "libyakisoba" "libsobacas"; do
    if [ ! -d "$repo" ]; then
        git clone "https://github.com/tsunoda14/${repo}.git"
    fi
    cd "$repo"
    autoreconf -i
    mkdir -p build && cd build
    [ "$repo" == "libyakisoba" ] && ../configure --sysconfdir=/usr/local/etc || ../configure
    make -j"$(nproc)"
    sudo make install
    cd "$DTV_DIR"
done
sudo ldconfig

# ============================================================
# 4. recisdbのビルド
# ============================================================
echo "=== 5/10: Rust と recisdb のビルド ==="
if ! command -v cargo &> /dev/null; then
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
fi
source "$HOME/.cargo/env" || export PATH="$HOME/.cargo/bin:$PATH"

sudo mkdir -p /usr/local/lib/pkgconfig
sudo tee /usr/local/lib/pkgconfig/libsobacas.pc > /dev/null <<EOF
prefix=/usr/local
libdir=/usr/local/lib
includedir=/usr/include

Name: libsobacas
Description: PCSC compatible ECM decoder library
Version: 0.0.0
Libs: -L\${libdir} -lsobacas
Cflags: -I\${includedir}/PCSC
EOF

cd "$DTV_DIR"
[ ! -d "recisdb-rs" ] && git clone --recursive https://github.com/kazuki0824/recisdb-rs.git
cd recisdb-rs
sed -i 's/pcsclite/sobacas/g' b25-sys/build.rs
cargo build -F dvb --release
sudo cp target/release/recisdb /usr/local/bin/

# ============================================================
# 5. キーの保存
# ============================================================
sudo mkdir -p /usr/local/etc
echo "$BCAS_CONTENT" | sudo tee /usr/local/etc/bcas_keys > /dev/null

# ============================================================
# 6. ISDBScanner 実行
# ============================================================
echo "=== 6/10: チャンネルスキャン ==="
sudo wget -q https://github.com/tsukumijima/ISDBScanner/releases/download/v1.3.3/isdb-scanner -O /usr/local/bin/isdb-scanner
sudo chmod +x /usr/local/bin/isdb-scanner

mkdir -p "$DTV_DIR/scanned"
if [ -z "$(ls -A "$DTV_DIR/scanned/" 2>/dev/null)" ]; then
    isdb-scanner "$DTV_DIR/scanned/"
fi

# ============================================================
# 7. mirakc Docker
# ============================================================
echo "=== 7/10: mirakc Docker セットアップ ==="
MIRAKC_DIR="$REAL_HOME/mirakc-custom"
mkdir -p "$MIRAKC_DIR"
cd "$MIRAKC_DIR"

# entrypoint.sh: services.json の上書きループなし
cat > entrypoint.sh << 'ENTRYPOINT'
#!/bin/bash
set -e
mkdir -p /var/lib/mirakc/epg
exec /usr/local/bin/mirakc --config /etc/mirakc/config.yml
ENTRYPOINT
chmod +x entrypoint.sh

# Dockerfile: コンテナ内でライブラリとrecisdbをビルド
cat > Dockerfile << 'DOCKERFILE'
FROM mirakc/mirakc:main-debian

RUN apt-get update && apt-get install -y --no-install-recommends \
    wget curl git autoconf automake cmake libtool build-essential pkg-config \
    g++ make gcc libclang-dev libdvbv5-dev libudev-dev libpcsclite-dev \
    && rm -rf /var/lib/apt/lists/*

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

RUN mkdir -p /tmp/build && cd /tmp/build && \
    git clone https://github.com/tsunoda14/libyakisoba.git && \
    git clone https://github.com/tsunoda14/libsobacas.git && \
    cd libyakisoba && autoreconf -i && mkdir -p build && cd build && \
    ../configure --sysconfdir=/usr/local/etc && make -j$(nproc) && make install && \
    cd /tmp/build/libsobacas && autoreconf -i && mkdir -p build && cd build && \
    ../configure && make -j$(nproc) && make install && \
    ldconfig && cd / && rm -rf /tmp/build

RUN mkdir -p /usr/local/lib/pkgconfig && \
    printf 'prefix=/usr/local\nlibdir=/usr/local/lib\nincludedir=/usr/include\nName: libsobacas\nDescription: PCSC compatible ECM decoder library\nVersion: 0.0.0\nLibs: -L${libdir} -lsobacas\nCflags: -I${includedir}/PCSC\n' \
    > /usr/local/lib/pkgconfig/libsobacas.pc

RUN mkdir -p /tmp/recisdb && cd /tmp/recisdb && \
    git clone --recursive https://github.com/kazuki0824/recisdb-rs.git && \
    cd recisdb-rs && \
    sed -i 's/pcsclite/sobacas/g' b25-sys/build.rs && \
    cargo build -F dvb --release && \
    cp target/release/recisdb /usr/local/bin/ && \
    chmod +x /usr/local/bin/recisdb && \
    cd / && rm -rf /tmp/recisdb

RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /root/.cargo /root/.rustup

COPY entrypoint.sh /opt/entrypoint.sh
RUN chmod +x /opt/entrypoint.sh
DOCKERFILE

# docker-compose.yml
cat > docker-compose.yml << 'DOCKERCOMPOSE'
services:
  mirakc:
    image: mirakc-custom:latest
    container_name: mirakc
    restart: unless-stopped
    entrypoint: /opt/entrypoint.sh
    ports:
      - "40772:40772"
    volumes:
      - ./config.yml:/etc/mirakc/config.yml:ro
      - mirakc-epg:/var/lib/mirakc/epg
      - /dev:/dev
      - /usr/local/etc/bcas_keys:/usr/local/etc/bcas_keys:ro
    privileged: true
    cap_add:
      - SYS_ADMIN
      - SYS_RAWIO
volumes:
  mirakc-epg:
DOCKERCOMPOSE

# config.yml: ISDBScannerの結果を元に生成
cp "$DTV_DIR/scanned/mirakc/config.yml" "$MIRAKC_DIR/config.yml"

# 初回起動時のみ scan-services を実行するため、最初は無効化しない
# sync-clocks / update-schedules は無効化 (手動スクリプトで更新)
cat >> "$MIRAKC_DIR/config.yml" << 'JOBSCONFIG'

jobs:
  scan-services:
    disabled: false
  sync-clocks:
    disabled: true
  update-schedules:
    disabled: true
JOBSCONFIG

# update-epg.sh: 番組情報の手動更新スクリプト
cat > "$REAL_HOME/update-epg.sh" << 'UPDATEEPG'
#!/bin/bash
# update-epg.sh - Manual EPG update script
# Run this when NOT watching TV (it will occupy the tuner for several minutes)
#
# Usage: sudo bash ~/update-epg.sh

set -e

echo "=== EPG Manual Update ==="
echo "WARNING: This will occupy the tuner for several minutes."
echo "Do NOT run this while watching TV."
echo ""

echo "[1/2] Syncing clocks..."
docker exec mirakc mirakc-arib sync-clocks 2>&1 | head -20
echo ""

echo "[2/2] Collecting EIT data (番組情報更新)..."
docker exec mirakc mirakc-arib collect-eits $(docker exec mirakc cat /var/lib/mirakc/epg/services.json 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
if isinstance(d, list):
    sids = [str(item[0] if isinstance(item, list) else item.get('id','')) for item in d[:200]]
    print(' '.join(['--sids=' + s for s in sids[:50]]))
elif isinstance(d, dict):
    sids = list(d.keys())[:50]
    print(' '.join(['--sids=' + str(s) for s in sids]))
" 2>/dev/null) 2>&1 | tail -5
echo ""
echo "=== EPG Update Complete ==="
UPDATEEPG
chmod +x "$REAL_HOME/update-epg.sh"

echo "Dockerイメージをビルド中(Rustコンパイルで数分かかります)..."
docker build -t mirakc-custom:latest .
sudo chmod 666 /dev/isdb2056video* 2>/dev/null || true
sudo chmod 666 /dev/px4video* 2>/dev/null || true
docker compose up -d

echo "mirakc 起動待機中..."
for i in $(seq 1 30); do
    if curl -s http://127.0.0.1:40772/api/tuners >/dev/null 2>&1; then
        echo "mirakc API 応答OK"
        break
    fi
    sleep 2
    echo "  待機中... ($((i*2))秒経過)"
done

# ============================================================
# 初回サービススキャン (scan-services 完了を待機)
# ============================================================
echo "=== 初回サービススキャン実行中(数分かかります)==="
for i in $(seq 1 60); do
    SVC_COUNT=$(curl -s http://127.0.0.1:40772/api/services 2>/dev/null | grep -c '"id"' || true)
    if [ "$SVC_COUNT" -gt 0 ]; then
        echo "サービススキャン完了: ${SVC_COUNT} 件のサービスを検出"
        break
    fi
    sleep 5
    echo "  スキャン中... ($((i*5))秒経過)"
done

if [ "${SVC_COUNT:-0}" -eq 0 ]; then
    echo "WARNING: サービススキャンがタイムアウトしました。"
    echo "手動で実行: docker exec mirakc bash -c 'recisdb tune --device /dev/isdb2056video0 --channel T27 - 2>/dev/null | timeout 15 mirakc-arib scan-services'"
fi

# scan-services を無効化して再起動 (起動時のチューナー占有を防ぐ)
sed -i '/  scan-services:/,/  sync-clocks:/ s/disabled: false/disabled: true/' "$MIRAKC_DIR/config.yml"
docker compose restart
echo "mirakc 再起動完了 (scan-services 無効化)"

# ============================================================
# 8. EDCB (EpgTimerSrv) のセットアップ
# ============================================================
echo "=== 8/10: EDCB セットアップ ==="
cd "$DTV_DIR"
[ ! -d "EDCB" ] && git clone https://github.com/xtne6f/EDCB
cd EDCB/Document/Unix
make -j"$(nproc)"
sudo make install
make extra
sudo make install_extra

sudo mkdir -p /var/local/edcb
sudo chown -R "$REAL_USER:$REAL_USER" /var/local/edcb
make setup_ini

# EMWUI
cd "$DTV_DIR"
[ ! -d "EDCB_Material_WebUI" ] && git clone https://github.com/EMWUI/EDCB_Material_WebUI
cp -r EDCB_Material_WebUI/HttpPublic /var/local/edcb/
cp -r EDCB_Material_WebUI/Setting    /var/local/edcb/

# BonDriver_LinuxMirakc
cd "$DTV_DIR"
[ ! -d "BonDriver_LinuxMirakc" ] && git clone https://github.com/matching/BonDriver_LinuxMirakc.git --recurse-submodules
cd BonDriver_LinuxMirakc

python3 << 'PYEOF'
with open('src/BonDriver_LinuxMirakc.cpp', 'r') as f:
    content = f.read()
content = content.replace('char szHeader[ 64 ];', 'char szHeader[ 256 ];')
import re
content = re.sub(r'char szHeader\[len\];', 'char szHeader[256];', content)
old = 'sprintf(szHeader, "Connection: close\\r\\nX-Mirakurun-Priority: %d", g_Priority);'
new = 'sprintf(szHeader, "Host: %s:%d\\r\\nConnection: close\\r\\nX-Mirakurun-Priority: %d", g_ServerHost, g_ServerPort, g_Priority);'
content = content.replace(old, new)
with open('src/BonDriver_LinuxMirakc.cpp', 'w') as f:
    f.write(content)
PYEOF

make -j"$(nproc)"
sudo cp BonDriver_LinuxMirakc.so /usr/local/lib/edcb/

sudo tee /usr/local/lib/edcb/BonDriver_LinuxMirakc.so.ini > /dev/null << 'EOT'
[GLOBAL]
SERVER_HOST="127.0.0.1"
SERVER_PORT=40772
SERVER_TYPE="http"
DECODE_B25=1
PRIORITY=10
SERVICE_SPLIT=0
EOT

cp "$DTV_DIR/scanned/EDCB-Wine/ChSet5.txt" /var/local/edcb/Setting/
cp "$DTV_DIR/scanned/EDCB-Wine/BonDriver_mirakc(BonDriver_mirakc).ChSet4.txt" \
   "/var/local/edcb/Setting/BonDriver_LinuxMirakc(LinuxMirakc).ChSet4.txt"

sed -i -e 's/^ALLOW_SETTING=.*/ALLOW_SETTING=true/' /var/local/edcb/HttpPublic/legacy/util.lua
mkdir -p /var/local/edcb/HttpPublic/video

# チューナー数の自動検出
ISDB_DEV_COUNT=$(ls /dev/isdb2056video* 2>/dev/null | wc -l)
PX4_DEV_COUNT=$(ls /dev/px4video* 2>/dev/null | wc -l)
if [ "$ISDB_DEV_COUNT" -gt 0 ]; then
    TUNER_COUNT="$ISDB_DEV_COUNT"
elif [ "$PX4_DEV_COUNT" -gt 0 ]; then
    TUNER_COUNT=$(( PX4_DEV_COUNT / 4 ))
    [ "$TUNER_COUNT" -eq 0 ] && TUNER_COUNT=1
else
    TUNER_COUNT=1
fi
echo "検出チューナー数: ${TUNER_COUNT}"

# CompatFlags=128 ([SET]セクション内に配置)
# KonomiTVがCMD_EPG_SRV_FILE_COPY2でEpgTimerSrv.ini/Common.iniを
# 取得する際に必要 (これが無いと番組表の録画設定プリセット一覧取得が
# HTTP Error 500 / EpgTimerSrv.ini is empty で失敗する)
sudo tee /var/local/edcb/EpgTimerSrv.ini > /dev/null << EOT
[SET]
EnableHttpSrv=1
EnableTCPSrv=1
RecEndMode=0
Data=1
HttpAccessControlList=+127.0.0.0/8,+10.0.0.0/8,+172.16.0.0/12,+192.168.0.0/16,+169.254.0.0/16,+100.64.0.0/10
CompatFlags=128
[BonDriver_LinuxMirakc.so]
Count=${TUNER_COUNT}
GetEpg=1
EPGCount=0
Priority=0
EOT

sudo tee /var/local/edcb/Common.ini > /dev/null << 'EOT'
[SET]
RecFolderPath0=/var/local/edcb/HttpPublic/video
RecFolderNum=1
EOT

sudo tee /var/local/edcb/EpgDataCap_Bon.ini > /dev/null << 'EOT'
[SET]
BonDriverDllPath=/usr/local/lib/edcb
EOT

sudo tee /etc/systemd/system/edcb.service > /dev/null << EOT
[Unit]
Description=EpgTimerSrv
After=network-online.target docker.service
Requires=docker.service
[Service]
Type=simple
User=$REAL_USER
WorkingDirectory=/usr/local/lib/edcb
ExecStart=/usr/local/bin/EpgTimerSrv
Restart=always
[Install]
WantedBy=default.target
EOT

sudo systemctl daemon-reload
sudo systemctl enable edcb
sudo systemctl start edcb

# ============================================================
# EPG 受信開始(EDCB起動後・KonomiTVインストール前に開始)
# バックグラウンドで実行するため、KonomiTVのインストール作業と
# 並行してEPGデータの受信が進む
# ============================================================
echo ""
echo "=== EPG 受信を開始します ==="
(
    echo "EDCBの起動を待機中..."
    for i in $(seq 1 60); do
        if curl -sf "http://127.0.0.1:5510/legacy/index.html" -o /dev/null 2>/dev/null; then
            echo "EDCB: 起動確認 (${i}秒)"
            break
        fi
        sleep 1
    done
    sleep 5

    echo "EPG取得リクエストを送信中..."
    CTOK=$(curl -s http://127.0.0.1:5510/legacy/index.html \
        | grep -o 'name="ctok" value="[^"]*"' | head -1 \
        | grep -o 'value="[^"]*"' | cut -d'"' -f2)
    RESULT=$(curl -s -X POST "http://127.0.0.1:5510/legacy/index.html" \
        -H "Content-Type: application/x-www-form-urlencoded" \
        -d "ctok=${CTOK}&epgcap=y" \
        | grep -o 'id="result">[^<]*')
    if echo "$RESULT" | grep -q "開始しました"; then
        echo "EPG受信: 開始しました"
    else
        echo "警告: EPG取得リクエストの応答: $RESULT"
    fi
) >> /var/log/dtv_epg_start.log 2>&1 &
echo "EPG受信をバックグラウンドで開始しました(KonomiTVインストール中に受信進行中)"
echo "進捗ログ: tail -f /var/log/dtv_epg_start.log"

# ============================================================
# 9. KonomiTV インストール
# ============================================================
echo "=== 9/10: KonomiTV インストール ==="

sudo npm install -g pm2

cd "$DTV_DIR"
curl -LO https://github.com/tsukumijima/KonomiTV/releases/latest/download/KonomiTV-Installer.elf
chmod +x KonomiTV-Installer.elf

sudo apt install -y expect

expect << 'EXPECT_EOF'
set timeout 300
log_file /tmp/konomi_install.log

spawn sudo ./KonomiTV-Installer.elf

expect -re {\[1/2/3/4/5\].*:} { send "1\r" }
expect -re {Docker \+ Docker Compose でインストールする.*:} { send "n\r" }
expect -re {フォルダのパス:} { send "/opt/KonomiTV\r" }
expect -re {\[EDCB/Mirakurun\].*:} { send "\r" }
expect -re {TCP API の URL:} { send "tcp://127.0.0.1:4510/\r" }
expect -re {\[FFmpeg.*\].*:} { send "FFmpeg\r" }
expect -re {録画フォルダのパス:} { send "/var/local/edcb/HttpPublic/video\r" }
expect -re {録画フォルダのパス:} { send "\r" }
expect -re {キャプチャ画像の保存先フォルダのパス:} { send "/var/local/edcb/HttpPublic/video\r" }
expect -re {キャプチャ画像の保存先フォルダのパス:} { send "\r" }

expect eof
EXPECT_EOF

KONOMI_CONFIG=$(find /opt /usr/local /home -name "config.yaml" -path "*/KonomiTV/*" 2>/dev/null | head -1)

if [ -z "$KONOMI_CONFIG" ]; then
    echo "KonomiTV の config.yaml が見つかりませんでした。パスを確認してください。"
else
    echo "KonomiTV config.yaml を更新中: $KONOMI_CONFIG"
    sudo sed -i 's/always_receive_tv_from_mirakurun: false/always_receive_tv_from_mirakurun: true/' "$KONOMI_CONFIG"
    sudo pm2 restart KonomiTV
fi

# ============================================================
# 10. 完了
# ============================================================

# KonomiTV アクセス URL を実際のローカルIPアドレスから収集
CONTAINER_NAME=$(hostname 2>/dev/null || echo "localhost")
KONOMI_URLS=()
KONOMI_URLS+=("https://my.local.konomi.tv:7000/|ローカルホスト")
while read -r addr iface; do
    IP=$(echo "$addr" | cut -d/ -f1)
    [ -z "$IP" ] || [ "$IP" = "127.0.0.1" ] && continue
    IP_DASH=$(echo "$IP" | tr '.' '-')
    KONOMI_URLS+=("https://${IP_DASH}.local.konomi.tv:7000/|${iface}")
done < <(ip -4 addr show | awk '/inet / {print $2, $NF}')

echo ""
echo "============================================================"
echo " セットアップ完了"
echo "============================================================"
echo ""
echo " サービス:"
echo "   mirakc:   http://127.0.0.1:40772/"
echo " EDCB Web:"
echo "   http://${CONTAINER_NAME}:5510/"
echo ""
echo " KonomiTV:"
for entry in "${KONOMI_URLS[@]}"; do
    URL="${entry%%|*}"
    LABEL="${entry##*|}"
    printf "   %-52s (%s)\n" "$URL" "$LABEL"
done
echo ""
echo " サービス操作:"
echo "   EDCB 再起動:      systemctl restart edcb"
echo "   KonomiTV 再起動:  pm2 restart KonomiTV"
echo "   mirakc 再起動:    cd ~/mirakc-custom && docker compose restart"
echo ""
echo " 番組情報の更新 (視聴していない時に実行):"
echo "   sudo bash ~/update-epg.sh"
echo ""
echo " EPG:"
echo "   確認:       ls /var/local/edcb/Setting/EpgData"
echo "   ログ:       tail -f /var/log/dtv_epg_start.log"
echo ""
echo " 注意: 1チューナー環境では、scan-services ジョブが起動時に"
echo "       チューナーを長時間占有するため無効化しています。"
echo "       番組情報を更新するには ~/update-epg.sh を手動で実行してください。"
echo "============================================================"

インストール後のEPG取得状況確認など(コンテナ内)

番組表データを取得中なので、下記でEPG取得状況を確認。tmpファイルが無くなればEPGの取得完了。

ls /var/local/edcb/Setting/EpgData

1チューナー環境で先にKonomiTVの動作確認を行いたいなら、チューナーがEPG受信中なので下記でEDCBを再起動してからKonomiTVで動作確認を。LXDコンテナを再起動してもよいかも。

# EDCB再起動
systemctl restart edcb

# KonomiTV再起動
pm2 restart KonomiTV

GPUパススルーする場合

セットアップ完了後、GPUをパスするーするならLXD-UIでGPUをコンテナにパススルーします。インテル環境の場合、これだけでGPUを利用した視聴が可能でした。

タイトルとURLをコピーしました