ビデオデッキが誕生して何年経つのでしょうか。

その間、VHSとベータの戦争があり、DVDが誕生し、BlueRayやHDDレコーダーへと進化を遂げ続けたTV録画環境

ついに最終話を迎えつつありますよ

我が家は、HDDレコーダを捨て、テレビサーバーに進化することにしました。

と、大げさなこと書いてますが、要するにTS抜きサーバー環境を構築しようって話しです。

HDDレコーダーも7年ぐらい経ったので、買い替えの時期だなーと思いつつ、市場を見ると、代わり映えのない機器ばかりの割には高いなと

TVは、TVerで良いのじゃないかと思うのですが、家族でめっちゃ使う人が、必須アイテムらしいので解決することにしました。

システム構成としては、電気代のかから無さそうなPCに、TS抜きチューナーを複数付けて、MirakurunとEPGStationで運用しようかと

出先からも視聴出来るように、nginxで公開し、Google認証機能も付けます。

ということで、行ってみよう


必要なハードウェア

PC

FUJITSU ESPRIMO Q556/R Core i5 のやつ メモリ 8GBぐらい 小さくってちゃんと4コアでお手頃です <- 当然、ヤフオクでゲットする

こいつは、nvmeなSSDとSATAなSSDを同時に載せれますよ。録画データの保存とOS領域を分けておきたいよね

キーボードを接続しないと起動時にうるさいので、BIOSでkeyboard error を無効にしましょうね

最初、RapberryPI4で構築したんですが、エンコードパワーが足りないので、Intelアーキテクチャに頼りました

TS抜きチューナと変換コネクタ

MyGica T230C x 必要なだけ

変換コネクタ PAL型 オス - F型

PX-Q1UD を使ってみたのですが、ドロップ酷くって使えませんでした。良い凡ドライバを見つけれなかったか、Ubuntuとの相性か?

ICカードリーダ

Amazonで適当な物

B-CASカード

どうにかして手に入れる

 

必要なスキル

Ubuntuのセットアップ

Dockerの簡単な使い方

Linuxでのテキスト編集

パーミッションの変更の仕方

nginxを構築出来る知識

httpsなサイトを構築出来る知識

 


1.Ubuntuインストール

とにかく、サーバーを構築しないと話が進みませんよね

こんな記事よんでる人なら、わかるでしょなことは、書きませんよ。厳し目に行きます。

とっとと、PCにUbuntu 24.04をインストールして下さい。

出来ればLVMで論理ボリュームを作成しておきましょうね。<- 録画領域を後で増やしたい人は、LVMが良いですね

SSHとか、日本語とかtimezoneを良い感じに設定しましょう。

わからない人は、chatgptに聞いて下さい。


2.T230c用ドライバ インストール

これは、簡単で、armでもx64でも同じ物を使用出来ます。

 

git clone https://github.com/osmc/dvb-firmware-osmc.git
cp dvb-firmware-osmc/{dvb-demod-si2168-d60-01.fw,dvb-tuner-si2141-a10-01.fw} /lib/firmware/


3.ICカードリーダの確認

ICカードリーダを用意していますが、B-CASが認識出来ているかチェックします。

ダメなら、もっとまともな ICカードリーダを購入しましょう。

 

apt install -y pcscd libpcsclite-dev libccid pcsc-tools

pcsc_scan

> Japanese Chijou Digital B-CAS Card (pay TV) と出ればOK


4.チャンネルを作っていきまーす

ここは、結構、長旅になります。

 

まず、スクリプトを作成します。

mkchconf.sh

#!/bin/sh
for ch in `seq 1 3`; do
        fr=`expr \( $ch - 1 \) \* 6 + 93`
        echo "[${ch}]"
        echo "\\tFREQUENCY = ${fr}000000"
        echo "\\tSYMBOL_RATE = 5274000"
        echo "\\tDELIVERY_SYSTEM = DVBC/ANNEX_A"
        echo ""
done
for ch in `seq 4 12`; do
        if [ $ch -lt 8 ]; then
                fr=`expr \( $ch - 4 \) \* 6 + 173`
        else
                fr=`expr \( $ch - 8 \) \* 6 + 195`
        fi
        echo "[${ch}]"
        echo "\\tFREQUENCY = ${fr}000000"
        echo "\\tSYMBOL_RATE = 5274000"
        echo "\\tDELIVERY_SYSTEM = DVBC/ANNEX_A"
        echo ""
