常駐監視型 Twitter メディアダウンローダー「berryjack/win32」

bjw32

Twitter の常駐監視型メディアダウンローダー berryjack の Windows 移植版を作成しました。品質無保証の人柱版として公開します。

概要

監視対象となるアカウントを定期的に巡回して、メディアファイル(画像、動画ファイル)が投稿されていたら自動的にダウンロードしてくれます。投稿されたメディアファイルを一括ダウンロードする点では、他の Twitter ダウンローダーと変わりがありませんが、起動しておくだけで複数のアカウントを定期的にチェックしてくれるところが大きな特徴です。

また、HTTP 経由でメディアタイムラインのみを取得するため、ツイート全文を取得する Twitter API 経由でのアクセスよりも少ないトラフィックで済み、直近 3200 ツイートしか取得できないという制限がないという特徴もあります。

各国の報道機関や犯罪組織の声明など発信される情報が注視されるようなアカウントや、イベントなどで期間限定のアカウントなどを監視するのに最適です。

ダウンロード

使い方

  1. 「list.txt」に監視対象のアカウントを1行に1つずつ書いて保存します。たとえば、アカウント「@AbeShinzo」「@tenkijp」「@jaxa_jp」「@pamyurin」の4つのアカウントを監視したい場合は、下記のように書きます。「@」マークはあってもなくても良いです。

bjw32_list

  1. バッチファイル「start-bjw.bat」をダブルクリックして実行します。コマンドプロンプトが起動して、ダウンロードが開始されます。ダウンロードした画像と動画ファイルは、download フォルダ内にアカウント毎に保存されます。

  2. 終了するには、コマンドプロンプトを閉じます。閉じるまで、ずっと監視&ダウンロードが実行されます。

注意事項・免責など

このバージョンは人柱版です。自己責任でお使いください。 このソフトを利用して発生したいかなる不具合、不都合も作者は責任を負うことができません。 また、Twitter やその他のサーバーに負荷をかけるような設定、運用は絶対に行なわないでください

ライセンス情報等は、配布しているアーカイブファイル内に同梱しています。

ツイキャス録画スクリプト

リアルタイム動画配信サービス「ツイキャス」の録画をしてくれるシェルスクリプトを書いてみました。…と言っても、ffmpeg の入力ソースに食わせるだけですので、スクリプトというよりは例によって自分の覚え書きのようなものになっています。


使い方は、実行権限を付与して

./twitcast.sh ツイキャスアカウント名

という具合です。配信中であれば ffmpeg による録画が開始されます。

なお、パスワード付き配信は試していませんが未対応だと思います。

MD5ハッシュ値をURLセーフな文字列に変換

大量に保存した画像ファイルなどの重複潰しも兼ねて、ファイル名を MD5 ハッシュ値にリネームして保存しています。

dyama/hashmv · GitHub

まったく同一のファイルであれば、ハッシュ値が同一になりますので、ファイル名が同一になります。上のスクリプト「hashmv」ではリネーム時に同一ファイルを上書きしますので、結果、重複ファイルを除去することができます。

実行イメージ

    $ hashmv foobar.jpg
    foobar.jpg -> ./d41d8cd98f00b204e9800998ecf8427e.jpg    

hashmv では、32 文字のハッシュ値のそのままファイル名に変換しているので、ちょっと長いなと思いました。md5sum のオプションを見ても、もうちょっとコンパクトな出力を得るオプションが見当たらなかったので、車輪の再発明上等でシェルスクリプトを書いてみました。

    #!bash
    function compress_hash()
    {
      echo $2 | sed \
        -e 's/aaa*/A/g' \
        -e 's/abb*/B/g' \
        -e 's/acc*/C/g' \
        -e 's/add*/D/g' \
        -e 's/aee*/E/g' \
        -e 's/aff*/F/g' \
        -e 's/baa*/G/g' -e 's/ecc*/g/g' \
        -e 's/bbb*/H/g' -e 's/edd*/h/g' \
        -e 's/bcc*/I/g' -e 's/eee*/i/g' \
        -e 's/bdd*/J/g' -e 's/eff*/j/g' \
        -e 's/bee*/K/g' -e 's/faa*/k/g' \
        -e 's/bff*/L/g' -e 's/fbb*/l/g' \
        -e 's/caa*/N/g' -e 's/fcc*/n/g' \
        -e 's/cbb*/M/g' -e 's/fdd*/m/g' \
        -e 's/ccc*/O/g' -e 's/fee*/o/g' \
        -e 's/cdd*/P/g' -e 's/fff*/p/g' \
        -e 's/cee*/Q/g' -e 's/0[0-9][0-9]*/q/g' \
        -e 's/cff*/R/g' -e 's/1[0-9][0-9]*/r/g' \
        -e 's/daa*/S/g' -e 's/2[0-9][0-9]*/s/g' \
        -e 's/dbb*/T/g' -e 's/3[0-9][0-9]*/t/g' \
        -e 's/dcc*/U/g' -e 's/4[0-9][0-9]*/u/g' \
        -e 's/ddd*/V/g' -e 's/5[0-9][0-9]*/v/g' \
        -e 's/dee*/W/g' -e 's/6[0-9][0-9]*/w/g' \
        -e 's/dff*/X/g' -e 's/7[0-9][0-9]*/x/g' \
        -e 's/eaa*/Y/g' -e 's/8[0-9][0-9]*/y/g' \
        -e 's/ebb*/Z/g' -e 's/9[0-9][0-9]*/z/g' \
        | cut -c -$1
    }

    for hashstr in $@
    do
      compress_hash 6 $hashstr
    done

