Posted by & filed under 未分類.

ツイッターで流れてきた記事がおもしろかった。

筆者の熱い文章からクソみたいなフォーマットのデータを日々扱っておられる苦労を感じられ、心から「おつかれさまです」という言葉をかけたいと思った。

ということで、データとしての整形の問題はセルの結合のみに限られないけれども、せめて結合だけでも解消したいというケースに使えるVBAを書いたので公開する。

以下は京都府から取得可能なオープンデータでアレなのを探してきたものである。( https://data.city.kyoto.lg.jp/node/92717 より 「平成14年4月7日執行京都府知事選挙 投票者数調」)

さすがにオープンデータというだけあってちゃんと行指向にデータが整理されているものが多く、探すのに苦労した。

ちなみに国勢調査の集計データは手作りアウトラインとなっていてアレ度が高く対象からは外した。

実行前

実行後

こんなのが役に立つようなことがなくなればいいですね。

Posted by & filed under 未分類.

FireTV 買ったんですよ。 ↓これです。

現行版はこれですね。

これまで raspberry pi B 2nd gen に kodi 入れて、TCPのNFSでは遅いからUDPのNFSにしてとかVRAMの量を調整したりとかシコシコ頑張っていたのですが(このページとか)、そんなことで頑張るよりも、

  1. Fire TVボックスを買う
  2. 開発モードを有効にしてKODIを入れる
  3. m2ts置き場のNASでDLNAをオンにする

これだけで超快適なビデオ観賞環境が整います。

WiFi も 5GHz が追加投資なく有効になるし、HDMI CECもバッチリ使える。 YouTubeにもリッチクライアントがある。
4K30fpsの動画もちょっとしんどいけど再生できる。m2tsの再生もめちゃハヤ。(kodiの設定で垂直同期を待つ設定を有効にしましょう。動画がカクつきます。)

raspberry pi で既に消耗していて、かつチューニングが面倒だ(raspberry pi でやること自体が目的ではなくなってしまった)と思っているひとはもう乗り換えてしまいましょう。おすすめです。


参考: 新型Fire TV StickにKodiをインストールして超省電力DLNAクライアントにしよう!: ゆるガジェCHANNEL

Posted by & filed under 未分類.

はじめに

実家を出てから、Wimax(初期)、Softbank airと乗り換えながらも10年近く無線インターネットを使いつづけてきたのだが、サービスが成熟してくると旧回線を締め出したり値上げをしたりと、(無線だから仕方がないにせよ)モッサリ速度でありながらユーザに冷たく当たってくる事業者に嫌気がさしてきた。

スマホもmioに移行が済んでることだし、一念発起してmioひかりを引くことにした。

折角の光だからDS-Liteでサクサクインタネットをしたい、ルータに高いお金払うのもちょっと気が引ける、ということで中華マルチポートPCで自作ルータにして動かしてやろうと…

思ったのが失敗だった。

しょうもない見落としが原因でむちゃくちゃハマったり構築に色々と苦労をしたので、ここに構築手順を残しておく。

使用機材

Qotomという中華メーカーのファンレスNUCを使った。

AliExpress.com Product – VGA Mini PC with 4 LAN port apply to Firewall and Router, Bay Trail j1900 low power consumption 10W, X86 Mini PC 12V

筆者の構成は 4GB mem 32G SSD。Wifiは技適のなさそうなモジュールだったので止めておいた。

その他、NETGEARの無線AP。

設定内容

当該機種には独立したGbEポートが4個搭載されているが、それら全てをブリッジに接続し「ツーツー(ハブ化)」の状態にした上で、firewallの設定で外に行って欲しくないものや入ってきて欲しくないものをブロックする格好にした。

外と内を区別するため、enp1s0をwan側ポートに定め、enp[2-4]s0はlanポートとした。
(firewallの設定においてこの決めごとを使う)

家の中でマルチキャストするサービスがいくつか動いているものについてはv4アドレスにのみアナウンスすることでパケットを外に漏らさないようにする。

使うソフトウェアは以下。

  • Archlinux(ベースシステム)
  • systemd-networkd(インストール済み)
  • dnsmasq
  • nftables
  • avahi (mdns利用に必要)
  • nss-mdns (mdns利用に必要)

インターネット接続の有効化 (IPv6)

とりあえずブリッジを作成し、IPv6でインターネットへ接続する設定をする。

/etc/systemd/network/ 以下に以下のファイルを作成する

  • bridge.netdev → ブリッジの定義
  • bridge.network → ブリッジのネットワーク設定(インターネットへの接続を担う)
  • all_port.network → 物理ポートのネットワーク設定(ブリッジとつなげるのみ)

 

bridge.netdev

[NetDev]
Name=br0
Kind=bridge

bridge.network

[Match]
Name=br0

all_port.network

[Match]
Name=enp[1-4]s0

[Network]
Bridge=br0

/etc/resolv.conf にプロバイダが提供するDNSを記述する。

nameserver 2001:XXXX:XXXX:XXXX
nameserver 2001:XXXX:XXXX:XXXX

この時点で systemctl restart systemd-networkd を実行、 ip a にてインタフェースの状態を確認すると以下のような表示になっているはずである。

[root@shortpath network]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP group default qlen 1000
    link/ether 00:0e:c4:ff:ff:ff brd ff:ff:ff:ff:ff:ff
3: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ae:d8:52:ff:ff:ff brd ff:ff:ff:ff:ff:ff
    inet6 2409:250:9160:XXXX:XXXX:XXXX:XXXX:XXXX/64 scope global mngtmpaddr noprefixroute dynamic
       valid_lft 2591603sec preferred_lft 604403sec
    inet6 fe80::acd8:XXXX:XXXX:XXXX/64 scope link
       valid_lft forever preferred_lft forever
4: enp2s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq master br0 state DOWN group default qlen 1000
    link/ether 00:0e:c4:ff:ff:ff brd ff:ff:ff:ff:ff:ff
5: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq master br0 state DOWN group default qlen 1000
    link/ether 00:0e:c4:ff:ff:ff brd ff:ff:ff:ff:ff:ff
6: enp4s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq master br0 state DOWN group default qlen 1000
    link/ether 00:0e:c4:ff:ff:ff brd ff:ff:ff:ff:ff:ff

また、www.google.com (IPv6 readyのホスト) にcurlを実行すると、結果が表示されるはずである。

[root@shortpath ~]# curl -v www.google.com
* Rebuilt URL to: www.google.com/
*   Trying 2404:6800:4004:81a::2004...
* TCP_NODELAY set
* Connected to www.google.com (2404:6800:4004:81a::2004) port 80 (#0)
> GET / HTTP/1.1
> Host: www.google.com
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Cache-Control: private
< Content-Type: text/html; charset=UTF-8
< Referrer-Policy: no-referrer
< Location: http://www.google.co.jp/?gfe_rd=cr&ei=1LuZWbjuHYPf8AfX9oXQBw
< Content-Length: 261
< Date: Sun, 20 Aug 2017 16:41:56 GMT
<
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.jp/?gfe_rd=cr&amp;ei=1LuZWbjuHYPf8AfX9oXQBw">here</A>.
</BODY></HTML>
* Connection #0 to host www.google.com left intact

この時点では、IPv4ホストへの接続はできない。

トンネルの作成 (IPv4を利用可に)

gw.transix.jp (IIJでのIPv4 over IPv6のゲートウェイサーバ) にip4ip6トンネルを作成し、IPv4ホストへの接続を可能にする。

以下ファイルを作成

  • transix.netdev → gw.transix.jpへのトンネルの定義
  • transix.network → トンネルの有効化と、デフォルトゲートウェイ化する設定
  • bridge.network → トンネルとの関連付けとIPFowardを設定

transix.netdev

[NetDev]
Name=transix
Kind=ip6tnl

[Tunnel]
Local=2409:250:9160:XXXX:XXXX:XXXX:XXXX:XXXX # br0に割り当てられたIPv6アドレス
Remote=2404:8e01::feed:100 # gw.transix.jp へのアドレス(IIJてくろぐより)
Mode=ipip6

transix.network

[Match]
Name=transix

[Route]
Destination=0.0.0.0/0

bridge.network

[Match]
Name=br0

# 以下を追加
[Network]
Address=192.168.0.1
Tunnel=transix
IPForward=ipv4

再度 systemctl restart systemd-networkd でネットワーク接続を再構築する。 ip a の表示は以下のように変わる。

4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ae:d8:52:ff:ff:ff brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/24 brd 192.168.0.255 scope global br0
       valid_lft forever preferred_lft forever
    inet6 2409:250:9160:XXXX:XXXX:XXXX:XXXX:XXXX/64 scope global mngtmpaddr noprefixroute dynamic
       valid_lft 2591751sec preferred_lft 604551sec
    inet6 fe80::acd8:XXXX:XXXX:XXXX/64 scope link
       valid_lft forever preferred_lft forever
5: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd ::
6: transix@br0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1452 qdisc noqueue state UNKNOWN group default qlen 1000
    link/tunnel6 2409:250:9160:XXXX:XXXX:XXXX:XXXX:XXXX peer 2404:8e01::feed:100
    inet6 fe80::28b5:4dff:fe04:b99d/64 scope link
       valid_lft forever preferred_lft forever

ここで cookpad.com (IPv4 onlyのホスト)にcurlを試す。

[root@shortpath ~]# curl -v cookpad.com
* Rebuilt URL to: cookpad.com/
*   Trying 52.197.89.148...
* TCP_NODELAY set
* Connected to cookpad.com (52.197.89.148) port 80 (#0)
> GET / HTTP/1.1
> Host: cookpad.com
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Date: Sun, 20 Aug 2017 17:00:16 GMT
< Content-Type: text/html; charset=iso-8859-1
< Content-Length: 228
< Connection: keep-alive
< Server: Apache
< Location: https://cookpad.com/
<
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://cookpad.com/">here</a>.</p>
</body></html>
* Connection #0 to host cookpad.com left intact

これでルータのインターネット接続の設定は完了となる。

参考にしたてくろぐブログでは物理インタフェースにIPv4を設定している。
筆者は「結線されていない」物理インタフェースにIPv4を振っていたため通信ができず、1週間悩んだ。
結線されていない状態でもipコマンドのTXやRXのカウントが増え、動いているように見えてしまうためハマるので注意。

なお、systemd-networkdの IPFoward=ipv4 の設定をすることでカーネルパラメータ net.ipv4.ip_forward が自動的に 1 に変更される。
ならびにトンネルの設定により自動的に ip6_tunnel カーネルモジュールがロードされる。

dnsmasqの設定

次に、IPv4ルータとして機能させるためdnsmasqをインストール、構成する。

前の手順で利用していた resolv.conf を /etc/resolv.dnsmasq.conf にコピーする。

/etc/dnsmasq.conf に以下の2行を記載する。

# リースの設定
dhcp-range=192.168.0.17,192.168.0.128,255.255.255.0,12h
# dnsmasqにDNSプロキシをさせる
resolv-file=/etc/resolv.dnsmasq.conf

systemctl enable dnsmasq
systemctl start dnsmasq

/etc/resolv.confの内容を localhostを見るように変更する

nameserver 127.0.0.1
nameserver ::1

avahi-daemonの設定

/etc/avahi/avahi-daemon.confを開き、enable-ipv6をnoに設定する。

ちなみに下の方に publish-aaaa-on-ipv4=yes といった形で「ipv4のアナウンスでipv6アドレスを配るよ」という設定があるがこれは変更しなくても良いだろう。

systemctl enable avahi-daemon
systemctl start avahi-daemon

nftablesの設定

iptablesの代わりにnftablesというツールでネットフィルタの設定をした。

ブリッジ層でwan側ポートに出入りするパケットにフラグを付け、lanとwanのインタフェース間でフォワードがされるパケットのフラグとプロトコルを見てフィルタするという構造にした。

フラグを付けた理由は、ブリッジ層ではL2の情報しか参照することができず、かつフォワード時にはどのインタフェースから入ったかの情報が参照できないためである。

ブリッジの通信でフォワードを制御したいときは、br_filterカーネルモジュールが必要になる場合がある。
sysctlで net.bridge.bridge-nfではじまる設定が存在すれば機能はロードされているので、br_filterのロードの設定は不要。

/etc/modules-load.d に br_filter.confを作成し、以下の br_filterと記載する。

br_filterがロードされると、以下の設定が利用可能になる。

net.bridge.bridge-nf-call-arptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-filter-pppoe-tagged = 0
net.bridge.bridge-nf-filter-vlan-tagged = 0
net.bridge.bridge-nf-pass-vlan-input-dev = 0

nf-call-*tables が有効になっている必要がある。

この状態で、nftables.confを書く。

/etc/nftables.conf

#!/usr/bin/nft -f
# ipv4/ipv6 Simple & Safe Firewall
# you can find examples in /usr/share/nftables/

# ブリッジのテーブル"filter"
table bridge filter {
  chain forwarding {
    type filter hook forward priority -100; policy accept;
    # mark wan port
    iifname enp1s0 mark set 0x100 accept
    oifname enp1s0 mark set 0x101 accept
  }
}

# IPv4のテーブル"filter"
table filter {
  # ルータ自身への入力
  chain incoming {
    type filter hook input priority 0; policy accept;
    ct state {established, related} accept
    ct state invalid drop
    oifname lo accept
    mark 0x100 drop # 外からの入力はdrop
  }

  chain fowarding {
    type filter hook forward priority 0; policy accept;
    # lan側のインタフェース間ではforwardしたいため、wanインタフェース向けのみdrop
    mark 0x101 drop
    mark 0x100 drop
  }
}

# IPv6のテーブル"filter"
table ip6 filter {
  chain incoming {
    type filter hook input priority 0; policy accept;
    ct state {established, related} accept
    ct state invalid drop
    oifname lo accept
    tcp dport {ssh, http} accept
    # ICMPのpingなどはaccept
    icmpv6 type {echo-request,nd-neighbor-solicit} accept
    # wanインタフェースからの入力はdrop
    mark 0x100 drop
  }

  chain fowarding {
    type filter hook forward priority 0; policy accept;
    ct state {established, related} accept
    ct state invalid drop
    # ICMPは全通し(ICMPによるIP自動割り当てのため)
    ip6 nexthdr icmpv6 accept;
    # wanからlan向けは拒否
    mark 0x100 drop
  }
}

systemctl enable nftables
systemctl start nftables

参考文献

http://techlog.iij.ad.jp/contents/dslite-raspi
全般的な設定方法の参考にした

https://wiki.archlinuxjp.org/index.php/Systemd-networkd#link_.E3.83.95.E3.82.A1.E3.82.A4.E3.83.AB
https://www.freedesktop.org/software/systemd/man/systemd.network.html
systemd-networkdの設定

https://wiki.archlinuxjp.org/index.php/Dnsmasq
dnsmasqの設定

https://www.systutorials.com/docs/linux/man/5-avahi-daemon/
avahiの設定

http://www.gcd.org/blog/2006/04/29/
bridge上を流れるIPパケットのフィルタ方法の参考

https://wiki.nftables.org/wiki-nftables/index.php/
nftablesの設定コマンドの参考

nftablesすげーいいよ! という説明資料

Posted by & filed under 未分類.

この記事は、2016 Java Advent calendarの7日目の記事である。

Java 8から利用できるStream APIについて。
宣言的に処理を記述出来るので、ハンマーを持つとなんとやらで、仕事においても、なにかにつけてStreamで書くという子供のようなことをしていた。

文字列のjoinするために Stream.of(array).collect(Collectors.joining(“, “)); などと書いてみたり
mapの戻り値でnew Object[] {}を使い疑似Tuple化して次の層に流したりといった具合である。

この記事は、このようにどこかしこをStreamというハンマーで叩きまくった結果、得た知見について紹介するものである。

BufferdReader.lines()からparallelで分散し、遅い出力先に吐く

最初にハマったのは、BufferedReaderのlinesを使い、並列処理をしながらMongoDBを更新するというバッチを書いたときだった。
mapで行データから更新メッセージを作成しforEachでMongoClientに食わせるという流れにしたところ、ガンガンヒープを食ってOOMエラーで落ちるのである。

当時は結局原因がわからなかったため、普通のループに書き直して終わらせたが、もしかすると、内部にあるキューが溢れるといった事象が起っているのではないかと想像した。

おおっ、これはネタとしては十分と思い、今日サンプルコードを実装したのが以下となる。
このコードは、mecab-neologd で配布されている辞書のxzファイルからダラダラ文字数を拾うというものである。

先に結論を言うと、そんなことは無いということだった。(すみません)

以下のconsole.log出力にあるInとOutの数の差がちょうどForkJoinPoolの数になっているので、map処理のところで正しくブロックされていることが伺える。このため、別の箇所でオブジェクトが正しく破棄されていなかったことが原因と思われる。

デフォルトForkJoinPoolを回避する

StreamのparallelはデフォルトのForkJoinPoolを使うようになっている。デフォルトのForkJoinPoolはCPUの個数だけしか同時実行しないため、マルチスレッド環境でparallelのタスクを複数並列で実行すると、思った時間に終わらないことがある。
(そもそもの話、マルチスレッド下でparallelStreamを使うこと自体避けるべきである)

とにかくなんとかしたい場合、新しいForkJoinPoolを作成し、submitすることで、指定したPoolのThreadを使って実行するようになる。上記のコードでも使っている。

参考: http://www.slideshare.net/dgomezg/parallel-streams-en-java-8

おまけ 自己満 collector

章が3つないとバランスが悪いので、思いつきで自己満collectorを作った。下に実行結果を貼っているが、まったくメリットが無いので、ふつうにforEachを使う方が良い。

Posted by & filed under 未分類.

メディア付きの投稿をした場合のコンテンツがどう表示されるかの確認。2

img_0907-1

リンクを挿入する場合のテスト

 

Posted by & filed under 未分類.

Gnomeも便利だったが、タイトルバーが分厚いとかデスクトップの遷移が面倒だとか、ラップトップの画面の狭さもあいまってストレスフルだった。
画面が狭いのなら、そもそもウィンドウをだらだら並べるのがおかしいと気付きタイルWMというのを試してみた。


上記のtweetを参考に、i3wmを使ってみたところ、単体では壁紙すら設定できないというシンプルさで、軽快に動く。かなり良い感じである。
しかしあまりにもシンプル過ぎて多少不便な点もあるため、そのあたりの改善方法をまとめた。
設定はすべて、.config/i3/config に書き足すものとする。

cheatsheet

https://i3wm.org/docs/refcard.html

デスクトップコンポジション

comptonを自動起動するとTearfreeになる。
Arch wikiの記事より。

exec compton -b

ボリュームコントロール

GNOMEの制御がデフォルトでは入らないので、pulseaudioもなくなる。以下は音量ボタンを押すことでalsa mixerをコントロールする設定。
Redditの記事では1単位で制御しているが、筆者の環境では最大が65535なので、512刻みにした。

bindsym XF86AudioRaiseVolume exec "amixer -q sset Master,0 512+ unmute"
bindsym XF86AudioLowerVolume exec "amixer -q sset Master,0 512- unmute"
bindsym XF86AudioMute exec "amixer -q sset Master,0 toggle"

https://www.reddit.com/r/i3wm/comments/2wbv8x/question_how_to_manage_audio_with_i3/

壁紙を有効にする

~/wallpaper.sh として、実行可能シェルを作る。

#!/bin/sh

(while true; do
     find ~/Pictures/wallpepers -type f \( -name '*.jpg' -o -name '*.png' \) -print0 |
         shuf -n1 -z | xargs -0 feh --bg-max
     sleep 15m
 done) &

以下をconfigに書く。

exec sh ~/wallpaper.sh

Posted by & filed under chinachu, Linux, tips.

まだ検証中だけれども、興味深いtipsがあったのでリンクを紹介しておく。
FFmpeg convert video w/ dropped frames, out of sync – superuser

同日 4:00 追記
上記リンクの方法のままではダメだったが、以下のコマンドで破損データの除去とタイムスタンプ情報が正規化されることは確認できた。
ffmpegは3.0.2を使用。

ffmpeg -i source.m2ts -vcodec copy -acodec copy output.m2ts

サイズも1割ほどちっちゃくなるので少し心配になるが、途中で再生がコマ飛びして音声が切れるといった状態だった民放ドラマについては、正常に再生できるようになった。
副音声付きtsを処理すると音声がマージされてしまうため、その部分のフィルタは必要。

Posted by & filed under 未分類.

ローストチキンが好きで年1回は必ず作っているのだが、毎回生焼けで後からレンチンするという情けないことになっている。

焼き過ぎず中まで加熱できればなぁと考えて閃いたのが、ホットプレート + 背の高い蓋 + 温度センサ + リレー + 温度管理ソフト を使い、長時間低温加熱できないか、というアイデア。なお、鳥ハムで定番となっているヨーグルティアでは丸鶏が入らない。

ヨーグルティアでは水に浸した状態で加熱するが、ホットプレートの場合は空気加熱となり、まったく条件が違うことから上手くいくかどうかも不明である。とりあえず適当な蓋すら見付ける事ができなかった。まずそれをコーナンで探すところから始める。ちなみに、鉄板焼き屋などが使っているステーキカバーという鉄板の上で食材に被せる蓋のような物があって、鉄板焼きフリークの間で大活躍しているらしい。

ググっているともはや基礎研究は済んでいる感がある。

参考URL

Arduinoとホットプレートを使ったリフロー装置の製作

Raspberry Pi Temperature Controller

Raspberry PI Temperature control board schematic

5vで20A/100VACパスできるリレー(google)

 

Posted by & filed under 未分類.

統計情報から、トラックバックしていただいていたことに気付いたので、現状どうなっているかをちょろっと書く。

遭遇した問題と対策

  • 連ドラとか短い番組ばかり撮って直ぐシャットダウンしているとEPG情報が枯渇する
    → 最低30分はシャットダウンしない
  • rasPIのKodi(XBMC)で録画を観るため、rasPIが動いている時間は落ちられると困る
    → rasPIのsystemdからchinachuサーバにsshログインし、chinachuサーバ側でSSHセッションを監視、セッションがある間は落とさない
  • ハイバーネートだとPT2やBCASリーダーが起動後に動かないときがある
    → PT2はモジュールのアンロードとロードで解消できたがBCASの方はなお不安定だったので、毎度シャットダウンするようにした。それによりchinachuサービスが先に死ぬようになりREST APIが使えず、jsonファイルを直参照して次回起動を変更するよう変更。

コード

gist良いなぁ。今回はブログ上に掲載。

root ~# crontab -l
# 起動後と3時間おきににlogwatchのレポートをメールする
@reboot /usr/sbin/logwatch --mailto haruo31@underthetree.jp
15 */3 * * * /usr/sbin/logwatch --mailto haruo31@underthetree.com

# upから30分以上経ち、かつchinachuが録画をしておらず、sshセッションがないとき
# メール送信し10秒後にshutdown -h
*/5 * * * * /usr/local/bin/up30min && /usr/local/bin/chinachuFree && /usr/local/bin/loginFree && ~root/shutmail.sh && sleep 10 && /sbin/shutdown -h now

# shutdown -r 10 せず敢えてsleepしているのはshutdown中であることで
# 何か制限があった気がしたから(ソースなし)

 

root ~# cat /usr/local/bin/up30min
#!/bin/sh

UTIME=`uptime -s`
BASE=`date -d"$UTIME" +%s`
EXP=`date -d-30min +%s`

[ "$BASE" -lt "$EXP" ]

exit $?

 

root ~# cat /usr/local/bin/chinachuFree
#!/usr/bin/env python3
# -*- charset: utf8 -*-

import sys
from ChinachuStatus import ChinachuStatus

if __name__ == '__main__':
    url = 'http://localhost:10772/api'
    res = ChinachuStatus(url).isChinachuBusy()
    if res:
        print("Chinachu is busy now.")
        sys.exit(1)
    print("Chinachu is free.")
    sys.exit(0)

 

root ~# cat /usr/local/bin/ChinachuStatus.py
#!/usr/bin/env python3
# -*- charset: utf8 -*-

from urllib.request import urlopen
import json
import io
import time

class ChinachuStatus:
    def __init__(self, urlBase):
        self.urlBase = urlBase
    
    def getNextReserve(self):
        response = urlopen(self.urlBase + '/reserves.json')
        res = json.load(io.TextIOWrapper(response, response.getheader('content-type').split('charset=')[1]))
        
        now = time.time()
        for e in [ int(ent['start'] / 1000) for ent in res if ent['start'] / 1000 > now ]:
            return e
    
    def isChinachuBusy(self):
        response = urlopen(self.urlBase + '/recording.json')
        res = json.load(io.TextIOWrapper(response, response.getheader('content-type').split('charset=')[1]))
        return len(res) > 0 or self.getNextReserve() - 600 < time.time()

if __name__ == '__main__':
    url = 'http://localhost:10772/api'
    print(ChinachuStatus(url).getNextReserve())
    print(ChinachuStatus(url).isChinachuBusy())

 

root ~# cat /usr/local/bin/loginFree
#!/bin/sh

if who -u | grep . > /dev/null; then
  exit 1
fi

exit 0

 

root ~# cat /usr/local/bin/chinachuNext
#!/usr/bin/env python3
# -*- charset: utf8 -*-

from ChinachuStatus import ChinachuStatus

if __name__ == '__main__':
    url = 'http://localhost:10772/api'
    # 5分前に起動
    print(ChinachuStatus(url).getNextReserve() - 300)

 

root ~# cat /etc/init.d/chinachu-nextsched
# /etc/rc6.d/K06chinachu-nextsched にリンクを張るのが多分正解 
#!/bin/sh

# Action script ensure that unattended-upgrades is finished 
# before a hibernate 

PATH=/sbin:/usr/sbin:/bin:/usr/bin
SHUTDOWN_HELPER=/usr/share/unattended-upgrades/unattended-upgrade-shutdown

echo 0 > /sys/class/rtc/rtc0/wakealarm
/bin/rm /home/chinachu/chinachu/data/*.lock’
/usr/local/bin/chinachuNext.old > /sys/class/rtc/rtc0/wakealarm
echo Wakeup schedule has been set successfully.

 

root ~# cat /usr/local/bin/chinachuNext.old
#!/usr/bin/env python3
# -*- charset: utf8 -*-

from urllib.request import urlopen
import json
import io
import time

class Reserves:
    def __init__(self, url):
        with open(url, mode='r', encoding='utf8') as f:
            self.o = json.load(f)
    
    def getNextTime(self):
        now = time.time()
        for e in [ int(ent['start'] / 1000) for ent in self.o if ent['start'] / 1000 > now ]:
            return e

if __name__ == '__main__':
    url = '/home/chinachu/chinachu/data/reserves.json'
    print(Reserves(url).getNextTime() - 300)

Posted by & filed under debian, iphone, Linux.

寝かしつけた息子を寝室に置いてテレビを観たいという嫁のリクエストを叶えるべく、ありもののwebcamと古いラップトップでwebcamの配信システムを構築した。およそ30秒ほど過去の息子の動静がiPadから確認できる。(むっちゃずれてるけど)音声も付いているので、起きたかどうかも音で確認できる。

仕様

  • HLSストリーミングの仕組みを利用し、shmに分割されたビデオtsを吐き出し直接nginxで公開する
  • HLS対応の出力はffmpegに実装されているものを使う
  • 音声つきビデオとする。ビデオが記録された時刻を画面左上に表示する

以下のサンプルの場合、iPadなどから http://yourhost/live/home/index.html にアクセスし、ビデオをタップすることでストリーミングが見られる。

構築にあたり気をつけた点

iPad対応にするため、ビデオのキーフレームをドキュメントにある90に指定(たぶん30とかでも良いのだろうが、チャートに従った)。
分割のタイミングとキーフレームが挿入されるタイミングを合わせた(試行錯誤の中でやった工夫なので、実際に必要かどうかは不明)。
ffmpegは使い終ったtsの削除をしてくれない。
音声がずれたりプチプチノイズが入る。サンプリングレートの指定をやめたことでノイズは消えたが、ズレはそのままとなっている。
apacheでなくnginxを使ったのは、最初rtmp方面から攻めたことによる名残である。rtmpの試行ではnginx-rtmp-moduleを使った。レイテンシが1分以上になる上にvlc以外で再生できないようなザンネンなストリームしか吐かせることができなかった。

用意するもの

もともとラップトップに入っていた debian gnu/linux を利用した。ffmpegはdebian-multimedia版のnonfreeライブラリを含むものを使用している。

必須

いずれもaptで導入。
nginx
ffmpeg
mplus fontdone
chrony -> ビデオにインポーズする時刻が狂うといろいろ都合が悪いため

あった方が良い

avahi -> ころころ変わるipなんか打ってられないという方に

デーモン化にはsystemdを利用した。

PCはdell latitude 4200 C2D。LogicoolのWebcam C525を利用。ラズパイで動いたらいいなー。

コード

code: /usr/local/bin/ffmpeg.sh

#!/usr/bin/env bash

BASE=/dev/shm/nginx-hls/home
mkdir -p $BASE
find $BASE -type f -delete

RES=640x360
#RES=960x544
#RES=1280x720

BRATE=600k
#BRATE=1200k
#BRATE=1800k
#BRATE=2500k

W=${RES%%x*}
H=${RES##*x}

cat > $BASE/index.html <<EOF
<html>
<head>
    <title>My son</title>
</head>
<body>
    <video src="http://yourhost/live/home/index.m3u8" height="$H" width="$W" >
    </video>
</body>
</html>
EOF

(sleep 12;
 while [ -e $BASE/index.m3u8 ]; do
   find $BASE -newer $BASE/index.m3u8 -prune -o -name '*.ts' -print | \
     grep -v -f $BASE/index.m3u8 | \
     xargs rm 2> /dev/null && true;
   sleep 6;
 done) &

trap "{ rm $BASE/index.m3u8; exit 0; }" TERM EXIT

ffmpeg \
 -y -threads 0 \
 -f v4l2 \
 -video_size $RES \
 -timestamps abs \
 -r 15 \
 -i /dev/video0 \
 -f alsa -thread_queue_size 1024 \
 -i "default:CARD=C525" \
 -ac 1 -strict -2 \
 -vcodec libx264 \
 -filter:v "drawtext=fontfile=/usr/share/fonts/truetype/mplus/mplus-2m-medium.ttf: text='%{localtime}': fontcolor=white@0.8: x=5: y=5" \
 -profile:v high -level 4.0 \
 -preset ultrafast \
 -pix_fmt yuv420p \
 -force_key_frames 'expr:gte(t,n_forced*6)' \
 -b:v $BRATE \
 -acodec libfaac \
 -b:a 40k \
 -filter:a "aresample=async=100, highpass=f=200, lowpass=f=3500" \
 -f segment -segment_time 6 \
 -segment_format mpegts \
 -segment_list $BASE/index.m3u8 \
 -segment_list_size 12 -segment_list_flags live \
 -segment_list_type m3u8 $BASE/%08d.ts

code: /etc/systemd/service/webcam.service

[Unit]
Description=Webcam HLS service
After=network.target auditd.service

[Service]
ExecStart=/usr/local/bin/ffmpeg.sh

[Install]
WantedBy=multi-user.target

* 以下登録のコマンド

# systemctl daemon-reload
# systemctl enable webcam

code: /etc/nginx/sites-available/default

server {
        ... snip ...
	location /live {
                types {
                    text/html html;
                    application/vnd.apple.mpegurl m3u8;
                    video/mp2t ts;
                }
                alias /dev/shm/nginx-hls;
                add_header Cache-Control no-cache;
	}
        ... snip ...
}

参考文献

iOS対応VIDEOの情報
https://developer.apple.com/library/ios/documentation/…/TP40008332-CH102-SW21

ffmpegで時刻のインポーズ
http://einar.slaskete.net/2011/09/05/adding-time-stamp-overlay-to-video-stream-using-ffmpeg/

grepのパターンをファイルから読む(ts削除のロジック)
http://unix.stackexchange.com/questions/83260/reading-grep-patterns-from-a-file

ALSA使うと [alsa @ 0x1d83640] ALSA buffer xrun. が出る不具合
http://stackoverflow.com/questions/28359855/alsa-buffer-xrun-induced-by-low-quality-source-in-ffmpeg-capture

ビットレート設定など
https://ffmpeg.org/ffmpeg.html