done
for ch in `seq 13 62`; do
        fr=`expr \( $ch - 13 \) \* 6 + 473`
        echo "[${ch}]"
        echo "\\tFREQUENCY = ${fr}000000"
        echo "\\tSYMBOL_RATE = 5274000"
        echo "\\tDELIVERY_SYSTEM = DVBC/ANNEX_A"
        echo ""
done
for ch in `seq 13 22`; do
        if [ $ch -lt 22 ]; then
                fr=`expr \( $ch - 13 \) \* 6 + 111`
        else
                fr=`expr \( $ch - 22 \) \* 6 + 167`
        fi
        echo "[C${ch}]"
        echo "\\tFREQUENCY = ${fr}000000"
        echo "\\tSYMBOL_RATE = 5274000"
        echo "\\tDELIVERY_SYSTEM = DVBC/ANNEX_A"
        echo ""
done
for ch in `seq 23 63`; do
        if [ -n "$1" -a $ch -gt 23 -a $ch -lt 28 ]; then
                fr=`expr \( $ch - 24 \) \* 6 + 233`
        else
                fr=`expr \( $ch - 23 \) \* 6 + 225`
        fi
        echo "[C${ch}]"
        echo "\\tFREQUENCY = ${fr}000000"
        echo "\\tSYMBOL_RATE = 5274000"
        echo "\\tDELIVERY_SYSTEM = DVBC/ANNEX_A"
        echo ""
done

 

catvrec.sh

#!/bin/sh

DEVNO=$1

for ch in `seq 1 62`; do
        dvbv5-zap -C JP -a ${DEVNO} -c channels.conf -r -P ${ch} -t 4 -o ${ch}.ts
done
for ch in `seq 13 63`; do
        dvbv5-zap -C JP -a ${DEVNO} -c channels.conf -r -P C${ch} -t 4 -o C${ch}.ts
done

 

chlist.sh

#!/bin/sh
for ch in `seq 1 62`; do
        if [ -f ${ch}.ts ]; then
                ./tschput ${ch}.ts | nkf --ic=CP932 | sed -e "s/^ */${ch}\t/;s/  */\t/g"
        fi
done
for ch in `seq 13 63`; do
        if [ -f C${ch}.ts ]; then
                ./tschput C${ch}.ts | nkf --ic=CP932 | sed -e "s/^ */C${ch}\t/;s/  */\t/g"
        fi
done

 

mkchyml.sh

#!/bin/sh
TTYPE=$1
lch=""
while read ch onid tsid sid scramble type channel; do
        if [ ! "$ch" = "$lch" ]; then
                echo "- name: $channel"
                echo "  type: $TTYPE"
                echo "  channel: '${ch}'"
                echo ""
        fi
        lch="$ch"
done

 

 

dvb-toolsインストール
apt-get install -y dvb-tools

 

ザッピングしてチューナーからチャンネルを取得

 

channels.confの作成
./mkchconf.sh > channels.conf

 

dvb_channel.confの作成
チャンネルスキャン -> dvb_channel.conf が作成される
./dvbv5-scan -C JP -a 0 -N channels.conf

 

チャネル情報取得 4秒 受信した x.TS 達が出来る
./catvrec.sh 0

 

さらに、TS 達から 日本語(SJIS)へ抽出する
apt install nkf
wget http://www.areanine.gr.jp/~nyano/archives/nikki/tschput.c
gcc tschput.c -o tschput

 

./chlist.sh > channels.txt

 

 

最後にMirakurun用のchannels.yml を作成する。
./mkchyml.sh GR < channels.txt > channels.yml

 

この channels.yml は、全て GR として記録されているし、スクランブルされていて見れないチャンネルも含まれているので、適宜編集して自分に合う物にしておこう。

例えば、見れないチャンネルは、削除し、BSの場合、GRのとこをBSに変更等です


5.tunner.ymlの作成

TS抜きチューナーの登録用ファイルです。

 

- name: T230_1
  types:
    - BS
    - GR
  command: dvbv5-zap -C JP -a 0 -c /app-config/channels.conf -r -P <channel>
  dvbDevicePath: /dev/dvb/adapter0/dvr0
  decoder: arib-b25-stream-test   