元のハッシュ値は [a-f0-9] の文字集合ですが、パターンマッチングによって [a-fA-Z0-9] の文字集合に置き替え、桁数を落としています。見たとおり、ある一定のパターンの繰り返しを単一文字に置き換えているので、上のスクリプトでは元ハッシュ値に対する可逆性はありません。さらに、破壊的に圧縮したハッシュ値をcut で任意の桁数に切り落としています。

次に、評価用のコードを示します。

    while :
    do
      hash=$(date +%s | md5sum | cut -f 1 -d ' ')
      compress_hash 6 $hash
      sleep 1
    done

1秒おきに、UNIX タイム文字列を変換して印字する評価コードです。実行すると

    dFc7wa
    vqA5cx
    rc1a1d
    ucufyV
    1d2b8c
    qm9Le4
    6sqc5c
    atavIr
    7bqc5r
    3auf7b
    ....

のように、6 桁の圧縮されたハッシュ値が出てきます。

短縮 URL などの用途に代表されるように、URL セーフな文字列で桁数を抑えつつもある程度のパターン数は稼ぎたい場合に有効かもしれません。なお、元のハッシュ値は基数が a-z0-f の 16 ですが、これは a-zA-Z0-9 の 62 となる為、6 桁でも約 568 億パターンも稼ぐことが可能です。

    # 62^10 = 839299365868340224 (約84京パターン)
    # 62^9  =  13537086546263552 (約1.3京パターン)
    # 62^8  =    218340105584896 (約218兆パターン)
    # 62^7  =      3521614606208 (約3.5兆パターン)
    # 62^6  =        56800235584 (約568億パターン)
    # 62^5  =          916132832 (約9億パターン)
    # 62^4  =           14776336 (約1477万パターン)

6 桁なら長くなく、覚えられない長さではないし、1/568億の確率で衝突するくらいの強さは持ってますので、ファイル管理には十分だと思いました。

なお、a-zA-Z0-9 の文字集合に加えて記号 /+= を許可する場合は、上のようなスクリプトではなくハッシュ値をそのまま base64 エンコードした方が手っ取り早いです。

Twitterの画像を一括ダウンロードするシェルスクリプト「berryjack」を書いた。

以下、スクリプトの解説が中心です。Windows で手軽に使いたい方は berryjack/win32 をダウンロードしてください。

数年前、知人より「Twitterの特定アカウントで公開されている画像ファイルを一括ダウンロードするスクリプトがないかな」と相談を受けて、Perl でNet::Twitter::Liteを使ってがりがりコーディングしました。そして、今年になってから Ruby を使う機会が増えたため、そのコードを Ruby で書き直してみました。

機能に対した差はなく、いずれも Twitter API 経由でタイムラインを取得していき、オブジェクト化されたツイートを舐めていくだけのものでしたが、画像ファイルのダウンロードに加え、発言やプロフィール情報の保存などなどをやっているうちにコードが増えてしまって、Perl で 343 行、書き直した Ruby でも 203 行と少し行数が増えてしまいました。

速度や行数に厳しい要求じゃないので、別にまあ良いのですが、Perl で書いていた頃から Twitter API がなんだか大掛かりすぎる感がありました。コンシューマキーやらアクセストークンやら、タイムラインの遡って取得していく方法や…、とタイムラインを取ってくるだけでも色々なバックエンドを知りつつ、リファレンスと睨めっこしなければなりません。

