├── .gitignore
├── src
├── public
│ ├── nav.css
│ ├── overlaid.css
│ └── style.css
├── views
│ ├── kill.slim
│ ├── fil_listD.slim
│ ├── cate_color.slim
│ ├── prg_dialog.slim
│ ├── pack_chk_view.slim
│ ├── nav.sass
│ ├── rsv_list_D.slim
│ ├── ch_tbl_list.slim
│ ├── rsv_conf.rb
│ ├── opt_dialog.rb
│ ├── fil_testrun.slim
│ ├── move_top.html
│ ├── fil_listD.rb
│ ├── control.rb
│ ├── cate_color.rb
│ ├── overlaid.sass
│ ├── top.slim
│ ├── pack_chk_view.rb
│ ├── rsv_conf.slim
│ ├── opt_dialog.js
│ ├── fil_res_dsp.slim
│ ├── log_view.slim
│ ├── fil_testrun.rb
│ ├── config.slim
│ ├── opt_dialog.slim
│ ├── ch_tbl_list.rb
│ ├── ch_tbl.slim
│ ├── prg_tbl.slim
│ ├── mpv_mon.slim
│ ├── ch_info.rb
│ ├── prg_dialog.rb
│ ├── fil_res_dsp.rb
│ ├── test.slim
│ ├── ch_info.slim
│ ├── tooltip.js
│ ├── log_view.rb
│ ├── rsv_list.slim
│ ├── rsv_list_old.slim
│ ├── mpv_mon.rb
│ ├── control.slim
│ ├── rsv_list.rb
│ ├── monitor.slim
│ ├── style.sass
│ ├── monitor.rb
│ ├── fil_list.slim
│ ├── top.rb
│ ├── fil_res_dsp.js
│ ├── rsv_tbl.slim
│ ├── layout.slim
│ └── rsv_list_D.rb
├── db
│ ├── run.sh
│ ├── UpdateChk.rb
│ ├── Keyval.rb
│ ├── FilterResult.rb
│ ├── PTOption.rb
│ ├── Filter.rb
│ ├── Phchid.rb
│ ├── DB.rb
│ ├── Log.rb
│ ├── Category.rb
│ └── base.rb
├── tool
│ ├── tailLog.rb
│ ├── getStatus.rb
│ ├── ts2hls_sample.sh
│ ├── channelScan.rb
│ ├── json2text.rb
│ └── reserve_SL.rb
├── Makefile
├── lib
│ ├── httpd_sub.rb
│ ├── YamlWrap.rb
│ ├── rewriteConst.rb
│ ├── setConst.rb
│ ├── misc.rb
│ └── deviceChk.rb
├── model
│ ├── EpgLock.rb
│ ├── ChannelM.rb
│ ├── LogRote.rb
│ ├── DiskKeep.rb
│ ├── Daily.rb
│ ├── Control.rb
│ ├── EpgNearCh.rb
│ ├── PacketChk.rb
│ ├── Const.rb
│ └── DelayedEPG.rb
├── require.rb
├── TV
│ ├── Arguments.rb
│ ├── misc.rb
│ └── Video.rb
└── timer_main.rb
├── EpgPatch
└── extra.rb
├── doc
├── getStatus.md
├── BS再編ログ
│ ├── 2021-06-01.txt
│ ├── 2020-03-31.txt
│ ├── 2021-02-02.txt
│ ├── 2021-04-13.txt
│ └── 2021-02-09.txt
├── jquery_local.md
├── TS_file_name.md
├── 動作確認環境.md
├── channelScan.md
├── reserve_SL.md
├── recpt1_px4.patch
├── Install.md
└── config.md
├── Readme.md
└── raspirec.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | tmp/epg.db
2 | .ruby-version
3 | .gitignore
4 |
--------------------------------------------------------------------------------
/src/public/nav.css:
--------------------------------------------------------------------------------
1 | nav {
2 | background-color: #2da052; }
3 |
--------------------------------------------------------------------------------
/src/views/kill.slim:
--------------------------------------------------------------------------------
1 |
2 | div this is kill
3 |
4 | javascript:
5 | setTimeout(function () { window.location.href = "/"; }, 2500);
6 |
--------------------------------------------------------------------------------
/src/views/fil_listD.slim:
--------------------------------------------------------------------------------
1 | /
2 | /
3 | /
4 |
5 | - require_relative 'fil_listD.rb'
6 | - dp = FilterListD.new( @id )
7 |
8 | p
9 | | 「#{dp.title}」 を削除しても良いですか?
10 |
11 |
--------------------------------------------------------------------------------
/EpgPatch/extra.rb:
--------------------------------------------------------------------------------
1 | #
2 | # raspirec 向け recpt1 使用時の BS slot 補正データ
3 | #
4 |
5 | ExtraPatch = {
6 | "BS9_2" => "BS9_1",
7 | "BS15_3" => "BS15_2",
8 | "BS23_3" => "BS23_2",
9 | }
10 |
--------------------------------------------------------------------------------
/src/views/cate_color.slim:
--------------------------------------------------------------------------------
1 |
2 | - require_relative 'cate_color.rb'
3 | - dp = Cate_color.new( )
4 |
5 | doctype html
6 |
7 | div
8 | table style="width:30em;"
9 | == dp.printTable()
10 |
--------------------------------------------------------------------------------
/src/views/prg_dialog.slim:
--------------------------------------------------------------------------------
1 |
2 |
3 | - require_relative 'prg_dialog.rb'
4 | - dp = Dialog_pid.new( @pid )
5 |
6 | doctype html
7 |
8 | div
9 | table.striped
10 | == dp.printTable()
11 |
12 |
--------------------------------------------------------------------------------
/src/views/pack_chk_view.slim:
--------------------------------------------------------------------------------
1 | /
2 | / packet chk log の表示
3 | /
4 |
5 |
6 | - require_relative 'pack_chk_view.rb'
7 | - dp = Pack_chk_view.new( @rid )
8 |
9 |
10 | == dp.print( )
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/views/nav.sass:
--------------------------------------------------------------------------------
1 | nav
2 | //background-color: #483d8b
3 | //background-color: sandybrown
4 | //background-color: royalblue
5 | //background-color: yellowgreen
6 | //background-color: #a0522d
7 | //background-color: #522da0
8 | background-color: #2da052
9 |
--------------------------------------------------------------------------------
/src/views/rsv_list_D.slim:
--------------------------------------------------------------------------------
1 | /
2 | / 予約一覧 詳細ダイアログ
3 | /
4 |
5 |
6 | - require_relative 'rsv_list_D.rb'
7 | - dp = ReservationListD.new( )
8 | - data = dp.getData( @rid )
9 |
10 | div
11 | form id="form"
12 | table.striped
13 | == dp.printTable( @rid )
14 |
15 |
--------------------------------------------------------------------------------
/src/views/ch_tbl_list.slim:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | #
4 |
5 | css:
6 | td,th {
7 | padding: 5px ;
8 | }
9 | .skip {
10 | color: #991155 ;
11 | }
12 |
13 | - require_relative 'ch_tbl_list.rb'
14 | - pt = ChTblList.new( )
15 |
16 | table.striped
17 | == pt.printTable()
18 |
--------------------------------------------------------------------------------
/src/db/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ "X$1" = "X-r" ]
4 | then
5 | #rm -f $HOME/develop/data/raspirec/db/epg.db
6 | rm -f /data/shiiya/rrtest/db/epg.db
7 | shift
8 | fi
9 |
10 | if cd $HOME/develop/raspirec
11 | then
12 | if [ "X$1" = "X-g" ]
13 | then
14 | ruby db/rec.rb
15 | fi
16 |
17 | ruby db/epgData.rb
18 | fi
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/tool/tailLog.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # log の tail
6 | #
7 |
8 | require 'optparse'
9 | require 'json'
10 |
11 | base = File.dirname( $0 )
12 | [ ".", "..", "src",base + "/.."].each do |dir|
13 | if test(?f,dir + "/require.rb")
14 | $: << dir
15 | end
16 | end
17 | require 'require.rb'
18 |
19 |
20 | tailLog( )
21 |
22 |
--------------------------------------------------------------------------------
/src/views/rsv_conf.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 予約確認
6 | #
7 |
8 |
9 | class ReservationConfirm
10 |
11 | def initialize( )
12 | end
13 |
14 | #
15 | # form のパラメータ設定
16 | #
17 | def setPara( )
18 | d = {}
19 | d[:dirs] = Commlib::datalist_dir()
20 | d[:jitanchk] = true
21 | d
22 | end
23 |
24 |
25 | end
26 |
27 |
--------------------------------------------------------------------------------
/src/Makefile:
--------------------------------------------------------------------------------
1 |
2 | PROGRAM = /usr/bin/sass
3 |
4 | OBJS = public/nav.css public/overlaid.css public/style.css
5 | all: $(OBJS)
6 |
7 | public/nav.css: views/nav.sass
8 | $(PROGRAM) --no-cache --sourcemap=none $< $@
9 |
10 | public/overlaid.css: views/overlaid.sass
11 | $(PROGRAM) --no-cache --sourcemap=none $< $@
12 |
13 | public/style.css: views/style.sass
14 | $(PROGRAM) --no-cache --sourcemap=none $< $@
15 |
16 |
17 |
--------------------------------------------------------------------------------
/doc/getStatus.md:
--------------------------------------------------------------------------------
1 |
2 | ## 名前
3 |
4 | tool/getStatus.rb
5 |
6 | ## 書式
7 |
8 | getStatus.rb [ -t n ]
9 |
10 | ## 説明
11 |
12 | 指定した時間(デフォルトは 600秒)の間、録画が始まらないかを判定して
13 | 終了ステータスを返す。
14 |
15 | | 終了ステータス | 意味|
16 | |----|-------|
17 | | 0 | 安全 |
18 | | -1 | busy |
19 |
20 | ## オプション
21 |
22 | -t n
23 | 時間の指定。単位は秒
24 |
25 |
26 | ## 注意
27 | * 事前に config.rb にて recpt1,epgdump の設定がされている事が必要です。
28 |
--------------------------------------------------------------------------------
/src/views/opt_dialog.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 番組表オプション
6 | #
7 | class Dialog_opt
8 |
9 | attr_reader :sp, :hp, :tt, :hn, :chflag
10 |
11 | def initialize( session )
12 |
13 | @chflag = session["from"] =~ /ch_tbl/ ? true : false
14 |
15 | pto = PTOption.new
16 | @sp = pto.sp
17 | @hp = pto.hp
18 | @hn = pto.hn
19 | @tt = pto.tt
20 | end
21 |
22 | end
23 |
24 |
--------------------------------------------------------------------------------
/src/lib/httpd_sub.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 |
4 | def makePrgtblUrl( session )
5 |
6 | if session[:from] != nil
7 | return session[:from]
8 | else
9 | if session[:band] != nil and session[:day] != nil and session[:time] != nil
10 | return "/prg_tbl/#{session[:band]}/#{session[:day]}/#{session[:time]}"
11 | elsif session[:band] != nil
12 | return "/prg_tbl/#{session[:band]}"
13 | else
14 | return "/prg_tbl"
15 | end
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/doc/BS再編ログ/2021-06-01.txt:
--------------------------------------------------------------------------------
1 |
2 | ・実施内容
3 | トランスポンダの移動
4 | https://www.apab.or.jp/2020-bs-band-reorganization-schedule.html
5 |
6 | ディズニー・チャンネル BS03 → BS23
7 |
8 | 2021年06月01日(火)の早朝(午前0時~午前6時)
9 |
10 | ・2021/06/01 以後の slot 構成(予想)
11 |
12 | BS03_0 WOWOWプライム
13 | BS03_1 NHKBSプレミアム
14 | BS03_2 ディズニーch → 未使用
15 |
16 | BS23_0 未使用 → ディズニーch
17 | BS23_1 未使用
18 |
19 | ・raspirec への影響
20 |
21 | BS03_2 → BS23_0 ならば自動で対応出来る筈。
22 |
23 | ・結果
24 |
25 | raspirec + recpt1 は問題なし。
26 | recdvb は、チャンネル情報の修正が必要。-> 修正済み
27 |
28 |
--------------------------------------------------------------------------------
/src/views/fil_testrun.slim:
--------------------------------------------------------------------------------
1 | /
2 | / フィルター条件 検索結果
3 | /
4 |
5 | - require_relative 'fil_testrun.rb'
6 | - fi = FilterTest.new( @id, @parms, session )
7 |
8 |
9 | table.striped
10 | tr
11 | th.nowrap No
12 | th.nowrap 放送局
13 | th.nowrap 日付
14 | th.nowrap 時間
15 | th.nowrap 予約
16 | th.nowrap タイトル
17 | th.nowrap 概要
18 | == fi.testrun(@params)
19 |
20 | div id="sample-dialog" title="詳細" style="display:none;"
21 | p
22 |
23 | div id="sample-dialog2" title="確認" style="display:none;"
24 | p
25 |
26 | == Commlib::include( SrcDir + "/views/fil_res_dsp.js" )
27 |
--------------------------------------------------------------------------------
/src/db/UpdateChk.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | require 'fileutils'
5 |
6 | class DBupdateChk
7 |
8 | def initialize( )
9 | if test(?f, DbupdateFN )
10 | @mtime = File.mtime( DbupdateFN )
11 | else
12 | @mtime = Time.at(0)
13 | end
14 | end
15 |
16 | def touch()
17 | FileUtils.touch(DbupdateFN)
18 | end
19 |
20 | def update?()
21 | if test(?f, DbupdateFN )
22 | mtime = File.mtime( DbupdateFN )
23 | if mtime > @mtime
24 | @mtime = mtime
25 | return true
26 | end
27 | end
28 | false
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/src/views/move_top.html:
--------------------------------------------------------------------------------
1 |
2 | ▲
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/views/fil_listD.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # フィルター削除 ダイアログ
6 | #
7 |
8 |
9 | class FilterListD
10 |
11 | attr_reader :title
12 |
13 | def initialize( id )
14 | @id = id
15 | @title = ""
16 |
17 | DBaccess.new().open do |db|
18 | filter = DBfilter.new()
19 | data = filter.select( db, id: @id )
20 | if data.size > 0
21 | d = data.first
22 | if d[:title] == nil or d[:title] == ""
23 | @title = d[:key]
24 | else
25 | @title = d[:title]
26 | end
27 | end
28 | end
29 | end
30 |
31 |
32 | end
33 |
34 |
--------------------------------------------------------------------------------
/src/model/EpgLock.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | #
4 | # 多重起動防止
5 | #
6 |
7 | module EpgLock
8 |
9 | def lock?()
10 | if test( ?f, EPGLockFN )
11 | now = Time.now
12 | mtime = File.mtime( EPGLockFN )
13 | if mtime > ( now - 3600 * 1) # 1時間以内
14 | #DBlog::sto( "Error: Lock file exist")
15 | return true
16 | end
17 | end
18 | false
19 | end
20 | module_function :lock?
21 |
22 | def lock()
23 | FileUtils.touch(EPGLockFN)
24 | end
25 | module_function :lock
26 |
27 | def unlock()
28 | if test(?f, EPGLockFN )
29 | File.unlink( EPGLockFN )
30 | end
31 | end
32 | module_function :unlock
33 |
34 | end
35 |
36 |
37 |
--------------------------------------------------------------------------------
/doc/jquery_local.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## jquery と materializ をローカルに配置して使う方法
4 |
5 | - 下記のファイルを public の下にダウンロードする。
6 |
7 | BaseDir はインストールしたディレクトリ
8 |
9 | ```
10 | % cd $BaseDir/src/public
11 | % wget https://code.jquery.com/jquery-3.3.1.min.js
12 | % wget https://code.jquery.com/ui/1.12.0/jquery-ui.min.js
13 | % wget http://code.jquery.com/ui/1.12.1/themes/pepper-grinder/jquery-ui.css
14 | % wget https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css
15 | % wget https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js
16 | ```
17 |
18 | - $HOME/.config/raspirec/config.rb の下記のパラメータを true に変更する。
19 |
20 | `Local_jquery = true`
21 |
22 | - プログラムを再起動する。
23 |
24 |
25 |
--------------------------------------------------------------------------------
/doc/TS_file_name.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## TS ファイル名の生成ルール
4 |
5 | - 出力する TS ファイル名の生成ルールは、config.rb 中の TSnameFormat で指定する。
6 |
7 | - デフォルトは、
8 | "%YEAR%-%MONTH%-%DAY%_%HOUR%:%MIN%_%DURATION%_%TITLE%_%CHNAME%"
9 | で、
10 | "2019-12-17_15:00_1800_ショッピング情報_BS11イレブン.ts" に展開される。
11 |
12 | - 使用出来るキーワードは下記のもの。
13 |
14 | |キーワード |意味 |
15 | |-------------|--------------|
16 | |%TITLE% | 番組タイトル |
17 | |%ST% | 開始日時( YYYYMMDDHHMM ) |
18 | |%ET% | 終了日時(同上) |
19 | |%BAND% | GR,BS,CS |
20 | |%CHNAME% | 放送局名 |
21 | |%YEAR% | 開始年 |
22 | |%MONTH% | 開始月 |
23 | |%DAY% | 開始日 |
24 | |%HOUR% | 開始時 |
25 | |%MIN% | 開始分 |
26 | |%SEC% | 開始秒 |
27 | |%WDAY% | 曜日 0(日曜日)から6(土曜日) |
28 | |%DURATION% | 録画時間(秒) |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/views/control.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # コントロール パネル
6 | #
7 | require 'find'
8 |
9 | class Control
10 |
11 | def initialize( )
12 | end
13 |
14 | #
15 | # from の初期値
16 | #
17 | def getData( )
18 | ret = { :tsft => false }
19 | keyval = DBkeyval.new
20 | DBaccess.new().open do |db|
21 | row = keyval.select( db, "tsft" )
22 | if row == "true"
23 | ret[ :tsft ] = true
24 | end
25 | end
26 |
27 | ts = []
28 | Find.find( TSDir ) do |f|
29 | if f =~ /\.ts$/
30 | f.sub!( TSDir + "/", '')
31 | ts << sprintf(" %s ",f,f)
32 | end
33 | end
34 | ret[:tsfile] = ts.join("\n")
35 |
36 | return ret
37 | end
38 |
39 | end
40 |
--------------------------------------------------------------------------------
/src/views/cate_color.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # カテゴリの配色表
6 | #
7 |
8 |
9 | class Cate_color
10 |
11 | def initialize( )
12 |
13 | end
14 |
15 | def printTable2( title, val, id = nil)
16 | color = sprintf("color%d",val )
17 | sprintf(%Q{ %s %s },title, color)
18 | end
19 |
20 |
21 | def printTable()
22 | r = []
23 |
24 | row = nil
25 | category = DBcategory.new
26 | DBaccess.new(DbFname).open do |db|
27 | row = category.selectL( db )
28 | if row != nil and row.size > 0
29 | row.each do |tmp|
30 | r << printTable2( tmp[:name], tmp[:id] )
31 | end
32 | end
33 | end
34 | r.join("\n")
35 | end
36 | end
37 |
38 |
--------------------------------------------------------------------------------
/src/lib/YamlWrap.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 | #
4 |
5 | #
6 | # YAML の非互換性を吸収するためのラッパー
7 | #
8 |
9 | module YamlWrap
10 |
11 | def load_file( fname )
12 | # pp "load_file() " + Gem::Version.new( Psych::VERSION ).to_s
13 | if Gem::Version.new( Psych::VERSION ) < Gem::Version.new( "4.0.0" )
14 | return YAML.load_file( fname )
15 | else
16 | return YAML.unsafe_load_file( fname )
17 | end
18 | end
19 |
20 | def load( fname )
21 | # pp "load() " + Gem::Version.new( Psych::VERSION ).to_s
22 | if Gem::Version.new( Psych::VERSION ) < Gem::Version.new( "4.0.0" )
23 | return YAML.load( fname )
24 | else
25 | return YAML.unsafe_load( fname )
26 | end
27 | end
28 |
29 | module_function :load_file
30 | module_function :load
31 |
32 | end
33 |
34 |
--------------------------------------------------------------------------------
/doc/動作確認環境.md:
--------------------------------------------------------------------------------
1 |
2 | ## 動作確認環境
3 |
4 | ### 録画機側
5 |
6 | #### その1
7 |
8 | * raspberry pi 3B+
9 | * Raspbian Stretch
10 | * PX-Q3U4
11 | * px4_drv
12 | * ruby 2.3.3p222
13 | * メモ
14 | * 録画は 6本同時までは可
15 | * 動作速度は、遅いが問題なく使える
16 |
17 |
18 | #### その2
19 |
20 | * raspberry pi zero w
21 | * Raspbian Buster
22 | * PX-Q3U4
23 | * px4_drv
24 | * ruby 2.5
25 | * メモ
26 | * 録画は2本同時までは可
27 | * 動作速度は、かろうじて動くぐらい。
28 | * Wi-Fi 接続の為、転送速度は 2Mbyte/秒 程度で、実用に耐えない。
29 |
30 | #### その 3
31 |
32 | * AMD Ryzen 7 2700 + MEM 16G
33 | * Ubuntu 18.04.3 LTS
34 | * PT2
35 | * pt1_drv
36 | * ruby 2.5
37 | * メモ
38 | * 録画は 8本以上可
39 | * 動作速度は、問題なく使える
40 |
41 | ### ブラウザ側
42 |
43 | * linux版 opera
44 | * 問題なし
45 | * linux版 firefox
46 | * 問題なし
47 | * android版 firefox
48 | * 一部のアクションが起きない不具合あり。
49 | * android版 opera
50 | * ドロップダウンリストが選択出来ない不具合あり。
51 |
--------------------------------------------------------------------------------
/doc/channelScan.md:
--------------------------------------------------------------------------------
1 |
2 | ## 名前
3 |
4 | tool/channelScan.rb 地デジ チャンネルスキャン
5 |
6 | ## 書式
7 |
8 | channelScan.rb [ -t n ]
9 |
10 | ## 説明
11 |
12 | 地上デジタル放送の帯域をチャンネルスキャンし、
13 | 放送波のあるチャンネルを表示します。
14 |
15 | ## オプション
16 |
17 | -t n
18 | 1チャンネル当たりの検出時間を n 秒にします。デフォルトは 10秒です。
19 |
20 | ## 例
21 |
22 | ```
23 | % ruby channelScan.rb
24 | # 10 : 0.0dB
25 | # 11 : 0.0dB
26 | # 12 : 0.0dB
27 | # 13 : 22.2dB GR0_30728 GR0_30730 NHKEテレ1長野 NHKEテレ3長野
28 | # 14 : 34.6dB GR0_30736 テレビ信州1
29 | # 15 : 41.1dB GR0_30760 長野放送
30 | # 16 : 31.5dB GR0_30752 SBC信越放送1
31 | # 17 : 25.7dB GR0_30720 NHK総合1・長野
32 | # 18 : 21.7dB GR0_30744 長野朝日放送
33 | # 19 : 0.0dB
34 | ...
35 | GR_EPG_channel = %w( 13 14 15 16 17 18 )
36 |
37 | ```
38 |
39 | 最終行が config.rb に設定する値になります。
40 |
41 | ## 注意
42 | * 事前に config.rb にて recpt1,epgdump の設定がされている事が必要です。
43 |
--------------------------------------------------------------------------------
/src/model/ChannelM.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | #
6 | #
7 |
8 |
9 | class ChannelM
10 |
11 | def initialize( )
12 |
13 | end
14 |
15 | #
16 | # skip の設定
17 | #
18 | def set( chid, skip )
19 |
20 | DBaccess.new().open(tran: true) do |db|
21 | channel = DBchannel.new
22 | val = skip == "on" ? 1 : 0
23 | channel.updateSkip( db, val, chid )
24 | end
25 | end
26 |
27 | #
28 | # 削除
29 | #
30 | def delete( chid )
31 |
32 | DBaccess.new().open(tran: true) do |db|
33 | channel = DBchannel.new
34 | channel.delete( db, chid )
35 | end
36 | end
37 |
38 |
39 | #
40 | # 無効化
41 | #
42 | def invalid( chid )
43 |
44 | DBaccess.new().open( tran: true ) do |db|
45 | channel = DBchannel.new
46 | channel.invalid( db, chid )
47 | end
48 | end
49 |
50 |
51 | end
52 |
53 |
--------------------------------------------------------------------------------
/src/tool/getStatus.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 録画サーバーが指定した時間に稼働中か判定する。
6 | # 0 -> wait
7 | # 0以外 -> busy
8 |
9 | require 'optparse'
10 |
11 | base = File.dirname( $0 )
12 | [ ".", "..","src", base + "/..", ].each do |dir|
13 | if test(?f,dir + "/require.rb")
14 | $: << dir
15 | end
16 | end
17 | require 'require.rb'
18 |
19 | #
20 | # 引数の解析
21 | #
22 | $opt = {
23 | :t => 600, # チェックする時間
24 | }
25 |
26 | OptionParser.new do |opt|
27 | opt.on('-t n') {|v| $opt[ :t ] = v.to_i }
28 | opt.parse!(ARGV)
29 | end
30 |
31 | if EpgLock::lock?() == false
32 | timer = Timer.new
33 | ( queue, recCount, nextRecTime ) = timer.getNextProg()
34 |
35 | sa = nextRecTime - Time.now.to_i
36 | if recCount > 0 or sa < $opt[:t]
37 | exit -1
38 | end
39 | else
40 | exit -1
41 | end
42 |
43 | exit 0
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/views/overlaid.sass:
--------------------------------------------------------------------------------
1 | nav
2 | height: 48px
3 | line-height: 48px
4 | i, [class^="mdi-"], [class*="mdi-"], i.material-icons, .button-collapse i
5 | height: 48px
6 | line-height: 48px
7 | .brand-logo
8 | font-size: 1.6rem
9 |
10 | .tabs
11 | height: 38px
12 |
13 | .tabs .tab a
14 | padding: 6px 5px 12px 5px
15 | height: 38px
16 | width: 6em
17 |
18 | .row .col
19 | padding-left: 5px
20 | padding-right: 5px
21 |
22 | #bands
23 | padding-left: 8px
24 | padding-right: 8px
25 |
26 |
27 | @media only screen and (min-width: 601px)
28 | nav
29 | height: 48px
30 | line-height: 48px
31 | .nav-wrapper i
32 | height: 48px
33 | line-height: 48px
34 | a.button-collapse
35 | height: 48px
36 | line-height: 48px
37 | i
38 | height: 48px
39 | line-height: 48px
40 |
41 | .row
42 | margin-bottom: 10px
43 |
44 |
--------------------------------------------------------------------------------
/src/public/overlaid.css:
--------------------------------------------------------------------------------
1 | nav {
2 | height: 48px;
3 | line-height: 48px; }
4 | nav i, nav [class^="mdi-"], nav [class*="mdi-"], nav i.material-icons, nav .button-collapse i {
5 | height: 48px;
6 | line-height: 48px; }
7 | nav .brand-logo {
8 | font-size: 1.6rem; }
9 |
10 | .tabs {
11 | height: 38px; }
12 |
13 | .tabs .tab a {
14 | padding: 6px 5px 12px 5px;
15 | height: 38px;
16 | width: 6em; }
17 |
18 | .row .col {
19 | padding-left: 5px;
20 | padding-right: 5px; }
21 |
22 | #bands {
23 | padding-left: 8px;
24 | padding-right: 8px; }
25 |
26 | @media only screen and (min-width: 601px) {
27 | nav {
28 | height: 48px;
29 | line-height: 48px; }
30 | nav .nav-wrapper i {
31 | height: 48px;
32 | line-height: 48px; }
33 | nav a.button-collapse {
34 | height: 48px;
35 | line-height: 48px; }
36 | nav a.button-collapse i {
37 | height: 48px;
38 | line-height: 48px; } }
39 | .row {
40 | margin-bottom: 10px; }
41 |
--------------------------------------------------------------------------------
/doc/BS再編ログ/2020-03-31.txt:
--------------------------------------------------------------------------------
1 |
2 | ・実施内容
3 |
4 | Dlife, FOX スポーツ&エンターテイメント が 2020/03/31 で停波
5 |
6 |
7 | ・2020/03/31 以前の slot 構成
8 |
9 | BS23_0 BS釣りビジョン
10 | BS23_1 BS日本映画専門チャンネル
11 | BS23_2 Dlife
12 |
13 | BS11_0 FOX スポーツ&エンターテイメント
14 | BS11_1 BSスカパー
15 | BS11_2 放送大学
16 |
17 | ・2020/03/31 以前の slot 構成
18 |
19 | BS23_0 BS釣りビジョン
20 | BS23_1 BS日本映画専門チャンネル
21 |
22 | BS11_0 BSスカパー
23 | BS11_1 放送大学
24 |
25 | ・raspirec への影響
26 |
27 | * Dlife の方は単純に無くなっただけなので問題なし。
28 | * Fox は無くなって、BSスカパー,放送大学のスロットが移動したはずなのに、EPG
29 | の情報は古いままなので、BSスカパー,放送大学が正常に受信出来ない。
30 |
31 | 停波前のSlot 停波後のSlot 停波後のEPG
32 | BS11_0 FOX BSスカパー 空き
33 | BS11_1 BSスカパー 放送大学 BSスカパー <- ズレ
34 | BS11_2 放送大学 空き 放送大学 <- ズレ
35 |
36 | 推測だが市販のTVチュナーは、EPGデータの上位に空きスロットがある場
37 | 合は 自動で slot番号を詰める機能を有していて問題ない模様。
38 |
39 | 従って、EPG のデータを強制的に読み替える機能がどこかに必要。
40 | -> Ver0.4.0 で機能追加し解決
41 |
42 |
--------------------------------------------------------------------------------
/doc/reserve_SL.md:
--------------------------------------------------------------------------------
1 |
2 | ## 名前
3 |
4 | tool/reserve_SL.rb 予約データの save & load
5 |
6 | ## 書式
7 |
8 | reserve_SL.rb [Options1] [Options2...] file
9 |
10 | ## 説明
11 |
12 | データベース中の予約関係データを保存、読み込みを行う。
13 |
14 | ## オプション
15 |
16 | ### Options1
17 |
18 | -s,--save
19 | データセーブモード : DBの中の予約関係データ を yaml 形式でダンプする。
20 |
21 | -l,--load
22 | データロードモード : ダンプしたファイルを DBに読み込む。
23 |
24 | ### Options2
25 |
26 | -f, --filter
27 | フィルターデータ
28 |
29 | -a, --auto
30 | 自動予約データ
31 |
32 | -r, --reserv
33 | 未録画 予約データ
34 |
35 | -o, --old
36 | 録画済み 予約データ
37 |
38 | -A, --ALL
39 | 全部(-f,-a,-r,-o)
40 |
41 | -d, --db db_file
42 | DBファイルの指定(デフォルトは config.rb 中の DbFname )
43 |
44 | -C, --clearTable
45 | 読み込む前にデータ削除
46 |
47 | file yaml 入力(-l時),出力(-s時) ファイル名
48 |
49 | ## 例
50 |
51 | ```
52 | % ruby tool/reserve_SL.rb --save -A backup.yaml # バックアップ
53 | % ruby tool/reserve_SL.rb --load -f -a backup.yaml # restore
54 | ```
55 |
56 |
57 |
58 | ## 注意
59 | * 事前に config.rb の設定がされている事が必要です。
60 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 |
2 | ## 目的
3 |
4 | 本プログラム(raspirec) は Linux 系OS上で、
5 | recpt1/recdvb,epgdump を使って TV番組を録画する録画サーバーを構築するための
6 | 録画予約システムです。
7 |
8 | 特に、ラズパイのようなシングルボードコンピュータ(SBC)で動作させる事に最適化しています。
9 |
10 | なお動作には recpt1/recdvb に対応しているドライバーがあるTVチューナー
11 | ( アースソフト社製 PT1〜3, PLEX社製 PX-W3U4、PX-W3PE4、PX-Q3U4、PX-Q3PE4 等)
12 | が必要です。
13 |
14 | ## 特徴
15 |
16 | * Raspberry Pi のようなシングルボードコンピュータ(SBC)でも実行できるように、
17 | 機能はシンプルで小型、軽量。
18 | * 処理能力の低いPCでも動作するように最適化。
19 | * 同時録画本数を多くする為に録画中はなるべく負荷を掛けない。
20 | (raspbery Pi 3B+ で 6本までは確認)
21 | * インストールが容易
22 | * WEBインターフェースで、EPG番組表から録画予約が可能。
23 | * 条件検索を設定することで、自動予約登録が可能。
24 | * 録画したTSファイルを、親機にコピーする機能
25 | * チューナーの数とは別に、同時録画の本数で制限を掛ける事が可能。
26 | (SBCの場合、能力不足により本数制限が必要になる。)
27 |
28 |
29 | ## 詳細
30 | インストール方法等の詳細は、
31 | [GitHub Pages](https://kaikoma-soft.github.io/src/raspirec.html)
32 | を参照して下さい。
33 |
34 |
35 | ## docker
36 | [dockerによる動作テスト環境](https://github.com/kaikoma-soft/docker-raspirec)
37 | を用意したので、簡単に動作テストを行う事が出来ます。
38 |
39 |
40 |
41 | ## ライセンス
42 | このソフトウェアは、Apache License Version 2.0 ライセンスのも
43 | とで公開します。詳しくは LICENSE を見て下さい。
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/views/top.slim:
--------------------------------------------------------------------------------
1 |
2 | css:
3 | .tbl {
4 | position: absolute;
5 | top: 0px;
6 | right: 0px;
7 | bottom: 0px;
8 | left: 0px;
9 | margin: auto;
10 |
11 | width: 800px;
12 | height: 550px;
13 | }
14 |
15 | - require_relative 'top.rb'
16 | - top = Top.new.setup
17 |
18 | .card.tbl
19 | .card-content
20 | span#title.card-title ステータス表示
21 | table
22 | tr
23 | td.nowrap 状況
24 | td.nowrap #{top[:stat]}
25 | tr
26 | td.nowrap 現/次番組名
27 | td.nowrap #{{top[:stat2]}}
28 | tr
29 | td.nowrap Disk 使用量/全容量
30 | td.nowrap #{top[:diskUsed]} / #{top[:diskTotal]} ( #{top[:diskWari]} )
31 | tr
32 | td.nowrap 予約数
33 | td.nowrap #{top[:reserveNum]}
34 | tr
35 | td.nowrap チューナー不足数
36 | td.nowrap
37 | == top[:ConflictNum]
38 | tr
39 | td.nowrap 過去3日間の録画エラー数
40 | td.nowrap
41 | == top[:ErrorNum]
42 | tr
43 | td.nowrap プログラム
44 | td.nowrap #{Const::ProgName} #{Commlib::getVer()}
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/views/pack_chk_view.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | #
6 | #
7 | class Pack_chk_view
8 |
9 | attr_reader :sp, :hp, :tt, :hn, :chflag
10 |
11 | def initialize( rid )
12 | @rid = rid
13 |
14 | end
15 |
16 | def print
17 |
18 | logfname = nil
19 | DBaccess.new().open do |db|
20 | reserve = DBreserve.new()
21 | r = reserve.select( db, id: @rid )
22 | l = r.first
23 | path = TSDir + "/"
24 | if l[:subdir] != nil and l[:subdir] != ""
25 | subdir2 = Commlib::normStr( l[:subdir] )
26 | path += subdir2.sub(/^\//,'').sub(/\/$/,'').strip + "/"
27 | end
28 | path += l[:fname]
29 | logfname = path + ".chk"
30 | end
31 |
32 | r = []
33 | r << ""
34 | if logfname != nil
35 | if test(?f, logfname )
36 | File.open( logfname, "r" ) do |fpr|
37 | fpr.each_line do |line|
38 | r << line.chomp
39 | end
40 | end
41 | else
42 | r << "file not found\n#{logfname}"
43 | end
44 | end
45 | r << " "
46 | r.join("\n")
47 | end
48 |
49 | end
50 |
51 |
--------------------------------------------------------------------------------
/src/tool/ts2hls_sample.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | #
4 | # hls 変換スクリプト
5 | #
6 | # パラメータの受け渡しは、下記の環境変数で行う
7 | #
8 | # SOURCE_CMD チューナーの起動コマンド
9 | # SOURCE_OUT "-" or ffmpeg 入力ファイル名
10 | # STREAM_DIR 出力先ディレクトリ
11 | # PLAYLIST PLAYLIST 出力ファイル名
12 |
13 | #PATH=/usr/bin:/usr/local/bin:/bin:$PATH
14 |
15 | #set -x
16 |
17 | if cd $STREAM_DIR
18 | then
19 | exec > ts2hls.log 2>&1
20 |
21 | $SOURCE_CMD "$SOURCE_OUT" | \
22 | ffmpeg \
23 | -re \
24 | -i pipe:0 \
25 | -r 10 \
26 | -bsf:v h264_mp4toannexb \
27 | -movflags faststart \
28 | -max_muxing_queue_size 1024 \
29 | -analyzeduration 10M -probesize 10M \
30 | -map 0:v:0 -map 0:a -ignore_unknown \
31 | -c:v h264 -g 10 \
32 | -c:a copy \
33 | -flags +cgop+global_header \
34 | -f hls \
35 | -hls_time 5 -hls_list_size 3 -hls_allow_cache 0 \
36 | -hls_flags delete_segments \
37 | -hls_segment_filename stream_%05d.ts \
38 | "$PLAYLIST" \
39 | > ffmpeg.log 2>&1
40 | else
41 | echo "dir not found $STREAM_DIR "
42 | fi
43 |
44 | # -vf "fps=10,yadif=0:-1:1"
45 |
--------------------------------------------------------------------------------
/doc/BS再編ログ/2021-02-02.txt:
--------------------------------------------------------------------------------
1 |
2 | ・実施内容
3 | トランスポンダの移動
4 | https://www.apab.or.jp/topics/2020/2101070001.html
5 |
6 | BS 19ch→21ch グリーンチャンネル
7 | BS 21ch→19ch J SPORTS 4
8 |
9 | 2021年2月2日(火)の早朝(午前0時~午前7時)
10 | リーンチャンネルは(午前0時~午前6時59分)
11 |
12 | ・2021/02/02 以前の slot 構成
13 |
14 | BS19_0 グリーンチャンネル
15 | BS19_1 J SPORTS 1
16 | BS19_2 J SPORTS 2
17 |
18 | BS21_0 WOWOWプラス
19 | BS21_1 J SPORTS 3
20 | BS21_2 J SPORTS 4
21 |
22 | ・2021/02/02 以後の slot 構成(予想)
23 |
24 | BS19_0 J SPORTS 4
25 | BS19_1 J SPORTS 1
26 | BS19_2 J SPORTS 2
27 |
28 | BS21_0 WOWOWプラス
29 | BS21_1 J SPORTS 3
30 | BS21_2 グリーンチャンネル
31 |
32 | ・raspirec への影響
33 |
34 | EPGデータが変更になったら、それを元に自動でDBを書き換えるので対応は
35 | 不要の筈。
36 |
37 | ・結果
38 |
39 | 予定通り EPG 取得後に channel情報変更がされ問題なし。
40 |
41 | 07:08:05: channel情報変更 グリーンチャンネル tsid 18224 -> 18258
42 | 07:08:05: channel情報変更 グリーンチャンネル stinfo_tp BS19 -> BS21
43 | 07:08:05: channel情報変更 グリーンチャンネル stinfo_slot 0 -> 2
44 | 07:08:05: channel情報変更 J SPORTS 4 tsid 18258 -> 18224
45 | 07:08:05: channel情報変更 J SPORTS 4 stinfo_tp BS21 -> BS19
46 | 07:08:05: channel情報変更 J SPORTS 4 stinfo_slot 2 -> 0
47 |
48 |
--------------------------------------------------------------------------------
/doc/BS再編ログ/2021-04-13.txt:
--------------------------------------------------------------------------------
1 |
2 | ・実施内容
3 | トランスポンダの移動
4 | https://www.apab.or.jp/2020-bs-band-reorganization-schedule.html
5 |
6 | BS釣りビジョン BS 23ch→11ch
7 | 日本映画専門チャンネル BS 23ch→21ch
8 |
9 | 2021年4月13日(火)の早朝(午前0時~午前6時)
10 |
11 | ・2021/04/13 以後の slot 構成(予想)
12 |
13 | BS23_0 BS釣りビジョン
14 | BS23_1 日本映画専門ch
15 |
16 | BS11_0 BSスカパー!
17 | BS11_1 放送大学
18 | BS11_2 空き -> BS釣りビジョン ?
19 |
20 | BS21_0 WOWOWプラス
21 | BS21_1 空き -> 日本映画専門チャンネル
22 | BS21_2 グリーンチャンネル
23 |
24 | ・raspirec への影響
25 |
26 | BS釣りビジョンが BS11_0 に入る可能性もあるので、その場合は現在当て
27 | ているパッチを外す必要あり。(BSスカパーの放送休止があるので可能性大?)
28 |
29 | 日本映画専門チャンネルは多分 BS21_1 に入るので、自動で対応出来る筈。
30 |
31 | ・結果
32 |
33 | BS11_0 BSスカパー!(EPG は BS11_1)
34 | BS11_1 放送大学 (EPG は BS11_2)
35 | BS11_2 空き -> BS釣りビジョン (EPG は BS11_3)
36 |
37 | BS21_0 WOWOWプラス
38 | BS21_1 空き -> 日本映画専門チャンネル (BS21_1)
39 | BS21_2 グリーンチャンネル
40 |
41 | 日本映画専門チャンネルは問題なく自動対応。
42 | BS釣りビジョンは BS11_2 に入ったが、BS11_* の EPG 上のスロット番号
43 | と実際に選局するものとズレを引き継いでしまっている。
44 | よって、EpgPatch を追加しなければならない。-> Ver1.0.3 で対応
45 |
46 |
--------------------------------------------------------------------------------
/src/db/Keyval.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | class DBkeyval
6 |
7 | include Base
8 |
9 | def initialize( )
10 | @list = {
11 | key: "key",
12 | val: "val",
13 | }
14 | @para = []
15 | @list.each_pair { |k,v| @para << v }
16 | @tbl_name = "keyval"
17 | end
18 |
19 | #
20 | # 格納
21 | #
22 | def insert(db, key, val )
23 | data = { key: key, val: val }
24 | ( sql, args ) = makeInsSql( @list, @para, @tbl_name, data )
25 | db.execute( sql, *args )
26 | end
27 |
28 |
29 | #
30 | # 検索
31 | #
32 | def select( db, key )
33 | sql = "select val from #{@tbl_name} where key = ? ;"
34 | row = db.execute( sql, key)
35 | if row != nil and row[0] != nil
36 | return row[0][0]
37 | end
38 | nil
39 | end
40 |
41 |
42 | #
43 | # 削除
44 | #
45 | def delete( db, key )
46 | sql = "delete from #{@tbl_name} where key = ? ;"
47 | db.execute( sql, key )
48 | end
49 |
50 | #
51 | # 更新
52 | #
53 | def upsert( db, key, val )
54 | sql = "INSERT OR REPLACE INTO #{@tbl_name} (key, val) VALUES (?,?) ;"
55 | db.execute( sql, key, val )
56 | end
57 |
58 | end
59 |
--------------------------------------------------------------------------------
/src/views/rsv_conf.slim:
--------------------------------------------------------------------------------
1 | /
2 | / 録画予約登録 確認
3 | /
4 |
5 | css:
6 | #item {
7 | width:8em;
8 | }
9 | #title {
10 | margin-bottom: 2em;
11 | }
12 | #submit {
13 | margin-top: 1em;
14 | }
15 | .nowrap {
16 | white-space: nowrap;
17 | }
18 | th {
19 | padding-right: 1em;
20 | }
21 | #in,#email_inline {
22 | height: 2em;
23 | }
24 | #email_inline {
25 | width: 50%;
26 | margin-left: 1em;
27 | }
28 | span { color: #000000}
29 |
30 | - require_relative 'rsv_conf.rb'
31 | - dp = ReservationConfirm.new( )
32 | - d = dp.setPara( )
33 |
34 |
35 | form id="form" method="POST" action="/rsv/add"
36 | table.striped
37 | tr
38 | th.nowrap オプション
39 | td.nowrap
40 | .row
41 | .col
42 | | 保存サブディレクトリ
43 | .col
44 | input#email_inline( type="text" list="dlist" name="dir" size="60" class="validate" value="" autocomplete="off" )
45 | datalist#dlist
46 | | #{{d[:dirs]}}
47 | .row
48 | label
49 | input.filled-in( type="checkbox" name="jitan" checked=(d[:jitanchk]))
50 | span#cbcomm チューナーが競合した場合に録画時間の短縮を許可する。
51 |
52 |
--------------------------------------------------------------------------------
/src/db/FilterResult.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | class DBfilterResult
6 |
7 | include Base
8 |
9 | def initialize( )
10 | @list = {
11 | id: "id",
12 | pid: "pid",
13 | rid: "rid",
14 | }
15 | @para = []
16 | @list.each_pair { |k,v| @para << v }
17 | @tbl_name = "filter_result"
18 | end
19 |
20 | #
21 | # 格納
22 | #
23 | def insert(db, pid, rid )
24 | data = { id: nil, pid: pid, rid: rid }
25 | ( sql, args ) = makeInsSql( @list, @para, @tbl_name, data )
26 |
27 | db.execute( sql, *args )
28 | end
29 |
30 |
31 | #
32 | # 検索
33 | #
34 | def select(db, pid: nil, tend: nil )
35 | ( sql, args ) = makeSelectSql( @para,@tbl_name, pid: pid, tend: tend )
36 |
37 | row = db.execute( sql, *args )
38 | row2hash( @list, row )
39 | end
40 |
41 |
42 | #
43 | # 削除
44 | #
45 | def delete( db, id )
46 | sql = "delete from filter_result where pid = ? ;"
47 | db.execute( sql, id )
48 | end
49 |
50 | #
51 | # 更新
52 | #
53 | def update( db, data, id )
54 | ( sql, args ) = makeUpdSql( @list, @tbl_name, data, id )
55 |
56 | db.execute( sql, *args )
57 | end
58 |
59 | end
60 |
--------------------------------------------------------------------------------
/src/db/PTOption.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | #
3 | # 番組表のオプション のload/save
4 | #
5 | class PTOption
6 |
7 | SP = "StationPage"
8 | HP = "HourPixel"
9 | HN = "HourNum"
10 | TT = "ToolTip"
11 |
12 | attr_reader :sp, :hp, :tt, :hn
13 |
14 | def initialize( )
15 | keyval = DBkeyval.new
16 | DBaccess.new().open do |db|
17 | @sp = keyval.select( db, SP )
18 | @hp = keyval.select( db, HP )
19 | @hn = keyval.select( db, HN )
20 | @tt = keyval.select( db, TT )
21 | end
22 |
23 | @sp = StationPage if @sp == nil
24 | @hp = 180 if @hp == nil
25 | @hn = 6 if @hn == nil
26 | if @tt == nil
27 | @tt = true
28 | else
29 | @tt = @tt == 1 ? true : false
30 | end
31 | end
32 |
33 | def save( para )
34 | hp = para["range1"]
35 | hn = para["range2"]
36 | sp = para["range3"]
37 | tt = para["tooltip"] == "on" ? 1 : 0
38 |
39 | keyval = DBkeyval.new
40 | DBaccess.new().open do |db|
41 | @sp = keyval.upsert( db, SP, sp ) if sp != nil
42 | @hp = keyval.upsert( db, HP, hp ) if hp != nil
43 | @hn = keyval.upsert( db, HN, hn ) if hn != nil
44 | @tt = keyval.upsert( db, TT, tt ) if tt != nil
45 | end
46 | end
47 |
48 | end
49 |
--------------------------------------------------------------------------------
/doc/BS再編ログ/2021-02-09.txt:
--------------------------------------------------------------------------------
1 |
2 | ・実施内容
3 | トランスポンダの移動 & スロット縮減
4 | https://www.apab.or.jp/topics/2020/2101070001.html
5 |
6 | BS 21ch→19ch J SPORTS 3
7 |
8 | 2021年2月9日(火)の早朝(午前0時~午前7時)
9 |
10 | ・2021/02/09 以前の slot 構成
11 |
12 | BS19_0 J SPORTS 4
13 | BS19_1 J SPORTS 1
14 | BS19_2 J SPORTS 2
15 |
16 | BS21_0 WOWOWプラス
17 | BS21_1 J SPORTS 3
18 | BS21_2 グリーンチャンネル
19 |
20 | ・2021/02/09 以後の slot 構成(予想)
21 |
22 | BS19_0 J SPORTS 4
23 | BS19_1 J SPORTS 1
24 | BS19_2 J SPORTS 2
25 | BS19_3 J SPORTS 3
26 |
27 | BS21_0 WOWOWプラス
28 | BS21_1 グリーンチャンネル
29 |
30 | ・raspirec への影響
31 |
32 | グリーンチャンネルの EPGでの slot番号がどうなるか(2のままか 1に移行する
33 | か)で、パッチを当てる可能性がある。( 2020/03 の Fox のパターン )
34 |
35 | 2021/02/09 以前 2021/02/09 以後 2021/02/09 以後のEPG
36 | BS21_0 WOWOWプラス 同左 同左
37 | BS21_1 J SPORTS 3 グリーンチャンネル 空き
38 | BS21_2 グリーンチャンネル 未使用 グリーンチャンネル
39 |
40 | ・結果
41 |
42 | 予想とは違いグリーンチャンネル は BS21_2 のままなので、通常の EPG 更
43 | 新時に行われる J SPORTS 3 のチャンネル情報の変更だけで済み。
44 |
45 | 2021-02-09 06:52:36 channel情報変更 J SPORTS 3 stinfo_slot 1 -> 3
46 | 2021-02-09 06:52:36 channel情報変更 J SPORTS 3 stinfo_tp BS21 -> BS19
47 | 2021-02-09 06:52:36 channel情報変更 J SPORTS 3 tsid 18257 -> 18227
48 |
49 | ・疑問点
50 | 今回と 2020/03 の Fox の場合は何が違うのか?
51 | Fox は 0番slot だから?
52 |
--------------------------------------------------------------------------------
/src/lib/rewriteConst.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | #
3 | # DevAutoDetection で、定数を書き換え
4 | #
5 |
6 | def constRewrite( name, val)
7 | sym = name.to_sym
8 | base = File.basename($0)
9 | if Object.const_defined?(sym) == true
10 | Object.send(:remove_const, sym)
11 | end
12 | DBlog::sto("Const rewrite #{base} #{name} = #{val}")
13 | Object.const_set(sym, val )
14 | end
15 |
16 |
17 | #
18 | # チューナーデバイスの自動設定
19 | #
20 | if Object.const_defined?(:RewriteConst) == true and
21 | RewriteConst == true and
22 | Object.const_defined?(:DevAutoDetection) == true and
23 | DevAutoDetection == true
24 | if ( data = DeviceChk.new.load()) != nil
25 | if data.total > 0
26 | constRewrite( "DeviceList_GR", data.listGR )
27 | constRewrite( "DeviceList_BSCS", data.listBC )
28 | constRewrite( "DeviceList_GBC", data.listGBC )
29 | else
30 | DBlog::error(nil,"デバイスの自動検出に失敗しました。 DevAutoDetection を false にして、手動で設定して下さい。")
31 | end
32 | end
33 | end
34 |
35 | # #
36 | # # EpgPatchEnable = :auto の場合の自動設定
37 | # #
38 | # if ( Object.const_defined?(:EpgPatchEnable) == true ) &&
39 | # EpgPatchEnable == :auto
40 | # if ( Object.const_defined?(:Recpt1_cmd) == true )
41 | # if Recpt1_cmd =~ /recdvb/
42 | # constRewrite( "EpgPatchEnable", false )
43 | # else
44 | # constRewrite( "EpgPatchEnable", true )
45 | # end
46 | # end
47 | # end
48 |
--------------------------------------------------------------------------------
/src/model/LogRote.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 予約一覧
6 | #
7 | require 'sys/filesystem'
8 |
9 | class LogRote
10 |
11 | Limit = 100 * 1000 # 100k byte 以上
12 | Max = 5 # 残すのは 5世代
13 |
14 | def initialize( )
15 | @flist = [
16 | LogFname,
17 | StdoutM,
18 | StderrM,
19 | StdoutH,
20 | StderrH,
21 | StdoutT,
22 | StderrT,
23 | ]
24 |
25 | end
26 |
27 | #
28 | # log ローテーションが必要か?
29 | #
30 | def need?()
31 | @flist.each do |fn|
32 | if test( ?f, fn )
33 | size = File.size( fn )
34 | if size > Limit
35 | return true
36 | end
37 | end
38 | end
39 | false
40 | end
41 |
42 | def exec()
43 | Max.downto( 1 ) do |n|
44 | dir = sprintf("%s/old_%02d",LogDir, n )
45 | if test( ?d, dir )
46 | if n == Max
47 | FileUtils.rm_r( dir )
48 | else
49 | to = sprintf("%s/old_%02d",LogDir, n +1 )
50 | File.rename( dir, to )
51 | end
52 | end
53 | end
54 | to = sprintf("%s/old_%02d",LogDir, 1 )
55 | Dir.mkdir( to )
56 | @flist.each do |fn|
57 | base = File.basename(fn)
58 | to = sprintf("%s/old_%02d/%s",LogDir, 1,base )
59 | File.rename( fn, to )
60 | FileUtils.touch( fn )
61 | end
62 | end
63 | end
64 |
65 |
--------------------------------------------------------------------------------
/src/require.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | require 'pp'
5 | require 'sqlite3'
6 | require 'fileutils'
7 | require 'json'
8 | require 'etc'
9 |
10 | require 'lib/setConst.rb' # 最初に
11 |
12 | require 'lib/httpd_sub.rb'
13 | require 'lib/commlib.rb'
14 | Commlib::makeSubDir()
15 |
16 | require 'lib/misc.rb'
17 | require 'db/DB.rb'
18 | require 'db/base.rb'
19 | require 'db/Category.rb'
20 | require 'db/Channel.rb'
21 | require 'db/EpgTable.rb'
22 | require 'db/Filter.rb'
23 | require 'db/FilterResult.rb'
24 | require 'db/Programs.rb'
25 | require 'db/Reserve.rb'
26 | require 'db/Keyval.rb'
27 | require 'db/Log.rb'
28 | require 'db/UpdateChk.rb'
29 | require 'db/Phchid.rb'
30 | require 'db/PTOption.rb'
31 |
32 | require 'lib/rewriteConst.rb' # DB の後に
33 |
34 | require 'model/Const.rb'
35 | require 'model/Tuner.rb'
36 | require 'model/TunerArray.rb'
37 | require 'model/TunerAssign2.rb'
38 | require 'model/FilterM.rb'
39 | require 'model/Reservation.rb'
40 | require 'model/Timer.rb'
41 | require 'model/Recpt1.rb'
42 | require 'model/EpgLock.rb'
43 | require 'model/Control.rb'
44 | require 'model/FileCopy.rb'
45 | require 'model/DiskKeep.rb'
46 | require 'model/LogRote.rb'
47 | require 'model/ChannelM.rb'
48 | require 'model/Monitor.rb'
49 | require 'model/PacketChk.rb'
50 | require 'model/EpgNearCh.rb'
51 | require 'model/GetEPG.rb'
52 | require 'model/Daily.rb'
53 | require 'model/EpgAutoPatch.rb'
54 |
--------------------------------------------------------------------------------
/doc/recpt1_px4.patch:
--------------------------------------------------------------------------------
1 | --- recpt1/recpt1/pt1_dev.h 2019-09-24 20:29:13.818064078 +0900
2 | +++ recpt1-px4/recpt1/pt1_dev.h 2019-09-24 20:30:08.485287187 +0900
3 | @@ -3,32 +3,28 @@
4 | #define _PT1_DEV_H_
5 |
6 | char *bsdev[NUM_BSDEV] = {
7 | - "/dev/pt1video1",
8 | "/dev/pt1video0",
9 | - "/dev/pt1video5",
10 | - "/dev/pt1video4",
11 | - "/dev/pt1video9",
12 | - "/dev/pt1video8",
13 | - "/dev/pt1video13",
14 | - "/dev/pt1video12",
15 | - "/dev/pt3video1",
16 | - "/dev/pt3video0",
17 | - "/dev/pt3video5",
18 | - "/dev/pt3video4"
19 | + "/dev/pt1video1",
20 | + "/dev/px4video0",
21 | + "/dev/px4video1",
22 | + "/dev/px4video4",
23 | + "/dev/px4video5",
24 | + "/dev/px4-DTV0",
25 | + "/dev/px4-DTV1",
26 | + "/dev/px4-DTV4",
27 | + "/dev/px4-DTV5",
28 | };
29 | char *isdb_t_dev[NUM_ISDB_T_DEV] = {
30 | "/dev/pt1video2",
31 | "/dev/pt1video3",
32 | - "/dev/pt1video6",
33 | - "/dev/pt1video7",
34 | - "/dev/pt1video10",
35 | - "/dev/pt1video11",
36 | - "/dev/pt1video14",
37 | - "/dev/pt1video15",
38 | - "/dev/pt3video2",
39 | - "/dev/pt3video3",
40 | - "/dev/pt3video6",
41 | - "/dev/pt3video7"
42 | + "/dev/px4video2",
43 | + "/dev/px4video3",
44 | + "/dev/px4video6",
45 | + "/dev/px4video7",
46 | + "/dev/px4-DTV2",
47 | + "/dev/px4-DTV3",
48 | + "/dev/px4-DTV6",
49 | + "/dev/px4-DTV7",
50 | };
51 |
52 | // 変換テーブル(ISDB-T用)
53 |
--------------------------------------------------------------------------------
/src/views/opt_dialog.js:
--------------------------------------------------------------------------------
1 |
2 |
41 |
--------------------------------------------------------------------------------
/src/views/fil_res_dsp.slim:
--------------------------------------------------------------------------------
1 | /
2 | / フィルター条件 検索結果
3 | /
4 |
5 | - require_relative 'fil_res_dsp.rb'
6 | - fd = FilterDisp.new( @params )
7 | - r = fd.print( @id, session )
8 |
9 | css:
10 | th {
11 | padding-right: 1em;
12 | }
13 | td,th {
14 | padding: 8px ;
15 | }
16 | .list-inline {
17 | padding-left: 0;
18 | margin-left: -5px;
19 | list-style: none;
20 | }
21 | .list-inline > li {
22 | display: inline-block;
23 | padding-right: 5px;
24 | padding-left: 5em;
25 | }
26 | label { color: #000000}
27 | td {white-space: nowrap; }
28 |
29 | form
30 | ul.list-inline
31 | li #{{r[:title]}}
32 | - if fd.filter?()
33 | li
34 | label
35 | input.cb type="checkbox" name="freeOnly" id="cb" checked=(fd.fo?)
36 | span 予約済みは表示しない
37 |
38 | table.striped
39 | tr
40 | th No
41 | th 放送局
42 | th 日付
43 | th 時間
44 | th 予約
45 | th タイトル
46 | th 概要
47 | | #{{r[:table]}}
48 |
49 | div id="sample-dialog" title="詳細" style="display:none;"
50 | p
51 |
52 | div id="sample-dialog2" title="確認" style="display:none;"
53 | p
54 |
55 | == Commlib::include( SrcDir + "/views/move_top.html" )
56 |
57 | == Commlib::include( SrcDir + "/views/fil_res_dsp.js" )
58 |
59 | javascript:
60 |
61 | $(function(){
62 | $('.cb').on('change', function () {
63 | if ($(this).prop('checked')) {
64 | var url="/fil_res_dsp/#{@id}" +"?FO=on";
65 | } else {
66 | var url="/fil_res_dsp/#{@id}";
67 | }
68 | window.location.href = url ;
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/src/views/log_view.slim:
--------------------------------------------------------------------------------
1 | /
2 | / ログの表示
3 | /
4 |
5 | css:
6 | td,th {
7 | padding: 5px ;
8 | }
9 | label {
10 | margin: 10px ;
11 | color: black;
12 | font-size: 1em;
13 | }
14 | .inline-block {
15 | display: inline-block; /* インラインブロック要素にする */
16 | padding: 0px;
17 | margin: 0px 2px 0px 2px;
18 | }
19 | .pagination {
20 | margin-left: 3em;
21 | }
22 |
23 | - require_relative 'log_view.rb'
24 | - dp = LogView.new( @level, @page )
25 | - radio = dp.radio()
26 |
27 | div
28 | form
29 | label 表示レベル
30 | label
31 | input( type="radio" name="level" value="1" checked=(radio[:debug]))
32 | span DEBUG
33 | label
34 | input(type="radio" name="level" value="2" checked=(radio[:info]))
35 | span 情報
36 | label
37 | input(type="radio" name="level" value="3" checked=(radio[:atte]))
38 | span 注意
39 | label
40 | input(type="radio" name="level" value="4" checked=(radio[:warn]))
41 | span 警告
42 | label
43 | input(type="radio" name="level" value="5" checked=(radio[:err]))
44 | span エラー
45 | == dp.pageSel()
46 |
47 | p
48 | table.striped
49 | tr
50 | th.nowrap レベル
51 | th.nowrap 日時
52 | th.nowrap 内容
53 | == dp.printTable()
54 |
55 | == Commlib::include( SrcDir + "/views/move_top.html" )
56 |
57 | javascript:
58 | $(function(){
59 | $( 'input[name="level"]:radio' ).change( function() {
60 | var radioval = $(this).val();
61 | //console.log(radioval)
62 | window.location.href = "/log_view/" + radioval
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/doc/Install.md:
--------------------------------------------------------------------------------
1 |
2 | ### インストール例
3 |
4 | #### 条件
5 |
6 | PC raspberrypi zero w
7 | TVチューナー PLEX PX-Q3U4
8 | OS raspbian-buster-lite
9 | ドライバー px4_drv
10 |
11 |
12 | #### インストール
13 |
14 | - インストールに必要なパッケージを apt install で インストール
15 |
16 | ```
17 | git
18 | autoconf
19 | raspberrypi-kernel-headers
20 | dkms
21 | cmake
22 | sqlite3
23 | ruby
24 | ruby-sqlite3
25 | ruby-sys-filesystem
26 | ruby-net-ssh
27 | ruby-sinatra
28 | ruby-slim
29 | ruby-sass
30 | ```
31 |
32 | - recpt1
33 |
34 | ```
35 | git clone https://github.com/stz2012/recpt1.git
36 | cd recpt1/recpt1
37 | sh autogen.sh
38 | ./configure
39 | PX-Q3U4 用に デバイス名を変更する。( recpt1_px4.patch 参照 )
40 | make
41 | sudo make install
42 | ```
43 |
44 | - px4_drv
45 |
46 | `git clone https://github.com/nns779/px4_drv.git`
47 |
48 | ドキュメントに従ってインストール
49 |
50 |
51 | - epgdump
52 |
53 | ```
54 | git clone https://github.com/Piro77/epgdump
55 | cd epgdump
56 | cmake .
57 | make
58 | sudo make install
59 | ```
60 |
61 |
62 | - raspirec 本体
63 |
64 | インストールするディレクトリに移動して
65 |
66 | ```
67 | git clone https://github.com/kaikoma-soft/raspirec.git
68 | cd raspirec
69 | mkdir -p ~/.config/raspirec
70 | cp config.rb.sample ~/.config/raspirec/config.rb
71 | エディタで、~/.config/raspirec/config.rb を適宜修正
72 | ```
73 |
74 | - 動作確認
75 |
76 | `% ruby raspirec.rb`
77 |
78 | でプログラムを起動する。
79 | すぐに終了するがバックグランドでサービスが走っているので、
80 |
81 | `http://localhost:4567/`
82 |
83 | にアクセスし、画面が出れば正常
84 | なお初回は、EPG番組表のデータ取得するまで多少の時間が掛かる。
85 |
--------------------------------------------------------------------------------
/src/views/fil_testrun.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # フィルター検索結果
6 | #
7 |
8 |
9 | class FilterTest
10 |
11 | def initialize( id, params, session )
12 | @id = id
13 | @params = params
14 | @session = session
15 | if @session != nil
16 | @fa_type = @session["fa_type"]
17 | end
18 | end
19 |
20 | #
21 | # 試行ボタン
22 | #
23 | def testrun(params)
24 | r = []
25 | fp = FilterM.new()
26 | d = fp.formAna( params )
27 | programs = DBprograms.new
28 | reserve = DBreserve.new
29 | DBaccess.new().open do |db|
30 | r2 = fp.search3( db, d )
31 | data = nil
32 | data = programs.selectSP( db, proid: r2 )
33 | size = sprintf("%d 件",data.size )
34 | size += "以上" if data.size == FilConst::SeachMax
35 | r << sprintf("検索結果 %s ", size)
36 |
37 | tdclas = [ "nowrap","item" ]
38 | data.each_with_index do |t, n |
39 | (day, time, w) = Commlib::stet_to_s( t[:start], t[:end] )
40 | cate = t[:categoryA][0][0]
41 | trcl = %W(color#{cate})
42 | res = reserve.select( db, evid: t[:evid], chid: t[:chid] )
43 | res2 = ""
44 | if res.size > 0
45 | if res[0][:stat] == RsvConst::Normal
46 | res2 = "○"
47 | else
48 | res2 = "×"
49 | end
50 | end
51 | data = [ n+1,t[:name], day,time, res2,t[:title],t[:detail] ]
52 | r << Commlib::printTR2( data, rid: t[:pid], trcl: trcl, tdcl: tdclas, )
53 | end
54 | end
55 | r.join("\n")
56 | end
57 |
58 |
59 | end
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/db/Filter.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | class DBfilter
6 |
7 | include Base
8 |
9 | def initialize( )
10 | @list = {
11 | id: "id",
12 | type: "type",
13 | title: "title",
14 | key: "key",
15 | exclude: "exclude",
16 | regex: "regex",
17 | band: "band",
18 | target: "target",
19 | chanel: "chanel",
20 | category: "category",
21 | wday: "wday",
22 | result: "result",
23 | jitan: "jitan",
24 | subdir: "subdir",
25 | freeonly: "freeonly",
26 | dedupe: "dedupe",
27 | }
28 | @para = []
29 | @list.each_pair { |k,v| @para << v }
30 | @tbl_name = "filter"
31 | end
32 |
33 | #
34 | #
35 | #
36 | def delete( db, id )
37 | sql = "delete from filter where id = ? ;"
38 | db.execute( sql, id )
39 | end
40 |
41 |
42 | #
43 | # 条件検索の取得
44 | #
45 | def select( db, id: nil, type: nil )
46 | ( sql, args ) = makeSelectSql( @para, @tbl_name, id: id, type: type)
47 | row = db.execute( sql, args )
48 | row2hash( @list, row )
49 | end
50 |
51 | #
52 | # 条件検索の追加
53 | #
54 | def insert( db, data )
55 |
56 | ( sql, args ) = makeInsSql( @list, @para, @tbl_name, data )
57 | db.execute( sql, *args )
58 | end
59 |
60 | #
61 | # 条件検索の書き換え
62 | #
63 | def update( db, data, id )
64 |
65 | ( sql, args ) = makeUpdSql( @list, @tbl_name, data, id )
66 | db.execute( sql, *args )
67 | end
68 |
69 | #
70 | # 検索結果の更新
71 | #
72 | def update_res(db, id, size )
73 | sql = "update filter set result = ? where id = ?;"
74 | r = db.execute( sql, size, id )
75 | end
76 |
77 | end
78 |
--------------------------------------------------------------------------------
/src/views/config.slim:
--------------------------------------------------------------------------------
1 |
2 | css:
3 | .title {
4 | margin: 1.5em 0.5em 1.0em 0.5em ;
5 | font-size: 1.2em ;
6 | }
7 | td,th {
8 | font-size: 0.9em ;
9 | }
10 | .pad {
11 | padding-left:2em ;
12 | }
13 | ul.browser-default {
14 | padding-left: 40px;
15 | }
16 | ul.browser-default li {
17 | list-style-type: inherit;
18 | }
19 | .title2 {
20 | margin: 0.5em 0.5em 0.5em 0.5em ;
21 | }
22 |
23 | - require_relative 'config.rb'
24 | - dp = Config.new()
25 |
26 |
27 | h2.title
28 | | 目次
29 | ol
30 | li.title2
31 | a href="#init" はじめに
32 | li.title2
33 | a href="#httpd" httpd
34 | li.title2
35 | a href="#dirFile" ディレクトリ、ファイル関係
36 | li.title2
37 | a href="#recTime" 録画タイミング関係
38 | li.title2
39 | a href="#tuner" チューナー関係
40 | li.title2
41 | a href="#epg" EPG関係
42 | li.title2
43 | a href="#RecExt" 録画自動延長
44 | li.title2
45 | a href="#dlgOpt" ダイアログのオプション初期値
46 | li.title2
47 | a href="#tsft" TSファイル転送
48 | li.title2
49 | a href="#hlsMon" HLS モニタ機能
50 | li.title2
51 | a href="#mpvMon" mpv モニタ機能
52 | li.title2
53 | a href="#PacketChk" パケットチェック機能
54 | li.title2
55 | a href="#other" その他
56 |
57 | hr
58 |
59 |
60 | div
61 | ol
62 | li.title
63 | a name="init" はじめに
64 |
65 | ul.browser-default
66 | li
67 | | config ファイルは、次の優先度で読み込まれます。
68 | ol
69 | li
70 | | 環境変数 RASPIREC_CONF で指定したファイル(拡張子は .rb)
71 | li
72 | | $HOME/.config/raspirec/config.rb
73 | li
74 | | 現在読み込んでいるのは、#{ConfigPath} です。
75 | li
76 | | 配列の定義で、%w( 1 2 3 ) は [ "1", "2", "3" ] と等価です。
77 |
78 | == dp.printHTML()
79 |
--------------------------------------------------------------------------------
/src/tool/channelScan.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 地デジ チャンネルスキャン
6 | #
7 |
8 | require 'optparse'
9 | require 'json'
10 |
11 | base = File.dirname( $0 )
12 | [ ".", "..","src", base + "/..", ].each do |dir|
13 | if test( ?f, dir + "/require.rb")
14 | $: << dir
15 | end
16 | end
17 | require 'require.rb'
18 |
19 |
20 | $opt = {
21 | :d => false, # debug
22 | :w => false, # write config
23 | :t => 10, # scan time
24 | }
25 |
26 | OptionParser.new do |opt|
27 | opt.on('-d') { $opt[:d] = true }
28 | opt.on('-w') { $opt[:w] = true }
29 | opt.on('-t n') {|v| $opt[:t] = v.to_i }
30 | opt.parse!(ARGV)
31 | end
32 |
33 |
34 | class ExecError < StandardError; end
35 |
36 |
37 | GR_start = 10
38 | GR_end = 52
39 | pt1 = Recpt1.new
40 | $rec_pid = []
41 |
42 | outdir = DataDir + "/json"
43 | unless test(?d, outdir )
44 | Dir.mkdir( outdir )
45 | end
46 |
47 | chs = []
48 | GR_start.upto(GR_end) do |ch|
49 | outfname = outdir + "/#{ch}.json"
50 | now1 = Time.now
51 | cn = pt1.getEpgJson_retry( ch, $opt[:t], outfname )
52 | now2 = Time.now
53 |
54 | name = ""
55 | id = ""
56 | if test( ?s, outfname )
57 | File.open( outfname, "r" ) do |fp|
58 | str = fp.read
59 | data = JSON.parse(str)
60 | if data.size > 0
61 | data.each do |d|
62 | name += d["name"] + " "
63 | id += d["id"] + " "
64 | end
65 | end
66 | end
67 | end
68 |
69 | if $opt[:d] == true
70 | printf("# %02d : %03.1fdB %s %s (%d)\n",ch, cn, id, name, now2 - now1 )
71 | else
72 | printf("# %02d : %03.1fdB %s %s\n",ch, cn, id, name )
73 | end
74 | if name != ""
75 | chs << ch
76 | end
77 |
78 | sleep(0.1)
79 | end
80 |
81 | printf("\nGR_EPG_channel = %%w( %s )\n",chs.join(" "))
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/views/opt_dialog.slim:
--------------------------------------------------------------------------------
1 |
2 |
3 | - require_relative 'opt_dialog.rb'
4 | - dp = Dialog_opt.new( session )
5 |
6 | doctype html
7 |
8 | css:
9 | label { color: #000000}
10 | #form {
11 | margin-top: 2em;
12 | margin-bottom: 2em;
13 | }
14 |
15 |
16 | form( id="form" method="post" action="" )
17 | .row
18 | .col.s1
19 | .col.s4 縦軸の長さ(1時間当たりのピクセル数)
20 | .col.s6
21 | input(type="range" id="range1" min="50" max="500" value="#{dp.hp}" step="10" name="range1")
22 | .col.s1
23 | span#value1 #{dp.hp}
24 | - if dp.chflag == false
25 | .row
26 | .col.s1
27 | .col.s4 縦軸の長さ(時間数)
28 | .col.s6
29 | input(type="range" id="range2" min="3" max="24" value="#{dp.hn}" step="1" name="range2")
30 | .col.s1
31 | span#value2 #{dp.hn}
32 | .row
33 | .col.s1
34 | .col.s4 1ページ当たりの放送局数 (個)
35 | .col.s6
36 | input(type="range" id="range3" min="1" max="60" value="#{dp.sp}" name="range3")
37 | .col.s1
38 | span#value3 #{dp.sp}
39 | .row
40 | .col.s1
41 | .col.s4 ツールチップ
42 | .col.s6
43 | label
44 | input.cb type="checkbox" name="tooltip" id="cb" checked=(dp.tt)
45 | span 表示する
46 | .col.s1
47 |
48 |
49 | javascript:
50 | var elem = document.getElementById('range1');
51 | var target = document.getElementById('value1');
52 | var elem2 = document.getElementById('range2');
53 | var target2 = document.getElementById('value2');
54 | var elem3 = document.getElementById('range3');
55 | var target3 = document.getElementById('value3');
56 |
57 | var rangeValue = function (elem, target) {
58 | return function(evt){
59 | target.innerHTML = elem.value;
60 | }
61 | }
62 | elem.addEventListener('input', rangeValue(elem, target));
63 | elem2.addEventListener('input', rangeValue(elem2, target2));
64 | elem3.addEventListener('input', rangeValue(elem3, target3));
65 |
66 |
--------------------------------------------------------------------------------
/src/views/ch_tbl_list.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # ch 毎の番組表
6 | #
7 |
8 |
9 | class ChTblList
10 |
11 | Base = "/ch_tbl"
12 |
13 | def initialize( )
14 | @tate = 24
15 | @ret = {}
16 | channel = DBchannel.new
17 | DBaccess.new().open() do |db|
18 | row = channel.select(db, order: "order by svid")
19 | row.each do |r|
20 | next if r[:updatetime] == -1
21 | cl = r[:skip] == 0 ? "disp" : "skip"
22 | tmp = sprintf(%Q{ %s },cl,r[:chid],r[:name])
23 | @ret[ r[:band] ] ||= []
24 | @ret[ r[:band] ] << tmp
25 | end
26 | end
27 |
28 | end
29 |
30 | #
31 | # 表
32 | #
33 | def printTable( )
34 | r = []
35 | tmp = {}
36 | %w( GR BS CS ).each do |band|
37 | n = 0
38 | tmp[ band ] ||= {}
39 | tmp[ band ][ n ] ||= []
40 |
41 | if @ret[band] != nil and @ret[band].size > @tate
42 | @ret[band].each_slice( @tate ) do |part|
43 | tmp[ band ][ n ] ||= []
44 | tmp[ band ][ n ] = part
45 | n += 1
46 | end
47 | else
48 | tmp[ band ][ n ] = @ret[band]
49 | end
50 | end
51 |
52 | r << " "
53 | tmp.keys.each do |band|
54 | size = tmp[band].size
55 | r << %Q( #{band} )
56 | end
57 | r << " "
58 |
59 | while true
60 | r << ""
61 | count = 0
62 | tmp.keys.each do |band|
63 | tmp[band].keys.each do |n|
64 | if tmp[band][n] != nil
65 | d = tmp[band][n].shift
66 | if d != nil
67 | r << "" + d + " "
68 | count += 1
69 | else
70 | r << " "
71 | end
72 | end
73 | end
74 | end
75 | r << " "
76 | break if count == 0
77 | end
78 | r.join("\n")
79 | end
80 |
81 |
82 | end
83 |
--------------------------------------------------------------------------------
/src/views/ch_tbl.slim:
--------------------------------------------------------------------------------
1 |
2 | #
3 | #
4 | #
5 |
6 |
7 | css:
8 | th {
9 | padding-right: 1em;
10 | }
11 | td,th {
12 | padding: 8px ;
13 | }
14 | .list-inline {
15 | padding-left: 0;
16 | margin-left: -5px;
17 | list-style: none;
18 | }
19 | .list-inline > li {
20 | display: inline-block;
21 | padding-right: 5px;
22 | padding-left: 5em;
23 | font-size: 1.2em ;
24 | }
25 | .l {
26 | font-size: 1.3em ;
27 | }
28 | .m {
29 | padding-left: 0.5em;
30 | font-size: 1.1em ;
31 | }
32 | label { color: #000000}
33 |
34 | - require_relative 'ch_tbl.rb'
35 | - pt = ChTbl.new( @ch )
36 |
37 | ul.list-inline
38 | li
39 | span.l
40 | | #{pt.name}
41 | span.m
42 | | ( ch:#{pt.phch}, svid:#{pt.svid} )
43 | li
44 | label
45 | input.cb type="checkbox" name="skip" id="cb" checked=(pt.skip)
46 | span 番組表、検索の対象から外す。
47 | li
48 | a.btn.waves-effect.waves-light.btn-small.lime.darken-2#option オプション
49 |
50 | .row
51 | div style="display:table;border-collapse: separate;border-spacing: 3px 0;"
52 | .dtr
53 | div.station style="width:2.3em;"
54 | a href="/cate_color" style="color:white;" 色
55 | == pt.yokojiku()
56 | .dtr
57 | .dtc
58 | == pt.tatejiku()
59 | == pt.prgTable( @ch )
60 |
61 |
62 | == Commlib::include( SrcDir + "/views/move_top.html" )
63 | == Commlib::include( SrcDir + "/views/prg_tbl.cssjs" )
64 | == Commlib::includeIf( pt.tt, SrcDir + "/views/tooltip.js" )
65 |
66 | div#option-dialog( style="display:none;" title="オプション" )
67 | p
68 |
69 | == Commlib::include( SrcDir + "/views/opt_dialog.js" )
70 |
71 | javascript:
72 |
73 | $(function(){
74 | $('.cb').on('change', function () {
75 | var url="/ch_tbl/#{@ch}"
76 | if ($(this).prop('checked')) {
77 | url = url + "?skip=on";
78 | } else {
79 | url = url + "?skip=off";
80 | }
81 | window.location.href = url ;
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/src/TV/Arguments.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | require 'optparse'
4 |
5 | class Arguments
6 |
7 | attr_reader :x # x座標
8 | attr_reader :y # y座標
9 | attr_reader :w # 幅
10 | attr_reader :h # 高さ
11 | attr_reader :font # font名
12 | attr_reader :d # debug level
13 | attr_reader :round # 巡回ボタン
14 | attr_reader :epg # EPG 取得
15 |
16 | def initialize(argv)
17 | @x = @y = @w = @h = @font = nil
18 | @round = nil
19 | @d = 0
20 | @epg = false
21 |
22 | if Object.const_defined?( :RaspirecTV_font ) == true
23 | @font = RaspirecTV_font
24 | end
25 | if Object.const_defined?( :RaspirecTV_GEO ) == true
26 | geoConv( RaspirecTV_GEO )
27 | end
28 |
29 | op = option_parser
30 | op.parse!(argv)
31 | rescue OptionParser::ParseError => e
32 | $stderr.puts e
33 | exit(1)
34 | end
35 |
36 | private
37 |
38 | def option_parser
39 | OptionParser.new do |op|
40 | op.on('-g', '--geometry WxH+X+Y','座標指定(WxH+X+Y or +X+Y)') { |t|
41 | geoConv( t )
42 | }
43 | op.on('-f', '--font font_name','font指定 (例:"Sans 14")'){ |t|
44 | @font = t
45 | }
46 | op.on('-r', '--round time',Integer,'巡回ボタン表示(time=巡回時間(秒))'){ |t|
47 | @round = t.to_i
48 | }
49 | op.on('-e', '--epg','EPG取得'){ |t|
50 | @epg = true
51 | }
52 | op.on('-d', '--debug','debug mode'){ |t|
53 | @d += 1
54 | }
55 | end
56 | end
57 |
58 | #
59 | # 座標の数値化 WxH+X+Y
60 | #
61 | def geoConv( geo )
62 | if geo =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/
63 | @w, @h, @x, @y = $1.to_i,$2.to_i,$3.to_i,$4.to_i
64 | elsif geo =~ /^(\d+)\+(\d+)$/
65 | @x, @y = $1.to_i,$2.to_i
66 | else
67 |
68 | end
69 | end
70 |
71 | end
72 |
73 | #
74 | # debug 出力
75 | #
76 | def dlog( str, level = 0 )
77 | if $arg != nil
78 | if $arg.d > level
79 | puts( str )
80 | end
81 | end
82 | end
83 |
84 |
--------------------------------------------------------------------------------
/src/views/prg_tbl.slim:
--------------------------------------------------------------------------------
1 |
2 | css:
3 | a {
4 | color: white ;
5 | }
6 | #band {
7 | margin-left: 10px;
8 | margin-right: 10px;
9 | padding-left: 10px;
10 | padding-right: 10px;
11 | }
12 | #date {
13 | margin-left: 10px;
14 | margin-right: 10px;
15 | width: 120px;
16 | }
17 |
18 | #option {
19 | margin-left: 200px;
20 | margin-right: 10px;
21 | }
22 | label { color: #000000}
23 |
24 |
25 | - require_relative 'prg_tbl.rb'
26 | - pt = PrgTbl.new( @band, @day, @time )
27 | - timeHref = pt.getTimeHref()
28 | - d = pt.setFormPara()
29 |
30 |
31 | .row
32 | .col
33 | == pt.dateSel()
34 | .col
35 | a.btn.waves-effect.waves-light.btn-small.green.darken-4 href="#{timeHref[0]}" 朝
36 | .col
37 | a.btn.waves-effect.waves-light.btn-small.green.darken-4 href="#{timeHref[1]}" 昼
38 | .col
39 | a.btn.waves-effect.waves-light.btn-small.green.darken-4 href="#{timeHref[2]}" 夜
40 | .col
41 | a.btn.waves-effect.waves-light.btn-small.green.darken-4 href="#{timeHref[3]}" 深夜
42 | .col
43 | a.btn.waves-effect.waves-light.btn-small.lime.darken-4 href="#{timeHref[4]}" -3H
44 | .col
45 | a.btn.waves-effect.waves-light.btn-small.lime.darken-4 href="#{timeHref[5]}" +3H
46 | .col
47 | == pt.bandSel()
48 | .col
49 | == pt.pageSel()
50 |
51 | .col
52 | a.btn.waves-effect.waves-light.btn-small.lime.darken-2#option オプション
53 |
54 |
55 | .row
56 | div style="display:table;border-collapse: separate;border-spacing: 3px 0;"
57 | .dtr
58 | div.station style="width:2.3em;"
59 | a href="/cate_color" style="color:white;" 色
60 | == pt.yokojiku()
61 |
62 | .dtr
63 | .dtc
64 | == pt.tatejiku()
65 | == pt.prgTable()
66 |
67 | == pt.nowLine()
68 |
69 | == Commlib::include( SrcDir + "/views/prg_tbl.cssjs" )
70 | == Commlib::includeIf( pt.tt, SrcDir + "/views/tooltip.js" )
71 | == Commlib::include( SrcDir + "/views/move_top.html" )
72 |
73 |
74 |
79 |
80 | == Commlib::include( SrcDir + "/views/opt_dialog.js" )
81 |
--------------------------------------------------------------------------------
/src/tool/json2text.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # EPG の json を読みやすく整形して出力する。
6 | #
7 |
8 | require 'optparse'
9 | require 'fileutils'
10 | require 'json'
11 |
12 |
13 | class Json2text
14 |
15 | PRO = "programs"
16 | TITLE = "title"
17 | EVID = "event_id"
18 | CHAN = "channel"
19 | DETA = "detail"
20 | EXTD = "extdetail"
21 | START = "start"
22 | ENDT = "end"
23 | DURA = "duration"
24 | CATE = "category"
25 | STINF = "satelliteinfo"
26 |
27 | def initialize( fname, opt )
28 | @count = 0
29 |
30 | File.open( fname, "r" ) do |fp|
31 | str = fp.read
32 | data = JSON.parse(str)
33 | if opt[:evid] != nil
34 | evid = opt[:evid].to_i
35 | data.each do |ch|
36 | if ch[ PRO ] != nil
37 | ch[ PRO ].each do |prog|
38 | if prog[ EVID ] == evid
39 | print( ch, prog )
40 | end
41 | end
42 | end
43 | end
44 | else
45 | data.each do |ch|
46 | if ch[ PRO ] != nil
47 | ch[ PRO ].each do |prog|
48 | print( ch, prog )
49 | end
50 | end
51 | end
52 | end
53 | end
54 | end
55 |
56 |
57 | def print( ch, prog)
58 | puts("") if @count > 0
59 |
60 | name = ch["name"]
61 | stinf = ch[STINF] != nil ?
62 | sprintf("%s_%s",ch[STINF]["TP"],ch[STINF]["SLOT"]) :
63 | ""
64 | [ CHAN, TITLE, DETA,EVID, START, ENDT, DURA ].each do |key|
65 | case key
66 | when START, ENDT
67 | val = Time.at( prog[ key ] / 1000 ).to_s
68 | when CHAN
69 | val = sprintf("%s %s %s", name, prog[ key ], stinf )
70 | else
71 | val = prog[ key ]
72 | end
73 | printf("%10s: %s\n", key, val )
74 | end
75 |
76 | @count += 1
77 | end
78 |
79 | end
80 |
81 |
82 | $opt = {
83 | :evid => nil, # event id 指定
84 | }
85 |
86 | OptionParser.new do |opt|
87 | opt.on('--evid ID') {|v| $opt[:evid] = v }
88 | opt.parse!(ARGV)
89 | end
90 |
91 |
92 | ARGV.each do |fname|
93 | Json2text.new( fname, $opt )
94 | end
95 |
96 |
--------------------------------------------------------------------------------
/src/views/mpv_mon.slim:
--------------------------------------------------------------------------------
1 | /
2 | / mpv モニタ
3 | /
4 |
5 | css:
6 | td,th {
7 | padding: 10px ;
8 | }
9 | th {
10 | width: 15em;
11 | }
12 | span { color: #000000}
13 | .busy {
14 | color: #f00000;
15 | }
16 | .radio {
17 | margin-right: 2em;
18 | }
19 |
20 |
21 | - require_relative 'mpv_mon.rb'
22 | - dp = MpvMon.new( @tunNum, @cmd )
23 | - dp.createData( )
24 |
25 |
26 | .row
27 | form id="form2" method="POST" action="/mpv_mon"
28 | table.striped
29 | tr
30 | th チューナー
31 | td
32 | == dp.deviceSelect()
33 | tr
34 | th デバイスファイル名
35 | td
36 | == dp.devfn()
37 | tr
38 | th.nowrap
39 | | 状態
40 | td
41 | == dp.statS
42 | tr
43 | th.nowrap
44 | | 選択中のチャンネル
45 | td
46 | == dp.selCh()
47 | tr
48 | th.nowrap
49 | | 番組名
50 | td
51 | | #{dp.prog_name}
52 | tr
53 | th.nowrap
54 | | 番組概要
55 | td
56 | | #{dp.prog_detail}
57 | tr
58 | th.nowrap
59 | | アクション
60 | td
61 | a.btn.waves-effect.waves-light.btn-small href="#{dp.base_url}/stop" class="#{dp.dis_stop()}" 停止
62 |
63 | .row
64 | div#band
65 | ul.tabs
66 | - dp.bands().each do |band|
67 | li.tab.col.s2
68 | a href="##{band}" class="#{dp.activeBand?( band )}"
69 | | #{dp.bandname[band]}
70 |
71 | div
72 | - dp.bands().each do |band|
73 | div.col.s8 id = band
74 | == dp.data[band]
75 |
76 |
77 |
78 |
79 |
80 |
81 | javascript:
82 | $(function(){
83 | $( 'input[name="devfn"]:radio' ).change( function() {
84 | var radioval = $(this).val();
85 | window.location.href = "/mpv_mon/" + radioval + "/disp/";
86 | });
87 | });
88 |
89 | $(document).ready(function(){
90 | $('.tabs').tabs();
91 | });
92 |
93 | $(document).ready(function(){
94 | $('select').formSelect();
95 | });
96 |
97 |
98 |
99 | == Commlib::include( SrcDir + "/views/move_top.html" )
100 |
--------------------------------------------------------------------------------
/src/views/ch_info.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # ch 情報
6 | #
7 |
8 |
9 | class ChaneruInfo
10 |
11 | attr_reader :name, :skip, :phch, :svid, :tt
12 |
13 | def initialize( )
14 | ( @data, @chid_prog ) = getChData( )
15 | @older = 3600 * 24 * 31 # 1ヶ月
16 | end
17 |
18 | #
19 | # channelデータの取得
20 | #
21 | def getChData( )
22 | ret = nil
23 | chid_prog = {}
24 | DBaccess.new().open do |db|
25 | channel = DBchannel.new
26 | ret = channel.select( db, order: "order by band_sort,chid" )
27 |
28 | # 過去から全ての予約数
29 | reserve = DBreserve.new
30 | prog = reserve.select( db )
31 | prog.each do |tmp|
32 | chid_prog[ tmp[:chid] ] ||= 0
33 | chid_prog[ tmp[:chid] ] += 1
34 | end
35 | end
36 | [ ret, chid_prog ]
37 | end
38 |
39 | #
40 | # 表
41 | #
42 | def printTable()
43 | ret = []
44 | n = 1
45 | b = %Q(削除 )
46 |
47 | now = Time.now.to_i
48 | @data.each do |d|
49 | t = d[:updatetime].to_i
50 | next if t == -1
51 | color = now - t > @older ? "color9" : ""
52 | date = t == 0 ? "-" : Time.at( t ).strftime("%Y-%m-%d %H:%M:%S")
53 | skip = d[:skip] == 1 ? "On" : "-"
54 | count = @chid_prog[ d[:chid] ] == nil ? 0 : @chid_prog[ d[:chid] ]
55 | #del = sprintf( b, count == 0 ? "" : "disabled" , d[:chid], )
56 | del = sprintf( b, "", d[:chid], )
57 |
58 | arg = [ n, d[:band], d[:chid], d[:name],d[:tsid],d[:onid],
59 | d[:svid],d[:stinfo_tp],d[:stinfo_slot], skip, date, count, del]
60 | ret << Commlib::printTR2( arg, trcl: [color] )
61 | n += 1
62 | end
63 | ret.join()
64 | end
65 |
66 | #
67 | # EPG パッチデータ
68 | #
69 | def epgPatchTable()
70 |
71 | ret = []
72 | if $epgPatch == nil
73 | $epgPatch = EpgPatch.new.getData()
74 | end
75 |
76 | $epgPatch.each_pair do |k,v|
77 | v.each_pair do |k2,v2|
78 | arg = [ k, k2, v2 ]
79 | ret << Commlib::printTR2( arg )
80 | end
81 | end
82 | ret.join()
83 |
84 | end
85 |
86 |
87 |
88 | end
89 |
--------------------------------------------------------------------------------
/src/views/prg_dialog.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 番組詳細
6 | #
7 |
8 |
9 | class Dialog_pid
10 |
11 | Base = "/prgtbl"
12 |
13 | def initialize( pid )
14 | @pid = pid
15 | end
16 |
17 | def printTable2( title, val)
18 | sprintf(%Q{ %s %s },title, val)
19 | end
20 |
21 | def extdetail2table( data )
22 | r = []
23 | r << %Q{}
24 | data.each do |tmp|
25 | title = tmp[ "item_description" ]
26 | item = tmp[ "item" ]
27 | r << %Q{ }
28 | r << %Q{ #{title} #{item} }
29 | r << %Q{ }
30 | end
31 | r << %Q{
}
32 |
33 | r.join("\n")
34 | end
35 |
36 |
37 |
38 | def getData()
39 | row = nil
40 | programs = DBprograms.new()
41 | category = DBcategory.new( )
42 |
43 | DBaccess.new(DbFname).open do |db|
44 | data = programs.selectSP( db, proid: @pid )
45 | if data.size > 0
46 | data2 = data.first
47 | cate = []
48 | data2[:categoryA].each do |tmp|
49 | if tmp[0] != 0
50 | cate << category.conv2str(db, tmp[0],tmp[1] ).join(" : ")
51 | end
52 | end
53 | return [ data2,cate ]
54 | end
55 | end
56 | [ nil, nil ]
57 | end
58 |
59 | #
60 | # データの表示
61 | #
62 | def printTable()
63 | r = []
64 |
65 | ( tmp, cate ) = getData()
66 | if tmp != nil and tmp.size > 0
67 | r << printTable2("放送局名", tmp[:name] + " (#{tmp[:chid]})" )
68 | r << printTable2("番組名", tmp[:title] + " (evid=#{tmp[:evid]})" )
69 | r << printTable2("概要", tmp[:detail] ) if tmp[:detail].strip != ""
70 | r << printTable2("録画時間", Commlib::stet_to_s( tmp[:start], tmp[:end] ).join(" "))
71 |
72 | if cate.size > 0
73 | r << printTable2("分類", cate.join(" \n") )
74 | end
75 |
76 | if tmp[:extdetail] != nil and tmp[:extdetail].strip != ""
77 | if ( data = YamlWrap.load( tmp[:extdetail])).size > 0
78 | r << printTable2("詳細情報", extdetail2table( data ))
79 | end
80 | end
81 | end
82 | r.join("\n")
83 | end
84 | end
85 |
86 |
--------------------------------------------------------------------------------
/src/db/Phchid.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | class DBphchid
6 |
7 | include Base
8 |
9 | def initialize( )
10 | @list = {
11 | id: "id",
12 | phch: "phch",
13 | chid: "chid",
14 | updatetime: "updatetime",
15 | }
16 | @para = []
17 | @list.each_pair { |k,v| @para << v }
18 | @tbl_name = "phchid"
19 | end
20 |
21 | #
22 | # 検索
23 | #
24 | def select( db, phch: nil, chid: nil, updatetime: nil )
25 | sql = "select id,phch,chid,updatetime from #{@tbl_name} "
26 | where = []
27 | args = []
28 | if phch != nil
29 | where << " phch = ? "
30 | args << phch
31 | end
32 | if chid != nil
33 | where << " chid = ? "
34 | args << chid
35 | end
36 | if updatetime != nil
37 | where << " updatetime < ? "
38 | args << updatetime
39 | end
40 |
41 | if where.size > 0
42 | sql += " where " + where.join(" and ")
43 | end
44 |
45 | row = db.execute( sql, args )
46 | row2hash( @list, row )
47 | end
48 |
49 | #
50 | # 追加
51 | #
52 | def add(db, phch, chid, updatetime )
53 | sql = "select id from #{@tbl_name} where phch = ? and chid = ? "
54 | row = db.execute( sql, phch, chid )
55 | if row.size == 0
56 | sql = "insert into #{@tbl_name} ( phch, chid, updatetime) values (?,?,?)"
57 | db.execute( sql, phch, chid, updatetime )
58 | else
59 | sql = "update #{@tbl_name} set updatetime = ? where phch = ? "
60 | db.execute( sql, updatetime, phch )
61 | end
62 | end
63 |
64 | #
65 | # touch
66 | #
67 | def touch(db, updatetime, phch: nil, chid: nil )
68 | sql = "update #{@tbl_name} set updatetime = ? "
69 | where = []
70 | args = [ updatetime ]
71 | if phch != nil
72 | where << " phch = ? "
73 | args << phch
74 | end
75 | if chid != nil
76 | where << " chid = ? "
77 | args << chid
78 | end
79 | if where.size > 0
80 | sql += " where " + where.join(" and ")
81 | end
82 |
83 | db.execute( sql,args )
84 | end
85 |
86 | #
87 | # 削除
88 | #
89 | def delete(db, phch )
90 | sql = "delete from #{@tbl_name} where phch = ? "
91 | db.execute( sql, phch )
92 | end
93 |
94 | end
95 |
--------------------------------------------------------------------------------
/src/views/fil_res_dsp.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # フィルター検索結果の表示
6 | #
7 |
8 | class FilterDisp
9 |
10 | def initialize( params )
11 | @freeOnly = params["FO"] == "on" ? true : false
12 | end
13 |
14 | def filter?()
15 | return @fa_flag == 0 ? true : false
16 | end
17 |
18 | def fo?()
19 | return @freeOnly
20 | end
21 |
22 | #
23 | # データの表示
24 | #
25 | def print( id, session )
26 | r = {}
27 | prog = DBprograms.new
28 | filresR = DBfilterResult.new
29 | reserve = DBreserve.new
30 | filter = DBfilter.new
31 | now = Time.now.to_i
32 | DBaccess.new().open do |db|
33 | data = filter.select( db, id: id )
34 | if data[0] != nil
35 | @fa_flag = data[0][:type] == FilConst::Filter ? 0 : 1
36 | data = filresR.select(db, pid: id )
37 | data2 = data.map{|v| v[:rid] }
38 | data3 = prog.selectSP( db, proid: data2 )
39 | count = 1
40 | total = data.size
41 | tmp = []
42 | tdclas = [ "item" ]
43 | data3.each do |t|
44 | next if t[:end] < now
45 | (day, time, w) = Commlib::stet_to_s( t[:start], t[:end] )
46 | cate = t[:categoryA][0][0]
47 | trcl = %W(color#{cate})
48 | res = reserve.select( db, evid: t[:evid], chid: t[:chid] )
49 | res2 = ""
50 | resid = nil
51 | if res.size > 0
52 | if @freeOnly == true
53 | total -= 1
54 | next
55 | end
56 | if res[0][:stat] == RsvConst::Normal or res[0][:stat] == RsvConst::RecNow
57 | res2 = "○"
58 | else
59 | res2 = "×"
60 | end
61 | resid = res[0][:id]
62 | end
63 | name = %Q( #{t[:name]} )
64 | data = [ count,name, day,time,res2,t[:title],t[:detail] ]
65 | tmp << Commlib::printTR2( data, rid: t[:pid], trcl: trcl, tdcl: tdclas, tdclf: 2, resid: resid )
66 | count += 1
67 | end
68 | r[:title] = sprintf("検索結果 %d 件 ", total )
69 |
70 | r[:table] = tmp.join("\n")
71 | end
72 | end
73 | r
74 | end
75 |
76 | end
77 |
78 |
--------------------------------------------------------------------------------
/src/model/DiskKeep.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | #
4 | # disk の空き容量確保
5 | #
6 | require 'find'
7 | require 'pp'
8 | require 'sys/filesystem'
9 |
10 | class DiskKeep
11 |
12 | def initialize( )
13 | @gb = 1000 * 1000 * 1000
14 | @mb = 1000 * 1000
15 | end
16 |
17 | def start( db )
18 |
19 | return if DiskKeepPercent == nil or DiskKeepPercent == false
20 |
21 | buf = []
22 | keep = DiskKeepPercent.to_f / 100
23 | stat = Sys::Filesystem.stat( TSDir )
24 |
25 | total = (stat.blocks * stat.block_size).to_f
26 | free = (stat.blocks_available * stat.block_size).to_f
27 | target = total * keep
28 | delsize = ( total * keep ) - free
29 |
30 | #printf("Disk容量 : %6.2f GB\n", total / @gb )
31 | #printf("確保目標 : %6.2f GB (%.1f%%)\n", target /@gb, keep * 100 )
32 | #printf("空き容量 : %6.2f GB (%.1f%%)\n", free / @gb, 100.0 * free / total )
33 |
34 | buf << sprintf("削除量 = %6.2f GB", delsize > 0 ? delsize / @gb : 0 )
35 |
36 | if free < ( total * keep )
37 | list = {}
38 | Find.find( TSDir ) do |path|
39 | if test( ?f, path )
40 | if path =~ /\.ts$/
41 | fs = File.stat( path )
42 | list[ fs.mtime ] ||= []
43 | list[ fs.mtime ] << path
44 | end
45 | end
46 | end
47 |
48 | t = 0
49 | catch(:break_loop) do
50 | list.keys.sort.each do |mtime|
51 | list[ mtime ].each do |path|
52 | fs = File.stat( path ).size
53 | t += fs
54 | buf << sprintf("ファイル削除 %s (%5.1f GB)", File.basename(path),fs / @gb )
55 | File.unlink( path )
56 | path += ".chk" # packect chk result
57 | File.unlink( path ) if test( ?f, path )
58 |
59 | if t > delsize
60 | buf << sprintf("削除合計 = %6.1f GB", t/ @gb )
61 | throw :break_loop
62 | end
63 | end
64 | end
65 | end
66 | end
67 |
68 | #
69 | # 空のディレクトリを削除
70 | #
71 | Dir.entries( TSDir ).sort.each do |dir1|
72 | if dir1 != '..' && dir1 != '.'
73 | path = TSDir + "/" + dir1
74 | if test( ?d, path )
75 | if Dir.entries( path ).size == 2
76 | buf << sprintf("rmdir %s",path )
77 | Dir.rmdir( path )
78 | end
79 | end
80 | end
81 | end
82 |
83 | buf.each do |tmp|
84 | DBlog::debug( db,"DiskKeep: " + tmp )
85 | end
86 | end
87 |
88 |
89 | end
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/lib/setConst.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | require 'lib/deviceChk.rb'
4 |
5 | #
6 | # config の設定
7 | #
8 | files = [ ]
9 | files << ENV["RASPIREC_CONF_OPT"] if ENV["RASPIREC_CONF_OPT"] != nil
10 | files << ENV["RASPIREC_CONF"] if ENV["RASPIREC_CONF"] != nil
11 | files << ENV["HOME"] + "/.config/raspirec/config.rb"
12 | files.each do |cfg|
13 | if test( ?f, cfg )
14 | require cfg
15 | ConfigPath = cfg
16 | break
17 | end
18 | end
19 | raise "config not found" if Object.const_defined?(:BaseDir) != true
20 |
21 |
22 | #
23 | # 後から追加した定数のデフォルト設定
24 | #
25 |
26 | if Object.const_defined?(:TitleRegex) != true
27 | tmp = [ # 題名の削除フィルターが未定義の場合のデフォルト
28 | /【N】/,
29 | /【SS】/,
30 | /【デ】/,
31 | /【再】/,
32 | /【双】/,
33 | /【多】/,
34 | /【天】/,
35 | /【字】/,
36 | /【新】/,
37 | /【無】/,
38 | /【解】/,
39 | /【終】/,
40 | /【初】/,
41 | ]
42 | Object.const_set("TitleRegex",tmp )
43 | end
44 |
45 | if Object.const_defined?(:SearchStringRegex) != true
46 | tmp = [ # 検索文字列の削除フィルターが未定義の場合のデフォルト
47 | /\#\d+\s?[・-]\s?\#\d+/,
48 | /[\#♯#][1234567890\d]+/,
49 | /第[一二三四五六七八九十1234567890\d]+話/,
50 | /「.*」/,
51 | ] + TitleRegex
52 | Object.const_set("SearchStringRegex",tmp )
53 | end
54 |
55 |
56 | #
57 | # 定数が設定されていない場合にデフォルト値を設定
58 | #
59 | def setDefaultConst( name, val )
60 | sym = name.to_sym
61 | if Object.const_defined?( sym ) != true
62 | Object.const_set(sym, val )
63 | end
64 | end
65 |
66 | setDefaultConst("MPMonitor",false )
67 | setDefaultConst("GBC_tuner_num", 0 )
68 | setDefaultConst("DeviceList_GBC",[])
69 | setDefaultConst("EpgBanTime", nil )
70 | setDefaultConst("DeviceChkFN", DBDir + "/devicechk.yaml" )
71 | setDefaultConst("EPG_tuner_limit",false )
72 | setDefaultConst("Browser_cmd", "/usr/bin/firefox" )
73 | setDefaultConst("AutoRecExt", false )
74 | setDefaultConst("ARE_sampling_time", 90 )
75 | setDefaultConst("ARE_epgdump_opt", %w( --tail 50M ) )
76 | setDefaultConst("RaspirecTV_GAPT", 1 )
77 |
78 |
79 | #
80 | # パケットチェック機能を有効にするか?
81 | #
82 | if ( Object.const_defined?(:PacketChk_enable) == true ) &&
83 | PacketChk_enable == true
84 | if Object.const_defined?(:PacketChk_cmd) == true &&
85 | test( ?f, PacketChk_cmd )
86 | Object.const_set("PacketChkRun", true )
87 | else
88 | tmp = sprintf("PacketChk_cmd not found %s : PacketChk_enable -> false\n",PacketChk_cmd)
89 | DBlog::warn( nil,tmp)
90 | Object.const_set("PacketChkRun", false )
91 | end
92 | else
93 | Object.const_set("PacketChkRun", false )
94 | end
95 |
96 |
--------------------------------------------------------------------------------
/src/views/test.slim:
--------------------------------------------------------------------------------
1 | html
2 | head
3 | title = @title
4 | meta http-equiv='Content-Type' content='text/html' charset='utf-8'
5 | link rel='stylesheet' href='/materialize/css/materialize.css' media='screen,projection'
6 | meta name= 'viewport' content='width=device-width, initial-scale=1.0'
7 |
8 | body
9 | script src="//code.jquery.com/jquery-1.11.3.min.js"
10 | script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"
11 |
12 | css:
13 | .line{
14 | position: absolute;
15 | border-top: solid 1px #FF4981;
16 | -moz-transform-origin:0% 0%;
17 | -webkit-transform-origin:0% 0%;
18 | transform-origin:0% 0%;
19 | float: left;
20 | }
21 |
22 | javascript:
23 | function DrawLine(params) {
24 | var param = jQuery.extend({
25 | ID: 0
26 | , x1: 0
27 | , y1: 0
28 | , x2: 0
29 | , y2: 0
30 | , line_style: "solid"
31 | , line_color: "#000"
32 | , line_width: "1px"
33 | , parent: $("body")
34 | , callback: function(){}
35 | }, params);
36 | if(param.x1 < param.x2){
37 | sx = param.x1;
38 | sy = param.y1;
39 | ex = param.x2;
40 | ey = param.y2;
41 | }
42 | else {
43 | sx = param.x2;
44 | sy = param.y2;
45 | ex = param.x1;
46 | ey = param.y1;
47 | }
48 | var w = Math.sqrt(Math.pow((sx - ex) ,2) + Math.pow((sy - ey) ,2));
49 | var base = Math.max(sx, ex) - Math.min(sx, ex);
50 | var tall = Math.max(sy, ey) - Math.min(sy, ey);
51 | var aTan = Math.atan(tall / base);
52 | var deg = aTan * 180 / Math.PI;
53 | deg = sy > ey ? 0 - deg: deg;
54 | var line = $("
")
55 | .addClass("line")
56 | .css({
57 | "left": sx,
58 | "top": sy,
59 | "width": w,
60 | "transform": "rotate(" + deg + "deg)",
61 | "-webkit-transform": "rotate(" + deg + "deg)",
62 | "border-top-style": param.line_style,
63 | "border-top-color": param.line_color,
64 | "border-top-width": param.line_width,
65 | });
66 | $(param.parent).append(line);
67 | }
68 |
69 | $(document).ready(function()
70 | {
71 | DrawLine({
72 | ID: "L2SW11P0-L2SW12P0",
73 | x1: 0,
74 | y1: 0,
75 | x2: 100,
76 | y2: 100,
77 | });
78 | DrawLine({
79 | ID: "L2SW11P1-L2SW12P1",
80 | x1: 100,
81 | y1: 100,
82 | x2: 200,
83 | y2: 0,
84 | });
85 | for(var i=0; i<=10; i++){
86 | $('#L2SW11P1-L2SW12P1').css({"left": 100 - i*10});
87 | alert();
88 | }
89 | });
90 |
--------------------------------------------------------------------------------
/src/views/ch_info.slim:
--------------------------------------------------------------------------------
1 | /
2 | / チャンネル情報の表示
3 | /
4 |
5 | css:
6 | td,th {
7 | padding: 5px ;
8 | white-space: nowrap;
9 | }
10 | span { color: #000000}
11 | td.ex_border {
12 | border: 2px dotted black;
13 | }
14 |
15 | - require_relative 'ch_info.rb'
16 | - dp = ChaneruInfo.new( )
17 | - eap = EpgAutoPatch.new()
18 |
19 |
20 | .row
21 | .col.s6
22 | .card.tbl
23 | .card-content
24 | table
25 | tr
26 | td chid
27 | td チャンネルID
28 | tr
29 | td name
30 | td 放送局名
31 | tr
32 | td tsid
33 | td トランスポートストリームID ( transport_stream_id )
34 | tr
35 | td onid
36 | td オリジナルネットワークID ( original_network_id )
37 | tr
38 | td svid
39 | td サービスID ( service_id )
40 | tr
41 | td stinfo_tp
42 | td 衛星の場合はトランスポート番号、地デジの場合は物理チャンネル番号
43 | tr
44 | td stinfo_slot
45 | td 衛星の場合はスロット番号
46 | tr
47 | td skip
48 | td 放送局一覧で、「番組表、検索の対象から外す。」が設定されているか
49 | tr
50 | td 予約数
51 | td 予約テーブル上にある全て(過去から未来)の予約数。
52 | tr
53 | td 更新日時
54 | td EPGの更新日時で、1ヶ月以上更新が無い場合は背景色が変わる。
55 |
56 |
57 |
58 | div
59 | table.striped
60 | tr
61 | th No
62 | th バンド
63 | th chid
64 | th name
65 | th tsid
66 | th onid
67 | th svid
68 | th stinfo_tp
69 | th stinfo_slot
70 | th skip
71 | th 更新日時
72 | th 予約数
73 | th 削除
74 | == dp.printTable()
75 |
76 | p style="padding-top:3em"
77 |
78 | a name="BSSLOT"
79 |
80 | .row
81 | .col.s10
82 | .card.tbl
83 | .card-content
84 | span#title.card-title BSデジタル放送のチャンネル割当
85 | table
86 | == eap.printBschHtml()
87 |
88 | .row
89 | .col.s6
90 | .card.tbl
91 | .card-content
92 | span#title.card-title BS チャンネルの自動補正
93 | table
94 | tr
95 | th 局名
96 | th 変更前
97 | th 変更後
98 | == eap.printConvHtml()
99 |
100 |
101 |
102 |
103 | javascript:
104 | $(".btn").click(function(event){
105 | var chid = $(this).attr('chid');
106 | event.preventDefault();
107 | //console.log( chid );
108 | $.ajax({
109 | url: '/ch_info/del/' + chid,
110 | type: "POST",
111 | data: '&chid=1',
112 | async: true,
113 | timeout: 30000
114 | }).done( function(results) {
115 | location.reload();
116 | })
117 | });
118 |
119 |
120 |
121 | == Commlib::include( SrcDir + "/views/move_top.html" )
122 |
--------------------------------------------------------------------------------
/src/model/Daily.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | #
6 | # 一日一回
7 | #
8 |
9 | class Daily
10 |
11 | @@lastTime = nil # 前回の実行時間
12 |
13 | def initialize( )
14 | end
15 |
16 | def run()
17 | keyval = DBkeyval.new
18 | key = "daily"
19 |
20 | if @@lastTime == nil
21 | DBaccess.new().open do |db|
22 | @@lastTime = keyval.select( db, key )
23 | if @@lastTime == nil
24 | @@lastTime = Time.now.to_i
25 | keyval.upsert(db, key, @@lastTime )
26 | return
27 | end
28 | end
29 | end
30 |
31 | threshold = (Time.now - ( 24 * 3600 )).to_i
32 | if @@lastTime < threshold
33 |
34 | #
35 | # 二重録画のチェック(デバック時のみ)
36 | #
37 | if $debug == true
38 | DBlog::sto("++++ 二重録画のチェック ++++")
39 | DBaccess.new().open( ) do |db|
40 | reserve = DBreserve.new
41 | reserve.dupRecChk( db )
42 | end
43 | end
44 |
45 | #
46 | # 古いデータの削除
47 | #
48 | DBaccess.new().open( tran: true ) do |db|
49 | DBlog::debug( db, "daily task start" )
50 | now = Time.now.to_i
51 | DBlog.new.deleteOld( db, now - LogSaveDay * 24 * 3600 )
52 | DBreserve.new.deleteOld( db, now - RsvHisSaveDay * 24 * 3600 )
53 |
54 | lr = LogRote.new()
55 | if lr.need?() == true
56 | DBlog::debug( db, "Log rotate" )
57 | sleep(1)
58 | lr.exec()
59 | end
60 |
61 | @@lastTime = Time.now.to_i
62 | keyval.upsert(db, key, @@lastTime ) # 日付更新
63 | end
64 |
65 | #
66 | # DB ファイルのバックアップ
67 | #
68 | sleep(1)
69 | DBlog::vacuum()
70 | sleep(1)
71 | wday = Time.now.strftime("%a")
72 | fname = sprintf( "%s.backup-%s", DbFname ,wday )
73 | FileUtils.cp( DbFname, fname )
74 | DBlog::debug( nil, "DB file backup (#{fname})" )
75 |
76 | #
77 | # json ファイルの掃除
78 | #
79 | older = Time.now - ( ( EPGperiod + 1 ) * 3600 )
80 | Dir.open( JsonDir ).each do |file|
81 | next if file == "." or file == ".."
82 | if file =~ /\.(json|tmp)$/
83 | path = JsonDir + "/" + file
84 | if test( ?f, path )
85 | mtime = File.mtime( path )
86 | if mtime < older
87 | File.unlink( path )
88 | end
89 | end
90 | end
91 | end
92 | end
93 | end
94 | end
95 |
96 |
97 |
98 | if File.basename($0) == "Daily.rb"
99 | base = File.dirname( $0 )
100 | [ ".", "..","src", base ].each do |dir|
101 | if test( ?f, dir + "/require.rb")
102 | $: << dir
103 | end
104 | end
105 | require 'require.rb'
106 |
107 | Daily.new.run
108 |
109 | end
110 |
111 |
--------------------------------------------------------------------------------
/src/views/tooltip.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
63 |
64 |
103 |
--------------------------------------------------------------------------------
/src/lib/misc.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 |
4 | #
5 | # 配管工事
6 | #
7 | def reopenSTD( sto, ste )
8 |
9 | old_out = $stdout.dup
10 | old_err = $stderr.dup
11 | $stdin.reopen("/dev/null")
12 | if $debug == true
13 | $stdout.reopen( sto, "a")
14 | $stderr.reopen( ste, "a")
15 | if false
16 | $stdout.sync = true
17 | $stderr.sync = true
18 | else
19 | Thread.new do
20 | while true
21 | $stdout.flush
22 | $stderr.flush
23 | sleep(1)
24 | end
25 | end
26 | end
27 | else
28 | $stdout.reopen("/dev/null", "w")
29 | $stderr.reopen("/dev/null", "w")
30 | end
31 |
32 | old_out.close
33 | old_err.close
34 | #[ old_out, old_err ]
35 | end
36 |
37 |
38 |
39 | #
40 | # trap の設置
41 | #
42 | def setTrap()
43 |
44 | name = File.basename( $0 )
45 | Signal.trap( :HUP ) { DBlog::sto("#{name} :HUP") ; endParoc() }
46 | Signal.trap( :INT ) { DBlog::sto("#{name} :INT") ; endParoc() }
47 | Signal.trap( :QUIT ) { DBlog::sto("#{name} :QUIT") }
48 | Signal.trap( :SYS ) { DBlog::sto("#{name} :SYS") }
49 | Signal.trap( :TERM ) { DBlog::sto("#{name} :TERM") ; endParoc() }
50 | #Signal.trap( :CHLD ) { DBlog::sto("#{name} :CHLD") }
51 | Signal.trap( :EXIT) {
52 | DBlog::sto( name + " " + $! )
53 | pp $!
54 | }
55 |
56 | if name == "timer_main.rb"
57 | Signal.trap( :CHLD ) {
58 | #DBlog::sto("#{name} :CHLD");
59 | childWait()
60 | }
61 | end
62 |
63 | end
64 |
65 |
66 | def tailLog( out = STDOUT )
67 | out.puts("tailLog()")
68 | bsize = 1024 * 1024
69 | outbuf = "x" * bsize
70 |
71 | fps = []
72 | [ StdoutH, StdoutT, StderrH, StderrT ].each do |fn|
73 | if test( ?f, fn )
74 | fp = open( fn )
75 | fp.sysseek(0, IO::SEEK_END)
76 | fps << fp
77 | end
78 | end
79 |
80 | count = 0
81 | while true
82 | if ( r = IO.select( fps, nil, nil,1 )) != nil
83 | r[0].each do |fp|
84 | if fp != nil
85 | fp.read( bsize, outbuf )
86 | if outbuf != nil
87 | outbuf.force_encoding("ASCII-8bit")
88 | outbuf.each_line do |l|
89 | next if l =~ /\/var\/lib\//
90 | next if l =~ /from \/usr\/lib\//
91 | next if l =~ /style\.css/
92 | next if l =~ /overlaid\.css/
93 | next if l =~ /^http:/
94 | next if l =~ /127\.0\.0/
95 | next if l =~ /192\.168\.1/
96 | next if l =~ /\/usr\/lib\/ruby/
97 | out.write( l )
98 | end
99 | end
100 | end
101 | end
102 | end
103 | sleep(0.1)
104 | end
105 | end
106 |
107 |
108 |
109 | def ppp( str )
110 | if $debug == true
111 | if @ts != nil
112 | DBlog::sto( str + " " + (Time.now - @ts).to_s )
113 | end
114 | end
115 | end
116 |
--------------------------------------------------------------------------------
/src/views/log_view.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | class LogView
4 |
5 | Base = "/log_view"
6 |
7 | def initialize( level, page )
8 | @radio = { :debug => false,
9 | :info => false,
10 | :atte => false,
11 | :warn => false,
12 | :err => false,
13 | }
14 | if level != nil
15 | case level.to_i
16 | when DBlog::Debug then @radio[:debug] = true
17 | when DBlog::Info then @radio[:info] = true
18 | when DBlog::Attention then @radio[:atte] = true
19 | when DBlog::Warning then @radio[:warn] = true
20 | when DBlog::Error then @radio[:err] = true
21 | end
22 | @level = level.to_i
23 | else
24 | @level = 2
25 | @radio[:info] = true
26 | end
27 |
28 | if page == nil
29 | @page = 1
30 | else
31 | @page = page.to_i
32 | end
33 | @page_line = 256
34 |
35 | @data = getData()
36 | end
37 |
38 | #
39 | # pageのセレクト
40 | #
41 | def pageSel( )
42 | r = []
43 | r << %Q{}
51 |
52 | r.join("\n")
53 | end
54 |
55 | def getData()
56 | log = DBlog.new
57 | ret = []
58 | DBaccess.new().open do |db|
59 | @total_size = log.count( db, level: @level - 1)
60 | @pageNum = 1
61 | if @total_size > @page_line
62 | @pageNum = @total_size / @page_line
63 | if @pageNum > 0
64 | @pageNum += 1 if (@total_size - ( @page_line * @pageNum )) > 0
65 | end
66 | end
67 | limit = nil
68 | if @pageNum > 1
69 | limit = "LIMIT #{@page_line} OFFSET #{@page_line * (@page - 1 )}"
70 | end
71 | ret = log.select( db, level: @level - 1, limit: limit )
72 | end
73 | ret
74 | end
75 |
76 | def printTable()
77 | log = DBlog.new
78 | ret = []
79 | @data.each do |r|
80 | r.shift
81 | if r[0] >= @level
82 | tdcl = %w( nowrap )
83 | r[0] = case r[0]
84 | when DBlog::Debug then "デバッグ"
85 | when DBlog::Info then "情報"
86 | when DBlog::Attention then tdcl << "atte" ; "注意"
87 | when DBlog::Warning then tdcl << "warn" ; "警告"
88 | when DBlog::Error then tdcl << "error" ; "エラー"
89 | end
90 | r[1] = Time.at( r[1] ).strftime("%Y-%m-%d %H:%M:%S")
91 | ret << Commlib::printTR2( r, tdcl: tdcl )
92 | end
93 | end
94 | ret.join("\n")
95 | end
96 |
97 | def radio()
98 | @radio
99 | end
100 |
101 | end
102 |
--------------------------------------------------------------------------------
/src/views/rsv_list.slim:
--------------------------------------------------------------------------------
1 | /
2 | / 予約一覧
3 | /
4 |
5 | css:
6 | td,th {
7 | padding: 5px ;
8 | }
9 | span { color: #000000}
10 |
11 |
12 | - require_relative 'rsv_list.rb'
13 | - dp = ReservationList.new( )
14 | - data = dp.getData()
15 |
16 | div
17 | table.striped
18 | tr
19 | th No
20 | th 放送局
21 | th 日付
22 | th 時間
23 | th 状態
24 | th 種別
25 | th タイトル
26 | == dp.printTable()
27 |
28 |
29 |
30 | div id="sample-dialog" title="詳細" style="display:none;"
31 | p
32 |
33 |
34 | javascript:
35 | $(".dialog").click(function(event){
36 | var rid = $(this).attr('rid');
37 | var recf = $(this).attr('recf');
38 | //event.preventDefault();
39 | $("#sample-dialog").load('/rsv_list_D/' + rid );
40 | $("#sample-dialog").dialog({
41 | modal: true,
42 | maxWidth: 1200,
43 | width: 1000,
44 | buttons: { //ボタン
45 | "修正": function() {
46 | $.ajax({
47 | url: '/rsv_list/Mod/' + rid,
48 | type:"POST",
49 | data: $('form').serialize() + '¶m=1',
50 | async: true,
51 | timeout: 30000
52 | }).done( function(results) {
53 | location.reload();
54 | })
55 | },
56 | "削除": function() {
57 | $.ajax({
58 | url: '/rsv_list/Del/' + rid,
59 | type:"POST",
60 | async: true,
61 | timeout: 30000
62 | }).done( function(results) {
63 | location.reload();
64 | })
65 | },
66 | "録画中止": function() {
67 | $.ajax({
68 | url: '/rsv_list/Stop/' + rid,
69 | type:"POST",
70 | async: true,
71 | timeout: 30000
72 | }).done( function(results) {
73 | location.reload();
74 | })
75 | },
76 | "閉じる": function() {
77 | $(this).dialog("close");
78 | }
79 | },
80 | open: function() {
81 | $(this).siblings('.ui-dialog-buttonpane').find('button:eq(3)').focus();
82 | if ( recf == 0 ) {
83 | $(".ui-dialog-buttonpane button:contains('録画中止')").button('disable');
84 | } else {
85 | $(".ui-dialog-buttonpane button:contains('修正')").button('disable');
86 | $(".ui-dialog-buttonpane button:contains('削除')").button('disable');
87 | }
88 | }
89 | });
90 | return false;
91 | });
92 |
93 | == Commlib::include( SrcDir + "/views/move_top.html" )
94 |
--------------------------------------------------------------------------------
/src/model/Control.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 予約一覧
6 | #
7 | require 'sys/filesystem'
8 |
9 | class Control
10 |
11 | def initialize( )
12 |
13 | end
14 |
15 | #
16 | # ログファイルのローテート
17 | #
18 | def logrote()
19 | lr = LogRote.new()
20 | DBlog::sto( "Log rotate" )
21 | lr.exec()
22 | end
23 |
24 | #
25 | # file 転送
26 | #
27 | def fcopy( params )
28 | Thread.new do
29 | fname = params[ "fname" ]
30 | from = TSDir + "/" + fname
31 | return unless test( ?f, from )
32 |
33 | subdir = File.dirname( fname )
34 | errmsg = nil
35 | fc = FileCopy.new
36 | ( speed, errmsg ) = fc.scp( TSFT_toDir, subdir, from )
37 |
38 | if errmsg == nil
39 | tmp = sprintf("手動転送終了: %s (%.1f Mbyte/sec)", fname, speed )
40 | else
41 | tmp = sprintf("手動転送失敗: %s : %s",errmsg , fname )
42 | end
43 | DBaccess.new().open() do |db|
44 | DBlog::info(db,tmp)
45 | end
46 | end
47 | end
48 |
49 | def tsft( arg ) # "true" で不許可
50 | DBaccess.new().open( tran: true ) do |db|
51 | DBkeyval.new.upsert( db, "tsft", arg )
52 | if arg != "true"
53 | DBupdateChk.new.touch()
54 | end
55 | end
56 | end
57 |
58 | def logdel( arg )
59 | DBaccess.new().open( tran: true ) do |db|
60 | sql = "delete from log "
61 | if arg != "all"
62 | n = arg.to_i
63 | if n > 0
64 | time = Time.now.to_i - n * 3600 * 24
65 | sql += " where time < #{time} ;"
66 | end
67 | end
68 | db.execute( sql )
69 | end
70 | end
71 |
72 | def epg()
73 | phchid = DBphchid.new
74 | t = Time.now.to_i - ( 3600 * 24 )
75 | DBaccess.new().open( tran: true ) do |db|
76 | row = phchid.select( db )
77 | row.each do |r|
78 | phchid.touch( db, t, phch: r[:phch] )
79 | end
80 | end
81 | DBupdateChk.new.touch()
82 | end
83 |
84 | def filupd( )
85 | FilterM.new.update()
86 | end
87 |
88 | def sendSignal( fname, signal = :HUP )
89 | if test( ?f, fname )
90 | File.open( fname,"r" ) do |fp|
91 | pid = fp.gets().to_i
92 | if pid > 0
93 | begin
94 | Process.kill( signal, pid )
95 | rescue Errno::ESRCH
96 | end
97 | end
98 | end
99 | end
100 | end
101 |
102 | def restart( )
103 | [ TimerPidFile, HttpdPidFile ].each do |fname|
104 | sendSignal( fname, :HUP )
105 | end
106 | end
107 |
108 | def stop( )
109 | if test( ?f, PidFile )
110 | File.open( PidFile,"r" ) do |fp|
111 | pid = fp.gets().to_i
112 | if pid > 0
113 | begin
114 | Process.kill( :TERM, pid )
115 | rescue Errno::ESRCH
116 | end
117 | end
118 | end
119 | end
120 | end
121 |
122 | end
123 |
124 |
--------------------------------------------------------------------------------
/src/views/rsv_list_old.slim:
--------------------------------------------------------------------------------
1 | /
2 | / 予約一覧
3 | /
4 |
5 | css:
6 | td,th {
7 | padding: 5px ;
8 | }
9 | .inline-block {
10 | display: inline-block; /* インラインブロック要素にする */
11 | padding: 0px;
12 | margin: 0px 2px 0px 2px;
13 | }
14 | .pagination {
15 | margin-left: 3em;
16 | }
17 |
18 | - require_relative 'rsv_list_old.rb'
19 | - dp = ReservationListOld.new( @params, @page )
20 | - data = dp.getData()
21 |
22 | form id="form" method="POST" action="/rsv_list_old"
23 | .inline
24 | | 検索文字列
25 | input#email_inline( type="text" name="search" class="validate" value="" autocomplete="off" )
26 | input( type="submit" value="検索実行" )
27 | == dp.pageSel()
28 |
29 | div
30 | table.striped
31 | tr
32 | th No
33 | th 放送局
34 | th 日付
35 | th 時間
36 | th 状態
37 | - if TSFT == true
38 | th 転送
39 | - if PacketChkRun == true
40 | th.center 検査
41 | th 種別
42 | th タイトル
43 | == dp.printTable()
44 |
45 |
46 | div id="sample-dialog" title="詳細" style="display:none;"
47 | p データを削除しますか?
48 |
49 | div id="packetchk" title="packet chk result" style="display:none;"
50 | p
51 |
52 | == Commlib::include( SrcDir + "/views/move_top.html" )
53 |
54 | javascript:
55 | $(".dialog").click(function(event){
56 | var rid = $(this).attr('rid');
57 | $("#sample-dialog").dialog({
58 | modal: true,
59 | maxWidth: 1200,
60 | width: 1000,
61 | buttons: { //ボタン
62 | "削除": function() {
63 | $.ajax({
64 | url: '/rsv_list/Del/' + rid,
65 | type:"POST",
66 | async: true,
67 | timeout: 30000
68 | }).done( function(results) {
69 | location.reload();
70 | })
71 | },
72 | "キャンセル": function() {
73 | $(this).dialog("close");
74 | }
75 | },
76 | open: function() {
77 | $( this ).siblings('.ui-dialog-buttonpane').find('button:eq(2)').focus();
78 | }
79 | });
80 | return false;
81 | });
82 |
83 | $(".packchk").click(function(event){
84 | var rid = $(this).attr('rid');
85 | //event.preventDefault();
86 | $("#packetchk").load('/pack_chk_view/' + rid );
87 | $("#packetchk").dialog({
88 | modal: true,
89 | maxWidth: 1200,
90 | width: 1000,
91 | buttons: { //ボタン
92 | "閉じる": function() {
93 | $(this).dialog("close");
94 | }
95 | },
96 | open: function() {
97 | $( this ).siblings('.ui-dialog-buttonpane').find('button:eq(2)').focus();
98 | }
99 | });
100 | return false;
101 | });
102 |
103 |
--------------------------------------------------------------------------------
/src/views/mpv_mon.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # モニター
6 | #
7 | require_relative 'monitor.rb'
8 |
9 | class MpvMon < Monitor
10 |
11 | attr_reader :statS, :devfn, :base_url
12 |
13 | @@ta = nil
14 |
15 | def initialize( num, cmd )
16 |
17 | super()
18 | @ta = $tunerArray
19 | @currenTune = nil
20 | @devfn = nil
21 | @tunNum = nil
22 |
23 | if num == nil
24 | @tunNum = @ta.first.serial
25 | else
26 | @tunNum = num.to_i
27 | end
28 |
29 | @ta.each do |t1|
30 | if t1.serial == @tunNum
31 | @currenTune = t1 # 現在選択中のチューナー
32 | @devfn = @currenTune.devfn
33 | break
34 | end
35 | end
36 |
37 | @cmd = cmd == nil ? "disp" : cmd
38 | @ta.chkDeviceStat()
39 | if @currenTune != nil
40 | @statS = @currenTune.getStatStr()
41 | @base_url = "/mpv_mon/#{@tunNum}"
42 | else
43 | @base_url = "/mpv_mon"
44 | end
45 |
46 | end
47 |
48 |
49 | #
50 | # device 選択
51 | #
52 | def deviceSelect()
53 | hina = %q(
54 |
55 | %s
56 |
57 | )
58 |
59 | a = []
60 | @ta.each do |t1|
61 | if t1.devfn != nil
62 | dev = t1.devfn
63 | sel = t1.serial == @tunNum ? "checked" : ""
64 | name = t1.name
65 | stat = t1.stat == :OK ? "" : "busy"
66 | a << sprintf( hina, t1.serial, sel, stat, name )
67 | end
68 | end
69 | a.join("\n")
70 | end
71 |
72 | #
73 | # 選局中のチャンネル
74 | #
75 | def selCh()
76 | return "-" if @devfn == nil or @currenTune.chName == nil
77 | return @currenTune.chName
78 | end
79 |
80 | #
81 | # 選局中の番組名
82 | #
83 | def prog_name()
84 | return "-" if @devfn == nil or @currenTune.prog_name == nil
85 | return @currenTune.prog_name
86 | end
87 |
88 | #
89 | # 選局中の番組概要
90 | #
91 | def prog_detail()
92 | return "-" if @devfn == nil or @currenTune.prog_detail == nil
93 | return @currenTune.prog_detail
94 | end
95 |
96 | #
97 | # 有効なバンドを返す。
98 | #
99 | def bands()
100 | r = []
101 | if @currenTune != nil
102 | r << "GR" if @currenTune.band[ Const::GR ] == true
103 | r << "BS" if @currenTune.band[ Const::BSCS ] == true
104 | r << "CS" if @currenTune.band[ Const::BSCS ] == true
105 | end
106 | return r
107 | end
108 |
109 | def activeBand?( band )
110 | if @currenTune != nil
111 | return @currenTune.band[ band ] == true ? "active" : ""
112 | end
113 | return ""
114 | end
115 |
116 | def stop_a()
117 | " 停止 "
118 | end
119 |
120 |
121 | def dis_stop( )
122 | if @currenTune != nil
123 | return @currenTune.rec_pid == nil ? "disabled" : ""
124 | end
125 | return ""
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/src/views/control.slim:
--------------------------------------------------------------------------------
1 |
2 | css:
3 | .tbl {
4 | position: absolute;
5 | width: 800px;
6 | height: 500px;
7 | margin-left: 1em;
8 | margin-top: 1em;
9 | }
10 | .filesel {
11 | width: 800px;
12 | }
13 | .btn, .switch {
14 | margin-left: 3em;
15 | }
16 | label { color: #000000}
17 |
18 | - require_relative 'control.rb'
19 | - dp = Control.new.getData()
20 |
21 | span#title.card-title コントロール パネル
22 |
23 | form( id="form" method="post" action="/control/fcopy" )
24 | table.tbl
25 | tr
26 | td.nowrap EPGの取得 (録画中は保留されます。)
27 | td.nowrap colspan="2"
28 | a.btn.waves-effect.waves-light.green.darken-4 href="/control/epg" 実行
29 | tr
30 | td.nowrap フィルター、自動予約の再構成
31 | td.nowrap colspan="2"
32 | a.btn.waves-effect.waves-light.green.darken-4 href="/control/filupd" 実行
33 | - if TSFT == true
34 | tr
35 | td.nowrap 自動 TSファイル転送
36 | td.nowrap colspan="2"
37 | div.switch
38 | label
39 | | 許可
40 | input#tsft type="checkbox" checked=(dp[:tsft])
41 | span.lever
42 | | 不許可
43 | tr
44 | td.nowrap 手動 TSファイル転送
45 | td.nowrap
46 | div.input-field.filesel
47 | select.filesel( name="fname" )
48 | option( value="" disabled selected )
49 | | ファイルを選択して下さい
50 | == dp[:tsfile]
51 | td.nowrap
52 | input type="submit" value="送信する" name="fcopy"
53 |
54 | tr
55 | td.nowrap ログ(DB)の削除
56 | td.nowrap colspan="2"
57 | a.btn.waves-effect.waves-light.green.darken-4 href="/control/logdel/all" 実行
58 | tr
59 | td.nowrap ログファイルの強制ローテート
60 | td.nowrap colspan="2"
61 | a.btn.waves-effect.waves-light.green.darken-4 href="/control/logRote" 実行
62 | tr
63 | td.nowrap プログラムの再起動
64 | td.nowrap colspan="2"
65 | a.btn.waves-effect.waves-light.green.darken-4 href="/control/restart" 実行
66 | tr
67 | td.nowrap プログラムの停止
68 | td.nowrap colspan="2"
69 | a.btn.waves-effect.waves-light.green.darken-4 href="/control/stop" 実行
70 | tr
71 | td.nowrap チャンネル情報
72 | td.nowrap colspan="2"
73 | a.btn.waves-effect.waves-light.green.darken-4 href="/ch_info" 表示
74 |
75 | tr
76 | td.nowrap config 情報
77 | td.nowrap colspan="2"
78 | a.btn.waves-effect.waves-light.green.darken-4 href="/config" 表示
79 |
80 | tr
81 | td.nowrap 補足説明
82 | td.nowrap colspan="2"
83 | a.btn.waves-effect.waves-light.green.darken-4 href="/help" 表示
84 |
85 |
86 | javascript:
87 | $('#tsft').change(function() {
88 | $('#tsft').each(function() {
89 | var r = $(this).prop('checked');
90 | //console.log(r);
91 | $.ajax({
92 | url: '/control/tsft/' + r,
93 | type:"get",
94 | async: false,
95 | })
96 | })
97 | })
98 |
99 | $(document).ready(function(){
100 | $('select').formSelect();
101 | });
102 |
--------------------------------------------------------------------------------
/src/db/DB.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | require 'sqlite3'
5 |
6 | class DBaccess
7 |
8 | attr_reader :db
9 |
10 | def initialize( dbFname = DbFname )
11 | @db = nil
12 | @DBfile = dbFname
13 | unless File.exist?(@DBfile )
14 | open( tran: true ) do |db|
15 | createDB()
16 | end
17 | File.chmod( 0600, @DBfile )
18 | end
19 | end
20 |
21 | def parse_caller(at)
22 | if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
23 | file = File.basename( $1 )
24 | line = $2.to_i
25 | method = $3
26 | return sprintf( "DB_caller : %s %d %s", file, line, method )
27 | end
28 | ""
29 | end
30 |
31 | #
32 | # DB open mode = :immediate or :deferred or :exclusive
33 | #
34 | def open( tran: false, mode: :immediate ) # tran = true transaction
35 | #DBlog::stoD( parse_caller( caller.first ) ) if $debug == true
36 | @db = SQLite3::Database.new( @DBfile )
37 | @db.busy_timeout(1000)
38 | ecount = 0
39 | roll = false
40 | begin
41 | roll = false
42 | if tran == true
43 | @db.transaction( mode ) do
44 | roll = true
45 | yield self
46 | end
47 | else
48 | yield self
49 | end
50 | rescue SQLite3::BusyException => e
51 | DBlog::sto("SQLite3::BusyException tran = #{tran.to_s} #{ecount}")
52 | begin
53 | @db.rollback() if roll == true
54 | rescue
55 | DBlog::sto("rollback fail #{$!}")
56 | end
57 | if ecount > 59
58 | Commlib::errPrint( "SQLite3::BusyException exit", $!, e )
59 | return
60 | else
61 | #Commlib::errPrint( "SQLite3::BusyException retry", $!, e )
62 | ecount += 1
63 | sleep( 1 )
64 | DBlog::sto("retry")
65 | retry
66 | end
67 | rescue => e
68 | Commlib::errPrint( "SQLite3::another error", $!, e )
69 | begin
70 | @db.rollback() if roll == true
71 | rescue
72 | DBlog::sto("rollback fail #{$!}")
73 | end
74 | return
75 | ensure
76 | close()
77 | end
78 | end
79 |
80 | def close
81 | if @db != nil
82 | @db.close()
83 | @db = nil
84 | end
85 | end
86 |
87 | def execute( *args )
88 | @db.execute( *args )
89 | end
90 |
91 | #def transaction( mode = :immediate ) # :deferred or :immediate
92 | # @db.transaction( mode ) do
93 | # yield
94 | # end
95 | #end
96 |
97 | def prepare( str )
98 | @db.prepare( str )
99 | end
100 |
101 | #
102 | # データがあるか
103 | #
104 | def getDB( fname )
105 | sql = "select * from data where fname = ?;"
106 | @db.execute( sql, fname ) do |row|
107 | return row
108 | end
109 | return nil
110 | end
111 |
112 |
113 | def getDBbyNmaeLike( *names )
114 | q = ""
115 | names.each do |name|
116 | q += "%#{name}%"
117 | end
118 | sql = "select DISTINCT * from data where fname like '#{q}' order by fname;"
119 | return @db.execute( sql )
120 | end
121 |
122 | end
123 |
--------------------------------------------------------------------------------
/src/timer_main.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | require 'pp'
4 | require 'optparse'
5 |
6 | base = File.dirname( $0 )
7 | [ ".", "..","src", base ].each do |dir|
8 | if test( ?f, dir + "/require.rb")
9 | $: << dir
10 | $baseDir = dir
11 | end
12 | end
13 | RewriteConst = true
14 | require 'require.rb'
15 |
16 | $debug = Debug
17 | OptionParser.new do |opt|
18 | opt.on('--debug') {|v| $debug = true }
19 | opt.parse!(ARGV)
20 | end
21 |
22 | reopenSTD( StdoutT, StderrT )
23 |
24 | #
25 | # 終了処理
26 | #
27 | def endParoc()
28 | DBlog::puts( "endParoc() #{$httpd_pid}" )
29 | if $rec_pid != nil
30 | $rec_pid.each_pair do |pid, v |
31 | if pid != nil
32 | begin
33 | Process.kill(:HUP, pid );
34 | Process.waitpid( pid, Process::WNOHANG )
35 | rescue Errno::ECHILD, Errno::ESRCH
36 | end
37 | DBlog::puts( "timer:endParoc() kill #{pid}" )
38 | end
39 | end
40 | end
41 | sleep(1)
42 | exit
43 | end
44 |
45 | $rec_pid = {} # 子プロセス の pid
46 | $mutex = Mutex.new
47 | $tunerArray = TunerArray.new
48 |
49 | #
50 | # :CHLD のハンドラ
51 | #
52 | def childWait()
53 | #DBlog::sto("childWait()")
54 | Thread.new do
55 | sleep(10) # 時間差をつける
56 | $rec_pid.keys.each do |k|
57 | if $rec_pid[k] == true
58 | begin
59 | if Process.waitpid( k, Process::WNOHANG ) != nil
60 | DBlog::sto("timer:childWait() pid=#{k} Terminated") # 成仏
61 | $rec_pid.delete(k)
62 | end
63 | rescue Errno::ECHILD, Errno::ESRCH
64 | $rec_pid.delete(k)
65 | end
66 | end
67 | end
68 | end
69 | end
70 |
71 | #
72 | # メモリの使用量調査
73 | #
74 | def memSpace( db = nil )
75 | require 'objspace'
76 | mem = ObjectSpace.memsize_of_all * 0.001 * 0.001
77 | rss = `ps -o rss= -p #{Process.pid}`.to_i * 0.001
78 | gcc = GC.count
79 | tmp = sprintf("memsize_of_all=%.2f MB ; RSS=%.2f MB ; GCC=%d",mem,rss, gcc)
80 | DBlog::debug(db, tmp )
81 | end
82 |
83 |
84 | setTrap()
85 |
86 | File.open( TimerPidFile, "w") do |fp|
87 | fp.puts( Process.pid )
88 | end
89 |
90 | DBlog::info(nil,"timer_main start #{Commlib::getVer()}")
91 | EpgLock::unlock()
92 |
93 | #
94 | # tool check
95 | #
96 | tools = [ Recpt1_cmd, Epgdump ]
97 | tools.each do |tool|
98 | unless test( ?x, tool )
99 | foundF = false
100 | ENV["PATH"].split(/:/).each do |p|
101 | if test( ?x, "#{p}/#{tool}" )
102 | foundF = true
103 | break
104 | end
105 | end
106 | if foundF == false
107 | DBlog::error( nil, "Error: #{tool} not found" )
108 | end
109 | end
110 | end
111 |
112 | #
113 | # チューナーデバイスの読み書き権限のチェック
114 | #
115 | devList = ( DeviceList_GR + DeviceList_BSCS + DeviceList_GBC )
116 | DeviceChk.new.devRWchk( devList )
117 |
118 | #
119 | # メモリ使用量のモニタ
120 | #
121 | if $debug == true and Debug_mem == true
122 | Thread.new do
123 | while true
124 | memSpace( nil )
125 | sleep( 600 )
126 | end
127 | end
128 | end
129 |
130 | tm = Timer.new
131 | tm.start()
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/views/rsv_list.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 予約一覧
6 | #
7 |
8 |
9 | class ReservationList
10 |
11 | def initialize( )
12 | @autoRsv = {} # 自動予約のIDリスト
13 | end
14 |
15 | def getData()
16 | reserve = DBreserve.new()
17 | programs = DBprograms.new
18 | filter = DBfilter.new
19 |
20 | now = Time.now.to_i
21 | DBaccess.new().open do |db|
22 | if (row = filter.select( db, type: FilConst::AutoRsv )) != nil
23 | row.each do |r|
24 | @autoRsv[ r[:id].to_i ] = true
25 | end
26 | end
27 | r = reserve.selectSP( db, tend: now)
28 | return r
29 | end
30 | nil
31 | end
32 |
33 | def printTD( str, clas: nil, id: nil, rid: nil, style: nil )
34 | attr = ""
35 | attr += %Q{class="#{clas.join(" ")}" } if clas != nil
36 | attr += %Q{id="#{id}" } if id != nil
37 | attr += %Q{rid="#{rid}" } if rid != nil
38 | attr += %Q{style="#{style}" } if style != nil
39 | %Q{ #{str} }
40 | end
41 |
42 | def printTR( data, id: nil, clas: nil )
43 | attr = []
44 | attr << %Q(class="#{clas.join(" ")}") if clas != nil
45 | attr << %Q(id="#{id}") if id != nil
46 |
47 | a = [ %Q{ } ]
48 | a += data
49 | a << %Q{ }
50 | a.join("\n")
51 | end
52 |
53 | #
54 | # データの表示
55 | #
56 | def printTable()
57 | r = []
58 |
59 | data = getData()
60 | if data != nil
61 | count = 1
62 | clas = %w( nowrap ) #item
63 | data.each do |t|
64 | time = Commlib::stet_to_s( t[:start], t[:end] )
65 | if t[:type] == 0
66 | type = "手動"
67 | else
68 | if @autoRsv[ t[:keyid] ] == true
69 | type = %Q( 自動 )
70 | else
71 | type = "自動(除)"
72 | end
73 | end
74 |
75 | bg = nil
76 | id = nil
77 | cate = t[:category]
78 | bg = %Q(color#{cate})
79 | clasS = %w( nowrap )
80 |
81 | ( stat, clasS, bg, recf ) = Commlib::statAna( t, clasS, bg )
82 |
83 | title = %Q( #{t[:title]} )
84 | st = ( Time.at(t[:start]) - 3600 ).strftime("%Y-%m-%d/%H")
85 | day = %Q( #{time[0]} )
86 | time2 = %Q( #{time[1]} #{time[2]} )
87 | name2 = %Q( #{t[:name]} )
88 | td = []
89 | td << printTD( count, clas: clas )
90 | td << printTD( name2,clas: clas )
91 | td << printTD( day,clas: clas )
92 | td << printTD( time2,clas: clas )
93 | td << printTD( stat,clas: clasS, id: id )
94 | td << printTD( type,clas: clas )
95 | td << printTD( title,clas: clas )
96 | r << printTR( td, clas: [ bg ] )
97 | count += 1
98 | end
99 | end
100 | r.join("\n")
101 | end
102 | end
103 |
104 | if File.basename($0) == "reservationList.rb"
105 |
106 |
107 | case ARGV[0]
108 | when "1"
109 | puts( pt.printTable( ))
110 | end
111 |
112 | end
113 |
114 |
--------------------------------------------------------------------------------
/src/views/monitor.slim:
--------------------------------------------------------------------------------
1 | /
2 | / モニター
3 | /
4 |
5 | - require_relative 'monitor.rb'
6 | - pt = Monitor.new()
7 | - pt.createData()
8 |
9 | css:
10 | td,th {
11 | padding: 5px ;
12 | }
13 | label {
14 | margin: 10px ;
15 | color: black;
16 | font-size: 1em;
17 | }
18 | .inline-block {
19 | display: inline-block; /* インラインブロック要素にする */
20 | padding: 0px;
21 | margin: 0px 2px 0px 2px;
22 | }
23 | .pagination {
24 | margin-left: 3em;
25 | }
26 | #live {
27 | margin-left: 1em;
28 | }
29 | #band {
30 | margin-top: 1em;
31 | }
32 |
33 | script src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"
34 |
35 | div
36 | video#live width="#{MonitorWidth}" crossOrigin="anonymous" autoplay="autoplay" controls="controls"
37 |
38 | div.row
39 | div#band
40 | ul.tabs
41 | - pt.band.each do |band|
42 | li.tab.col.s2
43 | a href="##{band}"
44 | | #{pt.bandname[band]}
45 |
46 | div
47 | - pt.band.each do |band|
48 | div.col.s8 id = band
49 | == pt.data[band]
50 |
51 |
52 |
53 | div id="sample-dialog1" title="wait" style="display:none;"
54 | p 準備中。 しばらくお待ち下さい。
55 |
56 |
57 |
58 | javascript:
59 |
60 |
61 | $(".hls").click(function(event){
62 | event.preventDefault();
63 | //$("#sample-dialog1").show();
64 | $("#sample-dialog1").dialog({
65 | modal: true,
66 | maxWidth: 1200,
67 | width: 600,
68 | buttons: { //ボタン
69 | "閉じる": function() {
70 | $(this).dialog("close");
71 | }
72 | }
73 | });
74 |
75 | url = $(this).attr('href');
76 | $.ajax({
77 | url: url,
78 | type:"GET",
79 | async: true,
80 | timeout: 30000
81 | })
82 | .done( function(results ){
83 | //console.log("URL : " + url);
84 | //console.log("results : " + results);
85 | setTimeout(function(){
86 | location.href='/monitor';
87 | },3000);
88 | }).fail(function (jqXHR, textStatus, errorThrown) {
89 | // 通信失敗時の処理
90 | console.log("ajax通信に失敗しました");
91 | console.log("jqXHR : " + jqXHR.status); // HTTPステータスが取得
92 | console.log("textStatus : " + textStatus); // タイムアウト、パースエラー
93 | console.log("errorThrown: " + errorThrown.message); // 例外情報
94 | console.log("URL : " + url);
95 | //alert("起動失敗");
96 | $("#sample-dialog1").html("起動に失敗しました。");
97 | })
98 | return false;
99 | });
100 |
101 |
102 |
103 | if (Hls.isSupported()) {
104 | var video = document.getElementById('live');
105 | var hls = new Hls();
106 | hls.loadSource('/stream/playlist.m3u8');
107 | hls.attachMedia(live);
108 | hls.on(Hls.Events.MANIFEST_PARSED, function () {
109 | video.play();
110 | });
111 | }
112 | function jumptolatest() {
113 | document.getElementById("live").currentTime = 99999999;
114 | }
115 | setTimeout("jumptolatest()", 2000);
116 |
117 | $(document).ready(function(){
118 | $('.tabs').tabs();
119 | });
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/src/model/EpgNearCh.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | #
4 | # 直近(10分)の予約がある物理チャンネルを返す。
5 | # 前回の履歴から重複はしないように
6 | #
7 |
8 | class EpgNearCh
9 |
10 | @@updT = { } # chid 毎の EPG更新時間(短縮)
11 |
12 | def initialize( t = 600 )
13 | @sepTime = t # 連続とみなす予約間隔
14 | end
15 |
16 | #
17 | # 直近に録画番組がある局のEPG 取得のために、更新時間を細工
18 | # @sepTime は予約間の隙間の許容値
19 | #
20 | def check( )
21 |
22 | #DBlog::stoD("EpgNearCh::check()")
23 | st = et = nil
24 | reserve = DBreserve.new
25 | phchid = DBphchid.new
26 | channel = DBchannel.new
27 | keyval = DBkeyval.new
28 | nearTime = Time.now.to_i + 60 * 10 # 直近とは 10分以内
29 | now = Time.now.to_i
30 | list = []
31 | res = []
32 |
33 | DBaccess.new().open( tran: true ) do |db|
34 |
35 | row = reserve.selectSP( db, stat: RsvConst::Normal, order: "order by start" )
36 | if row.size == 0 or ( row.first[:start] > nearTime )
37 | return []
38 | end
39 |
40 | # 直近の連続した予約を抽出
41 | row.each do |r|
42 | if st == nil
43 | st = r[:start]
44 | et = r[:end]
45 | list << r
46 | else
47 | if r[:start].between?( st, et + @sepTime )
48 | et = et > r[:end] ? et : r[:end]
49 | list << r
50 | else
51 | break
52 | end
53 | end
54 | end
55 |
56 | updtime = {} # chid 毎の EPG更新時間(正規)
57 | row = phchid.select( db )
58 | row.each do |tmp|
59 | chid = tmp[:chid]
60 | if updtime[chid] == nil or updtime[chid] < tmp[:updatetime]
61 | updtime[chid] = tmp[:updatetime]
62 | end
63 | end
64 |
65 | chid2phch = channel.makeChid2Phch(db)
66 |
67 | list.each do |r|
68 | chid = r[:chid]
69 | phch = chid2phch[ chid ]
70 | flag = false
71 | if updtime[chid] < ( now - 10 * 60 )
72 | if @@updT[chid] == nil or @@updT[chid] < ( now - 10 * 60 )
73 | flag = true
74 | end
75 | end
76 | # if $debug == true
77 | # sa1 = r[:start] - now
78 | # sa2 = now - updtime[chid]
79 | # sa3 = now - ( @@updT[chid] == nil ? now : @@updT[chid] )
80 | # mark = flag == true ? "+" : " "
81 | # tmp = sprintf("%s %6s 録 %5d 秒前 EPG1 %5d 秒前 EPG2 %5d 秒前 %s",
82 | # mark, phch, sa1,sa2,sa3, r[:title])
83 | # DBlog::stoD(tmp)
84 | # end
85 | if flag == true
86 | @@updT[chid] = now
87 | res << phch
88 | end
89 | end
90 | res.uniq!
91 |
92 | if res.size > 0
93 | DBlog::debug( db,"EpgNearCh #{res.join(" ")}" )
94 | end
95 | end
96 | return res
97 | end
98 |
99 |
100 | def ppp()
101 | pp @@updT
102 | end
103 |
104 | end
105 |
106 |
107 | if File.basename($0) == "EpgNearCh.rb"
108 | base = File.dirname( $0 )
109 | [ ".", "..","src", base ].each do |dir|
110 | if test( ?f, dir + "/require.rb")
111 | $: << dir
112 | $baseDir = dir
113 | end
114 | end
115 | require 'require.rb'
116 |
117 | $debug = true
118 | ge = EpgNearCh.new
119 | pp ge.check()
120 | ge.ppp()
121 |
122 | exit
123 |
124 |
125 | end
126 |
127 |
128 |
--------------------------------------------------------------------------------
/src/lib/deviceChk.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | require 'yaml'
4 | require 'lib/YamlWrap.rb'
5 |
6 | #
7 | # デバイスのチェック
8 | #
9 | class DeviceChk
10 |
11 | attr_reader :data
12 |
13 | def initialize( )
14 | end
15 |
16 | def run( )
17 | @data = DeviceChkData.new
18 | yaml = YAML.dump(@data)
19 |
20 | File.open( DeviceChkFN, "w") do |fp|
21 | fp.puts( yaml )
22 | end
23 |
24 | return @data
25 | end
26 |
27 | def load( )
28 | @data = nil
29 | if test( ?f, DeviceChkFN )
30 | File.open( DeviceChkFN, "r") do |fp|
31 | @data = YamlWrap.load_file(fp)
32 | end
33 | else
34 | run()
35 | end
36 | return @data
37 | end
38 |
39 | #
40 | # パーミッションのチェック
41 | #
42 | def devRWchk( list )
43 | list.sort.each do |devfn|
44 | if devfn =~ /frontend0/
45 | base = File.dirname( devfn )
46 | chkRW( File.join( base,"demux0" ))
47 | chkRW( File.join( base,"dvr0" ) )
48 | chkRW( devfn )
49 | else
50 | chkRW( devfn )
51 | end
52 | end
53 | end
54 |
55 | def chkRW( fn )
56 | if FileTest.chardev?( fn ) or FileTest.blockdev?( fn )
57 | if FileTest.writable?( fn ) == true and FileTest.readable?( fn ) == true
58 | # DBlog::sto("device R/W check OK #{fn}")
59 | else
60 | DBlog::error(nil, "Error: device R/W check NG #{fn}")
61 | end
62 | else
63 | DBlog::error(nil, "Error: device file not found #{fn}")
64 | end
65 | end
66 |
67 |
68 | end
69 |
70 |
71 | class DeviceChkData
72 |
73 | attr_reader :listGR, :listBC, :listGBC, :total
74 |
75 | GR = "地デジ"
76 | BC = "BS/CS"
77 | GBC = "GR/BS/CS"
78 |
79 | def makeDevName( hina, nums, type )
80 | nums.each do |n|
81 | devfn = "/dev/" + sprintf( hina, n )
82 | if FileTest.chardev?( devfn ) or FileTest.blockdev?(devfn)
83 | DBlog::sto("device found #{devfn} #{type}")
84 | case type
85 | when GR then @listGR << devfn
86 | when BC then @listBC << devfn
87 | when GBC then @listGBC << devfn
88 | end
89 | else
90 | break
91 | end
92 | end
93 | end
94 |
95 | def initialize( )
96 |
97 | @listGR = [] # 地デジ
98 | @listBC = [] # BS/CS
99 | @listGBC = [] # GR/BS/CS
100 |
101 | numT = [2, 3, 6, 7, 10, 11, 14, 15 ] # 地デジ
102 | numS = [0, 1, 4, 5, 8, 9, 12, 13, ] # BS/CS
103 | num3 = Array.new(16){|n| n } # 3波
104 |
105 | numT2 = [1, 3, 5, 7, ] # dvb ドライバー 地デジ
106 | numS2 = [0, 2, 4, 6, ] # dvb ドライバー BS/CS
107 |
108 | makeDevName( "px4video%d",numS, BC ) # PX-Q3U4/Q3PE4/Q3PE5
109 | makeDevName( "pt1video%d",numS, BC ) # PT1/PT2
110 | makeDevName( "pt3video%d",numS, BC ) # PT3
111 |
112 | makeDevName( "px4video%d",numT, GR ) # PX-Q3U4/Q3PE4/Q3PE5
113 | makeDevName( "pt1video%d",numT, GR ) # PT1/PT2
114 | makeDevName( "pt3video%d",numT, GR ) # PT3
115 |
116 | makeDevName( "pxmlt5video%d", num3, GBC) # PX-MLT5PE
117 | makeDevName( "pxmlt8video%d", num3, GBC) # PX-MLT8PE
118 | makeDevName( "isdb2056video%d",num3, GBC) # DTV02-1T1S-U
119 | makeDevName( "isdb6014video%d",num3, GBC) # DTV02A-4TS-P
120 |
121 | makeDevName( "dvb/adapter%d/frontend0", numT2, GR ) # dvb
122 | makeDevName( "dvb/adapter%d/frontend0", numS2, BC ) # dvb
123 |
124 | @total = @listGR.size + @listBC.size + @listGBC.size
125 |
126 | end
127 |
128 | end
129 |
130 | if File.basename($0) == "deviceChk.rb"
131 | DeviceChk.new
132 | end
133 |
134 |
135 |
--------------------------------------------------------------------------------
/src/views/style.sass:
--------------------------------------------------------------------------------
1 |
2 | body
3 | padding: 3px
4 | background: #d3d3d3
5 | img
6 | margin: 10px
7 | border: 5px
8 | solid: #000000
9 |
10 | .fss
11 | font-size: 90%
12 | padding: 5px
13 |
14 | .nowrap
15 | white-space: nowrap
16 |
17 | a
18 | color: navy
19 |
20 | tr.color0, div.color0
21 | border: solid 1px #999
22 | background-color: #cfe5fc !important
23 |
24 | tr.color1, div.color1
25 | border: solid 1px #999
26 | background-color: #d1d4e9 !important
27 |
28 | tr.color2, div.color2
29 | border: solid 1px #999
30 | background-color: #ffdde2 !important
31 |
32 | tr.color3, div.color3
33 | border: solid 1px #999
34 | background-color: #f1cfef !important
35 |
36 | tr.color4, div.color4
37 | border: solid 1px #999
38 | background-color: #bbdefb !important
39 |
40 | tr.color5, div.color5
41 | border: solid 1px #999
42 | background-color: #b2dfdb !important
43 |
44 | tr.color6, div.color6
45 | border: solid 1px #999
46 | background-color: #c8e6c9 !important
47 |
48 | tr.color7, div.color7
49 | border: solid 1px #999
50 | background-color: #f0f4c3 !important
51 |
52 | tr.color8,div.color8
53 | border: solid 1px #999
54 | background-color: #ffe8c2 !important
55 |
56 | tr.color9, div.color9
57 | border: solid 1px #999
58 | background-color: #ffdccc !important
59 |
60 | tr.color10, div.color10
61 | border: solid 1px #999
62 | background-color: #e7dfd8 !important
63 |
64 | tr.color11, div.color11
65 | border: solid 1px #999
66 | background-color: #e2ffb9 !important
67 |
68 | tr.color12, div.color12
69 | border: solid 1px #999
70 | background-color: #aff0ce !important
71 |
72 | tr.color13, div.color13
73 | border: solid 1px #999
74 | background-color: #b7ffeb !important
75 |
76 | tr.color14, div.color14
77 | border: solid 1px #999
78 | background-color: #ffcac0 !important
79 |
80 | tr.color15, div.color15
81 | border: solid 1px #999
82 | background-color: #c2d1ff !important
83 |
84 | tr.color16, div.color16
85 | border: solid 1px #999
86 | background-color: #e6c8ff !important
87 |
88 | tr.color17, div.color17
89 | border: solid 1px #999
90 | background-color: #8c9eff !important
91 |
92 | tr.colorGray, div.colorGray
93 | border: solid 1px #999
94 | background-color: #aaaaaa !important
95 |
96 | td.alertR, div.alertR
97 | border: solid 3px red !important
98 |
99 | td.alertB, div.alertB
100 | border: solid 3px black !important
101 |
102 | td.alertBD, div.alertBD
103 | border: dashed 3px black !important
104 |
105 | td.alertGD, div.alertGD
106 | border: dashed 3px gray !important
107 |
108 |
109 | #in,#email_inline
110 | height: 2em
111 |
112 | #email_inline
113 | width: 50%
114 | margin-left: 1em
115 |
116 | #title
117 | margin: 0.5em 0.5em 0.5em 0.5em
118 | font-size: 1.5em
119 |
120 | td.error
121 | color: crimson !important
122 |
123 | td.warn
124 | color: purple !important
125 |
126 | td.atte
127 | color: indigo !important
128 |
129 | /* 上に戻るボタン
130 | .pagetop
131 | display: none
132 | position: fixed
133 | bottom: 30px
134 | right: 15px
135 | a
136 | display: block
137 | background-color: #ccc
138 | text-align: center
139 | color: #222
140 | font-size: 24px
141 | text-decoration: none
142 | padding: 5px 10px
143 | filter: alpha(opacity = 50)
144 | -moz-opacity: 0.5
145 | opacity: 0.5
146 | &:hover
147 | display: block
148 | background-color: #b2d1fb
149 | text-align: center
150 | color: #fff
151 | font-size: 24px
152 | text-decoration: none
153 | padding: 5px 10px
154 | filter: alpha(opacity = 50)
155 | -moz-opacity: 0.5
156 | opacity: 0.5
157 |
158 |
--------------------------------------------------------------------------------
/src/TV/misc.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | #
4 | # raspirecTV.rb 関連
5 | #
6 |
7 | #
8 | # チャンネル情報
9 | #
10 | class ChList
11 |
12 | def initialize()
13 | @phch = {}
14 | @chList = {}
15 | channel = DBchannel.new
16 | DBaccess.new().open do |db|
17 | row = channel.select( db, order: "order by band_sort,svid" )
18 | row.each do |r|
19 | next if r[:updatetime] == -1
20 | next if r[:skip] == 1
21 | chid = r[:chid ]
22 | tmp = Commlib::makePhCh( r )
23 | band = r[:band]
24 | @phch[ chid ] = ChInfo.new(phch: tmp, svid: r[:svid], band: band, chname: r[:name] )
25 | @chList[ band ] ||= {}
26 | @chList[ band ][ r[:name]] = chid
27 | end
28 | end
29 | end
30 |
31 | def getChList()
32 | return @chList
33 | end
34 |
35 | def getPhCh( chid )
36 | return @phch[ chid ]
37 | end
38 | end
39 |
40 | #
41 | # 番組情報の取得
42 | #
43 | class Prog
44 |
45 | attr_reader :data
46 | def getData( chid )
47 | if @updTime == nil or @updTime < Time.now.to_i
48 | getInfo()
49 | end
50 | return @data[ chid ]
51 | end
52 |
53 | def getDetail( chid )
54 | tmp = @data[ chid ]
55 | if tmp != nil
56 | ret = "\n" + tmp.prog_name + "\n\n"
57 | ret += Commlib::stet_to_s( tmp.start, tmp.endt ).join(" ") + "\n\n"
58 | if tmp.prog_detail != nil
59 | ret += tmp.prog_detail + "\n"
60 | end
61 | if tmp.prog_extdetail != nil and tmp.prog_extdetail.strip != ""
62 | if ( data = YamlWrap.load( tmp.prog_extdetail)).size > 0
63 | ret += "\n----- 詳細情報 ------\n"
64 | data.each do |tmp|
65 | title = tmp[ "item_description" ]
66 | item = tmp[ "item" ]
67 | ret += "<<< #{title} >>>\n"
68 | ret += item + "\n\n"
69 | end
70 | end
71 | end
72 | else
73 | ret = ""
74 | end
75 |
76 | return ret
77 | end
78 |
79 | def getInfo( )
80 | @data = {}
81 | @updTime = nil
82 | programs = DBprograms.new
83 | DBaccess.new().open do |db|
84 | now = Time.now.to_i
85 | row = programs.selectSP( db, tstart: now, tend: now )
86 | row.each do |r1|
87 | chid = r1[:chid]
88 | @data[chid] = ChInfo.new( prog_name: r1[:title],
89 | prog_detail: r1[:detail],
90 | prog_extdetail: r1[:extdetail],
91 | start: r1[:start],
92 | endt: r1[:end],
93 | chname: r1[:name]
94 | )
95 | if @updTime == nil or @updTime > r1[:end]
96 | @updTime = r1[:end]
97 | #pp Time.at( @updTime ).to_s + " " + r1[:title]
98 | end
99 | end
100 | end
101 | end
102 | end
103 |
104 | class ChInfo
105 | attr_accessor :prog_name, :prog_detail, :prog_extdetail, :phch, :svid, :band
106 | attr_accessor :start, :endt, :chname
107 |
108 | def initialize( prog_name: nil,
109 | prog_detail: nil,
110 | prog_extdetail: nil,
111 | start: nil,
112 | endt: nil,
113 | phch: nil,
114 | svid: nil,
115 | band: nil,
116 | chname: nil
117 | )
118 | @prog_name = prog_name
119 | @prog_detail = prog_detail
120 | @prog_extdetail = prog_extdetail
121 | @phch = phch
122 | @svid = svid
123 | @band = band
124 | @start = start
125 | @endt = endt
126 | @chname = chname
127 |
128 | end
129 | end
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/src/model/PacketChk.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # パケットチェック
6 | #
7 |
8 | class PacketChk < FileCopy # FileCopy を流用
9 |
10 |
11 | def initialize( )
12 | @reserve = DBreserve.new
13 | end
14 |
15 | def start( time_limit )
16 | lock() { start2( time_limit ) }
17 | end
18 |
19 | def start2( time_limit )
20 |
21 | list = nil
22 | DBaccess.new().open( tran: true ) do |db|
23 | list = @reserve.getPacketChk( db )
24 | DBkeyval.new.upsert( db, StatConst::KeyName, StatConst::PacketChk )
25 | end
26 | DBlog::sto( "Packet Check 開始 #{list.size}") if list.size > 0
27 |
28 | abNormal = []
29 | list.each do |l|
30 |
31 | if $recCount > 0 # 録画中は実行しない
32 | DBlog::sto("rec now -> Packet Check 停止" )
33 | break
34 | end
35 | next if l[:fname] == nil
36 |
37 | path = TSDir + "/"
38 | if l[:subdir] != nil and l[:subdir] != ""
39 | subdir2 = Commlib::normStr( l[:subdir] )
40 | path += subdir2.sub(/^\//,'').sub(/\/$/,'').strip + "/"
41 | end
42 | path += l[:fname]
43 | #DBlog::sto( path );
44 |
45 | if test( ?f , path )
46 | size = File.size( path )
47 | t = (Time.now + (size / (PacketChk_rate * 2 ** 20))).to_i
48 | if t > time_limit
49 | DBlog::stoD( sprintf("time limit %s > %s", t, time_limit ))
50 | next
51 | end
52 |
53 | ( drer, pcr, speed ) = run( path )
54 | pcr = pcr == "OK" ? 0 : 1 if pcr != 0
55 |
56 | if speed > 0
57 | DBaccess.new().open( tran: true ) do |db|
58 | tmp = sprintf("PacketChk 終了: %s (%.1f Mbyte/sec)", l[:fname], speed )
59 | DBlog::info(db,tmp)
60 | val = @reserve.makeDropNum( drer, pcr, 0 )
61 | @reserve.updateStat( db, l[:id], dropNum: val )
62 | end
63 | else
64 | tmp = sprintf("PacketChk 失敗: %s", l[:fname] )
65 | DBlog::warn(nil,tmp)
66 | abNormal << [ l[:id], l[:fname] ]
67 | end
68 | else
69 | tmp = sprintf("PacketChk 失敗(not found): %s", l[:fname] )
70 | DBlog::sto( tmp )
71 | abNormal << [ l[:id], l[:fname] ]
72 | end
73 | end
74 |
75 | DBaccess.new().open( tran: true ) do |db|
76 | if abNormal.size > 0
77 | abNormal.each do |tmp|
78 | ( id, fname ) = tmp
79 | val = @reserve.makeDropNum( 0, 0, 1 )
80 | @reserve.updateStat( db, id, dropNum: val )
81 | end
82 | end
83 | DBkeyval.new.upsert( db, StatConst::KeyName, StatConst::None )
84 | end
85 |
86 |
87 | end
88 |
89 | #
90 | # tspacketchk の実行
91 | #
92 | def run( path )
93 |
94 | args = [ PacketChk_cmd ]
95 | args += PacketChk_opt.split().push( path )
96 | drer = pcr = speed = 0
97 |
98 | logfname = path + ".chk"
99 | File.open(logfname, File::RDWR|File::CREAT, 0644) do |fl|
100 | if fl.flock(File::LOCK_EX|File::LOCK_NB) == false
101 | DBlog::debug( nil,"chk log locked(#{path})")
102 | else
103 | IO.popen( args, "r") do |io|
104 | io.each_line do |line|
105 | if line =~ /drop\+error\s+=\s+(\d+)/
106 | drer = $1.to_i
107 | elsif line =~ /Check Time.*?\(([\d\.]+) Mbyte\/sec/
108 | speed = $1.to_f
109 | elsif line =~ /PCR Wrap-around check\s+=\s+(OK|NG)/i
110 | pcr = $1
111 | end
112 | fl.puts( line )
113 | end
114 | end
115 | end
116 | end
117 | size = File.size( logfname )
118 | DBlog::stoD( "PacketCheck end #{drer},#{pcr},#{speed},#{size}")
119 | return [ drer,pcr,speed]
120 | end
121 | end
122 |
--------------------------------------------------------------------------------
/src/model/Const.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 定数定義
6 | #
7 |
8 | module Const
9 |
10 | ProgName = "raspirec"
11 | ProgVer = "Ver 1.5.3"
12 | GitTag = "Ver1.5.3"
13 |
14 | GR = "GR"
15 | GRJ = "地デジ"
16 | BS = "BS"
17 | CS = "CS"
18 | BSCS = "BSCS"
19 | GBC = "GRBSCS"
20 | Wday = { 0 => "(日)",
21 | 1 => "(月)",
22 | 2 => "(火)",
23 | 3 => "(水)",
24 | 4 => "(木)",
25 | 5 => "(金)",
26 | 6 => "(土)", }
27 |
28 | FAtype = "fa_type"
29 | LastEpgTime = "lastepgtime"
30 | GB = 1024 * 1024 * 1024
31 | MB = 1024 * 1024
32 | PlayListFname = "playlist.m3u8" # playList ファイル名
33 |
34 | end
35 |
36 | module StrConst
37 | Jitan = "チューナーが競合した場合に録画時間の短縮を許可する。"
38 | NotUse = "この予約を無効とする。"
39 | Dedupe = "過去に録画したタイトルと一致した場合は無効にする。"
40 | FreeOnly = "無料放送のみ"
41 | end
42 |
43 |
44 | #
45 | # 予約関係
46 | #
47 | module RsvConst
48 |
49 | Normal = 0 # 予約中 正常
50 | Conflict = 1 # 予約中 競合
51 | NormalEnd = 2 # 正常終了
52 | AbNormalEnd= 3 # 異常終了
53 | RecNow = 4 # 録画中
54 | RecStop = 5 # 番組消失
55 | RecStop2 = 6 # 手動操作による録画中止
56 | NotUseA = 7 # 予約無効(自動予約)
57 | NotUse = 9 # 予約無効(手動)
58 | WaitStat = [0,1,4,7,9] # 予約中、録画中の status
59 | EndStat = [2,3,5] # 終了した status
60 | RecStat = [4] # 録画中の status
61 | ActStat = [0,1,4] # 有効な status
62 | AllStat = [0,1,2,3,4,5,6,7,9] # All status
63 |
64 | Manual = 0 # 手動予約
65 | Auto = 1 # 自動予約
66 | #NotUse = 1 # 不使用
67 | #Use = 0 # 使用
68 | JitanOn = 0 # 時短許可
69 | JitanOff = 1 # 時短不許可
70 | JitanEOn = 0 # 録画時時短を実行する。
71 | JitanEOff = 1 # 〃 しない
72 | FO = 1 # 無料放送のみ制限する
73 | Off = 0 # off
74 | Dedupe = 1 # 重複予約を無効化
75 | Ftp_Complete = 1 # 転送完了
76 | Ftp_AbNormal = 2 # 異常終了
77 | HashSetFlag = 999 # title の hash を設定したかのフラグ
78 | end
79 |
80 | #
81 | # フィルター関係
82 | #
83 | module FilConst
84 |
85 | Filter = 0 # フィルター
86 | AutoRsv = 1 # 自動予約
87 | RegexOn = 1 # 正規表現
88 | RegexOff = 0 # 単純文字列検索
89 | TypeMan = 0 # 手動予約
90 | TypeAuto = 1 # 自動予約
91 | JitanOn = 0 # 時短許可
92 | JitanOff = 1 # 時短不許可
93 | BandGR = 1 # GR
94 | BandBS = 2 # BS
95 | BandCS = 4 # CS
96 | BandALL = 7 # GR + BS + CS
97 | TargetT = 0 # 検索対象 = タイトル
98 | TargetTS = 1 # 検索対象 = タイトル+概要
99 | SeachMax = 512 # 検索最大値
100 |
101 | end
102 |
103 |
104 | #
105 | # ステータス関係
106 | #
107 | module StatConst
108 | KeyName = "Status"
109 | None = 0
110 | FileCopy = 1
111 | EPGget = 2
112 | RecNow = 4
113 | PacketChk = 5
114 | end
115 |
116 | #
117 | # 自動予約一覧の sort type
118 | #
119 | module ARSort
120 | Title = 0
121 | Reg = 1
122 | Cate = 2
123 | Num = 3
124 | Reverse= 4
125 | Off = 0 # off
126 | On = 1 # on
127 | KeyNameT = "ARSortType"
128 | KeyNameR = "ARSortReverse"
129 | end
130 |
131 | #
132 | # フィルター一覧の sort type
133 | #
134 | module FilSort
135 | KeyNameT = "FilSortType"
136 | KeyNameR = "FilSortReverse"
137 | end
138 |
--------------------------------------------------------------------------------
/src/views/monitor.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # HLS モニター
6 | #
7 |
8 |
9 | class Monitor
10 |
11 |
12 | attr_reader :band, :list, :data, :bandname
13 |
14 | def initialize( )
15 |
16 | @base_url = "/monitor"
17 |
18 | end
19 |
20 | #
21 | # 表示データ生成
22 | #
23 | def createData(recFlag = true)
24 | @list = getChData( )
25 | @band = @list.keys
26 | @bandname = { "rec" => "録画中",
27 | "file" => "録画済み",
28 | }
29 | @band.each do |band|
30 | band2 = band == "GR" ? "地デジ" : band
31 | @bandname[ band ] = band2
32 | end
33 | @data = make_table( @list )
34 |
35 | if recFlag == true
36 | if ( rec = getRecData()) != nil
37 | title = "rec"
38 | @band.unshift( title )
39 | @data[ title ] = rec
40 | end
41 | end
42 | end
43 |
44 | #
45 | # 録画済みTSファイル一覧(不使用)
46 | #
47 | def getFileData()
48 | paths = {}
49 | Find.find( TSDir ) do |path|
50 | if test(?f, path )
51 | if path =~ /\.ts$/
52 | if File.size( path ) > 0
53 | fname = path.sub( TSDir + "/" , '' )
54 | paths[ File.basename( fname ) ] = CGI.escape( fname )
55 | end
56 | end
57 | end
58 | end
59 | if paths.size > 0
60 | ret = []
61 | ret << %q(
)
62 | paths.keys.sort.each do |path|
63 | id = paths[ path ]
64 | ret << %Q( #{path} )
65 | end
66 | ret << %q( )
67 | return ret.join("\n")
68 | else
69 | return nil
70 | end
71 | end
72 |
73 | #
74 | # 録画中の番組を取得
75 | #
76 | def getRecData()
77 | paths = {}
78 | reserve = DBreserve.new
79 | DBaccess.new().open do |db|
80 | row = reserve.selectSP( db, stat: RsvConst::RecNow )
81 | row.each do |l|
82 | path = TSDir + "/"
83 | if l[:subdir] != nil and l[:subdir] != ""
84 | subdir2 = Commlib::normStr( l[:subdir] )
85 | path += subdir2.sub(/^\//,'').sub(/\/$/,'').strip + "/"
86 | end
87 | if l[:fname] != nil
88 | path += l[:fname]
89 | if test( ?f, path )
90 | paths[ l[:fname] ] = l[:id]
91 | end
92 | end
93 | end
94 | end
95 |
96 | if paths.size > 0
97 | ret = []
98 | ret << %q()
99 | paths.keys.each do |path|
100 | id = paths[ path ]
101 | ret << %Q( #{path} )
102 | end
103 | ret << %q( )
104 | return ret.join("\n")
105 | else
106 | return nil
107 | end
108 | end
109 |
110 | #
111 | # 放送局一覧
112 | #
113 | def make_table( list )
114 |
115 | ret = {}
116 | list.keys.each do |band|
117 | tmp = []
118 | tmp << %q() # class="striped"
119 | tmp << %q()
120 |
121 | n = 0
122 | list[ band ].each_pair do |k,v|
123 | tmp << %Q( #{k} )
124 | n += 1
125 | if n > 4
126 | tmp << %q( )
127 | tmp << %q()
128 | n = 0
129 | end
130 | end
131 | tmp << %q( )
132 | tmp << %q(
)
133 |
134 | ret[ band ] = tmp.join("\n")
135 | end
136 | return ret
137 | end
138 |
139 |
140 | #
141 | # channelデータの取得
142 | #
143 | def getChData( )
144 | channel = DBchannel.new
145 |
146 | list = {}
147 | DBaccess.new().open do |db|
148 | row = channel.select( db, order: "order by band_sort,svid" )
149 | row.each do |r|
150 | next if r[:updatetime] == -1
151 | next if r[:skip] == 1
152 | list[ r[:band] ] ||= {}
153 | list[ r[:band] ][ r[:name] ] = r[:chid ]
154 | end
155 | end
156 | list
157 | end
158 |
159 |
160 | end
161 |
--------------------------------------------------------------------------------
/src/public/style.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | body {
3 | padding: 3px;
4 | background: #d3d3d3; }
5 |
6 | img {
7 | margin: 10px;
8 | border: 5px;
9 | solid: #000000; }
10 |
11 | .fss {
12 | font-size: 90%;
13 | padding: 5px; }
14 |
15 | .nowrap {
16 | white-space: nowrap; }
17 |
18 | a {
19 | color: navy; }
20 |
21 | tr.color0, div.color0 {
22 | border: solid 1px #999;
23 | background-color: #cfe5fc !important; }
24 |
25 | tr.color1, div.color1 {
26 | border: solid 1px #999;
27 | background-color: #d1d4e9 !important; }
28 |
29 | tr.color2, div.color2 {
30 | border: solid 1px #999;
31 | background-color: #ffdde2 !important; }
32 |
33 | tr.color3, div.color3 {
34 | border: solid 1px #999;
35 | background-color: #f1cfef !important; }
36 |
37 | tr.color4, div.color4 {
38 | border: solid 1px #999;
39 | background-color: #bbdefb !important; }
40 |
41 | tr.color5, div.color5 {
42 | border: solid 1px #999;
43 | background-color: #b2dfdb !important; }
44 |
45 | tr.color6, div.color6 {
46 | border: solid 1px #999;
47 | background-color: #c8e6c9 !important; }
48 |
49 | tr.color7, div.color7 {
50 | border: solid 1px #999;
51 | background-color: #f0f4c3 !important; }
52 |
53 | tr.color8, div.color8 {
54 | border: solid 1px #999;
55 | background-color: #ffe8c2 !important; }
56 |
57 | tr.color9, div.color9 {
58 | border: solid 1px #999;
59 | background-color: #ffdccc !important; }
60 |
61 | tr.color10, div.color10 {
62 | border: solid 1px #999;
63 | background-color: #e7dfd8 !important; }
64 |
65 | tr.color11, div.color11 {
66 | border: solid 1px #999;
67 | background-color: #e2ffb9 !important; }
68 |
69 | tr.color12, div.color12 {
70 | border: solid 1px #999;
71 | background-color: #aff0ce !important; }
72 |
73 | tr.color13, div.color13 {
74 | border: solid 1px #999;
75 | background-color: #b7ffeb !important; }
76 |
77 | tr.color14, div.color14 {
78 | border: solid 1px #999;
79 | background-color: #ffcac0 !important; }
80 |
81 | tr.color15, div.color15 {
82 | border: solid 1px #999;
83 | background-color: #c2d1ff !important; }
84 |
85 | tr.color16, div.color16 {
86 | border: solid 1px #999;
87 | background-color: #e6c8ff !important; }
88 |
89 | tr.color17, div.color17 {
90 | border: solid 1px #999;
91 | background-color: #8c9eff !important; }
92 |
93 | tr.colorGray, div.colorGray {
94 | border: solid 1px #999;
95 | background-color: #aaaaaa !important; }
96 |
97 | td.alertR, div.alertR {
98 | border: solid 3px red !important; }
99 |
100 | td.alertY, div.alertY {
101 | border: solid 3px yellow !important; }
102 |
103 | td.alertB, div.alertB {
104 | border: solid 3px black !important; }
105 |
106 | td.alertBD, div.alertBD {
107 | border: dashed 3px black !important; }
108 |
109 | td.alertGD, div.alertGD {
110 | border: dashed 3px gray !important; }
111 |
112 | #in, #email_inline {
113 | height: 2em; }
114 |
115 | #email_inline {
116 | width: 50%;
117 | margin-left: 1em; }
118 |
119 | #title {
120 | margin: 0.5em 0.5em 0.5em 0.5em;
121 | font-size: 1.5em; }
122 |
123 | td.error {
124 | color: crimson !important; }
125 |
126 | td.warn {
127 | color: purple !important; }
128 |
129 | td.atte {
130 | color: indigo !important; }
131 |
132 | /* 上に戻るボタン */
133 | .pagetop {
134 | display: none;
135 | position: fixed;
136 | bottom: 30px;
137 | right: 15px; }
138 | .pagetop a {
139 | display: block;
140 | background-color: #ccc;
141 | text-align: center;
142 | color: #222;
143 | font-size: 24px;
144 | text-decoration: none;
145 | padding: 5px 10px;
146 | filter: alpha(opacity=50);
147 | -moz-opacity: 0.5;
148 | opacity: 0.5; }
149 | .pagetop a:hover {
150 | display: block;
151 | background-color: #b2d1fb;
152 | text-align: center;
153 | color: #fff;
154 | font-size: 24px;
155 | text-decoration: none;
156 | padding: 5px 10px;
157 | filter: alpha(opacity=50);
158 | -moz-opacity: 0.5;
159 | opacity: 0.5; }
160 |
--------------------------------------------------------------------------------
/src/db/Log.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | class DBlog
6 |
7 | Stdout = 0 # level 0 標準出力のみ、記録しない
8 | Debug = 1 # level 1 デバック
9 | Info = 2 # level 2 情報
10 | Attention = 3 # level 3 注意
11 | Warning = 4 # level 4 警告
12 | Error = 5 # level 5 エラー
13 |
14 | def initialize( )
15 | @list = {
16 | id: "id",
17 | level: "level",
18 | time: "time",
19 | str: "str",
20 | }
21 | @para = []
22 | @list.each_pair { |k,v| @para << v }
23 | @tbl_name = "log"
24 | end
25 |
26 | def self.vacuum( )
27 | sql = "vacuum;"
28 | DBaccess.new().open do |db|
29 | db.execute( sql )
30 | end
31 | end
32 |
33 |
34 | def self.write( db, str, level )
35 | now = Time.now
36 | if level > 0
37 | sql = "insert into log (level,time,str) values (?,?,?);"
38 | if db == nil
39 | DBaccess.new().open do |db|
40 | db.execute( sql, level, now.to_i, str)
41 | end
42 | else
43 | db.execute( sql, level, now.to_i, str)
44 | end
45 | end
46 | txt = sprintf("%s: %s\n",now.strftime("%H:%M:%S"),str)
47 | File.open( LogFname, "a" ) do |fp|
48 | fp.puts( txt )
49 | fp.flush
50 | end
51 | #STDOUT.puts( txt )
52 | #STDOUT.flush
53 | end
54 |
55 | def self.sto( str )
56 | write( nil, str, Stdout )
57 | end
58 |
59 | def self.stoD( str ) # デバック時のみ出力
60 | if $debug == true
61 | write( nil, str, Stdout )
62 | end
63 | end
64 |
65 | def self.debug( db, str )
66 | write( db, str, Debug )
67 | end
68 |
69 | def self.info( db, str )
70 | write( db, str, Info )
71 | end
72 |
73 | def self.atte( db, str )
74 | write( db, str, Attention )
75 | end
76 |
77 | def self.warn( db, str )
78 | write( db, str, Warning )
79 | end
80 |
81 | def self.error(db, str )
82 | write( db, str, Error )
83 | end
84 |
85 | def self.puts( *args )
86 | if args.size == 3
87 | ( db, level, str ) = args
88 | elsif args.size == 2
89 | ( level, str ) = args
90 | db == nil
91 | elsif args.size == 1
92 | str = args.first
93 | level = Stdout
94 | else
95 | raise
96 | end
97 |
98 | now = Time.now
99 | if level > 0
100 | sql = "insert into log (level,time,str) values (?,?,?);"
101 | if db == nil
102 | DBaccess.new().open do |db|
103 | db.execute( sql, level, now.to_i, str)
104 | end
105 | else
106 | db.execute( sql, level, now.to_i, str)
107 | end
108 | end
109 | txt = sprintf("%s: %s\n",now.strftime("%H:%M:%S"),str)
110 | File.open( LogFname, "a" ) do |fp|
111 | fp.puts( txt )
112 | end
113 | STDOUT.puts( txt )
114 | end
115 |
116 |
117 | def select( db, level: nil, limit: nil )
118 | where = []
119 | args = []
120 | sql = "select * from log "
121 | if level != nil
122 | where << "level > ?"
123 | args << level
124 | end
125 |
126 | if where.size > 0
127 | sql += " where " + where.join(" and ")
128 | end
129 | sql += " order by id desc "
130 |
131 | if limit != nil
132 | sql += limit
133 | end
134 |
135 | row = db.execute( sql, *args )
136 | end
137 |
138 | #
139 | # カウント
140 | #
141 | def count( db, level: nil )
142 | sql = "select count( id ) from log "
143 | argv = []
144 | where = []
145 | if level != nil
146 | where << "level > ?"
147 | argv << level
148 | end
149 |
150 | if where.size > 0
151 | sql += " where " + where.join(" and ")
152 | end
153 |
154 | row = db.execute( sql, *argv )
155 | return row[0][0]
156 | end
157 |
158 | #
159 | # 削除
160 | #
161 | def deleteOld( db, time )
162 | sql = "delete from log where time < ? ;"
163 | db.execute( sql, time )
164 | end
165 |
166 |
167 | end
168 |
--------------------------------------------------------------------------------
/src/db/Category.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | class DBcategory
6 |
7 | include Base
8 |
9 | def initialize( )
10 | @listL = {
11 | id: "id",
12 | name: "name",
13 | }
14 | @listM = {
15 | id: "id",
16 | pid: "pid",
17 | name: "name",
18 | }
19 | @paraL = []
20 | @listL.each_pair { |k,v| @paraL << v }
21 | @paraM = []
22 | @listM.each_pair { |k,v| @paraM << v }
23 | @changed = true
24 | @tbl_nameL = "categoryL"
25 | @tbl_nameM = "categoryM"
26 | end
27 |
28 |
29 | #
30 | # id から文字列に変換
31 | #
32 | def conv2str(db, l = nil, m = nil )
33 | rl = rm = ""
34 | if l != nil and l != 0
35 | rl = selectL(db, id: l).first[:name]
36 | end
37 | if m != nil and m != 0
38 | rm = selectM(db, id: m).first[:name]
39 | end
40 |
41 | [ rl, rm ]
42 | end
43 |
44 | #
45 | # カテゴリの id 列(6-55 6-57 9-68) から文字列に変換 (大項目のみ)
46 | #
47 | def ids2str(db, ids )
48 | cate = {}
49 | ids.split(/\s/).each do |tmp|
50 | l = m = 0
51 | if tmp =~ /(\d+)-(\d+)/
52 | l,m = $1.to_i,$2.to_i
53 | elsif tmp =~ /(\d+)/
54 | l,m = $1.to_i,nil
55 | end
56 | if l == 0
57 | cate = { "全て" => 1 }
58 | break
59 | else
60 | ls, m = conv2str(db, l, nil )
61 | cate[ ls ] = 1
62 | end
63 | end
64 | return cate.keys.sort.join(", ")
65 | end
66 |
67 |
68 | #
69 | # json文字列から id に変換
70 | #
71 | def conv2id(db, data )
72 | r = []
73 | data.each_with_index do |cate,n|
74 | l = cate["large"]["ja_JP"]
75 | m = cate["middle"]["ja_JP"]
76 | tryc = lid = mid = 0
77 | begin
78 | raise if (lid = selectC(db, l: l )) == nil
79 | rescue
80 | insertL(db, l )
81 | if tryc == 0
82 | tryc += 1
83 | retry
84 | else
85 | raise
86 | end
87 | end
88 | tryc = 0
89 | begin
90 | raise if (mid = selectC(db, l: lid, m: m )) == nil
91 | rescue
92 | insertM(db, lid, m )
93 | if tryc == 0
94 | tryc += 1
95 | retry
96 | else
97 | raise
98 | end
99 | end
100 | r << [ lid,mid ]
101 | end
102 | r.sort!
103 | ( 3 - r.size).times {|n| r << [0,0] }
104 | r
105 | end
106 |
107 | #
108 | # キャッシュ作成
109 | #
110 | def createCache( db )
111 | @main = {}
112 | @sub = {}
113 | sql = "select * from categoryL ;"
114 | row = db.execute( sql )
115 | if row != nil and row.size != 0
116 | row.each do |tmp|
117 | @main[ tmp[1] ] = tmp[0]
118 | end
119 | end
120 |
121 | sql = "select * from categoryM ;"
122 | row = db.execute( sql )
123 | if row != nil and row.size != 0
124 | row.each do |tmp|
125 | @sub[ tmp[1] ] ||= {}
126 | @sub[ tmp[1] ][ tmp[2] ] = tmp[0]
127 | end
128 | end
129 | @changed = false
130 |
131 | end
132 |
133 | def selectL(db, id: nil, name: nil)
134 | ( sql, args ) = makeSelectSql( @paraL, @tbl_nameL, id: id, name: name)
135 | row = db.execute( sql, args )
136 | row2hash( @listL, row )
137 | end
138 |
139 | def selectM(db, id: nil, pid: nil, name: nil)
140 | ( sql, args ) = makeSelectSql( @paraM, @tbl_nameM, id: id, pid: pid,name: name)
141 | row = db.execute( sql, args )
142 | row2hash( @listM, row )
143 | end
144 |
145 | def selectC(db, l: nil, m: nil)
146 | createCache( db ) if @changed == true
147 | if l != nil and m != nil
148 | if @sub[ l ] != nil
149 | return @sub[ l ][ m ]
150 | end
151 | elsif l != nil
152 | return @main[ l ]
153 | end
154 | nil
155 | end
156 |
157 | def insertL(db, cate )
158 | sql = "insert into categoryL values (null, ? );"
159 | db.execute( sql, cate )
160 | @changed = true
161 | end
162 |
163 | def insertM(db, pid, cate )
164 | sql = "insert into categoryM values (null, ?, ? );"
165 | db.execute( sql, pid, cate, )
166 | @changed = true
167 | end
168 |
169 |
170 | end
171 |
--------------------------------------------------------------------------------
/src/views/fil_list.slim:
--------------------------------------------------------------------------------
1 | /
2 | / フィルター一覧 兼 自動予約一覧
3 | /
4 |
5 | css:
6 | #item {
7 | width:8em;
8 | }
9 | a.btn {
10 | margin: 1em 2em 2em 2em;
11 | }
12 | #button {
13 | margin: 0px 5px 0px 5px;
14 | }
15 | #cbcomm {
16 | color: #000000;
17 | }
18 | label { color: #000000}
19 | #title {
20 | margin: 0.5em 10em 0.5em 0.5em;
21 | font-size: 1.5em
22 | }
23 | #sort {
24 | margin: 0.5em 0.5em 0.5em 0.5em;
25 | padding-right: 2em ;
26 | background-color: #e1e2e3;
27 | }
28 | #rb {
29 | padding: 0em 2em 0em 1.6em;
30 | font-size: 1.3em
31 | }
32 | td,th {
33 | padding-top: 7px;
34 | padding-bottom: 7px;
35 | }
36 |
37 | - require_relative 'fil_list.rb'
38 | - dp = FilterList.new( @params, session )
39 |
40 |
41 | .row
42 | .col
43 | // h1#title ==dp.printTitle()
44 | - if dp.type == FilConst::AutoRsv
45 | h1#title 自動予約一覧
46 | input type="hidden" id="url" value="/aut_rsv_list"
47 | - else
48 | h1#title フィルター一覧
49 | input type="hidden" id="url" value="/fil_list"
50 |
51 | .col#sort
52 | label
53 | span#rb 表示順
54 | label
55 | input name="stype" type="radio" value="title" checked=(dp.radioST[ARSort::Title])
56 | span#rb タイトル
57 | label
58 | input name="stype" type="radio" value="reg" checked=(dp.radioST[ARSort::Reg])
59 | span#rb 登録順
60 | label
61 | input name="stype" type="radio" value="cate" checked=(dp.radioST[ARSort::Cate])
62 | span#rb ジャンル
63 | label
64 | input name="stype" type="radio" value="num" checked=(dp.radioST[ARSort::Num])
65 | - if dp.type == FilConst::Filter
66 | span#rb 一致件数順
67 | - else
68 | span#rb 有効予約/一致件数順
69 |
70 | label
71 | input.cb#cb type="checkbox" name="reverse" checked=(dp.radioST[ARSort::Reverse])
72 | span 逆順
73 |
74 | .row
75 | table.striped
76 | tr
77 | th No
78 | th タイトル or 検索文字列
79 | th ジャンル(大項目)
80 | - if dp.type == FilConst::AutoRsv
81 | th 有効予約/一致件数
82 | - else
83 | th 一致件数
84 | th アクション
85 | == dp.printTable()
86 |
87 |
88 | div id="sample-dialog" title="詳細" style="display:none;"
89 | p 削除しますか?
90 |
91 |
92 | javascript:
93 | $(".item").click(function(event){
94 | //event.preventDefault();
95 | var rid = $(this).attr('rid');
96 | $("#sample-dialog").load('/fil_listD/' + rid );
97 | $("#sample-dialog").dialog({
98 | modal: true,
99 | maxWidth: 1200,
100 | width: 1000,
101 | buttons: { //ボタン
102 | "削除": function() {
103 | $.ajax({
104 | url: '/fil_del/' + rid,
105 | type:"POST",
106 | async: true,
107 | timeout: 30000
108 | }).done( function(results) {
109 | location.reload();
110 | })
111 | },
112 | "閉じる": function() {
113 | $(this).dialog("close");
114 | }
115 | },
116 | open: function() {
117 | $( this ).siblings('.ui-dialog-buttonpane').find('button:eq(2)').focus();
118 | }
119 | });
120 | return false;
121 | });
122 |
123 | == Commlib::include( SrcDir + "/views/move_top.html" )
124 |
125 | javascript:
126 | $(function(){
127 | $( 'input[name="stype"]:radio' ).change( function() {
128 | var radioval = $(this).val();
129 | var url = document.getElementById( "url" ).value ;
130 | var url2 = url + "?sort_type=" + radioval
131 | console.log(url2)
132 | window.location.href = url2
133 | });
134 | });
135 |
136 | $(function(){
137 | $('.cb').on('change', function () {
138 | var url = document.getElementById( "url" ).value ;
139 | if ($(this).prop('checked')) {
140 | url = url + "?reverse=on";
141 | } else {
142 | url = url + "?reverse=off";
143 | }
144 | window.location.href = url ;
145 | });
146 | });
147 |
--------------------------------------------------------------------------------
/src/views/top.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 予約一覧
6 | #
7 | require 'sys/filesystem'
8 |
9 | class Top
10 |
11 | def initialize( )
12 | $status = {} if $status == nil
13 | end
14 |
15 | def sec2str( sec )
16 | if sec > 59
17 | s = sec % 60
18 | m = sec.to_i / 60
19 | if m > 59
20 | h = m.to_i / 60
21 | m = m % 60
22 | if m != 0
23 | return sprintf("%d時間 %d分",h,m)
24 | else
25 | return sprintf("%d時間",h )
26 | end
27 | else
28 | return sprintf("%d分",m)
29 | end
30 | else
31 | return sprintf("%d秒",sec)
32 | end
33 | end
34 |
35 | def setup()
36 | r = {}
37 |
38 | # Disk情報
39 | stat = Sys::Filesystem.stat( DataDir )
40 | diskTotal = (stat.blocks * stat.block_size).to_f
41 | diskFree = (stat.blocks_available * stat.block_size).to_f
42 | used = diskTotal - diskFree
43 | r[:diskTotal] = sprintf("%.1f GB", diskTotal / Const::GB )
44 | r[:diskFree] = sprintf("%.1f GB", diskFree / Const::GB )
45 | r[:diskUsed] = sprintf("%.1f GB", used / Const::GB )
46 | wari = 100.0 * used / diskTotal
47 | r[:diskWari] = sprintf("%.1f %%", wari )
48 |
49 | # 予約情報
50 | now = Time.now.to_i
51 | reserve = DBreserve.new
52 | reserveNum = 0 # 予約数
53 | nowRecTitle = [] # 録画中のタイトル
54 | nextRecTime = nil # 次の予約時間
55 | nextRecTitle = "" # 次番組のタイトル
56 | conflictNum = 0 # 競合中の数
57 | epg = 0 # EPG取得中か
58 | remainingTime = 0 # 録画残り時間
59 | stat2 = 0
60 | errNum = 0 # 録画エラー数
61 |
62 | DBaccess.new().open do |db|
63 | row = reserve.select( db, tend: now, order: "order by start" )
64 | reserveNum = row.size
65 | row.each do |r|
66 |
67 | case r[:stat]
68 | when RsvConst::Normal then
69 | start2 = r[:start] - Start_margin
70 | if nextRecTime == nil or start2 < nextRecTime
71 | nextRecTime = start2
72 | nextRecTitle = r[:title]
73 | end
74 | when RsvConst::RecNow then
75 | nowRecTitle << r[:title]
76 | remainingTime = r[:end] if remainingTime < r[:end]
77 | when RsvConst::Conflict then
78 | conflictNum += 1
79 | end
80 | end
81 |
82 | point = now - ( 3 * 24 * 3600 )
83 | row = reserve.select( db, tend: point, order: "order by start" )
84 | reserveNum = row.size
85 | row.each do |r|
86 | errNum += 1 if r[:stat] == RsvConst::AbNormalEnd
87 | if r[:dropNum ] != nil
88 | ( drer, pcr, execerror ) = reserve.parseDropNum( r[:dropNum ] )
89 | errNum += 1 if drer > PacketChk_threshold
90 | end
91 | end
92 |
93 | stat2 = DBkeyval.new.select(db,StatConst::KeyName )
94 | end
95 |
96 | r[:stat2] = ""
97 | if nowRecTitle.size > 0
98 | r[:stat] = sprintf("録画中 ( 残り %s )",
99 | sec2str( remainingTime - Time.now.to_i ))
100 | r[:stat2] = nowRecTitle.join(" ")
101 | else
102 | if test( ?f, EPGLockFN )
103 | r[:stat] = case stat2
104 | when StatConst::FileCopy then "ファイル転送中"
105 | when StatConst::PacketChk then "ファイルチェック中"
106 | else "EPG取得中"
107 | end
108 | elsif reserveNum == 0 or nextRecTime == nil
109 | r[:stat] = "予約待ち"
110 | elsif nextRecTime > 0
111 | t = sec2str( nextRecTime - now )
112 | r[:stat] = sprintf("待機中 ( 開始まで %s )",t )
113 | r[:stat2] = nextRecTitle
114 | else
115 | r[:stat] = "-"
116 | end
117 | end
118 | if conflictNum > 0
119 | r[:ConflictNum] = %Q( #{conflictNum} )
120 | else
121 | r[:ConflictNum] = conflictNum
122 | end
123 |
124 | r[:reserveNum] = reserveNum
125 |
126 | # 過去のエラー数
127 | if errNum > 0
128 | r[:ErrorNum] = %Q( #{errNum} )
129 | else
130 | r[:ErrorNum] = errNum
131 | end
132 |
133 | r
134 | end
135 |
136 | end
137 |
138 |
--------------------------------------------------------------------------------
/src/model/DelayedEPG.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | #
4 | # 録画済みの TSファイルから、EPG データを採取する
5 | #
6 | # ・32bit OS のRaspbian で 大きいファイルの最後に seek しようとすると失敗
7 | # するので、最初の 1Gbyte で。
8 | # ・それでも epgdump が coreダンプするものがあり、録画した TS から抽出
9 | # するのは無理か
10 |
11 | class DelayedEPG
12 |
13 | def initialize( time_limit )
14 |
15 | @ts = Time.now
16 | phchid = DBphchid.new
17 | reserve = DBreserve.new
18 |
19 | list = []
20 | chid2upt = {} # chid 対 更新時間
21 | chid2phch = {} # chid 対 phch
22 |
23 | DBaccess.new().open( tran: true ) do |db|
24 |
25 | # EPG取得 の最終実施時間の取得
26 | ph = phchid.select(db)
27 | ph.each do |tmp|
28 | chid2upt[ tmp[:chid] ] = tmp[:updatetime]
29 | chid2phch[ tmp[:chid] ] = tmp[:phch]
30 | end
31 | #pp chid2upt
32 |
33 | # 最近の録画リスト取得
34 | order = "order by end desc"
35 | row = reserve.select( db, stat: RsvConst::NormalEnd, order: order, limit: "10" )
36 | exist = {} # 重複
37 | row.each do |tmp|
38 | #pp "+ #{tmp[:title]} #{Time.at(tmp[:end]).to_s}"
39 | chid = tmp[:chid]
40 | et = tmp[:end]
41 | if chid2upt[chid] < et
42 | #pp et - chid2upt[chid]
43 | path = makePath( tmp )
44 | if test(?f, path )
45 | if exist[ chid ] == nil
46 | list << tmp
47 | exist[ chid ] = true
48 | end
49 | else
50 | pp "file not found #{path}"
51 | end
52 | end
53 | end
54 | end
55 |
56 | return if list.size == 0
57 |
58 | ge = GetEPG.new
59 | count = { :upd => 0, :ins => 0, :del => 0, :same => 0 }
60 |
61 | list.reverse.each do |tmp| # 処理は古い方から
62 |
63 | # 中断の判断
64 | if Time.now > time_limit
65 | DBlog::sto("DelayedEPG time limit: #{timeLimit}" )
66 | return
67 | end
68 |
69 | tspath = makePath( tmp )
70 | pp "#{tmp[:title]} #{tspath} #{tmp[:chid]}"
71 | json = JsonDir + "/#{tmp[:chid]}.json"
72 | phch = chid2phch[ tmp[:chid] ]
73 | next if phch == nil
74 |
75 | @ts = Time.now
76 | ppp("execEpgdump() start")
77 | execEpgdump( tspath, json )
78 | if test(?f, json ) and File.size( json ) > 100
79 | ge.readJsonProc( json, phch, count )
80 | else
81 | DBlog::sto( "json file error (#{json})" )
82 | end
83 | ppp("execEpgdump() end")
84 | end
85 |
86 | str = sprintf("ins=>%4d,upd=>%4d,same=>%4d",
87 | count[:ins],count[:upd],count[:same] )
88 | DBlog::debug(nil, "delayed EPG取得終了 #{str}" )
89 |
90 | end
91 |
92 | def makePath( tmp )
93 | subd = tmp[:subdir] == "" ? "" : "/" + Commlib::normStr(tmp[:subdir])
94 | path = sprintf("%s%s/%s",TSDir, subd, Commlib::normStr(tmp[:fname]) )
95 | end
96 |
97 | def execEpgdump( tspath, json )
98 | bsize = 1024
99 | outbuf = "x" * bsize;
100 | cmd = [ Epgdump, "json", "-", json, :err=>[:child, :out] ]
101 | limit = 1024 * 1024 * 1024 # 1Gbyte
102 | fsize = 0
103 | st = Time.now
104 |
105 | IO.popen( cmd, "r+" ) do |io|
106 | File.open( tspath, "r") do |fr|
107 | begin
108 | while fr.sysread( bsize, outbuf ) != nil
109 | io.write( outbuf )
110 | fsize += outbuf.size
111 | break if fsize > limit
112 | break if ( Time.now - st ) > 120 # 120秒で timeout
113 | end
114 | rescue EOFError => e
115 | rescue => e
116 | pp e
117 | end
118 | io.close_write
119 | end
120 | end
121 | end
122 |
123 | end
124 |
125 |
126 | if File.basename($0) == "DelayedEPG.rb"
127 | base = File.dirname( $0 )
128 | [ ".", "..","src", base ].each do |dir|
129 | if test( ?f, dir + "/require.rb")
130 | $: << dir
131 | $baseDir = dir
132 | end
133 | end
134 |
135 | require 'require.rb'
136 |
137 | phchid = DBphchid.new
138 | DBaccess.new().open( tran: true ) do |db|
139 | phchid.add(db, "BS15_0","BS_200", (Time.now - 3600 * 24 ).to_i )
140 | end
141 |
142 | $debug = true
143 |
144 | DelayedEPG.new( Time.now + 600 )
145 |
146 | exit
147 |
148 | end
149 |
--------------------------------------------------------------------------------
/src/views/fil_res_dsp.js:
--------------------------------------------------------------------------------
1 |
2 |
121 |
--------------------------------------------------------------------------------
/src/TV/Video.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | #
4 | # raspirecTV.rb 関連
5 | #
6 |
7 | class Video
8 |
9 | attr_reader :recPid
10 |
11 | def initialize( serial = 0 )
12 | @port = UDPbasePort + serial
13 | if RemoteMonitor == true
14 | @videoPort = "udp://#{RecHostName}:#{@port}/"
15 | else
16 | @videoPort = "udp://localhost:#{@port}/"
17 | end
18 | @ipc = makeTmpFn( serial, "cmd" ) # mpv のコマンド入力
19 | @recPid = nil
20 | @mpvpid = nil
21 | @serial = serial
22 | @nullP = File.open( "/dev/null","w+")
23 | end
24 |
25 | #
26 | # 中間(作業)ファイル名の作成
27 | #
28 | def makeTmpFn( serial, key = "fifo" )
29 | fn = sprintf("/tmp/raspirecTV_%03d.%s",serial, key)
30 | return fn
31 | end
32 |
33 | #
34 | # socket にコマンド書き込み
35 | #
36 | def ipcSendMsg( fname, msg )
37 | dlog( msg )
38 | if RemoteMonitor == false
39 | s = UNIXSocket.new(fname)
40 | s.write(msg + "\n" )
41 | s.close
42 | else
43 | cmd = %W( ssh #{XServerName} socat - #{fname} )
44 | dlog( cmd.join(" ") )
45 | Open3.popen3( *cmd ) do | stdin, o, e, t|
46 | stdin.puts( msg )
47 | stdin.close
48 | end
49 | end
50 | end
51 |
52 | #
53 | # 停止
54 | #
55 | def stop( )
56 | [ @mpvPid, @recPid ].each do |pid|
57 | if pid != nil
58 | begin
59 | Process.kill( :KILL, pid )
60 | Process.waitpid( pid )
61 | rescue
62 | end
63 | end
64 | end
65 | @mpvPid = @recPid = nil
66 |
67 | File.unlink( @ipc ) if FileTest.socket?( @ipc )
68 | end
69 |
70 |
71 | #
72 | # チャンネル変更
73 | #
74 | def chChange( chinfo, devfn, pageName )
75 |
76 | return false if @recPid == nil
77 | begin
78 | Process.kill( :HUP, @recPid )
79 | rescue
80 | end
81 | @recPid = nil
82 | sepT = RaspirecTV_GAPT
83 | sleep( sepT )
84 | th = Thread.new() do
85 | cmd = makeRecCmd( chinfo, devfn )
86 | dlog( cmd.join(" ") )
87 | opt = {}
88 | if $arg.d < 2
89 | opt = { :out => @nullP, :err => @nullP }
90 | end
91 | st = Time.now
92 | begin
93 | @recPid = spawn( *cmd, opt )
94 | Process.waitpid( @recPid )
95 | rescue
96 | end
97 | sa = Time.now - st
98 | if sa < 1.0
99 | msg = "#{Recpt1_cmd} の起動に失敗しました。"
100 | $queue.push($event.new(:msg, nil, msg ))
101 | end
102 | end
103 | sleep( sepT )
104 | ipcSendMsg( @ipc, "loadfile #{@videoPort}" )
105 | sleep( 3 )
106 | tmp = sprintf("\"%s : %s\"" ,pageName, chinfo.chname )
107 | ipcSendMsg( @ipc, "set title #{tmp}" )
108 | return true
109 | end
110 |
111 |
112 |
113 | #
114 | # recpt1/recdvb のコマンド組み立て
115 | #
116 | def makeRecCmd( chinfo, devfn )
117 |
118 | ch = chinfo.phch
119 | sid = chinfo.band == Const::GR ? "hd" : chinfo.svid
120 | dev = devfn
121 | time = 9999
122 | udp = true
123 | port = @port
124 | addr = RemoteMonitor == false ? "localhost" : XServerName
125 |
126 | pt1 = Recpt1.new
127 | ret = pt1.makeCmd( ch,time,b25: true,sid: sid, dev: dev,udp: udp, port: port, addr: addr )
128 |
129 | return ret
130 | end
131 |
132 | #
133 | # recdvb を起動
134 | #
135 | def play( chinfo, tun )
136 |
137 | reccmd = makeRecCmd( chinfo, tun.devfn )
138 | mpvcmd = []
139 | if RemoteMonitor == true
140 | mpvcmd = %W( ssh -t -t #{XServerName} env DISPLAY=:0 )
141 | end
142 | title = sprintf("--title=\"%s : %s\"" ,tun.name, chinfo.chname )
143 | mpvcmd << Mpv_cmd
144 | mpvcmd += Mpv_opt + [ "--no-terminal",
145 | title,
146 | "--input-ipc-server=" + @ipc,
147 | @videoPort
148 | ]
149 | opt = {}
150 | if $arg.d < 2
151 | opt = { :out => @nullP, :err => @nullP }
152 | end
153 |
154 | dlog( mpvcmd.join(" "))
155 | @mpvPid = spawn( *mpvcmd, opt) # exec mpv
156 |
157 | dlog( reccmd.join(" "))
158 | @recPid = spawn( *reccmd, opt ) # exec recpt1/dvb
159 |
160 | Thread.new(@recPid) do |pid|
161 | begin
162 | Process.waitpid( pid )
163 | rescue
164 | end
165 | end
166 | end
167 |
168 | end
169 |
--------------------------------------------------------------------------------
/src/views/rsv_tbl.slim:
--------------------------------------------------------------------------------
1 |
2 | css:
3 | label { color: #000000}
4 |
5 | - require_relative 'rsv_tbl.rb'
6 | - pt = RsvTbl.new( @day, @time )
7 | - timeHref = pt.getTimeHref()
8 |
9 |
10 | .row
11 | .col
12 | .div
13 | == pt.dateSel()
14 | .col
15 | a.btn.waves-effect.waves-light href="#{timeHref[0]}" 朝
16 | .col
17 | a.btn.waves-effect.waves-light href="#{timeHref[1]}" 昼
18 | .col
19 | a.btn.waves-effect.waves-light href="#{timeHref[2]}" 夜
20 | .col
21 | a.btn.waves-effect.waves-light href="#{timeHref[3]}" 深夜
22 | .col
23 | a.btn.waves-effect.waves-light.btn-small.lime.darken-4 href="#{timeHref[4]}" -3H
24 | .col
25 | a.btn.waves-effect.waves-light.btn-small.lime.darken-4 href="#{timeHref[5]}" +3H
26 |
27 | .row
28 | div style="display:table;border-collapse: separate;border-spacing: 3px 0;"
29 | .dtr
30 | div.station style="width:2.3em;"
31 | a href="/cate_color" style="color:white;" 色
32 | == pt.yokojiku()
33 | .dtr
34 | .dtc
35 | == pt.tatejiku()
36 | == pt.prgTable()
37 |
38 | == pt.nowLine()
39 |
40 |
41 | div id="sample-dialog" title="詳細" style="display:none;"
42 | p
43 |
44 | == Commlib::include( SrcDir + "/views/move_top.html" )
45 | == Commlib::include( SrcDir + "/views/tooltip.js" )
46 |
47 | css:
48 | .inline-block {
49 | display: inline-block; /* インラインブロック要素にする */
50 | /* background-color:#66cc33; */
51 | padding: 0px;
52 | margin: 0px 2px 0px 2px;
53 | }
54 | div.dtr {
55 | display:table-row;
56 | }
57 |
58 | div.dtc {
59 | display:table-cell;
60 | }
61 |
62 | div.item {
63 | margin: 0px 0px 0px 0px;
64 | padding: 0px;
65 | width: 160px;
66 | opacity: 0.9;
67 | overflow:hidden;
68 | }
69 |
70 | div.time {
71 | height: 176px;
72 | margin: 4px;
73 | padding: 5px;
74 | color: white;
75 | background-color:#333;
76 | width: 2em;
77 | }
78 | div.station {
79 | width:160px;
80 | margin: 0px;
81 | padding: 5px;
82 | border: none;
83 | color: white;
84 | background-color:#333;
85 | display: table-cell;
86 | }
87 |
88 | .ui-dialog-buttonset {
89 | width: 100%;
90 | display: flex;
91 | display: -webkit-flex;
92 | text-align: center;
93 | justify-content: space-around;
94 | -webkit-justify-content: space-around;
95 | }
96 |
97 | javascript:
98 | $('.dropdown-trigger').dropdown();
99 |
100 | $(document).ready(function(){ $('.tabs').tabs(); });
101 |
102 | $(document).ready(function(){ $('select').formSelect(); });
103 |
104 | $(".item").hover(function() {
105 | $(this).css('border','solid #a23456 1px');
106 | }, function() {
107 | $(this).css('border','');
108 | });
109 |
110 |
111 | $(".item").click(function(event){
112 | var rid = $(this).attr('rid');
113 | //event.preventDefault();
114 | $("#sample-dialog").load('/rsv_list_D/' + rid );
115 | $("#sample-dialog").dialog({
116 | modal: true,
117 | maxWidth: 1200,
118 | width: 1000,
119 | buttons: { //ボタン
120 | "修正": function() {
121 | $.ajax({
122 | url: '/rsv_list/Mod/' + rid,
123 | type:"POST",
124 | data: $('form').serialize() + '¶m=1',
125 | async: true,
126 | timeout: 30000
127 | }).done( function(results) {
128 | location.reload();
129 | })
130 | },
131 | "削除": function() {
132 | $.ajax({
133 | url: '/rsv_list/Del/' + rid,
134 | type:"POST",
135 | async: true,
136 | timeout: 30000
137 | }).done( function(results) {
138 | location.reload();
139 | })
140 | },
141 | "閉じる": function() {
142 | $(this).dialog("close");
143 | }
144 | },
145 | open: function() {
146 | $( this ).siblings('.ui-dialog-buttonpane').find('button:eq(2)').focus();
147 | }
148 | });
149 | return false;
150 | });
151 |
--------------------------------------------------------------------------------
/src/views/layout.slim:
--------------------------------------------------------------------------------
1 | doctype html
2 |
3 | html
4 | head
5 | title = @title
6 | meta http-equiv='Content-Type' content='text/html' charset='utf-8'
7 | meta name= 'viewport' content='width=device-width, initial-scale=1.0'
8 |
9 | body
10 | - if Local_jquery == false
11 | script src='https://code.jquery.com/jquery-3.3.1.min.js'
12 | script src='https://code.jquery.com/ui/1.12.0/jquery-ui.min.js'
13 | link rel='stylesheet' href='http://code.jquery.com/ui/1.12.1/themes/pepper-grinder/jquery-ui.css'
14 |
15 | link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
16 | script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"
17 |
18 | - if Local_jquery == true
19 | script src='/jquery-3.3.1.min.js'
20 | script src='/jquery-ui.min.js'
21 | link rel='stylesheet' href='/jquery-ui.css'
22 | link rel='stylesheet' href='/materialize.min.css' media='screen,projection'
23 | script src='/materialize.min.js'
24 |
25 | link rel='stylesheet' href='/style.css'
26 | link rel='stylesheet' href='/overlaid.css'
27 | link rel='stylesheet' href='/nav.css'
28 |
29 | div.navbar-fixed
30 | nav
31 | div.nav-wrapper
32 | /! .yellow.darken-3
33 | a.right.brand-logo href='/'
34 | | raspirec
35 | a.sidenav-trigger.top-nav href="#!" data-target="mobile-demo"
36 | i.material-icons menu
37 | ul.left.hide-on-med-and-down#nav-mobile
38 | li
39 | a.dropdown-trigger href="#!" data-target="DD0" style="padding-left:3em;padding-right:3em;" 番組表
40 | li
41 | a.dropdown-trigger href="#!" data-target="DD1" style="padding-left:4em;padding-right:4em;" 予約
42 | li
43 | a.dropdown-trigger href="#!" data-target="DD2" style="padding-left:5em;padding-right:4em;" その他
44 |
45 | ul.dropdown-content id="DD0" style="padding-left:0em;padding-right:0em;"
46 | li
47 | a href='/prg_tbl/GR0' 地デジ 番組表
48 | - if ( BSCS_tuner_num + GBC_tuner_num ) > 0
49 | li
50 | a href='/prg_tbl/BS0' BS 番組表
51 | li
52 | a href='/prg_tbl/CS0' CS 番組表
53 | li
54 | a href='/fil_list' フィルター
55 | li
56 | a href='/ch_tbl_list' 放送局一覧
57 |
58 | ul.dropdown-content id="DD1" style="padding-left:0em;padding-right:0em;"
59 | li
60 | a href='/rsv_list' 予約一覧
61 | li
62 | a href='/rsv_tbl' 予約状況表
63 | li
64 | a href='/rsv_list_old' 録画済み一覧
65 | li
66 | a href='/aut_rsv_list' 自動予約
67 |
68 | ul.dropdown-content id="DD2" style="padding-left:0em;padding-right:0em;"
69 | li
70 | a href='/search' 番組検索
71 | li
72 | a href='/log_view' ログ
73 |
74 | - if MonitorFunc == true
75 | li
76 | a href='/monitor' hls モニター
77 |
78 | - if MPMonitor == true
79 | li
80 | a href='/mpv_mon' mpv モニター
81 | li
82 | a href='/control' コントロールパネル
83 |
84 | - if Debug == true
85 | li
86 | a href='/kill' restart httpd
87 | li
88 | a href='/kill2' restart http,timer
89 |
90 | ul.sidenav#mobile-demo
91 | li
92 | a href='/prg_tbl/GR0' 地デジ 番組表
93 | - if ( BSCS_tuner_num + GBC_tuner_num ) > 0
94 | li
95 | a href='/prg_tbl/BS0' BS 番組表
96 | li
97 | a href='/prg_tbl/CS0' CS 番組表
98 | li
99 | a href='/fil_list' フィルター
100 | li
101 | a href='/ch_tbl_list' 放送局一覧
102 | li
103 | a href='/rsv_list' 予約一覧
104 | li
105 | a href='/rsv_tbl' 予約状況表
106 | li
107 | a href='/rsv_list_old' 録画済み一覧
108 | li
109 | a href='/aut_rsv_list' 自動予約
110 | li
111 | a href='/search' 番組検索
112 |
113 | - if MonitorFunc == true
114 | li
115 | a href='/monitor' hls モニター
116 |
117 | - if MPMonitor == true
118 | li
119 | a href='/mpv_mon' mpv モニター
120 | li
121 | a href='/log_view' ログ
122 | li
123 | a href='/control' コントロールパネル
124 | p
125 |
126 | == yield
127 | javascript:
128 | $(".dropdown-trigger").dropdown();
129 | $(document).ready(function(){
130 | $('.sidenav').sidenav();
131 | });
132 |
133 |
--------------------------------------------------------------------------------
/src/tool/reserve_SL.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 予約関係データの save & load
6 | #
7 |
8 | require 'optparse'
9 | require 'yaml'
10 |
11 | base = File.dirname( $0 )
12 | [ ".", "..", "src",base + "/.."].each do |dir|
13 | if test(?f,dir + "/require.rb")
14 | $: << dir
15 | end
16 | end
17 | require 'require.rb'
18 |
19 |
20 | $opt = {
21 | :mode => :save, # save or load
22 | :fname => nil, # output/input file
23 | :o => false, # 過去の予約データ
24 | :f => false, # フィルター only
25 | :r => false, # 予約データ
26 | :a => false, # 自動予約データ
27 | :C => false, # データ削除
28 | :db => DbFname, # DB ファイル
29 | }
30 | $pname = File.basename( $0 )
31 | $version = "1.1.0"
32 |
33 | def usage()
34 | usageStr = <<"EOM"
35 | Usage: #{$pname} [Options1] [Options2...] file
36 |
37 | Options1:
38 | -s, --save save モード(デフォルト)
39 | -l, --load load モード
40 |
41 | Options2:
42 | -f, --filter フィルターデータ
43 | -a, --auto 自動予約データ
44 | -r, --reserv 未録画 予約データ
45 | -o, --old 録画済み 予約データ
46 | -A, --ALL 全部(-f,-a,-r,-o)
47 | -d, --db db_file DBファイルの指定(デフォルトは config.rb 中の DbFname )
48 | -C, --clearTable 読み込む前にデータ削除
49 |
50 | file 入力/出力 ファイル名
51 |
52 | #{$pname} ver #{$version}
53 | EOM
54 | puts usageStr
55 | exit 1
56 | end
57 |
58 | OptionParser.new do |opt|
59 | opt.on('-s','--save') { $opt[:mode] = :save }
60 | opt.on('-l','--load') { $opt[:mode] = :load }
61 | opt.on('-d f','--db f') {|v| $opt[:db] = v }
62 |
63 | opt.on('-f','--filter') { $opt[:f] = true }
64 | opt.on('-a','--auto') { $opt[:a] = true }
65 | opt.on('-r','--reserv') { $opt[:r] = true }
66 | opt.on('-o','--old') { $opt[:o] = true }
67 | opt.on('-A','--ALL') { $opt[:f] = $opt[:a] = $opt[:r] = $opt[:o] = true;}
68 | opt.on('-C','--clearTable') { $opt[:C] = true; }
69 | opt.parse!(ARGV)
70 | end
71 |
72 | if ARGV.size == 1
73 | $opt[:fname] = ARGV[0]
74 | else
75 | usage()
76 | end
77 |
78 | reserve = DBreserve.new
79 | filter = DBfilter.new
80 | data = {}
81 | now = Time.now.to_i
82 |
83 | if $opt[:mode] == :save
84 |
85 | data[:fil] ||= []
86 | data[:auto] ||= []
87 | data[:rsv] ||= []
88 | data[:old] ||= []
89 |
90 | DBaccess.new( $opt[:db] ).open do |db|
91 | row = filter.select( db )
92 | row.each do |r|
93 | if $opt[:f] == true and r[:type] == 0
94 | data[:fil] << r
95 | end
96 | if $opt[:a] == true and r[:type] == 1
97 | data[:auto] << r
98 | end
99 | end
100 |
101 | row = reserve.select( db )
102 | row.each do |r|
103 | if $opt[:o] == true and r[:stat] > 1
104 | data[:old] << r
105 | elsif $opt[:r] == true and r[:stat] < 2
106 | data[:rsv] << r
107 | end
108 | end
109 | end
110 |
111 | str = YAML.dump(data)
112 | if $opt[:fname] != nil and $opt[:fname] != "-"
113 | File.open( $opt[:fname], "w" ) do |fp|
114 | fp.puts(str)
115 | end
116 | else
117 | STDOUT.puts(str)
118 | end
119 |
120 | elsif $opt[:mode] == :load
121 |
122 | if $opt[:fname] != nil and $opt[:fname] != "-"
123 | File.open( $opt[:fname], "r" ) do |fp|
124 | str = fp.read()
125 | end
126 | else
127 | str = STDIN.read()
128 | end
129 | data = YamlWrap.load( str )
130 |
131 | DBaccess.new( $opt[:db] ).open( tran: true ) do |db|
132 |
133 | if $opt[:C] == true
134 | filter.select( db ).each do |r|
135 | if ( $opt[:a] == true and r[:type] == 1 ) or
136 | ( $opt[:f] == true and r[:type] == 0 )
137 | filter.delete( db, r[:id] )
138 | end
139 | end
140 | reserve.select( db ).each do |r|
141 | if ( $opt[:o] == true and r[:stat] > 1 ) or
142 | ( $opt[:r] == true and r[:stat] < 2 )
143 | reserve.delete( db, id: r[:id] )
144 | end
145 | end
146 | end
147 |
148 | if $opt[:f] == true
149 | data[:fil].each do |r|
150 | r[:id] = nil
151 | filter.insert( db, r )
152 | end
153 | end
154 | if $opt[:a] == true
155 | data[:auto].each do |r|
156 | r[:id] = nil
157 | filter.insert( db, r )
158 | end
159 | end
160 |
161 | if $opt[:o] == true
162 | data[:old].each do |r|
163 | r[:id] = nil
164 | reserve.insert( db, r )
165 | end
166 | end
167 | if $opt[:r] == true
168 | data[:rsv].each do |r|
169 | r[:id] = nil
170 | reserve.insert( db, r )
171 | end
172 | end
173 | end
174 |
175 | end
176 |
--------------------------------------------------------------------------------
/src/views/rsv_list_D.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 | #
5 | # 予約一覧 詳細ダイアログ
6 | #
7 |
8 |
9 | class ReservationListD
10 |
11 | def initialize( )
12 | end
13 |
14 | def getData( id )
15 | DBaccess.new().open do |db|
16 | programs = DBprograms.new()
17 | reserve = DBreserve.new
18 | data = reserve.select( db, id: id )
19 | if data.size > 0
20 | t1 = data.first
21 | t2 = programs.selectSP( db, evid: t1[:evid], chid: t1[:chid] )
22 | t3 = t2.first
23 | if t3 != nil
24 | t3.each_pair do |k,v|
25 | t1[k] = v
26 | end
27 | end
28 | return t1
29 | end
30 | end
31 | nil
32 | end
33 |
34 |
35 |
36 | def switch( name, init = 0, onoff = nil )
37 | on = "On"
38 | off = "Off"
39 | if onoff != nil
40 | on = onoff[0]
41 | off = onoff[1]
42 | end
43 |
44 | str = <
46 |
47 |
48 | #{off}
49 |
50 |
51 | #{on}
52 |
53 |
54 | EOS
55 | str
56 | end
57 |
58 | def checkbox( name, comment, val )
59 | checked = ""
60 | if val == 1
61 | checked = "checked=\"checked\""
62 | end
63 | str = <
65 |
66 | #{comment}
67 |
68 | EOS
69 | str
70 | end
71 |
72 | def inputText( name, comment: "", val: "", size: 60, dlist: nil )
73 | dl = ""
74 | if dlist != nil and dlist.class == Array
75 | tmp = []
76 | tmp << %q( )
77 | tmp << dlist.map{|item| sprintf(" ",item) }
78 | tmp << %q( )
79 | dl = tmp.join("\n")
80 | end
81 |
82 | str = <
85 |
86 | #{dl}
87 |
88 | EOS
89 | str
90 | end
91 |
92 | def printTable( rid )
93 | if ( t = getData( rid )) != nil
94 | printTable2( [t,nil] )
95 | end
96 | end
97 |
98 |
99 | #
100 | # 自動録画用のオプション項目の出力
101 | #
102 | def printOpt( dp, use_use: true )
103 |
104 | dirs = Commlib::datalist_dir()
105 |
106 | chk = %q(checked="checked")
107 | jitanchk = dp[:jitan] == 0 ? chk : ""
108 | usechk = ( dp[:stat] == RsvConst::NotUse or dp[:stat] == RsvConst::NotUseA ) ? chk : ""
109 | subdir = dp[:subdir]
110 |
111 | str1 = <
113 | 保存サブディレクトリ
114 |
115 |
116 | #{dirs}
117 |
118 |
119 |
120 |
121 |
122 | チューナーが競合した場合に録画時間の短縮を許可する。
123 |
124 | EOS
125 | str2 = <
127 |
128 |
129 | この予約を無効とする。
130 |
131 | EOS
132 | if use_use != true
133 | return str1
134 | else
135 | return str1 + str2
136 | end
137 | end
138 |
139 | #
140 | # データの表示
141 | #
142 | def printTable2( data, use_use = true )
143 | r = []
144 | t = data[0]
145 | t[:date] = Commlib::stet_to_s( t[:start], t[:end] ).join(" ")
146 |
147 | opt = printOpt( t, use_use: use_use )
148 | name = t[:name] == nil ? "" : t[:name]
149 | detail = t[:detail] == nil ? "" : t[:detail]
150 |
151 | [["放送局名", name + " " + "(#{t[:chid]})" ],
152 | ["番組名", t[:title] + " " + "(evid=#{t[:evid]})" ],
153 | ["内容", detail ],
154 | ["録画時間", t[:date] ],
155 | ["種別", t[:type] == 0 ? "手動予約" : "自動予約"],
156 | ["オプション", opt ],
157 | ].each do |tmp|
158 | r << printTable3(tmp[0], tmp[1])
159 | end
160 |
161 | if t[:stat] == RsvConst::RecNow
162 | r << Commlib::print_hidden( id: "recFlag", name: "RecNow", val: "1" )
163 | end
164 | r.join("\n")
165 | end
166 |
167 | def printTable3( title, val)
168 | sprintf(%Q{ %s %s },title, val)
169 | end
170 |
171 | end
172 |
173 | if File.basename($0) == "rsv_list_D.rb"
174 |
175 | [ ".", ".." ].each do |dir|
176 | if test(?f,dir + "/require.rb")
177 | $: << dir
178 | end
179 | end
180 | require 'require.rb'
181 |
182 | rt = ReservationListD.new()
183 | rt.printTable( 4 )
184 |
185 | exit
186 |
187 | case ARGV[0]
188 | when "1"
189 | puts( rt.printTable( 1 ) )
190 | end
191 |
192 | end
193 |
--------------------------------------------------------------------------------
/src/db/base.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | module Base
6 |
7 | #
8 | # select文の生成
9 | #
10 | def makeSelectSql(para, tname,
11 | id: nil,
12 | pid: nil,
13 | type: nil,
14 | svid: nil,
15 | evid: nil,
16 | chid: nil,
17 | order: nil,
18 | name: nil,
19 | tstart: nil,
20 | tend: nil,
21 | limit: nil,
22 | keyid: nil,
23 | updatetime: nil,
24 | skip: nil,
25 | stat: nil,
26 | recpt1pid: nil
27 | )
28 | where = []
29 | args = []
30 | sql = "select " + para.join(",") + " from #{tname} "
31 | if id != nil
32 | where << " id = ? "
33 | args << id
34 | elsif pid != nil
35 | where << " pid = ? "
36 | args << pid
37 | elsif type != nil
38 | where << " type = ? "
39 | args << type
40 | elsif name != nil
41 | where << " name = ? "
42 | args << name
43 | elsif chid != nil and evid != nil
44 | where << " chid = ? and evid = ? "
45 | args += [ chid, evid ]
46 | elsif chid != nil
47 | where << " chid = ? "
48 | args << chid
49 | elsif keyid != nil
50 | where << " keyid = ? "
51 | args << keyid
52 | elsif updatetime != nil
53 | where << " updatetime < ? "
54 | args << updatetime
55 | elsif evid != nil
56 | where << " evid = ? "
57 | args << evid
58 | # else # デバック用
59 | # if tstart == nil and tend == nil and stat == nil
60 | # DBlog::sto( "Error: makeSelectSql()" )
61 | # if $debug == true
62 | # DBlog::sto( caller(0)[0..9].join("\n" ))
63 | # end
64 | # end
65 | end
66 |
67 | # ## デバック用
68 | # found = []
69 | # %W( id pid type name chid keyid updatetime evid tstart tend ).each do |n|
70 | # r = if eval( "defined? #{n}" )
71 | # eval( n )
72 | # else
73 | # nil
74 | # end
75 | # found << n if r != nil
76 | # end
77 | # if found.size > 1
78 | # tmp = found.join(",")
79 | # if tmp != "chid,evid" and
80 | # tmp != "chid,evid,tstart" and
81 | # tmp != "id,type" and
82 | # tmp = sprintf("@@@: makeSelectSql %d %s\n%s\n",found.size, found.join(","),caller(0).join("\n"))
83 | # DBlog::sto( tmp )
84 | # end
85 | # end
86 |
87 | if tstart != nil and tend != nil
88 | where << " ( ? < end and start < ? ) " # 番組表の top と Bottom
89 | args += [ tstart, tend ]
90 | elsif tstart == nil and tend != nil
91 | where << " ? < end "
92 | args << tend
93 | elsif tstart != nil and tend == nil
94 | where << " ? < start "
95 | args << tstart
96 | end
97 |
98 |
99 | if stat != nil
100 | if stat.class == Array
101 | where << " (" + Array.new( stat.size, "stat = ? " ).join(" or ") + ") "
102 | args += stat
103 | else
104 | where << " stat = ? "
105 | args << stat
106 | end
107 | end
108 |
109 | if recpt1pid != nil
110 | where << " recpt1pid = ? "
111 | args << recpt1pid
112 | end
113 |
114 | if skip != nil
115 | where << " skip = ? "
116 | args << skip
117 | end
118 |
119 | if where.size > 0
120 | sql += " where " + where.join(" and ")
121 | end
122 |
123 | if order == nil
124 | sql += " order by id "
125 | else
126 | sql += order
127 | end
128 | if limit != nil
129 | sql += " limit ? "
130 | args << limit
131 | end
132 | sql += ";"
133 |
134 | [ sql, args ]
135 | end
136 |
137 | #
138 | # insert文の生成
139 | #
140 | def makeInsSql( list, para, tname, data )
141 | args = []
142 | list.each_pair do |k,v|
143 | args << data[ k ]
144 | end
145 | sql = "insert into #{tname} ( " + para.join(",") + ") values ("
146 | sql += (Array[ "?"] * para.size).join(",")
147 | sql += " )"
148 | [ sql, args ]
149 | end
150 |
151 | #
152 | # update 文の生成
153 | #
154 | def makeUpdSql( list, tname, data, id )
155 | args = []
156 | para = []
157 | list.each_pair do |k,v|
158 | next if k == :id
159 | args << data[ k ]
160 | para << "#{v} = ?"
161 | end
162 | sql = "update #{tname} set " + para.join(",")
163 | sql += " where id = #{id} "
164 | [ sql, args ]
165 | end
166 |
167 | def row2hash( list, row )
168 | r = []
169 | if row != nil
170 | row.each do |tmp|
171 | h = {}
172 | list.each_key { |k| h[k] = tmp.shift }
173 | r << h
174 | end
175 | end
176 | r
177 | end
178 |
179 | #
180 | # 最後に insert した rowid を返す。
181 | #
182 | def last_insert_rowid(db)
183 | sql = "select last_insert_rowid();"
184 | r = db.execute( sql )
185 | r[0][0]
186 | end
187 |
188 | end
189 |
--------------------------------------------------------------------------------
/doc/config.md:
--------------------------------------------------------------------------------
1 |
2 | ## config.rb の説明
3 |
4 | * config ファイルは、次の優先度で読み込まれます。
5 |
6 | * 環境変数 RASPIREC_CONF で指定したファイル(拡張子は .rb)
7 | * $HOME/.config/raspirec/config.rb
8 |
9 |
10 | * 配列の定義で、`%w( 1 2 3 )` は `[ "1", "2", "3" ]` と等価です。
11 |
12 | ## 各パラメータの意味は次の通りです。
13 |
14 | #### httpd
15 |
16 | | 定数名 | 説明 |
17 | |--------------------|------|
18 | |Http_port|httpd のポート番号を指定する。 WEBインターフェースには http://XXX.YYY.ZZZ:${Http_port}/ でアクセスする。|
19 |
20 | #### ディレクトリ、ファイル関係
21 |
22 | | 定数名 | 説明 |
23 | |--------------------|------|
24 | |Recpt1_cmd|recpt1 コマンドの path を指定する。|
25 | |Recpt1_opt|recpt1 コマンドのオプションを指定する。b25 デコードをする場合は --b25 を指定する。|
26 | |Epgdump|epgdump コマンドの path を指定する。|
27 | |BaseDir|raspirec がインストールされているディレクトリを設定する。( raspirec.rb があるディレクトリ )|
28 | |DataDir|データベースや録画したファイルの置き場所を指定する。|
29 |
30 | #### 録画タイミング関係
31 |
32 | | 定数名 | 説明 |
33 | |--------------------|------|
34 | |Start_margin|番組開始前の録画マージンを指定(秒)。録画は PC の時計を基準に、開始します。|
35 | |After_margin|番組終了後の録画マージンを指定(秒)。録画は PC の時計を基準に、終了します。|
36 | |Gap_time|録画が連続し、前番組が時短になった場合の、前番組録画終了から次番組開始間隔を指定(秒)。 前番組は Start_margin + Gap_time の秒数だけ録画時間が削られます。 TVチューナー依存ですが、あまり短いと動作が不安定になります。|
37 |
38 | #### チューナー関係
39 |
40 | | 定数名 | 説明 |
41 | |--------------------|------|
42 | |GR_tuner_num|地デジチュナー数|
43 | |BSCS_tuner_num|BSCSチュナー数|
44 | |GBC_tuner_num|地デジ/BS/CS チューナー数|
45 | |Total_tuner_limit|トータルチュナー数制限を掛ける場合に指定する。使用しない場合は false|
46 |
47 | #### EPG関係
48 |
49 | | 定数名 | 説明 |
50 | |--------------------|------|
51 | |GR_EPG_channel|地デジ EPG 受信局を設定する。 東京スカイツリーならば%w( 27 26 25 24 22 23 21 16 ) になります。 参考 |
52 | |BS_EPG_channel|BS EPG 受信局を設定する。BSを受信しない場合は空にする。 通常の番組情報は、どれか 1局だけ受信すれば十分だが、詳細情報を取得するには、その情報を取りたい局を指定する必要がある。|
53 | |CS_EPG_channel|CS EPG 受信局を設定する。CSを受信しない場合は空にする。|
54 | |GR_EpgRsvTime|地デジ EPG受信時間 (秒)|
55 | |BS_EpgRsvTime|BS EPG受信時間 (秒)|
56 | |CS_EpgRsvTime|CS EPG受信時間 (秒)|
57 | |EPGperiod|EPG 取得周期 (H)|
58 | |EpgBanTime|EPG の取得禁止時間帯の指定。(24H制 時間単位) 1時から5時までを禁止したい場合は、[ 1,2,3,4,5 ] と指定する。使用しない場合は nil|
59 | |EPG_tuner_limit|EPG取得時の使用チュナー数に制限を掛ける場合に指定する。使用しない場合は false|
60 |
61 |
62 | #### 自動録画延長機能
63 | | 定数名 | 説明 |
64 | |--------------------|------|
65 | |AutoRecExt | 自動録画延長機能 true で有効。 詳細は こちら を参照 |
66 | |ARE_sampling_time | 番組終了の n 秒前に EPG 採取 |
67 | |ARE_epgdump_opt | 最後尾切り出しの epgdump のオプション |
68 |
69 |
70 | #### ダイアログのオプション初期値
71 |
72 | | 定数名 | 説明 |
73 | |--------------------|------|
74 | |D_FreeOnly|無料放送のみ|
75 | |D_dedupe|重複予約は無効化する|
76 | |D_jitan|チューナー不足の場合に時短を許可|
77 |
78 | #### TSファイル転送
79 |
80 | | 定数名 | 説明 |
81 | |--------------------|------|
82 | |TSFT|true=転送機能有効 true以外=無効 この機能を使うには、送り先のホストに対して,パスワードなしでssh,scpアクセス可能なように設定されていることが必要です。|
83 | |TSFT_host|送り先 ホスト名|
84 | |TSFT_user|送り先 login名|
85 | |TSFT_toDir|送り先Dir|
86 | |TSFT_rate|想定転送速度 ( Mbyte/秒 ) この数字を使って、空き時間に転送する/しないを判断します。|
87 |
88 | #### HLS モニタ機能
89 |
90 | | 定数名 | 説明 |
91 | |--------------------|------|
92 | |MonitorFunc|true=モニタ機能を有効, false=無効。 無効の場合は、下記のパラメータは無視される。設定方法は こちら を参照して下さい。|
93 | |StreamDir|ストリーム出力先ディレクトリ|
94 | |MonitorWidth|モニタ画面の横幅|
95 | |HlsConvCmd|HLS変換コマンド|
96 |
97 | #### mpv モニタ機能
98 |
99 | | 定数名 | 説明 |
100 | |--------------------|------|
101 | |MPMonitor|true = mpv モニタ機能を有効、false = 無効 無効の場合は、下記のパラメータは無視される。設定方法は こちら を参照して下さい。|
102 | |DevAutoDetection| DeviceList_GR, DeviceList_BSCS, DeviceList_GBC を自動設定する。true = 有効。|
103 | |DeviceList_GR|地デジのデバイスファイルを指定する。|
104 | |DeviceList_BSCS|BS,CS のデバイスファイルを指定する。|
105 | |DeviceList_GBC | 地デジ,BS,CS のデバイスファイルを指定する。|
106 | |Mpv_cmd|mpv の実行ファイル名(絶対パス)|
107 | |Mpv_opt|mpv の引数を指定する。
108 | |RemoteMonitor|表示するマシンが別の場合 ture, 同一の場合に false|
109 | |UDPbasePort|使用する UDP のポート番号、デバイスの数だけプラスされる。|
110 | |XServerName|RemoteMonitor が true の場合に、mpvを実行、表示するマシンのマシン名を設定する。|
111 | |RecHostName|RemoteMonitor が true の場合に、チューナー(raspirecが実行されている)のマシン名を設定する。|
112 | |Lsof_cmd|lsof コマンドへのパス。Ubuntu では /usr/bin/lsof |
113 | |Browser_cmd| 番組表を表示するブラウザの実行ファイル名(絶対パス) |
114 | |RaspirecTV_font|フォント指定|
115 | |RaspirecTV_GEO|座標指定 ( WxH+X+Y or X+Y )|
116 | |RaspirecTV_SOCAT|socat の実行ファイル名(絶対パス)(RemoteMonitor == true の時のみ)|
117 |
118 | #### その他
119 |
120 | | 定数名 | 説明 |
121 | |--------------------|------|
122 | |TSnameFormat|TSファイル名の生成ルール。詳細は 「コントロールパネル/補足説明」を参照して下さい。|
123 | |LogSaveDay|ログの保持期間(日)|
124 | |RsvHisSaveDay|録画済み記録の保持期間(日)|
125 | |DiskKeepPercent|録画したTSファイルを古い順に削除して指定したDisk容量(%)を確保する。(指定するのは空き容量) 機能を無効にする場合は false を指定する。|
126 | |Local_jquery|オフライン環境で動作させる為に jquery, materialize のライブラリをローカルにコピーした場合に、true にする。 詳細は doc/jquery_local.md を参照の事。 通常は false|
127 | |StationPage|番組表で、1ページ当たりの放送局数 (個) の初期値|
128 | |Debug|ture で Debug モード。ログファイルを出力するようになる。オプションで、 -d を指定するのと同じ。|
129 | |Debug_mem|ture で メモリの消費量をモニタするようになる。|
130 | |TitleRegex|「自動予約」ボタンを押して番組検索に遷移した時に、番組タイトルから「題名」を生成する為の、余計な文字を削除する正規表現の配列|
131 | |SearchStringRegex|「自動予約」ボタンを押して番組検索に遷移した時に、番組タイトルから「検索文字列」を生成する為の、余計な文字を削除する正規表現の配列|
132 | |EpgPatchEnable|EPGPatch機能を有効にする。 false = 無効。デフォルトは有効|
--------------------------------------------------------------------------------
/raspirec.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/ruby
2 | # coding: utf-8
3 | #
4 |
5 |
6 | #
7 | # main
8 | #
9 | require 'rubygems'
10 | require 'optparse'
11 | require 'sqlite3'
12 |
13 | base = File.dirname( $0 )
14 | $: << base + "/src"
15 |
16 | require 'require.rb'
17 |
18 |
19 | $opt = {
20 | :f => false, # forground
21 | :lc => false, # log clear
22 | :dc => false, # db clear
23 | :conf => nil, # config file 指定
24 | }
25 | $debug = Debug
26 | LoopMax = 99
27 |
28 | OptionParser.new do |opt|
29 | opt.on('-d') {|v| $debug = true }
30 | opt.on('-f') {|v| $opt[:f] = !$opt[:f] }
31 | opt.on('--kill') { Control.new.stop(); exit } # kill process
32 | opt.on('--lc') {|v| $opt[:lc] = true } # log clear
33 | opt.on('--dc') {|v| $opt[:dc] = true } # db clear
34 | opt.on('--config FILE') {|v| $opt[:conf] = v } # config
35 | opt.parse!(ARGV)
36 | end
37 |
38 | #
39 | # 終了処理
40 | #
41 | def endParoc()
42 | $endParoc = true
43 | count = 0
44 | [ $timer_pid, $httpd_pid ].each do |pid|
45 | begin
46 | DBlog::sto( "main::endParoc() kill #{pid}" )
47 | Process.kill(:TERM, pid );
48 | count += 1
49 | rescue
50 | end
51 | end
52 | count.times do |n|
53 | sleep(1)
54 | putc(".")
55 | STDOUT.flush
56 | end
57 | exit
58 | end
59 |
60 |
61 | #
62 | # デーモン化
63 | #
64 | def daemonStart( )
65 | if fork # 親
66 | exit!(0)
67 | else # 子
68 | Process::setsid
69 | if fork # 親
70 | exit!(0)
71 | else # 子
72 | Dir::chdir("/")
73 | File::umask(0)
74 |
75 | STDIN.reopen("/dev/null")
76 | if $debug == true
77 | STDOUT.reopen( StdoutM, "w")
78 | STDERR.reopen( StderrM, "w")
79 | else
80 | STDOUT.reopen("/dev/null", "w")
81 | STDERR.reopen("/dev/null", "w")
82 | end
83 | yield if block_given?
84 | end
85 | end
86 | end
87 |
88 | $timer_main = "#{SrcDir}/timer_main.rb"
89 | $httpd_main = "#{SrcDir}/httpd_main.rb"
90 |
91 | #
92 | # main loop
93 | #
94 | def mainLoop()
95 | DBlog::sto("main loop start #{Commlib::getVer()}")
96 |
97 | File.open( PidFile, "w") do |fp|
98 | fp.puts( Process.pid )
99 | end
100 |
101 | if $opt[:conf] != nil
102 | ENV["RASPIREC_CONF_OPT"] = $opt[:conf]
103 | end
104 |
105 | if Object.const_defined?(:DevAutoDetection) == true and
106 | DevAutoDetection == true
107 | DeviceChk.new.run
108 | end
109 |
110 | pids = []
111 | pids << Thread.new do
112 | tcount = 0
113 | while true
114 | $timer_pid = fork do
115 | args = [ $timer_main ]
116 | args << "--debug" if $debug == true
117 | exec("ruby", *args )
118 | end
119 | Process.waitpid( $timer_pid )
120 | DBlog::warn(nil, "timer_main end")
121 | sleep 5
122 | break if $endParoc == true
123 | break if tcount > LoopMax
124 | tcount += 1
125 | end
126 | end
127 |
128 | pids << Thread.new do
129 | hcount = 0
130 | while true
131 | $httpd_pid = fork do
132 | args = [ $httpd_main, "-o", "0.0.0.0"]
133 | if Http_port != nil and Http_port > 0
134 | args += [ "-p", Http_port.to_s ]
135 | end
136 | args += %w( -- --debug ) if $debug == true
137 | exec("ruby", *args )
138 | end
139 | Process.waitpid( $httpd_pid )
140 | DBlog::warn(nil, "httpd_main end")
141 | sleep 5
142 | break if $endParoc == true
143 | break if hcount > LoopMax
144 | hcount += 1
145 | end
146 | end
147 |
148 | if $opt[:tail] == true
149 | pids << Thread.new do
150 | tailLog()
151 | end
152 | end
153 |
154 | pids.each {|t| t.join}
155 |
156 | end
157 |
158 |
159 | #
160 | # 初期化
161 | #
162 | rmlist = []
163 | if $opt[:lc] == true
164 | rmlist += [
165 | EPGLockFN,
166 | DbupdateFN,
167 | LogFname,
168 | StdoutM,
169 | StderrM,
170 | StdoutH,
171 | StderrH,
172 | StdoutT,
173 | StderrT
174 | ]
175 | end
176 |
177 | if $opt[:dc] == true
178 | rmlist << DbFname
179 | end
180 |
181 | if rmlist.size > 0
182 | rmlist.each do |fn|
183 | if test( ?f, fn )
184 | pp "unlink(#{fn})"
185 | File.unlink( fn )
186 | File.open( fn, "w" ) {|fp| } unless fn == DbFname
187 | end
188 | end
189 | exit
190 | end
191 |
192 |
193 |
194 |
195 | #
196 | # 初期化
197 | #
198 | Commlib::makeSubDir()
199 |
200 | File.open( MainLockFN, File::RDWR|File::CREAT, 0644) do |fl|
201 | if fl.flock(File::LOCK_EX|File::LOCK_NB) == false
202 | puts("raspirec locked\n")
203 | exit
204 | end
205 |
206 | [ EPGLockFN ].each do |fn|
207 | if test(?f, fn )
208 | File.unlink( fn )
209 | end
210 | end
211 | $rec_pid = {}
212 | $endParoc = false
213 |
214 | setTrap()
215 |
216 | [ $timer_main , $httpd_main].each do |fname|
217 | unless test( ?f, fname )
218 | puts("Error: #{fname} not found")
219 | exit
220 | end
221 | end
222 |
223 | if $opt[:f] == false
224 | daemonStart{ mainLoop() }
225 | else
226 | mainLoop()
227 | end
228 | end
229 |
230 |
231 |
232 |
--------------------------------------------------------------------------------