- name: T230_2
  types:
    - BS
    - GR
  command: dvbv5-zap -C JP -a 1 -c /app-config/channels.conf -r -P <channel>
  dvbDevicePath: /dev/dvb/adapter1/dvr0
  decoder: arib-b25-stream-test   

 

この例では、2つのチューナーを接続する場合です。

1つの場合、2つ目は、削除し、3つ以上だと、T230_3 等というように増やしましょう。


6.mirakurun と epgstation のセットアップ

まず、docker compose を使用します。

Ubuntuにセットアップしておいて下さい。

 

セットアップだ

cd ~
git clone https://github.com/l3tnun/docker-mirakurun-epgstation.git
cd docker-mirakurun-epgstation
cp docker-compose-sample.yml docker-compose.yml
cp epgstation/config/enc.js.template epgstation/config/enc.js
cp epgstation/config/config.yml.template epgstation/config/config.yml
cp epgstation/config/operatorLogConfig.sample.yml epgstation/config/operatorLogConfig.yml
cp epgstation/config/epgUpdaterLogConfig.sample.yml epgstation/config/epgUpdaterLogConfig.yml
cp epgstation/config/serviceLogConfig.sample.yml epgstation/config/serviceLogConfig.yml
docker compose run --rm -e SETUP=true mirakurun

 

設定をコピーだ

cp channels.conf ~/docker-mirakurun-epgstation/mirakurun/conf/.
cp tuners.yml ~/docker-mirakurun-epgstation/mirakurun/conf/.
cp channels.yml ~/docker-mirakurun-epgstation/mirakurun/conf/channels.yml


7.実行だ

起動方法

cd ~/docker-mirakurun-epgstation

docker compose up -d

 

停止方法

cd ~/docker-mirakurun-epgstation

docker compose down


8.アクセスだ

http://サーバーIP:8888/

 

見れたら、おめでとうだ

番組表は、数分間待つと出てくるはず

番組表が更新されれば、放送中も見れるようになります。

 

そうそう、動画の格納場所ですが、docker-compose.yml に指定されているので、これを修正します。

version: '3.7'
services:

    epgstation:

        volumes:

            - /video:/app/recorded

 


9.nginxでリバースプロキシして、google認証で守られた epgstation を外から見れるようにしよう

まず、google認証するのは、oauth2-proxy でやります。

こいつは、nginxにgoogle認証機能を付加する代理認証proxyです。

動作させるのは簡単で、oauth2_proxy.cfg を作成して、このファイルを引数に起動すれば良いだけです。

 

google認証を行うには、認証用のOAuth2.0 キーが必要です。

なので、google cloud の APIとサービス - 認証情報 - OAuth 2.0 クライアント ID(ウェブアプリケーション用) とシークレットを取得して下さい。

承認済みの JavaScript 生成元:https://[外用ドメイン]

承認済みのリダイレクト URI:https://[外用ドメイン]/oauth2/callback

外用ドメインは、ドメイン屋さんで買って下さい。

 

google認証サンプル

/etc/oauth2_proxy/oauth2_proxy.cfg

http_address = "127.0.0.1:4180"
redirect_url = "https://[外用ドメイン]/oauth2/callback"
upstreams = ["http://localhost:4180/"]
client_id = "hogehoge.apps.googleusercontent.com"
client_secret = "hogehogesecret"
oidc_issuer_url = "https://[外用ドメイン]"
provider = "google"
cookie_secret = "hogehogerandom"
authenticated_emails_file = "/etc/oauth2_proxy/epgemail.txt"

 

cookie_secretは、コマンドで出力しても良いですし、どこぞのサイトで24バイトで出力させた物を使えば良いです。

head -c 16 /dev/urandom | base64

 

自分のサーバーで、TV視聴するのに、誰でもかれでも観れるのは良くないですよね。

なので、以下のファイルを作って、必要なアカウントを絞り込みましょう

認証ファイルのサンプル

/etc/oauth2_proxy/epgemail.txt

このメールアドレスはスパムボットから保護されています。閲覧するにはJavaScriptを有効にする必要があります。

このメールアドレスはスパムボットから保護されています。閲覧するにはJavaScriptを有効にする必要があります。

 

