EPGStationの状態をサ終したLINE Notifyに代わってLINE Messege APIでLINEに送るシェルスクリプトです。
このシェルスクリプトはPiedHarrierさんのEPGS-to-LINEを参考に作り変えています。
(Qiitaの記事を書くのは初めてなので少し不自然な部分があるかもしれません。)
必要なもの
EPGStationが導入済のLinux PC
PHPが動く、かつドメインがありSSL付きで外部に公開されているサーバー
→ない場合の対処法あり
LINE Business ID作成時の
・メールアドレス
・SMS認証用の電話番号
環境
Ubuntu 24.04.2 LTS
EPGStation v2.10.0
Docker 28.4.0
docker-mirakurun-epgstationでepgstationを構築済み
LINE NotifyとLINE Nessege APIの違い
送信上限
LINE Notify → 1,000 回/時間
LINE Messege API → 200回/月
利用価格
LINE Notify → 無料
LINE Messege API → 無料 (送信上限を上げられる有料プランあり)
(上限に達するとエラーで送信できなくなります。)
プログラムを保存
以下のシェルスクリプトをrun.sh
という名前でローカルに保存してください。
必ず「UnixLF」で保存してください。CRLFなどだと正しく動きません。
#!/bin/bash
set -eu
########################################################################
#License:
#https://github.com/PiedHarrier/EPGS-to-LINE
########################################################################
########################################################################
# トークン
########################################################################
LINE_TOKEN="ここにチャネルアクセストークン貼付け"
LINE_TO="ここにユーザーIDまたはグループID貼付け"
########################################################################
# JSON エスケープ(簡易)
########################################################################
escape_json() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\r'/\\r}"
s="${s//$'\t'/\\t}"
printf '%s' "$s"
}
########################################################################
# epoch -> JST 表示
########################################################################
epoch_to_jst() {
local epoch_s="$1"
if [ -z "$epoch_s" ] || [ "$epoch_s" = "0" ]; then
printf '%s' "未設定"
return
fi
local utc
utc=$(date -u -d "@${epoch_s}" '+%Y-%m-%d %H:%M:%S')
date -d "${utc} +9 hours" '+%m/%d(%a) %H:%M'
}
########################################################################
# main
########################################################################
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <reserve|update|delete|prestart|prepfailed|start|end|recfailed|encod_end>" >&2
exit 1
fi
ret="$1"
CHANNELNAME="${CHANNELNAME:-放送局名なし}"
NAME="${NAME:-タイトル未設定}"
DESCRIPTION="${DESCRIPTION:-番組概要が指定されていません。}"
EXTENDED="${EXTENDED:-}"
STARTAT_MS="${STARTAT:-}"
ENDAT_MS="${ENDAT:-}"
DURATION_MS="${DURATION:-}"
ERROR_CNT="${ERROR_CNT:-N/A}"
DROP_CNT="${DROP_CNT:-N/A}"
SCRAMBLING_CNT="${SCRAMBLING_CNT:-N/A}"
start_epg_time=""
end_epg_time=""
if [ -n "$STARTAT_MS" ] && printf '%s' "$STARTAT_MS" | grep -qE '^[0-9]+$'; then
start_epg_time=$(( STARTAT_MS / 1000 ))
fi
if [ -n "$ENDAT_MS" ] && printf '%s' "$ENDAT_MS" | grep -qE '^[0-9]+$'; then
end_epg_time=$(( ENDAT_MS / 1000 ))
fi
startat="$(epoch_to_jst "${start_epg_time:-}")"
endat="$(epoch_to_jst "${end_epg_time:-}")"
if [ -n "$DURATION_MS" ] && printf '%s' "$DURATION_MS" | grep -qE '^[0-9]+$'; then
duration_minutes=$(( DURATION_MS / 60000 ))
else
duration_minutes="未設定"
fi
# ----- ここから printf -v を使わない組み立て -----
case "$ret" in
reserve)
content=$(printf '%s\n%s\n%s\n%s ~ %s %s分\n%s\n%s' \
"✅ 予約追加" "$NAME" "$CHANNELNAME" "$startat" "$endat" "$duration_minutes" "$DESCRIPTION" "$EXTENDED")
;;
delete)
content=$(printf '%s\n%s\n%s' "💨 予約削除" "$NAME" "$CHANNELNAME")
;;
update)
content=$(printf '%s\n%s\n%s\n%s ~ %s %s分' \
"🔁 予約更新" "$NAME" "$CHANNELNAME" "$startat" "$endat" "$duration_minutes")
;;
prestart)
content=$(printf '%s\n%s\n%s' "🔷 録画準備開始" "$NAME" "$CHANNELNAME")
;;
prepfailed)
content=$(printf '%s\n%s\n%s' "💥 録画準備失敗" "$NAME" "$CHANNELNAME")
;;
start)
content=$(printf '%s\n%s\n%s\n%s' "⏺ 録画開始" "$NAME" "$CHANNELNAME" "$startat")
;;
encod_end)
content=$(printf '%s\n%s\n%s' "⏹ エンコード終了" "$NAME" "$CHANNELNAME")
;;
end)
content=$(printf '%s\n%s\n%s\n%s ~ %s %s分\n%s' \
"⏹ 録画終了" "$NAME" "$CHANNELNAME" "$startat" "$endat" "$duration_minutes" "エラー: ${ERROR_CNT}, ドロップ: ${DROP_CNT}, スクランブル: ${SCRAMBLING_CNT}")
;;
recfailed)
content=$(printf '%s\n%s\n%s' "❌ 録画失敗" "$NAME" "$CHANNELNAME")
;;
*)
echo "未知のコマンド: $ret" >&2
exit 1
;;
esac
# ----- 終わり -----
escaped_text=$(escape_json "$content")
json="{\"to\":\"${LINE_TO}\",\"messages\":[{\"type\":\"text\",\"text\":\"${escaped_text}\"}]}"
curl -sS -X POST "https://api.line.me/v2/bot/message/push" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${LINE_TOKEN}" \
-d "${json}" || {
echo "LINE message push failed" >&2
exit 1
}
exit 0
LINE Messaging APIの登録とチャネルの作成
LINE Messaging APIを使うためには、まずLINE Official Accountを作成し、Messaging APIを有効化します。これでAPIチャネルが作成されます。
LINE Official Accountを作成
ブラウザで https://manager.line.biz/ にアクセスし、下にある[アカウントを登録]を押してLINEアカウントまたはメールアドレスでアカウントを登録します。
フォームに必要な情報を入力(名前、メールなど)して、LINE Official Accountを作成します。
Messaging APIを有効化
LINE Official Account Manager https://manager.line.biz/ にログインし、対象のアカウントを選択。
[設定(右上にあります)] > [Messaging API] に移動し、[Messaging API を有効化] をクリック。
プロバイダを選択(初めての場合は新規作成)。これでMessaging APIチャネルが自動的に作成されます。
初めての場合、開発者アカウントの作成を求められることがあります。必要な情報を入力して進めます。
また、SMS認証を求められることもあります。認証して進めます。
LINE Developers Consoleにログイン:
https://developers.line.biz/console/ にアクセスし、同じLINEアカウントでログイン。
作成したプロバイダとチャネルが表示されていることを確認します。
これでLINE Messege APIを使う準備完了です。次にアクセストークンを取得します。
チャネルアクセストークンの取得
スクリプトがAPI呼び出し時の認証に使う長期アクセストークンを取得します。
LINE Developers Console で、作成したMessaging APIチャネルを選択。
[Messaging API] タブに移動。
チャネルアクセストークン(長期)の欄で[発行]をクリックし、トークンを発行します。
発行されたトークンをコピーし、run.shの
LINE_TOKEN="ここにチャネルアクセストークン貼付け"
に貼り付けます (例: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")。
このトークンは機密情報なので、安全に保管しましょう。
セキュリティ向上のため、[セキュリテイ設定]タブからIP制限を設定することもできます。
ユーザーIDまたはグループIDの取得
スクリプトのLINE_TOに使うIDです。プッシュメッセージを送る相手(自分自身やグループ)のIDが必要です。IDはWebhookイベントから取得します。
ボットを友達追加
LINEアプリで、作成したLINE Official Accountを友達追加します(Consoleの[Messaging API] タブにQRコードがあります)。
Webhookを設定してIDを取得
Webhookは、ユーザーがボットにメッセージを送ったときにイベントを受け取るためのURLです。これでuserIdを取得できます。
Webhook URLの設定
LINE Developers Console > チャネル > [Messaging API] タブ > [Webhook設定] > [編集] をクリック。
自分のサーバーのURLを入力(例: https://example.com/webhook/api.php )。SSL証明書が必要です(無料のLet's Encryptなどで大丈夫です)。
サーバーがない場合は、後述の「自分のサーバーがない場合の対処法」を参照してください。
最初は同一ディレクトリ内にIDを記述するphpにしたのですが、何故か動かなかったのでサーバーに送られてきたIDをDiscordのWebhookに送信するphpを置き、そこに送信しました。
IDをDiscord WebHookに送信するPHP
<?php
// DiscordのWebhook URLを設定(WebHook URLを貼り付けてください。)
$discordWebhookUrl = 'https://discord.com/api/webhooks/XXXXXXXXXX/XXXXXXXX';
// LINE Webhookからのリクエストを処理
$input = file_get_contents('php://input');
$data = json_decode($input, true);
// イベントが存在する場合
if (isset($data['events']) && count($data['events']) > 0) {
// 最初のイベントからユーザーIDを取得(通常、メッセージイベントの場合)
$event = $data['events'][0];
if (isset($event['source']['userId'])) {
$userId = $event['source']['userId'];
// DiscordにユーザーIDを送信するメッセージを作成
$message = [
'content' => "届いたユーザーID: $userId"
];
// cURLを使ってDiscord WebhookにPOST
$ch = curl_init($discordWebhookUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
// レスポンスをログ出力(オプション)
error_log("Discord response: $response");
}
}
// LINE APIからのリクエストに200 OKを返す
http_response_code(200);
echo 'OK';
?>
元のテキストに保存するphpも載せておきます。
<?php
// POSTデータを読み込む
$json = file_get_contents('php://input');
$data = json_decode($json, true);
// データが正しくデコードされたか確認
if ($data && isset($data['events'][0]['source']['userId'])) {
$userId = $data['events'][0]['source']['userId'];
// 同一ディレクトリのid.txtにユーザーIDを記述(上書きモード)
file_put_contents('id.txt', $userId);
// デバッグ用にデータをログ出力(本番では削除またはログファイルに)
error_log(print_r($data, true));
}
// LINEにOKを返す(200 OK)
http_response_code(200);
echo 'OK';
?>
自分のサーバーがない場合の対処法
自分のサーバーがない場合はPythonなどで簡易的なサーバーを立て、Ngrokなどで外部公開しましょう。
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
print(data) # ここにuserIdなどが表示される
return 'OK', 200
if __name__ == '__main__':
app.run()
Secure Shere NetとCloudflare Tunnelの無料プランではエラーになり送信できませんでした。
[更新] をクリックし、[検証] でテスト。
このように表示されたら成功です。
このように表示された場合は正しく通信できていません。URLを確認してやり直してください。
正しく検証できたら[Webhookの利用] をクリックしてWebHookを有効化します。
LINEアプリからボットにメッセージを送る
LINEアプリからボットにメッセージを送ります。(例: "hello")
サーバーにIDが届き、ユーザーID(例: "U1234567890abcdef...")が表示されます。
発行されたトークンをコピーし、run.shの
LINE_TO="ここにユーザーIDまたはグループID貼付け"
に貼り付けます (例: "U1234567890abcdef...")。
スクリプトの設置と設定
run.shをEPGStationのルートディレクトリ(%ROOT%)の下のconfigディレクトリに保存
(例: /path/to/epgstation/config/run.sh)
。
必ず「UnixLF」で保存してください。CRLFなどだと正しく動きません。
実行権限を付与
chmod +x /path/to/epgstation/config/run.sh
docker-mirakurun-epgstationで構築している場合はこの先の手順を行っても正しく動作しない場合があります。
docker-mirakurun-epgstationで構築している場合は
https://qiita.com/saturi/items/6f8d9d2524a8d1e92095#docker-mirakurun-epgstationで構築している場合
も参考にしてください。
EPGStationでのConfig設定
EPGStationのconfigファイルに以下を追記します。
reserveNewAddtionCommand: '/bin/sh %ROOT%/config/run.sh reserve'
reserveUpdateCommand: '/bin/sh %ROOT%/config/run.sh update'
reservedeletedCommand: '/bin/sh %ROOT%/config/run.sh delete'
recordingPreStartCommand: '/bin/sh %ROOT%/config/run.sh prestart'
recordingPrepRecFailedCommand: '/bin/sh %ROOT%/config/run.sh prepfailed'
recordingStartCommand: '/bin/sh %ROOT%/config/run.sh start'
recordingFinishCommand: '/bin/sh %ROOT%/config/run.sh end'
recordingFailedCommand: '/bin/sh %ROOT%/config/run.sh recfailed'
encodingFinishCommand: '/bin/sh %ROOT%/config/run.sh encod_end'
設定項目名 | イベント発生タイミング |
---|---|
reserveNewAddtionCommand | 新しい予約が追加されたとき |
reserveUpdateCommand | 既存の予約が更新されたとき |
reservedeletedCommand | 予約が削除されたとき |
recordingPreStartCommand | 録画開始直前(チューナー準備中) |
recordingPrepRecFailedCommand | 録画準備に失敗したとき |
recordingStartCommand | 録画が開始されたとき |
recordingFinishCommand | 録画が完了したとき |
recordingFailedCommand | 録画中に失敗したとき |
encodingFinishCommand | エンコードが完了したとき |
EPGStationを再起動して適用
停止
cd ~/git/docker-mirakurun-epgstation (Pathは自分の環境に合わせて変えてください。)
docker compose down
起動
cd ~/git/docker-mirakurun-epgstation (Pathは自分の環境に合わせて変えてください。)
docker compose up -d
簡易テスト
プログラムが正しく動くかテストします。
以下コマンドを実行し、LINEアプリに正しくメッセージが来るか確認してください。
cd /path/to/epgstation/config (Pathは自分の環境に合わせて変えてください。)
bash run.sh reserve
あとはEPGStationのWebUIから録画予約などをしてみて正しくメッセージが届けば成功です。
docker-mirakurun-epgstationで構築している場合
dockerコンテナ内にEPGStationがある場合、上記の方法ではEPGStation側からrun.shを呼び出せません。
(つまり手動でrun.shを打つと動くのにEPGStationでrun.shを呼び出そうとしてもrun.shが呼び出されないという問題が起こります。)
docker内からrun.shを呼び出す方法
docker psを実行してコンテナ名を確認
docker psを実行してコンテナ名を確認します。
euser@SV-REC01:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
697917e7b531 docker-mirakurun-epgstation-epgstation "npm start" 4 hours ago Up 4 hours 0.0.0.0:8888-8889->8888-8889/tcp, [::]:8888-8889->8888-8889/tcp epgstation-v2
a99c725ceacb mariadb:10.5 "docker-entrypoint.s…" 4 hours ago Up 4 hours 3306/tcp mysql-epgstation-v2
61b0245f5ec9 chinachu/mirakurun "docker-entrypoint.s…" 4 hours ago Up 4 hours 0.0.0.0:9229->9229/tcp, [::]:9229->9229/tcp, 0.0.0.0:40772->40772/tcp, [::]:40772->40772/tcp mirakurun
一番右側のNAMES
の部分にある名前がコンテナ名です。(この場合は[epgstation-v2]です。)
docker cpでrun.shをコピー(お手軽)
この方法は簡単ですが、dockerの再起動 (もしくはPC自体の再起動)でrun.shが消えてしまいます。
永続化したい場合はこの次の方法をおすすめします。
ホストPCからコンテナ内にコピー
docker cp ./run.sh epgstation-v2:/app/config/run.sh
コンテナ内で実行権限を付与
docker exec -it epgstation-v2 chmod +x /app/config/run.sh
これで EPGStation から呼び出せるようになります。
メモ:
/app
は epgstation の %ROOT%
にあたるディレクトリです
docker-compose.yml にマウント設定を入れる(永続化・おすすめ)
この方法であれば再起動しても消えないのでこの方法がおすすめです。
docker-compose.ymlにマウント設定をする
書き方はホストPCのPath:Docker内のPath
です。
epgstation:
container_name: epgstation-v2
build:
context: "./epgstation"
dockerfile: "debian.Dockerfile"
volumes:
#この下の行を追加 (Pathは環境に合わせて変えてください。) (相対パスでも動くはずですが、トラブルを避けるために絶対パスにしています。)
- /home/euser/git/docker-mirakurun-epgstation/epgstation/config/run.sh:/app/config/run.sh # ← ☆追加
- /etc/localtime:/etc/localtime:ro
- ./epgstation/config:/app/config
- ./epgstation/data:/app/data
- ./epgstation/thumbnail:/app/thumbnail
- ./epgstation/logs:/app/logs
- /mnt/data1:/app/recorded
こうすると、ホスト側の ./config フォルダに置いた run.sh が 自動でコンテナの /app/config と同期されます。
→ 以降はホストで run.sh を編集するだけでコンテナ内に反映されます。
実行権限を付与
chmod +x /path/to/epgstation/config/run.sh
ホスト側で実行権限をつけるとDockerにも反映されます。
Docker再起動して適用
停止
cd ~/git/docker-mirakurun-epgstation (Pathは自分の環境に合わせて変えてください。)
docker compose down
起動
cd ~/git/docker-mirakurun-epgstation (Pathは自分の環境に合わせて変えてください。)
docker compose up -d
送信テスト
以下を実行し正しくメッセージが届くか確認してください。
docker exec -it epgstation-v2 /bin/bash /app/config/run.sh reserve
届く場合は成功です。EPGStationのWebUI側から操作して正しく動くか確認してください。
届かない場合はrun.shで使われているcURLがDockerコンテナにインストールされていない可能性があります。以下コマンドでcURLをインストールしてください。
#Dockerコンテナへ入る
docker exec -it epgstation-v2 /bin/bash
#パッケージリストの更新
apt update
#cURLのインストール
apt install curl
再度以下を実行し正しくメッセージが届くか確認してください。
docker exec -it epgstation-v2 /bin/bash /app/config/run.sh reserve
メッセージが届けば成功です。EPGStationのWebUI側から操作して正しく動くか確認してください。
この方法ではDockerコンテナ・PCの再起動でcURLが消えてしまい、run.shを動かすには再度インストールが必要です。永続化したい場合はDockerファイルにapt-get install curlを追加し、ビルドしてください。やり方→ https://qiita.com/saturi/items/6f8d9d2524a8d1e92095#dockerコンテナのcurlのインストールと再ビルド
詳細テスト
cd /path/to/epgstation/config (Pathは自分の環境に合わせて変えてください。)
# 予約追加イベントをテスト
bash run.sh reserve
# 予約更新イベントをテスト
bash run.sh update
# 録画開始をテスト
bash run.sh start
# 録画終了をテスト
bash run.sh end
# エンコード終了をテスト
bash run.sh encod_end
#EPGStationから渡される変数をテストしたい場合
NAME="テスト番組" CHANNELNAME="TEST" DESCRIPTION="テスト説明文" STARTAT=1728000000 ENDAT=1728003600 DURATION=3600000 bash run.sh start
DockerコンテナのcURLのインストールと再ビルド
Dockerコンテナ・PCの再起動後もcURLが使えるようにDockerfileを書き換えてからDockerコンテナを再ビルドします。
再ビルドではデータは消えないはずですが、バックアップを取っておくことをおすすめします。
EPGStationを停止
cd ~/git/docker-mirakurun-epgstation (Pathは自分の環境に合わせて変えてください。)
docker compose down
Dockerfileは以下のようになっていると仮定して進めます。この部分は環境によって変えてください。
DockerfileのPath/home/euser/git/docker-mirakurun-epgstation/epgstation/debian.Dockerfile
FROM l3tnun/epgstation:master-debian
ENV DEV="make gcc git g++ automake curl 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 $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 && \
\
#ffmpeg build
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 && \
\
# 不要なパッケージを削除
apt-get -y remove $DEV && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /tmp/*
これを以下のように変更します。
FROM l3tnun/epgstation:master-debian
ENV DEV="make gcc git g++ automake curl 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 $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 && \
\
#ffmpeg build
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 && \
\
# 不要なパッケージを削除
apt-get -y remove $DEV && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /tmp/* && \ #☆この行から変更あり
\
# --- 最終イメージに curl と httpsを使うための ca-certificates を残す ---
apt-get update && \
apt-get -y install --no-install-recommends curl ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
プロジェクトルートフォルダに移動し、ビルド実行
cd ~/git/docker-mirakurun-epgstation (Pathは自分の環境に合わせて変えてください。)
docker compose build --no-cache epgstation
コマンドは「epgstation」です。「epgstation-v2」ではありません。
実行できない場合はdocker-compose.ymlにかかれている名前を使用してください。
services:
epgstation: # ←この部分
container_name: epgstation-v2
...
全文
services:
mirakurun:
container_name: mirakurun
build:
context: Mirakurun
dockerfile: docker/Dockerfile
image: chinachu/mirakurun
cap_add:
- SYS_ADMIN
- SYS_NICE
ports:
- "40772:40772"
- "9229:9229"
volumes:
- /etc/localtime:/etc/localtime:ro
- ./Mirakurun/config/:/app-config/
- ./Mirakurun/data/:/app-data/
environment:
TZ: "Asia/Tokyo"
devices:
- /dev/px4video0:/dev/px4video0
- /dev/px4video1:/dev/px4video1
- /dev/px4video2:/dev/px4video2
- /dev/px4video3:/dev/px4video3
- /dev/bus:/dev/bus
restart: always
logging:
driver: json-file
options:
max-file: "1"
max-size: 10m
mysql:
container_name: mysql-epgstation-v2
image: mariadb:10.5
volumes:
- mysql-db:/var/lib/mysql
environment:
MYSQL_USER: epgstation
MYSQL_PASSWORD: epgstation
MYSQL_ROOT_PASSWORD: epgstation
MYSQL_DATABASE: epgstation
TZ: "Asia/Tokyo"
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --performance-schema=false --expire_logs_days=1 # for mariadb
restart: always
logging:
options:
max-size: "10m"
max-file: "3"
epgstation: # ← !!!この部分!!!
container_name: epgstation-v2
build:
context: "./epgstation"
dockerfile: "debian.Dockerfile"
volumes:
- /home/euser/git/docker-mirakurun-epgstation/epgstation/config/run.sh:/app/config/run.sh
- /etc/localtime:/etc/localtime:ro
- ./epgstation/config:/app/config
- ./epgstation/data:/app/data
- ./epgstation/thumbnail:/app/thumbnail
- ./epgstation/logs:/app/logs
- /mnt/data1:/app/recorded
environment:
TZ: "Asia/Tokyo"
depends_on:
- mirakurun
- mysql
ports:
- "8888:8888"
- "8889:8889"
#user: "1000:1000"
restart: always
volumes:
mysql-db:
driver: local
必ず「--no-cache」をつけてください。つけないと古いレイヤーが使われ変更が反映されない場合があります。
ビルドが終わったらEPGStationを立ち上げます。
cd ~/git/docker-mirakurun-epgstation (Pathは自分の環境に合わせて変えry)
docker compose up -d
Dockerコンテナ内でcURLが使えるか確認
docker exec -it epgstation-v2 bash
curl --version
トラブルシューティング
ありがちなトラブルです。
無効なオプションです。と表示される
bash run.sh
: 無効なオプションです set: 使用法: set [-abefhkmnptuvxBCEHPT] [-o option-name] [--] [-] [arg ...] run.sh: 行 5: $'\r': コマンドが見つかりません run.sh: 行 12: $'\r': コマンドが見つかりません run.sh: 行 18: 予期しないトークン $'{\r'' 周辺に構文エラーがあります 'un.sh: 行 18: escape_json() {
このエラーの原因は基本的にCRLFで保存したことによる「改行コードが Windows(CRLF) のまま(\r が残っている)」または「ファイル先頭に UTF-8 BOM が混入している」です。
対処法 → UnixLFで保存し直してください。
EPGStationから呼び出せない。かつEPGStationのSystemログに以下のようなものが残っている。
[2025-10-04T14:12:50.630] [INFO] system - execute cmd: /bin/bash /app/config/run.sh delete
[2025-10-04T14:12:50.664] [ERROR] system - failed: /bin/bash /app/config/run.sh delete. exit: 1
exit: 1
は「EPGStation がスクリプトを呼べているがスクリプトが途中で失敗した」ことを意味します。
このエラーはcURLがインストールされていない環境で起きやすいです。cURLがインストールされているか確認し、されていなければインストールしてください。
インストール方法
#Dockerコンテナへ入る(dockerを使っていない場合はスキップ)
docker exec -it epgstation-v2 /bin/bash
#パッケージリストの更新
apt update
#cURLのインストール
apt install curl
exit: 2
と表示されてプログラムが動かない(以下のようなエラーログが出る。)
euser@SV-REC01:~/git/docker-mirakurun-epgstation$ docker exec -it epgstation-v2 /bin/sh -x /app/config/run.sh reserve || echo "exit:$?"
+ set -eu +
+ LINE_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ LINE_TO=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ [ 1 -ne 1 ]
+ ret=reserve
+ CHANNELNAME=放送局名なし
+ NAME=タイトル未設定
+ DESCRIPTION=番組概要が指定されていません。
+ EXTENDED=
+ STARTAT_MS=
+ ENDAT_MS=
+ DURATION_MS=
+ ERROR_CNT=N/A
+ DROP_CNT=N/A
+ SCRAMBLING_CNT=N/A
+ start_epg_time=
+ end_epg_time=
+ [ -n ]
+ [ -n ]
+ epoch_to_jst
+ local epoch_s=
+ [ -z ]
+ printf %s 未設定
+ return
+ startat=未設定
+ epoch_to_jst
+ local epoch_s=
+ [ -z ]
+ printf %s 未設定
+ return
+ endat=未設定
+ [ -n ]
+ duration_minutes=未設定
+ printf -v content %s\n%s\n%s\n%s ~ %s %s分\n%s\n%s ✅ 予約追加 タイトル未設定 放送局名なし 未設定 未設定 未設定 番組概要が指定されていません。 /app/config/run.sh: 98: printf: Illegal option -v exit:2
exit: 2
とは シェルスクリプト自体が呼び出せているが、中でエラーになって異常終了している。ことを意味します。
これはrun.shの記述ミスが主な原因です。上記のエラーの場合はprintf -vというオプションが使えなかったということを意味します。
dockerなどの軽量環境では使えないオプションがあるようです。(printf -vなど)なのでこのページのrun.shはprintf -vを使わないように修正済ですが、同様に環境によって他の部分でエラーがでる可能性があります。その場合はrun.shを修正してください。
追記
APIの利用上限に一瞬で到達してしまったので、録画開始とエンコード開始・終了あと各種異常以外は送信しないようにしたほうがいいかもしれません。
設定項目名 | イベント発生タイミング |
---|---|
reserveNewAddtionCommand | 新しい予約が追加されたとき |
reserveUpdateCommand | 既存の予約が更新されたとき |
reservedeletedCommand | 予約が削除されたとき |
recordingPreStartCommand | 録画開始直前(チューナー準備中) |
recordingPrepRecFailedCommand | 録画準備に失敗したとき |
recordingStartCommand | 録画が開始されたとき |
recordingFinishCommand | 録画が完了したとき |
recordingFailedCommand | 録画中に失敗したとき |
encodingFinishCommand | エンコードが完了したとき |
参考サイト