そんな時 Twitter をWWWブラウザで眺めていると、メディアの取得は JavaScript を使って非同期的に要求・表示していることに気付きました。いや、それ自体は一応前から知ってたんですが、昨今のダイナミックに動きまくる大手ウェブサービスの暗号みたいなソースを見るのにちょっと億劫になっていて、挙動と中でやっている事の予想はついていたものの、まともに調べたことがありませんでした。タイムライン取得はどのサイトを見ても Twitter API 経由が定石のようですし…。

Twitter 側が提示している正規のアクセス方法はあくまでも Twitter API 経由でのアクセスですが、使わせてようとしているわりには使いづらかったり、何かと制限がついてきたりとなかなか良いイメージがありません。 その Twitter API も大掛かりすぎて、ちょっとだけ食傷気味になっていましたので、HTTPリクエストと返ってくるJSON情報だけでなんとかなるはずだと思って、シェルスクリプトで書いてみました。

Twitter をブラウザでアクセスした際に表示される「画像 / 動画」ページは、下にスクロールしていくと、非同期的にサーバにクエリを投げて画像 URL を含む JSON データを取得しています。Firefox の 開発ツールを開いて JavaScript でクエリーを投げている URL を検知すると、

https://twitter.com/i/profiles/show/[Twitterアカウント名]/media_timeline[パラメータ]

にアクセスしていることが分かります。

ここから、まずパラメータを省略してアクセスしてみると最新のツイートから遡って20件前後のツイートを取得しているようでした。さらにこの取得したツイートのIDを用いて、さらに古いツイートを取得するためのパラメータを生成すれば良さそうです。

つまり、最初のアクセスはパラメータなし。2回目以降は、直前のアクセスで取得した情報を基に URL を生成、そこにアクセス。というパターンです。

いくつかパラメータを投げることができるみたいですが、最低限必要なパラメータはcontextual_tweet_idmax_idのようです。前者は、直前に取得した tweet ID の一番若い番号、後者はその番号をマイナス1した値みたいです。Tweet ID は全世界的に一意である整数値番号で、Perl ではMath::BigIntを使わないと扱えないくらい大きい桁数になっています。

そのことを踏まえ、適当にパターンマッチさせて書いてやると、次のような感じになりました。


初回アクセス時にはparamが空になって、最新のツイートを取得します。そこから、まずは画像ファイルの情報を列挙、その後に次のクエリーを投げるためのパラメータ検知、生成しています。最も古いツイートまで到達したりして情報が取得できなかったら抜けます。

拍子抜けしてしまうほど簡単でした。14行ておま。Perl や Ruby の Twitter API なモジュール、Gem を使ってやっていたのが虚しいくらいです。もちろん、これ以外にもいろいろな機能を実装していた上に、Unicode なツイート内容などを扱い始めるとシェルスクリプトでは都合が悪い(勝手が悪い)部分も多々存在します。

絶対的優位と言えそうな点は、media_timeline に対してクエリーを投げているので、画像ファイルのダウンロードが目的であれば不必要なその他のツイートは取得せずに、ピンポイントでダウンロードできる点じゃないでしょうか。(そっちの方が Twitter のサーバに対しても優しいかも。1万ツイートしていて画像ファイルが100個しかないアカウントに対して全ツイートを取得するのは非効率的すぎますし、そもそも API 制限で全ては取得できませんし…。Twitter API もメディアファイルがついているものだけを取得できないのかなぁ)

ということで、いくつかオプションを追加して叩けば落ちてくるシェルスクリプト「berryjack」を GitHub に置いておきました。何百番煎じかもしれませんが、興味がある方は見てみてください。

ちなみに、一度だけダウンロードしたい方は、次のようなソフトもあります。

シェルスクリプトによる簡易Wikiシステム

以前、PQI Air Card でも動く簡易 Wiki を書いてみました。PQI Air Card 自体、標準で Perl が動いていますし実用性はあんまりないですが、シェルスクリプトベースでも簡単に出来てしまいます。

wiki.sh

メインスクリプトです。

#!/bin/bash

dir=p
#BASENAME="/mnt/sd/DCIM/122_TREK/busybox basename"
BASENAME="basename"
tmp=`$BASENAME $QUERY_STRING`
page=${tmp:-index}

if [ "$REQUEST_METHOD" = "POST" ]; then
  read text
  echo "$text" | sed -f www.sed > $dir/$page
fi

if [ -f $dir/$page ]; then
  body=`cat $dir/$page`
else
  body='This is new page.
Please write body and push the save button.'
fi

title=`echo "$page" | sed 's/_/ /g'`

cat<<EOF
Content-Type: text/html