起動方法

nohup oauth2-proxy --config /etc/oauth2_proxy/oauth2_proxy.cfg &

こいつを Docker化しておくのがお勧めです。

 

Dockerfile

FROM ubuntu:24.04
EXPOSE 4180/tcp
RUN apt -y update
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Tokyo
RUN apt install -y tzdata
RUN apt install -y wget vim
RUN wget https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.5.1/oauth2-proxy-v7.5.1.linux-amd64.tar.gz
RUN tar -xvf oauth2-proxy-v7.5.1.linux-amd64.tar.gz

docker-compose.yml

services:
 oauth:
  build:
   context: .
  ports:
    - "0.0.0.0:4180:4180"    
  tty: true
  command: /oauth2-proxy-v7.5.1.linux-amd64/oauth2-proxy --config /oauth2_proxy/oauth2_proxy.cfg
  volumes:
   - ./oauth2_proxy:/oauth2_proxy

networks:
 app-tier:
  driver: host

 

これを nginx で定義すると、以下です。

SSLは、Let's encryptを使ってます。

 

server {
        listen       443 ssl;
        listen  [::]:443 ssl;
        server_name [外用ドメイン];
        index index.html index.htm;

        location / {
                auth_request /oauth2/auth;
                error_page 401 = /oauth2/sign_in;

                proxy_pass    http://[内部epgstationアドレス]:8888;

                proxy_set_header    Host    $host;
                proxy_set_header    X-Real-IP    $remote_addr;
                proxy_set_header    X-Forwarded-Host       $host;
                proxy_set_header    X-Forwarded-Proto     https;
                proxy_set_header    X-Forwarded-Server    $host;
                proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;

        }

        location /oauth2/ {
            proxy_pass       http://127.0.0.1:4180;
                proxy_set_header Host                    $host;
                proxy_set_header X-Real-IP               $remote_addr;
                proxy_set_header X-Scheme                $scheme;
        }
        location = /oauth2/auth {
                proxy_pass       http://127.0.0.1:4180;
                proxy_set_header Host             $host;
                proxy_set_header X-Real-IP        $remote_addr;
                proxy_set_header X-Scheme         $scheme;
                proxy_set_header Content-Length   "";
                proxy_pass_request_body           off;
        }
        location /api/config {
                proxy_pass    http://[内部epgstationアドレス]:8888;
                proxy_set_header Host                    $host;
                proxy_set_header X-Real-IP               $remote_addr;
                proxy_set_header X-Scheme                $scheme;
        }

    ssl_certificate /etc/letsencrypt/live/[外用ドメイン]/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/[外用ドメイン]/privkey.pem;
 # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}


10.ハードウェアエンコーディングしてみたい

 

色々と しっかり動作するようになると欲をかきますよね

じゃー、エンコードをハードウェアでやってみたいですよねっねっ 早いし CPU食わないし

ということで、CPUの底力を使ってハードエンコへ誘いましょう

 

手順としては、ホストで、ハードエンコ用のデバイスファイルを作成し、それをコンテナへ公開します。

コンテナ内で、ハードエンコ用の設定定義を行います。

ffmpegを使うコンテナを再構成し、再起動すれば完成です。

 

修正ポイントを色替えしてます。

まず、ホスト側での準備です。

apt install vainfo
apt-get -y install i965-va-driver

echo 'KERNEL=="render*" GROUP="render", MODE="0666"' | sudo tee /etc/udev/rules.d/99-render.rules
udevadm control --reload-rules && sudo udevadm trigger

これで、準備Ok

vainfoで、使えるハードエンコを見ておきましょうね。

 

次にDocker側です

docker-mirakurun-epgstation/docker-compose.yml の追記修正です

version: '3.7'
  services:
    epgstation:
      devices:
         - /dev/dri/renderD128:/dev/dri/renderD128

 

docker-mirakurun-epgstation/epgstation/debian.Dockerfile の追記修正です

    apt-get -y install yasm libx264-dev libmp3lame-dev libopus-dev libvpx-dev && \
    apt-get -y install libx265-dev libnuma-dev i965-va-driver && \

#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-vaapi \
      --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 \

 

docker-mirakurun-epgstation/epgstation/config/enchw.js の作成です

まず、エンコードスクリプトをコピーして、それを修正します。

copy enc.js enchw.js

enchw.js

~~~~~

// 字幕用
Array.prototype.push.apply(args, ['-fix_sub_duration']);
// input,ビデオストリーム設定
Array.prototype.push.apply(args, ['-hwaccel', 'vaapi', '-vaapi_device', '/dev/dri/renderD128']);
Array.prototype.push.apply(args, ['-i', input]);
Array.prototype.push.apply(args, ['-map', '0:v', '-c:v', 'h264_vaapi']);
Array.prototype.push.apply(args, ['-vf', 'format=nv12,hwupload']);
// オーディオストリーム設定
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, ['-profile:v', '77', '-level', '40', '-qp', '26']);
// 出力ファイル
Array.prototype.push.apply(args, [output]);

~~~~~

(async () => {

~~~~~

    child.on('close', (code) => {
        if( code == 251 ) {
                //仕方無いか、誰か解決して
                process.exitCode = 0;
        } else {
                process.exitCode = code;
        }

~~~~~
    });

})();

 

docker-mirakurun-epgstation/epgstation/config.yml の追記修正です

~~

encode:

    - name: H.264Hard
      cmd: '%NODE% %ROOT%/config/enchw.js'
      suffix: .mp4
      rate: 4.0

~~

 

これで、docker-mirakurun-epgstation-epgstation のイメージを破棄して、再起動すればOkのはずです。

docker compose down

docker image rm docker-mirakurun-epgstation-epgstation:latest

docker compose up -d

 

エンコードメニューで、H.264Hard が選択出来ればOkです。

enchw.jsは、コピーして色々とバリエーションを作ることで、さまざまなエンコを楽しめますよ

 

ついでに、放映中のとこも、ハードエンコにしてしまいましょう。

※チューニング所を赤くしておきました。

stream:
    live:
        ts:
            mp4:
                - name: Hard
                  cmd:
                      '%FFMPEG% -re -dual_mono_mode main -hwaccel vaapi -vaapi_device /dev/dri/renderD128 -i pipe:0
                      -sn -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v h264_vaapi -vf format=nv12,hwupload
                      -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof
                      -y -f mp4 pipe:1'
            hls:
                - name: Hard
                  cmd:
                      '%FFMPEG% -re -dual_mono_mode main -hwaccel vaapi -vaapi_device /dev/dri/renderD128
                      -i pipe:0 -sn -map 0 -ignore_unknown
                      -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1
                      -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a
                      aac -ar 48000 -b:a 192k -ac 2 -c:v h264_vaapi -vf format=nv12,hwupload  
                      -flags +loop-global_header %OUTPUT%'
                - name: Hardlow
                  cmd:
                      '%FFMPEG% -re -dual_mono_mode main -hwaccel vaapi -vaapi_device /dev/dri/renderD128
                      -i pipe:0 -sn -map 0 -ignore_unknown
                      -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1
                      -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a
                      aac -ar 24000 -b:a 64k -ac 2 -c:v h264_vaapi -qp 40 -vf format=nv12,hwupload  
                      -flags +loop-global_header %OUTPUT%'
 

 

 


小ネタ

 

動画ファイル移動

そういったインターフェースが無いので、データベースを修正して移動します。

動画の格納場所を調査します。

docker exec -it docker-mirakurun-epgstation-mysql-1 mysql -u epgstation -pepgstation epgstation -e "select id, filepath from video_file;"

+-----+------------------------------------------------------------------------------------------------------------------+
| id  | filepath                                                                                                                                           |
+-----+-------------------------------------------------------------------------------------------------------------------+
|   2 | 2024年06月13日08時00分00秒-hogehoge[解][字].mp4                                                                         |
|   6 | 2024年06月14日08時00分00秒-hogehoge2[解][字].mp4                                                                        |

対象動画のidを指定して、ディレクトリ等(hogedir/)を付け加えます。

docker exec -it docker-mirakurun-epgstation-mysql-1 mysql -u epgstation -pepgstation epgstation -e "update video_file set filepath = concat('hogedir/' ,filepath) where id = 6;"

この後、実際の動画ファイルを移動しましょう。

 

一括動画削除とリスト

APIを使って動画をリストアップしたり、一括削除します。

python3でスクリプト化して、日々使いましょう。

 