<TITLE>$title - Wiki</TITLE>
<H1>$title</H1>
<HR>
<H2>CONTENT:</H2>
<PRE>
$body
</PRE>
<HR>
<H2>EDITOR:</H2>
<FORM action=wiki.sh?$page method=post>
<TEXTAREA cols=60 rows=7 name=text>$body</TEXTAREA>
<INPUT type=submit value=SAVE></FORM>
EOF

cat<<EOF
<HR>
<H2>PAGE LIST:</H2>
EOF

for item in `ls $dir`;
do
  echo "<dt><a href=wiki.sh?$item>" $item "</a>"
done

echo "<HR>"
echo "my wiki system by dyama &lt;dyama@member.fsf.org&gt;"

www.sed

URL文字列デコード用 sed ファイル。

#!/bin/sed

s/^text=//
s/+/ /g

s/%0D%0A/\n/g
s/%0A/\n/g
s/%0D/\n/g

s/%21/!/g
s/%3F/?/g
s/%2B/+/g
s/%3D/=/g
s/%7C/|/g
s/%23/#/g
s/%24/$/g
s/%5E/^/g
s/%26/\&/g
s/%28/(/g
s/%29/)/g
s/%5C/\\/g
s/%60/`/g
s/%7E/~/g
s/%5B/[/g
s/%5D/]/g
s/%7B/{/g
s/%7D/}/g
s/%3B/;/g
s/%27/'/g
s/%3A/:/g
s/%22/"/g
s/%3C/</g
s/%3E/>/g
s/%2C/,/g
s/%2F/\//g
s/%09/\t/g

s/%25/%/g

wiki.sh に +rx して、ページ保存用のディレクトリ「./p」を 777 で作成し、wiki.sh にアクセスすると次のように表示されます。

wiki

新規ページを作成するには、URL文字列に?pagenameをつけてアクセスします。wiki.sh の URL がhttp://localhost/wiki.shの場合、http://localhost/wiki.sh?pagenameになります。 ページの削除機能はないので、端末からログインして、ディレクトリ p 以下の対象ファイルを削除すればOKです。

また、SVN などに噛ませてページの更新時毎にディレクトリ p 以下をコミットしてやれば、ヒストリー機能も実現できそうです。楽チンですね。

ANSI エスケープシーケンスについて

端末の色やカーソル制御を行うには、curses を利用するのが一般的ですが、ライブラリを利用しなくとも、シェル経由である決まりのエスケープシーケンス文字列を端末に投げることにより、色やカーソル制御を行うことができます。

次のページに詳しく記載があります。

Bash Prompt HOWTO: ANSI エスケープシーケンス: 色とカーソル操作

  • 昨今の bash では\033\eに設定されているため、例えば\033[\0m\e[0mと記述できます。
  • また色指定の\e[31mなどを\[\]で囲む記述がありますが、囲まなくても大丈夫のようです。
    • 例) \[\033[41m\]\[\033[1;37m\] string \[\033[0m\]\e[41;1;37m string \e[0m
    • だいぶすっきり。

前景色・背景色の指定

xtermなど、一般的な端末でサポートされている ANSI カラーの 8 色です。太字設定をすると、それぞれの色が高輝度・低輝度な 2 種類の色になるため、事実上 16 色になります。

0 1 2 3 4 5 6 7

赤の前景なら\e[31m、青の背景なら\e[44という風に、それぞれ30番台と40番台の1の位が上の表となります。また、太字(強調文字・高輝度な文字)表現にするには、\e[32;1mのようにセミコロン区切りで属性を加えてやります。この1桁の属性値は順番は問わないようで\e[1;32mでも同じ意味になります。 属性値は 1.太字、4.下線、5.点滅、7.反転、8.非表示 らしいです。

色の見え具合は端末によって結構変動します。上の表は原色のビビットカラーで表現していますが、最近のオサレな端末なんかは、色がギラギラして見えづらいことに対する配慮のためだろうと思いますが、デフォルトでは抑えた色で表現するよう定義されています。端末によっては色のバインドを変更することも出来ますし、見え具合はあんまり保証されていません。

また、xterm の拡張256色サポートをしている端末も結構あります。こちらはRGB値を数値指定して端末上に表現しているものですから、見え具合の差異は少ないと思われます。 256色サポートについては、5年ほど前に2chの「コンソールゲーム」スレに書き込んでいたログに書いていた内容がまだ残っているようです。

vim で簡単ドット絵エディタ

前に作っていたコンソールベースのゲームは、0から7をひたすら書いたテキストファイルをグラフィックファイルとして用いています。 シンタックスハイライトに次のように定義してやると、vim が簡単ドット絵エディタになります。

let b:current_syntax = "amap"
syn match g0 /0/
syn match g1 /1/
syn match g2 /2/
syn match g3 /3/
syn match g4 /4/
syn match g5 /5/
syn match g6 /6/
syn match g7 /7/
hi g0 ctermfg=gray ctermbg=black
hi g1 ctermfg=gray ctermbg=red
hi g2 ctermfg=gray ctermbg=green
hi g3 ctermfg=gray ctermbg=yellow
hi g4 ctermfg=gray ctermbg=blue
hi g5 ctermfg=gray ctermbg=magenta
hi g6 ctermfg=gray ctermbg=cyan
hi g7 ctermfg=gray ctermbg=white

前景色を見やすいように gray に設定しているので、set t_Co=256を設定しておかないといけないかもしれません。 あとは対象のファイルを開いてset syntax=amapしてやるだけです。

00000400000
00004440000
00444444400
44407440744
44411111444
04444444440

こういうテキストファイルが

you know what

例のアレのように表現されます。

作図っていうと、ビジュアルモードの矩形選択や一括置換が力を発揮しますね。

端末に表示するスクリプト

最近、ruby が楽しくて仕方ありません。上記のファイルを端末に表示するスクリプトです。libcaca の img2text の劣化みたいですね。

#!/usr/bin/env ruby
# -*- mode:ruby; coding:utf-8 -*- 

class String
  def number?
    self =~ /\A-?\d+(.\d+)?\Z/
  end
end

def printimg(file)
  File.open(file) do |f|
    buf = f.read
    cur = nil
    buf.each_char do |c|
      if c.number?
        if cur != c
          print "\e[4" + c + ";1m "
          cur = c
        else
          print " "
        end
      else
        print "\n"
      end
    end
    print "\e[0m"
  end
end

ARGV.each do |file|
  printimg file
  puts "---"
end

このスクリプトに

66666666666666666666607777707770777077777707707777777077777777777777777077700777
66666666666666666666077777077707770777777707770770777077777777777777777707077777
66666666666666666666607777077077770777077707777070777007777777777777777770777777
66666666666666666666660077077077770777077077777707077070777707770777777777077777
66666666666666666666666607077077770777077000077777707077077000770777777777077777
66666666666666666666666666607077707777077077700777770770000707070777077777077700
66666666666666666666666666607077707770707077777077777707707707707070707777707077
66666666666666666666666666660077707770707077777777777777770707777070770777700707
66666666666666666666666666666077707700000000007777777777000000000000000777707707
66666666666666666666666666666077707770777711177777777777777777111177770777707707
66666666666666666666666666666077707707077711117777777777777777111117707077707707
66666666666666666666666666666077770707777777777777777777777777777777777077707707
67774747477747774746677747774707770707171777777777777777777777771717777077070707
67466747474747474746674746746007770707777777777770777777777777777777770777070770
67466777477747746746674746746007770707777777777777777777777777777777770777070770
67466747474747474746674746740707770770777777777777777777777777777777707770770770
67774747474747474777477746707700777077077777711111111111111111111111111707111117
66666666666666666666666666077070777701111111111111111111111111117770111111711111
67474747477747774746677740700711111111111111111111111111111111177777011111111711
67474747474667466746674660040701111111111111111111111111111111117770111111111171
67774747474747774746667406740701111111111111111111111111111111111111111111111110
67474747474747466746666746740700111111111111111111111111111111111117770111111000
67474777477747774777477746707770011111111111110000000000111111111177777011110001
66666666666666666666666666077777000111111110000000000000000001111117770111110001

という内容のテキストを食わせてやると

charlot

こんな感じになります。

さらに、拡張256色モードを駆使すると

xterm-256-map

こんな具合に!

enter image description here

こんな具合に!!!

…ゲームなんかで多様する茶色や肌色みたいな非原色が使えるのは嬉しいんですが、256種類も色があると

  • vim で描けない
  • 管理したり作ったりするのが大変
  • 色のバインドに迷う

と、色々弊害も出てきます。

今回はすべて背景色だけで描画していますが、前景色付きの文字を組み合わせて微妙な色を表現したり、さらにはUnicode文字の網掛けなどを駆使しつつ広大な縦横文字数を持つ端末を必須にするなどすれば、事実上、なんでも表現ができてしまいそうな気がします。

表現力にこだわりたいなら、SDL でも GL でもフレームバッファでも何でも使えばいいと思うので、色のつかいすぎは「端末だってここまでできるんだよ!」的なデモ用途にしか使う意味がないのかもしれません。で、そうなると、前述の libcaca などの高品位なデモを見ていればいいわけで・・・。

ゲーム用途には、大小アルファベットと数字で表現できる64色+RGBバインドくらいがちょうどいいのかなーと思うこの頃です。
ちなみに、モノクロでダンプできる rogue はいろんな意味で美しいです。

シェルスクリプトでCGIチャットを書いてみました。

CGIは、標準入出力と環境変数を用いて動的コンテンツを生成する仕組みなので、これらを扱うことができるプログラムだったら記述言語は問いません。CGIと言えば「CGI/Perl」の組み合わせだけが突出して有名ですが、CGI/Cだって世の中にはたくさんあります。 さらに極端な事を言えば、CGI/awkだってCGI/shだって不可能ではありません。

ただし、CGI/shといったものは少なくとも私の知る限りでは、現在推奨はされていません。 Webインタフェイスとして動作するためのセキュリティを高めることに対する煩雑さであったり、大量のリクエストを効率的に捌くための本質的な性能であったり様々でしょうが、わざわざシェルスクリプトを引っ張り出してこなくても、Perl や Ruby、Python と言った気の利いた高級言語が使える環境が整っていることがほとんどでしょう。

じゃあ何故 CGI 用途にシェルスクリプトを使うかっていうと、限定された趣味の世界で、趣味目的で書く、というところが現実的なんじゃないかなって思っています。また、「(高級言語を使わなくても)シェルスクリプトだっていろいろ出来るよ!」という気持ちもあるかもしれません。(PQI Air Card のようなめちゃくちゃ小さい Linux システムにもシェルは搭載されているので、CGI/sh で簡単なウィキシステムを書いて遊んでみたりしました。)

bash.CGI

CGI として動作させるために重要なのが、リクエストの解析です。必要なものは環境変数に詰まっていますし、POST送信の内容は標準入力を読めば自分で何とでも出来ますが、シンプルかつしっかり動くbash.CGIというコードのサンプルがあるので、それをモジュールとして使いたいと思います。このコードは、CGI/Perl における CGI.pm みたいなもので、GETによるURLに付随してくるパラメータやPOST送信されたデータをシェル変数に置き換えてくれるので、いろいろ捗ります。

cgi_get_POST_vars()cgi_decodevar()cgi_getvars() の3つの関数定義と、呼び出しの大元である cgi_getvars BOTH ALL コマンドを貼っつけたスクリプトファイルを bashcgi.sh として保存しておきます。

bootstrap

最近のナウいユーザインタフェイスをちゃっちゃか実現するために Twitter の bootstrap を使っています。今回はサーバーのルートに /css や /js といったディレクトリが展開されるよう配置しています。

shellchat

さていよいよ本題。シェルスクリプトによる CGI チャットのソースコードは次のようになりました。

#!/bin/bash

. bashcgi.sh

title='shellchat!'
logfile='./log.txt'
linenb='15'

if [ -n "$nick" -a -n "$msg" ]; then
  msg=$(echo "$msg" | sed 's/</\&lt;/g' | sed 's/\(http:\/\/[^  ]*\)/<a href=\1 target=_blank>\1<\/a>/g')
  nick=$(echo "$nick" | sed 's/</\&lt;/g')
  timestamp=$(date +"%m/%d %H:%M:%S")
  echo "<code>$nick</code> <span>$msg</span> - <span class=small>$timestamp</span><br />" >> "$logfile"
  cat<<EOF
Content-Type: text/html

<meta http-equiv="Refresh" content="0; URL=$SCRIPT_NAME?nick=$nick" />
EOF
else
  cat<<EOF
Content-Type: text/html
Pragma: no-cache
Cache-Control: no-cache

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="Refresh" content="40; URL=$SCRIPT_NAME?nick=$nick" />
    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <script src="/js/bootstrap.min.js"></script>
    <title>$title</title>
  </head>
  <body>
    <div class="container-fluid">
      <div class="row">
        <div class="col-md-2"></div>
          <div class="col-md-8">
            <h1>$title</h1>
            <div class="well">
EOF

  [ -f "$logfile" ] && tail -n $linenb "$logfile"

  cat<<EOF
            </div>
          </div>
          <div class="col-md-2"></div>
        </div>
        <div class="row">
          <div class="col-md-2"></div>
          <form class="form-inline" action="$SCRIPT_NAME" method="post" enctype="application/x-www-form-urlencoded">
            <div class="col-md-8">
              <p class="text-center">
                <input class="form-control" type="text" name="nick" size="5" placeholder="Name" value="$nick" />
                <input class="form-control" type="text" name="msg"  size="59" placeholder="Input message here." />
                <button type="submit" class="btn btn-default">
                  <span class="glyphicon glyphicon-comment"></span>
                </button>
              </p>
            </div>
          </form>
          <div class="col-md-2"></div>
        </div>
        <div class="row">
          <div class="col-md-2"></div>
          <div class="col-md-8">
            <hr />
            <p class="text-right">
              shellchat by <a href="http://dyama.org/" target="_blank">dyama.org</a>
            </p>
          </div>
          <div class="col-md-2"></div>
        </div>
      </div>
    </div>
  </body>
</html>
EOF
fi

exit 0

全体的にどべえーっと長ったらしい HTML タグがありますが、ほとんどがデザイン部です。実際に処理しているのは数行しかありません。 冒頭で bashcgi.sh を読み込んで、シェル変数に POST 送信の内容を詰め込んでいます。変数 nick と msg があれば、ログに追記した後、ユーザにはページをリフレッシュするよう META タグを返しています。また、変数がない場合はそのままログを表示しています。

セキュリティ的にも性能としても、何もやっていない分、いろいろと問題があるのですが、一応ちゃんと動いています。

shellchat

実際に動かすと、上記のように表示されます。

音声合成エンジン Open JTalk について

動画の生放送サイトを見ていると、リアルタイムに視聴者から来るコメントを合成音声によって読み上げている人が増えているようで、合成音声のジャンルも賑わってきたようです。ボーカロイドの初音ミクが登場するよりも前、AquesTalk をいじってみて調整次第ではかなり「聞ける」音声になるんだなあ、と実感した覚えがあります。 自分は合成音声を入れた動画を作成したり、配信したりするわけではないのですが、IRCのログやTwitterのタイムラインの読み上げ程度には使ってみたくなりました。 ということで、今更感が拭えませんが、Open JTalkを使ってみた時のメモを書いておきます。

Open JTalk とは

オープンソースの日本語音声合成エンジンです。音声合成エンジンには、入力文章の処理部と発音部に機能が分かれるんですが、文章処理にはオープンソースの形態素解析エンジン MeCab を利用しているようです。ChaSen や Kakasi に並んで有名なエンジンですね。発音部には、HMM-based Speech Synthesis System (HTS)というエンジンを利用しているようです。 Open JTalk のデモページでは、WWWブラウザ経由で音声合成を試すことができます。 試しに作ってみた音声を置いておきます。能登っぽい声ですね。 こちらのデモページでは、更新履歴を見る限り、Open JTalk のバージョン 1.06 を利用しているようです。

インストール

インストールした時点での私の環境は Ubuntu 13.04 x64 版です。2013年6月現在、Ubuntu のリポジトリに入っているのはバージョン 1.05 で、公式サイトにて公開されている最新版は 1.06 です。詳しい事は後述しますが、これらのバージョンによって利用できる音声データファイルの形式が違ってきます。

自炊ZIPをスマホや電子ブックリーダー向けに最適化する

去る2月、楽天の Kobo を中古購入しました。投げ売り価格の2500円でした。私は風呂でよく本を読むので、浴槽に落としてもそんなに凹まない安価な端末を探していました。期待をまるでしていない、という意味では Kobo は最高の端末です。

2500円という価格にしては、Linuxが中で動いているし、電子ブックリーダーでしか触ることのできない電子ペーパーを扱えたり、WiFiに対応していたり、本体を開ければ簡単にカスタマイズできたり、と Raspberry Pi よりも「香ばしい」端末なんですけどね。あんなに薄いのにバッテリーまで搭載しているし。

ソフトウェア面は最悪です。電子ブックリーダーなのに本が読みにくい。本棚の表示が遅かったり、改ページが遅かったり。電子ペーパーの画素書き換えの遅さではなく、それ以上にソフトウェアが下手に組んである遅さが気になりました。20世紀末の Windows 98 全盛期くらいの最新 PC くらいのスペックは持ってるんだし、ブックリーダーに用途は特化されているんだから、「もうちょっとどうにかしてよ」という感じ。サービス面に至っては、今更論評するまでもなしですね。

Kobo は至るところで散々叩かれまくっているので、用途を割り切るつもりで購入しました。割り切って使うと決めて購入したら、現物を触ってみてもそんなに落差がないです。
「ああ、やっぱ遅いな」とかそのくらいの印象でした。同時に、「割り切って使う」という目的はなんとかクリアできそうという実感も。

hangousuihan

hangousuihan は、画像ファイルを含むZIP圧縮ファイルに対し、解凍、画像処理、再圧縮の一連の処理を行うツールです。以前、Windows 向けに書きました。このツールの目的は、スマートフォンやタブレットPC、電子ブックリーダーなどの端末の画面にジャストフィットするサイズに画像を縮小して、圧縮ファイル自体をコンパクトにすることです。超高解像度のマンガファイル、1冊300MBなんていうのは家に保存しておいて、実際に見るのは30MBくらいのファイルで充分ですよね、というアプローチです。私が持っている Kobo も、micro SD カードを差すことができますが、水没を前提と考えているので、あまりお金をかけて拡張する気がありません。内蔵メモリだけで済ませたいところです。

Windows の場合、カチッとしたアプリケーションを拵えない限り使いものにはなりませんが、UNIX 系 OS の場合はスクリプトを書けば事足ります。私は普段、Linux しか使ってませんので、次のシェルスクリプトを用いています。

[bash]

!/bin/bash

wd="/tmp/$$"

for target in "$@"
do
[ ! -d "$wd" ] && mkdir "$wd"
unzip -j "$target" -d "$wd"
[ ! -d "$wd/result" ] && mkdir "$wd/result"
tname=basename &quot;$target&quot;
for item in find &quot;$wd&quot; -type f
do
name=basename &quot;$item&quot;
convert -type grayscale -quality 40 -resize 600×800 "$item" "$wd/result/$name"
zip -9 "/tmp/$tname".cbz "$wd/result/$name"
done
mv "/tmp/$tname".cbz /media/$USER/KOBOeReader/
rm -rf "$wd"
done
[/bash]

Kobo の画面解像度にフィットする 600×800 に画像を圧縮し、グレイスケール化しています。Ubuntu では、Kobo のファイルシステムのマウント場所は /media/$USER/KOBOeReader となるので、そこにダイレクトに CBZ で出力するようにしています。

使い方は、

[bash]$ ./hangousuihan.sh comic01.zip comic02.zip comic03.zip[/bash]

のように、変換したいファイルパスを引数に与えてやればOKです。

tumblrの画像を一括ダウンロードするスクリプト

tumblrの画像を一括ダウンロードするbash向けシェルスクリプトを書いてみました。

あまり効率的ではないですが、投稿を1ページずつ取得して、含まれる画像を抜き出しています。また、ダウンロードが完了したらZIPファイルに固めてくれます。

[bash]

!/usr/bin/env bash

#

simple picture downloader for tumblr

#

written by dyama (http://dyama.org/), Feb 1 2013

#

usage:

gettumblr http://example.tumblr.com/

function gettumblr()
{
url=$1

# fix the url
url=${url#http://} # this pattern is not regex!
url=${url%%/*}

# check the url and get tumblr user acount
if [[ $url =~ ^([a-z0-9]+)\.tumblr\.com$ ]]; then
    USER=${BASH_REMATCH[1]}
    echo OK, Target user name is $USER. &gt;&amp;2
else
    echo Error: $url is not tumblr URL. &gt;&amp;2
    return 1
fi

# working directory
wdir=&quot;/tmp/gettumblr$$&quot;
if [ ! -d $wdir ]; then
    mkdir $wdir
    if [ $? -ne 0 ]; then
        echo Error: Cannot make working directory at $wdir. &gt;&amp;2
        return 1
    fi
fi

pushd $wdir

for i in {1..999};
do
    echo Getting posted page $i ... &gt;&amp;2
    wget -O - -q &quot;http://$url/page/$i&quot; | grep -oE 'http[^&quot;]+post[^&quot;]+' \
        | grep $USER &gt; list
    [ $? -ne 0 ] &amp;&amp; break;
    echo -e '\tGetting included image ...' &gt;&amp;2
    grep -v frame list | wget -q -i - -O - | grep 'og:image' \
        | grep -v facebook | grep -oE 'http[^&quot;]+' | wget -i - -nv
done

file=$wdir/tumblr_$USER.zip
zip $file *

popd

mv $file .
rm -rf $wdir

echo Done. &gt;&amp;2

}

gettumblr "$*"
exit $?
[/bash]

関数 gettumblr() の冒頭部分のパターンによる変数保持値の編集や正規表現パターンマッチ構文は、bash依存です。また、grep の -o オプション(パターンに一致した行の、パターンに一致した部分だけを標準出力に印字する)も多用しています。このオプションは古めの grep にはなかったはずなので、注意が必要です。

ターゲットのtumblrユーザが存在しない場合、ファイルが削除できなかった場合など、例外処理はまったく書いていなかったり、一回で済むような処理を分けて書いていたりと、トライアンドエラーの跡が残ったままですので、もし流用する場合には適宜書き換えてください。