第1引数:キーワード

オプション引数:-delete  削除

オプション引数:--yes  削除の時、問答無用

 

listrecorded.py

#!/bin/python3

import urllib.parse
import urllib.request
import json
import argparse

parser = argparse.ArgumentParser(description="using \n [Keyword] \nex)\n listrecorded.py -delete -y 'キーワード' ")
parser.add_argument('keyword', help='keyword')
parser.add_argument('-delete', help='delete', action='store_true')
parser.add_argument('-y', '--yes', help='delete yes', action='store_true')
args = parser.parse_args()
keyword = args.keyword
isdelete = args.delete
isdeleteyes = args.yes

urlget = "http://localhost:8888/api/recorded?isHalfWidth=false&offset=0&limit=10000&keyword="+urllib.parse.quote(keyword)
urldelete = "http://localhost:8888/api/recorded/"

response = urllib.request.urlopen(urlget)
jsonData = json.load(response)

for jsonObj in jsonData["records"]:
    id = jsonObj["id"]
    for videoFileObj in jsonObj["videoFiles"]:
        name=videoFileObj["filename"]
       
        print("{0}:{1}".format(id,name))
       
        if isdelete == True :
            yesno = 'N'
            if isdeleteyes == False:
                yesno = input("Delete Files. OK? [y/N]: ").lower()
       
            if yesno in ['y', 'yes']:
                url=urldelete+str(id)
                headers = {"Content-Type": "application/json"}
                res=urllib.request.Request(url,json.dumps(jsonObj).encode(),headers,method="DELETE")
                try:
                    with urllib.request.urlopen(res) as f:
                        print(f.status,end="")
                        if (200==f.status):
                            print(" DELETE",end="")
                            print(":",id)
                except Exception:
                    pass
 

番組リスト表示と予約

番組表を検索し、ルールに追加します。

番組改編の時とかに、新ドラマを一気に予約する時とかに使います

使い方)

検索日時範囲:7

ジャンル:3

キーワード:[新]

予約

./gettv.py -dayrange 7 -genre 3 -keyword "\[新\]" -reserve

 

#!/bin/python3

import time
import urllib.parse
import urllib.request
import json
import datetime
import pytz
import argparse
import re

DAYRANGE = 7
ISFREE = True
GR = True
BS = False
CS = False
SKY = False
GENRE = None
KEYWORD = None
ISRESERVE = False
ENCODECODEC = "H.264"

DEBUG = False

# ベースURL
schedule_url = "http://localhost:8888/api/schedules"
rule_url = "http://localhost:8888/api/rules"

# オプション処理
parser = argparse.ArgumentParser(description='using \n \nex)\n gettv.py -dayrange 7 -genre 3 -keyword "\\[新\\]" -reserve ')
parser.add_argument('-dayrange', help='dayrange', type=int)
parser.add_argument('-free', help='isfree', action='store_true', default=True)
parser.add_argument('-gr', help='gr', action='store_true', default=True)
parser.add_argument('-bs', help='bs', action='store_true')
parser.add_argument('-cs', help='cs', action='store_true')
parser.add_argument('-sky', help='sky', action='store_true')
parser.add_argument('-genre', help='genre', type=int)
parser.add_argument('-keyword', help='keyword')
parser.add_argument('-reserve', help='reserve', action='store_true', default=False)
parser.add_argument('-encodecodec', help='encodecodec')
if DEBUG == False :
    args = parser.parse_args()
    if args.dayrange != None:
        DAYRANGE = args.dayrange
    ISFREE=args.free
    GR=args.gr
    BS=args.bs
    CS=args.cs
    SKY=args.sky
    if args.genre != None:
        GENRE = args.genre
    if args.keyword != None:
        KEYWORD = args.keyword
    ISRESERVE=args.reserve
    if args.encodecodec != None:
        ENCODECODEC = args.encodecodec

#時間範囲
jst = pytz.timezone('Asia/Tokyo')
t1 = int(time.time()*1000)
t2 = t1+ DAYRANGE*3600*24*1000

#ジャンル
genre = {
    0:'ニュース/報道',
    1:'スポーツ',
    2:'情報/ワイドショー',
    3:'ドラマ',
    4:'音楽',
    5:'バラエティ',
    6:'映画',
    7:'アニメ/特撮',
    8:'ドキュメンタリー/教養',
    9:'劇場/公演',
    10:'趣味/教育',
    11:'福祉',
    12:'スポーツ(CS)',
    13:'映画(CS)',
    14:'拡張',
    15:'その他'
}

# クエリパラメータ
params = {
    "startAt": t1,
    "endAt": t2,
    "isHalfWidth" : False,
    "isFree" : ISFREE,
    "GR" : GR,
    "BS" : BS,
    "CS" : CS,
    "SKY" : SKY
}

# クエリパラメータをエンコード
query_string = urllib.parse.urlencode(params)

# URL構築
urlget = f"{schedule_url}?{query_string}"

# GETリクエストを発行
response = urllib.request.urlopen(urlget)
jsonData = json.load(response)
for chanObj in jsonData:
    channel = chanObj["channel"]
    for prgObj in chanObj["programs"]:
        s = prgObj["startAt"]/1000
        e = prgObj["endAt"]/1000
        g = 15
        if "genre1" in prgObj :
            g = prgObj["genre1"]
        sg = 0
        if "subGenre1" in prgObj :
            sg = prgObj["subGenre1"]
       
        #色々フィルタ        
        if GENRE != None:
            if g != GENRE :
                continue
       
        ctype = channel["channelType"]
        hit = False
        if ctype == "GR" and GR == True :
            hit=True
        if ctype == "BS" and BS == True :
            hit=True
        if ctype == "CS" and CS == True :
            hit=True
        if ctype == "SKY" and SKY == True :
            hit=True
        if hit == False:
            continue
       
        if KEYWORD != None:
            mc = re.match(KEYWORD, prgObj["name"])
            if mc == None:
                continue
       
        s_jst = datetime.datetime.fromtimestamp(s, tz=datetime.timezone.utc).astimezone(jst)
        e_jst = datetime.datetime.fromtimestamp(e, tz=datetime.timezone.utc).astimezone(jst)
       
        print("{0}\t{1}\t[{2}]\t[{3}]\t{4}\t{5}".format(
                                        s_jst.strftime('%Y-%m-%d %H:%M:%S'),
                                        e_jst.strftime('%Y-%m-%d %H:%M:%S'),
                                        genre[g],
                                        ctype,
                                        channel["name"],
                                        prgObj["name"]))

        if ISRESERVE == True:
            yesno = input("Add reserve rule. OK? [y/N]: ").lower()
            if yesno in ['y', 'yes']:

                #予約情報
                reserveinfo = {
                    "isTimeSpecification": False,
                    "searchOption": {
                        "keyword": prgObj["name"],
                        "keyCS": False,
                        "keyRegExp": False,
                        "name": True,
                        "description": False,
                        "extended": False,
                        "ignoreKeyCS": False,
                        "ignoreKeyRegExp": False,
                        "ignoreName": False,
                        "ignoreDescription": False,
                        "ignoreExtended": False,
                        "GR": True if ctype == "GR" else False,
                        "BS": True if ctype == "BS" else False,
                        "CS": True if ctype == "CS" else False,
                        "SKY": True if ctype == "SKY" else False,
                        "genres": [
                        {
                            "genre": g,
                            "subGenre": sg
                        }
                        ],
                        "isFree": prgObj["isFree"],
                    },
                    "reserveOption": {
                        "enable": True,
                        "allowEndLack": True,
                        "avoidDuplicate": False
                    },
                    "saveOption": {
                        "directory": prgObj["name"],
                        "recordedFormat": ""
                    },
                    "encodeOption": {
                        "mode1": ENCODECODEC,
                        "directory1": prgObj["name"],
                        "isDeleteOriginalAfterEncode": True
                    }
                }
               
                reserve_str = json.dumps(reserveinfo, ensure_ascii=False, indent=4)

                # URL構築
                urlpost = f"{rule_url}"

                # 予約ルール 追加
                headers = {"Content-Type": "application/json"}
                res=urllib.request.Request(urlpost,reserve_str.encode(),headers,method="POST")
                try:
                    with urllib.request.urlopen(res) as f:
                        print(f.status,end="")
                        if (200==f.status):
                            print(" POST",end="")
                            print(":",id)
                except Exception as e:
                    print(e)
                    pass
Joomla templates by a4joomla