txqz blog

3946件中1から15件目を表示します。このリソース群のタイトルリスト、またリソースのAtom表現RSS1.0表現も参照できます。

64bit版Ubuntu10.04にPT2とFoltiaHDを入れてiPhoneでアニメを見ながら通勤する

foltia用にと思ってSycomGZ2000P55/870を買ったけどあまり時間がなくて少しずついじってるうちに2ヶ月くらい経ってしまった。いい加減ドキュメント化する。

このサーバにUbuntu10.04を入れて録画サーバとする。Ubuntuなのは趣味。

縦画面対応

つなげているディスプレイの一つがDELL 2407WFPで、こいつは縦置きしているのでまず縦画面に正常に出力されるようにしないと首が痛い。

上のシステム→設定→モニタから、画面の回転がすぐできるようになっている。Nvidiaのドライバもすぐインストールできるようになっていて便利。いままでは、いちいち対応ビデオカードのドライバを落としてこないといけなかった。

が、Nvidiaのドライバをインストールすると、先ほど設定した画面の回転が無効になる。しなければしないで、隣においたD-SUB接続のWSXGA+モニタの表示がおかしい(解像度を変えて5:4にすれば、縦横比はおかしいものもちゃんと画面いっぱいに表示される。本来の解像度(8:5)にするとずれる)

Nvidiaのドライバをインストールしたあとで、/etc/X11/xorg.confを編集し、Section "Device"の下にOption "Rotate" "CCW"を追加して再起動すれば、ちゃんと縦長になる。CCWはCounterClockWise(反時計回り)の略で、名古屋市営地下鉄名城線のユーザにはおなじみ。逆に回転したら"CW"としてみたらいい。

Bluetooth接続のキーボードとマウスは、9.04のときはインストール作業中から普通に使えてたが、このバージョンではインストール後にシステム→設定→Bluetoothから設定しないと使えない感じだった。マウスはそれで使えるようになったが、キーボードはPINの設定が上手くいかない…と思ったけど、テンキー側で押したら成功した。

Synergyを入れる

わざわざキーボードとマウスの設定をしておいてナンだけど、MBPに差してるHHKBから操作をしたいので、Synergyの設定をする。MBPはすでにSynergyホストの設定がQuickSynergy経由でされている。

アプリケーション→Ubuntuソフトウェアセンターから右上の検索で"Synergy"で検索し、QuickSynergyをインストールする。アプリケーション→アクセサリにQuickSynergyが表示される。これでBluetoothキーボードとマウスはどこかに閉まってしまう。

自動起動の設定もする。/etc/gdm/Init/Default のsysmodmap=/etc/X11/Xmodmapの上の行くらいに

SYNERGYC=`gdmwhich synergyc`
if [ x$SYNERGYC != 'x' ] ; then
    $SYNERGYC Synergyサーバ機のIPアドレス
fi

PT2を使えるようにする

ということで、環境はUbuntu10.04 64bitのi7-860マシンにSCR3310-NTTComのICカードリーダーに赤いB-CASカードという構成で、PT2を使えるようにしてみる。

foltiaユーザを作成

あとからFoltiaと連携させることを考えて、foltiaユーザでできることはなるべくfoltiaユーザでさせる。

$ sudo adduser foltia
ユーザ `foltia' を追加しています...
新しいグループ `foltia' (1001) を追加しています...
新しいユーザ `foltia' (1001) をグループ `foltia' に追加しています...
ホームディレクトリ `/home/foltia' を作成しています...
`/etc/skel' からファイルをコピーしています...
新しいUNIXパスワードを入力してください: 
新しいUNIX パスワードを再入力してください: 
passwd: パスワードは正しく更新されました
Changing the user information for foltia
Enter the new value, or press ENTER for the default
    Full Name []: 
    Room Number []: 
    Work Phone []: 
    Home Phone []: 
    Other []: 
Is the information correct? [Y/n] Y

カードリーダードライバのインストール

$ sudo apt-get install pcsc-tools
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の特別パッケージがインストールされます:
  libpcsc-perl
以下のパッケージが新たにインストールされます:
  libpcsc-perl pcsc-tools
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 2 個。
133kB のアーカイブを取得する必要があります。
この操作後に追加で 508kB のディスク容量が消費されます。
続行しますか [Y/n]? Y
取得:1 http://jp.archive.ubuntu.com/ubuntu/ lucid/universe libpcsc-perl 1.4.8-1 [60.0kB]
取得:2 http://jp.archive.ubuntu.com/ubuntu/ lucid/universe pcsc-tools 1.4.16-1 [73.3kB]
133kB を 3s で取得しました (37.6kB/s)
未選択パッケージ libpcsc-perl を選択しています。
(データベースを読み込んでいます ... 現在 124924 個のファイルとディレクトリがインストールされています。)
(.../libpcsc-perl_1.4.8-1_amd64.deb から) libpcsc-perl を展開しています...
未選択パッケージ pcsc-tools を選択しています。
(.../pcsc-tools_1.4.16-1_amd64.deb から) pcsc-tools を展開しています...
man-db のトリガを処理しています ...
desktop-file-utils のトリガを処理しています ...
python-gmenu のトリガを処理しています ...
Rebuilding /usr/share/applications/desktop.ja_JP.utf8.cache...
python-support のトリガを処理しています ...
libpcsc-perl (1.4.8-1) を設定しています ...
pcsc-tools (1.4.16-1) を設定しています …

$ sudo apt-get install libccid
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の特別パッケージがインストールされます:
  pcscd
以下のパッケージが新たにインストールされます:
  libccid pcscd
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 2 個。
195kB のアーカイブを取得する必要があります。
この操作後に追加で 627kB のディスク容量が消費されます。
続行しますか [Y/n]? Y
取得:1 http://jp.archive.ubuntu.com/ubuntu/ lucid/universe libccid 1.3.11-1 [111kB]
取得:2 http://jp.archive.ubuntu.com/ubuntu/ lucid/universe pcscd 1.5.3-1ubuntu4 [83.9kB]
195kB を 3s で取得しました (54.2kB/s)
未選択パッケージ libccid を選択しています。
(データベースを読み込んでいます ... 現在 124967 個のファイルとディレクトリがインストールされています。)
(.../libccid_1.3.11-1_amd64.deb から) libccid を展開しています...
未選択パッケージ pcscd を選択しています。
(.../pcscd_1.5.3-1ubuntu4_amd64.deb から) pcscd を展開しています...
man-db のトリガを処理しています ...
ureadahead のトリガを処理しています ...
ureadahead will be reprofiled on next reboot
libccid (1.3.11-1) を設定しています ...

pcscd (1.5.3-1ubuntu4) を設定しています …

B-CASカードをちゃんと読み込めているか確認する。

$ pcsc_scan 
PC/SC device scanner
V 1.4.16 (c) 2001-2009, Ludovic Rousseau <ludovic.rousseau@free.fr>
Compiled with PC/SC lite version: 1.5.3
Scanning present readers...
0: SCM SCR 3310 NTTCom 00 00

Mon May  3 20:41:54 2010
 Reader 0: SCM SCR 3310 NTTCom 00 00
  Card state: Card inserted, Unresponsive card, 

Unresponsive card というのはカードの裏表が逆だということだそうなので、入れ替える。

$ pcsc_scan
PC/SC device scanner
V 1.4.16 (c) 2001-2009, Ludovic Rousseau <ludovic.rousseau@free.fr>
Compiled with PC/SC lite version: 1.5.3
Scanning present readers...
0: SCM SCR 3310 NTTCom 00 00

Mon May  3 20:43:52 2010
 Reader 0: SCM SCR 3310 NTTCom 00 00
  Card state: Card inserted, 
  ATR: 3B F0 12 00 FF 91 81 B1 7C 45 1F 03 99

ATR: 3B F0 12 00 FF 91 81 B1 7C 45 1F 03 99
+ TS = 3B --> Direct Convention
+ T0 = F0, Y(1): 1111, K: 0 (historical bytes)
  TA(1) = 12 --> Fi=372, Di=2, 186 cycles/ETU
    21505 bits/s at 4 MHz, fMax for Fi = 5 MHz => 26881 bits/s
  TB(1) = 00 --> VPP is not electrically connected
  TC(1) = FF --> Extra guard time: 255 (special value)
  TD(1) = 91 --> Y(i+1) = 1001, Protocol T = 1 
-----
  TA(2) = 81 --> Protocol to be used in spec mode: T=1 - Unable to change - defined by interface bytes
  TD(2) = B1 --> Y(i+1) = 1011, Protocol T = 1 
-----
  TA(3) = 7C --> IFSC: 124
  TB(3) = 45 --> Block Waiting Integer: 4 - Character Waiting Integer: 5
  TD(3) = 1F --> Y(i+1) = 0001, Protocol T = 15 - Global interface bytes following 
-----
  TA(4) = 03 --> Clock stop: not supported - Class accepted by the card: (3G) A 5V B 3V 
+ Historical bytes: 
+ TCK = 99 (correct checksum)

Possibly identified card (using /usr/share/pcsc/smartcard_list.txt):
3B F0 12 00 FF 91 81 B1 7C 45 1F 03 99
    Japanese Chijou Digital B-CAS Card (pay TV)

PT2のドライバを入れる。

Foltia作者のブログに書いてあるドライバを入れる。Debianでの構築例を見つつ、実行。

まず、必要なパッケージの用意

$ sudo apt-get install g++ libboost-filesystem-dev libboost-thread-dev libglib2.0-dev libboost-regex-dev build-essential autoconf libpcsclite-dev mercurial

foltiaユーザでドライバを入れる

$ hg clone http://hg.honeyplanet.jp/pt1/ PT2
全チェンジセットを取得中
チェンジセットを追加中
マニフェストを追加中
ファイルの変更を追加中
117 のチェンジセット(305 の変更を 115 ファイルに適用)を追加
ブランチ default へ更新中
ファイル状態: 更新数 31、マージ数 0、削除数 0、衝突未解決数 0

$ cd PT2/driver/
$ make

バージョンによっては、ここでerror: implicit declaration of function 'schedule_timeout_interruptible'というエラーメッセージが表示される。検索すると2chのスレが出てくるので、そのとおりにpt1_i2c.cに#include <linux/sched.h>を追加すればちゃんとmakeされる。

$ sudo make install
$ sudo cp pt1_drv.ko /lib/modules/2.6.32-21-generic/kernel/drivers/video/pt1_drv.ko
$ sudo depmod -a
$ sudo modprobe pt1_drv
$ sudo cp etc/99-pt1.rules /etc/udev/rules.d/

デバイスファイルの生成を確認

$ ls -l /dev/pt1*
crw-rw-rw- 1 root video 250, 0 2010-05-16 12:10 /dev/pt1video0
crw-rw-rw- 1 root video 250, 1 2010-05-16 12:10 /dev/pt1video1
crw-rw-rw- 1 root video 250, 2 2010-05-16 12:10 /dev/pt1video2
crw-rw-rw- 1 root video 250, 3 2010-05-16 12:10 /dev/pt1video3

recpt1の設定をする。最新リビジョンにはb25デコードライブラリが同梱されていないので、同梱されているリビジョンをチェックアウトする。

$ hg clone http://hg.honeyplanet.jp/pt1/ PT2_old -r 73
$ cd PT2_old/arib25
$ make
cd src; make all
make[1]: ディレクトリ `/home/foltia/PT2_old/arib25/src' に入ります
gcc -O2 -g -fPIC -Wall `pkg-config libpcsclite --cflags` -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o b_cas_card.o b_cas_card.c
gcc -O2 -g -fPIC -Wall `pkg-config libpcsclite --cflags` -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o multi2.o multi2.c
gcc -O2 -g -fPIC -Wall `pkg-config libpcsclite --cflags` -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o ts_section_parser.o ts_section_parser.c
gcc -O2 -g -fPIC -Wall `pkg-config libpcsclite --cflags` -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o td.o td.c
gcc  -o b25 arib_std_b25.o b_cas_card.o multi2.o ts_section_parser.o td.o `pkg-config libpcsclite --libs` -lm
gcc  -shared -o libarib25.so arib_std_b25.o b_cas_card.o multi2.o ts_section_parser.o `pkg-config libpcsclite --libs` -lm -Wl,-soname,libarib25.so.0
make[1]: ディレクトリ `/home/foltia/PT2_old/arib25/src' から出ます

$ sudo make install
cd src; make install
make[1]: ディレクトリ `/home/foltia/PT2_old/arib25/src' に入ります
mkdir -p /usr/local/include/arib25
install -m644 arib_std_b25.h b_cas_card.h portable.h /usr/local/include/arib25
install -m755 b25 /usr/local/bin
install -m755 libarib25.so /usr/local/lib/libarib25.so.0.2.4
ln -sf /usr/local/lib/libarib25.so.0.2.4 /usr/local/lib/libarib25.so.0
ln -sf /usr/local/lib/libarib25.so.0 /usr/local/lib/libarib25.so
ldconfig
make[1]: ディレクトリ `/home/foltia/PT2_old/arib25/src' から出ます

$ cd ../recpt1
$ make
revh=`hg parents --template 'const char *version = "r{rev}:{node|short} ({date|isodate})";\n' 2>/dev/null`; \
    if [ -n "$revh" ] ; then \
        echo "$revh" > version.h; \
    else \
        echo "const char *version = \"'1.0.0'\";" > version.h; \
    fi
gcc -MM recpt1.c decoder.c mkpath.c -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DB25 > .deps
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DB25  -c -o recpt1.o recpt1.c
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DB25  -c -o decoder.o decoder.c
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DB25  -c -o mkpath.o mkpath.c
gcc  -o recpt1 recpt1.o decoder.o mkpath.o `pkg-config libpcsclite --libs` -larib25 -lm -lpthread

$ sudo make install
install -m 755 recpt1 /usr/local/bin
$ sudo sh -c 'echo "/usr/local/lib" > /etc/ld.so.conf.d/recpt1.conf'
$ sudo ldconfig

pt1のスレッドが走っているか確認する。

$ dmesg | grep pt1
[    9.343588] pt1-pci.c: r116:38a793ac3d9d 2010-03-25 
[   10.418279] pt1_thread run

録画してみる

$ recpt1 --b25 --strip 19 30 ~/test.ts
using B25...
enable B25 strip
Signal=34.658791dB
Recording…

VLCで再生してみるとローカルでは問題なく再生できたが、UDPを飛ばしてMacのVLCで再生しようとすると上手くいかなかった。VLCメニューの「ファイル」→「ネットワークを開く」→「RTP/UDPストリームを開く」と進んでいき、IPアドレスを録画鯖のIPアドレス、ポートはそのまま1234にしたが、うまく再生できなかった。よくよく調べると、これはモードがマルチキャストになっていたからで、ユニキャストモードにしてポート番号だけ指定すれば受信できた。よく考えれば、録画鯖のrecpt1で宛先IPアドレスを指定してUDPパケットを垂れ流しているので、受信側で改めてIPアドレスを指定するのも変な話だ。

UDP以外の他マシン再生方法を探していたらLinux板のデジタル放送スレでHTTP版を公開している人がいた。

769 :新参者 :2010/04/29(木) 01:05:03 ID:3hyiY+PL
とりあえずhttpサーバー版の暫定版をアップしてみました。 
makeの方法は最近のrecpt1と同じでいけると思います。 

(driveとarib25をインストールした後) 
-- 
./autogen.sh 
./configure --enable-b25 
./make 
-- 

recpt1 --b25 --strip --http 8888 

みたいにすると8888番ポートでデーモン起動し、 
VLCなどで 
http://hostname:8888/25 

などするとUHF25チャンネルが視聴できるはずです。 

http://2sen.dip.jp/cgi-bin/pt1up/source/up0280.gz 

ログをちゃんと吐かないとか、家にBSアンテナがないので試していないとか 
色々問題がありますが・・ 

動作報告等ありましたら嬉しいです。 
(サポートできるかどうかは不明ですが・・) 

(当方は32bit ubuntu 8.10でテストしました)

FoltiaHDのインストール

さて、recpt1は動いたので次はFoltiaのインストールをする。foltia - SQLite対応へを見ながらすすめる。

必要なパッケージをあらかた入れておく。wineはTsSplitterのために必要なので、TS分割をtss.py(後述)で行う場合は不要だった。

$ sudo apt-get install wine mplayer imagemagick subversion git-core yasm sqlite3 postgresql php5 php5-sqlite

sqliteのテーブルを作成するSQLファイルが付属されているので、これを使ってテーブルを作成する。

$ sqlite3 /home/foltia/foltia.sqlite < mktable.sqlite.txt

Apacheの設定を行う。Apacheをfoltia:foltiaで動かし、ドキュメントルートなどを適切に設定する。デフォルトの設定では、Foltiaは録画したTSファイルを/home/foltia/php/tvに、WebUIのPHPスクリプトを/home/foltia/phpに置く。

$ vi /etc/apache2/apache2.conf
User foltia
Group foltia
DocumentRoot "/home/foltia/php"
AddDefaultCharset off
Alias /foltia/ "/home/foltia/php/"

<Directory "/home/foltia/php">
</Directory>

$ /etc/init.d/apache2 restart

configファイルは推奨設定が.templateの拡張子付きで設置されているので、コピーして編集する。

$ cd /home/foltia/php
$ cp foltia_config2.php.template foltia_config2.php

$ cd ../perl
$ cp foltia_conf1.pl.template foltia_conf1.pl

$ ./getxml2db.pl long

DBIとDBI::SQLiteが必要だそうなのでCPANで入れる。また、今回はPostgreSQLは使わないが、Perlのソース中でDBD:Pgを読み込むようになっているので、ことごとくコメントアウトしてしまう。

ここまで行ってhttp://host/foltia にアクセスすると、放映予定が表示される。

ログローテート

デバッグログがすべて~foltia/debuglog.txtに残され、とくにインストールしたてで設定のおぼつかない状態ではエラーログ祭りになっている。私の場合、1週間くらいほかっておいたらこのファイルのサイズが4GBに達していてトレースも満足にできない状態だった。ちゃんとローテートしておく。

$ vi /etc/logrotate.d/foltia
/home/foltia/debuglog.txt {
    dayly
    rotate 6
    size=100k
    missingok
}

インストール後につまずいたところ

音がずれる

Foltiaのキモの処理の一つ、録画したファイルのiPod向け変換はipodtranscode.plが行っている。mplayerでサムネイルを作成し、ffmepgでh.264ファイルを作成し、mplayerでwavを切り出し、それをneroAacEncかfaacでaacに変換してMP4Boxでくっつけてipod向けmp4ファイルにしている。

この処理中、MP4Boxでaacファイルをbase.mp4にaddしようとするとbuffer overflow detectedというエラーがでた。検索したところ、どうやらUbuntuのバグなのらしい。ここに書いてある通りmpeg4ip-serverパッケージのmp4creatorで代用し、うまくいったが今度は音がずれた。なのでneroAacEncは使わずに、faacを使うことにした。こちらはうまくいった。

音が出ない

あまり詳しいログをとってないので良くないのだけど、ipodtranscode.plのやり方で動画ファイルを作成してiTunesに取り込むと、音声が再生されなかった。なので、mplayerでwavを切りだしてaacにしてMP4Boxで動画と合わせる処理を全てコメントアウトして、ffmpeg内で音声のエンコードも行うことにした。これでiTunesでの再生でも音声が再生されるようになった。

iTunesに取り込めない

それで喜んでいたのだが、そのうちPodcast経由でiTunesに取り込めなくなった。動画をMacにダウンロードしてiTunesの「ムービー」に突っ込めば再生できるのに、なぜかPodcast経由だと取り込めないということがあった。それでほっておくと翌日の更新では取り込めたりした。よくわからない。

いろいろ試行錯誤して、iPhone3G向けに以下のフォーマットでエンコードすればだいたい上手く行くことがわかった。

ffmpeg -y -i hoge.m2t -vcodec libx264 -b 512k -bt 512k -coder 0 -level 30 -s 640x360 -aspect 16:9 -qmin 10 -r 30000/1001 -acodec libfaac -ar 44100 -ab 128k -threads 8 -vpre hq -vpre ipod640 hoge.mp4

ffmpegのバージョン・オプションは以下のとおり:

$ ffmpeg -version
FFmpeg version SVN-r23392, Copyright (c) 2000-2010 the FFmpeg developers
  built on Jun  1 2010 00:01:38 with gcc 4.4.3
  configuration: --enable-libmp3lame --enable-libx264 --enable-libxvid
--enable-libfaac --enable-libfaad --enable-pthreads --enable-gpl
--enable-nonfree
  libavutil     50.16. 0 / 50.16. 0
  libavcodec    52.72. 0 / 52.72. 0
  libavformat   52.67. 0 / 52.67. 0
  libavdevice   52. 2. 0 / 52. 2. 0
  libavfilter    1.20. 0 /  1.20. 0
  libswscale     0.10. 0 /  0.10. 0
FFmpeg SVN-r23392
libavutil     50.16. 0 / 50.16. 0
libavcodec    52.72. 0 / 52.72. 0
libavformat   52.67. 0 / 52.67. 0
libavdevice   52. 2. 0 / 52. 2. 0
libavfilter    1.20. 0 /  1.20. 0
libswscale     0.10. 0 /  0.10. 0

……と喜んでいたら、このオプションでうまくいったのは「荒川アンダーザブリッジ」くらいで、あとは軒並み失敗した。この件については、tss.pyでTS分割を事前にしておけば上手くいくことがわかった。

$ tss.py motoneta.m2t

この方法は、今のところ「ハートキャッチプリキュア」以外で成功している。「ハートキャッチプリキュア」は、逆にTS分割させたファイルをエンコードしようとすると以下のようなエラーが出て失敗する:

2010/07/18_09:30:50 ipodtranscode starting up.
2010/07/18_09:31:20 ipodtranscode DEBUG 165847,1853,1853-23-20100718-0830.m2t,60,3,23
2010/07/18_09:31:20 ipodtranscode DEBUG mp4filenamestring -1853-23-20100718-0830
2010/07/18_09:31:20 ipodtranscode Call captureimagemaker 1853-23-20100718-0830.m2t
2010/07/18_09:31:20 captureimagemaker DEBUG mplayer -ss 00:00:10 -vo jpeg:outdir=/home/foltia/php/tv/1853.localized/img/1853-23-20100718-0830/ -vf scale=192:108 -ao null -sstep 9  /home/foltia/php/tv/1853-23-20100718-0830.m2t
2010/07/18_09:31:30 ipodtranscode DEBUG thmfilename MAQ-1853-23-20100718-0830.THM
2010/07/18_09:31:30 ipodtranscode DEBUG mplayer -ss 00:01:20 -vo jpeg:outdir=/home/foltia/php/tv/1853.localized/mp4/ -ao null -vf framestep=300step,scale=160:90,expand=160:120 -frames 1 /home/foltia/php/tv/1853-23-20100718-0830.m2t
2010/07/18_09:31:31 ipodtranscode tss /home/foltia/php/tv/1853-23-20100718-0830.m2t
2010/07/18_09:31:39 recwrap Launch ipodtranscode.pl
2010/07/18_09:31:39 ipodtranscode processes exist. exit:
2010/07/18_09:32:25 ffmpeg -y -i /home/foltia/php/tv/1853-23-20100718-0830_tss.m2t -vcodec libx264 -b 512k -bt 512k -coder 0 -level 30 -s 640x360 -aspect 16:9 -qmin 10 -r 30000/1001 -acodec libfaac -ar 44100 -ab 128k -threads 8 -vpre hq -vpre ipod640 /home/foltia/php/tv/1853-23-20100718-0830.mp4
2010/07/18_09:32:26 ipodtranscode ERR /home/foltia/php/tv/1853-23-20100718-0830.mp4 Not found.
2010/07/18_09:32:26 ipodtranscode ERR ; Fail MAQ-1853-23-20100718-0830.MP4
2010/07/18_09:32:26 ipodtranscode DEBUG 165847,1853,1853-23-20100718-0830.m2t,130,3,23

なので、TS分割させたファイルをffmpegにかけ、その後mp4ファイルが生成されていなければTS分割させてないファイルをffmpegにかけ直すようにipodtranscode.plを書き換えた。

その他失敗するケース

録画中に解像度が変わるとエンコードに失敗し、解像度が変わったタイミングで映像は止まり、音声だけが再生されるようになる。仕様上番組開始の30秒前から録画が始まるので、前の番組と録画したい番組とで解像度が異なると失敗してしまう。今のところこの問題が発生しているのは以下の番組:

同じMXで再放送されていた「刀語」の場合は、放送前のCMもSDだったらしく、エンコードは成功していた。

まあいろいろ大変ですが、今後もいろいろ試行錯誤していきます。

Foltiaの生成したファイルをiPhoneから見ているところ
foltia on iPhone

いじってる間見ていたサイト

関連資料

2010年6月27日の#sobe2foltiaネタを発表したので、そのときのスライドを埋め込んでおきます。

foltiaのご紹介
タグ

recpt1のショートカットを作成した

recpt1で地デジを視聴しようとして、毎回

$ recpt1 --b25 --strip --udp --addr テレビを見たいマシンのIPアドレス --port 1234 チャンネル番号 - /dev/null

とタイプするのは面倒だし、地デジのチャンネル番号が全然馴染みのないもので毎回調べるのが面倒だったので、ショートカットスクリプトの作成を始めた。東京MXが見たければ

$ tv --on mx

でいいし、途中でBS11に変えたかったら

$ tv --ch bs11

でいい。ソースは今のところこんな感じ:

#!/bin/sh
on()
{
    pid=`ps x | grep recpt1 | grep -v grep | awk '{print $1}'`
    if [ "x$pid" = "x" ]; then
        vlc='vlc --global-key-deinterlace 2 udp://'
        recpt1="recpt1 --b25 --strip --udp --addr $1 --port 1234 $2 - /dev/null"
        if [ "$1" = "localhost" ]; then
            `$vlc & $recpt1`
        else
            `$recpt1`
        fi
    else
        change $pid $2;
    fi
}
off()
{
    pid=`ps x | grep recpt1 | grep -v grep | awk '{print $1}'`
    `kill $pid`
}
change()

    `recpt1ctl --pid $1 --channel $2`
}
name2id()
{
    ret=0
    case "$1" in
          "nhk") ret=27;;
          "etv") ret=26;;
          "ntv") ret=25;;
          "anb") ret=24;;
        "asahi") ret=24;;
          "tbs") ret=22;;
           "tx") ret=23;;
           "cx") ret=21;;
         "fuji") ret=21;;
           "mx") ret=20;;
          "bs1") ret=101;;
          "bs2") ret=102;;
         "bshi") ret=103;;
        "bsntv") ret=141;;
      "bsasahi") ret=151;;
        "bstbs") ret=161;;
      "bsjapan") ret=171;;
       "bsfuji") ret=181;;
        "wowow") ret=191;;
         "bs11") ret=211;;
         "bs12") ret=222;;
              *) ret=$1;;
    esac
    return $ret
}

OPT=`getopt -o c:h: -l on,off,change:,channel:,ch:,host: -- $*`
eval set -- $OPT

channel=27
host="localhost"
mode="--on"
while [ -n "$1" ]; do
    case $1 in
        --on|--off) mode=$1; shift 1;;
        --channel|--change|--ch|-c)  name2id $2; channel=$?; shift 2;;
        --host|-h) host=$2; shift 2;;
        --) shift; break;;
        *) echo "unknown option($1) used."; exit 1;;
    esac
done

case $mode in
    --on) on $host $channel;;
    --off) off;;
    *) ;;
esac
タグ

VIASO/K-power追悼

三菱UFJニコスが生まれて、今まで別だったUFJカード・NICOSカード・DCカードのログインポータルがひとつになった。とは言っても単に旧3種と新しいMUFGカードのログイン画面へのリンクや共通キャンペーン情報などがあるだけで、この先には元あったログイン画面が残っている。

私はMUFG系だとニコスのVIASOカードとUFJのK-powerカードを持っていて、どちらも目的を分けてそれなりに使っていた。そのうち私はこう使っているみたいなのを書こうと思っていたのだけど、旧来のカードの新規募集が軒並み終了されてしまった。もう遅いけど、第1回紫蘇カンファレンスで話したことも含めて、これらのカードについて振り返っておこうと思う。

Viasoカード

ニコスが発行しているものとUFJが発行しているものとで、海外旅行保険やポイントの付き方・モールの取り扱いや還元率が若干違うが、以下はニコス版の話。

100円で0.5ptついて12ヶ月で1000pt以上集めると集めたポイント分だけキャッシュバックされる。キャッシュバックされるタイミングを自分で選べないかわりに、1000pt以上あれば付いたポイントが無駄にならない。1年で1000pt集めるのは一見大変そうだが、「楽ペイ」で払うと100円で1ptになる。楽ペイは支払が自動的にリボルディング払いになるというものだが、最初の1ヶ月は金利が付かないので、限度額が10万なら楽ペイの月支払額を10万円にしておけば実質1回払いと変わらない。ETCやケータイ会社からの請求は100円で1ptつき、楽ペイと組み合わせると1.5ptになる。さらに、ショッピングモールが充実していて、たとえばboopleでの決済に+5%のポイントがつく。オリコモールだと「5倍」だけど、オリコはデフォルトで0.5%還元なので2.5%にしかならない。

ということでネット通販をするのに大変良かったのだが、2009年9月に楽ペイでのポイント2倍が廃止された。その旨伝えるハガキに、ちゃっかり退会方法が載っていたのが面白くて、これで退会してしまったらキャッシュバックが受けられない。10ヶ月で238958円このカードで決済して、7581円分のキャッシュバックを受ける権利があるので、それだけもらったら退会しようと思う。還元率は約3.3%になった。

K-powerカード

ドコモ・au・ソフトバンク・ウィルコムからの請求が、合計月1万円を上限に割り引かれるカード。パンフレットには「通話料金」とあるけど、端末の月賦やコンテンツ代金など、ケータイ会社からの請求ならなんでも対象になる。ケータイ会社からの請求が1000円ごとに1pt、その他の請求が1000円ごとに1ptついて、月に合計30pt集めると翌月のケータイ会社からの請求の10%が翌々月に還ってくる。VIASOカードと同様楽ペイで払うとポイントが2倍つく。つまり、最も都合のいい使い方(ケータイ10000円・その他5000円・楽ペイ)をすると、15000円で1000円還元、還元率6.67%となる。

K-powerカードのキャッシュバック率
1ヵ月の合計ショッピングポイントキャッシュバック率
0~29ポイント0%
30~49ポイント10%
50~99ポイント15%
100~149ポイント20%
150~199ポイント30%
200~249ポイント40%
250ポイント以上50%

実際はそんなに上手くいかない。まず、あくまで2種類のポイントを合算して30ptを超える必要があるので、ケータイ9500円・その他5501円だと、合計15000円を超えているからキャッシュバックされると思いがちだが、ケータイ18pt・その他10ptで合計28pt(楽ペイ前提)であり、30ptに満たないので1円も還元されない。

また、キャッシュバック率の決定→キャッシュバックされる金額の決定→実際のキャッシュバックまで2ヶ月のディレイがある。つまり、還元率を高くしようと思ったら、入会した最初の1ヶ月はケータイ代をこれで決済しないで、次の月から10000円を目指していく必要がある。やめるときはケータイ代だけ残し、翌月にキャッシュバックされたのを見て退会する必要がある。3ヶ月15000円を決済して3ヶ月目に1000円キャッシュバックされるので、この3ヶ月だけ見ると還元率は2.2%であり、記事広告だけ見て6%還元! すごい! と思って契約すると肩透かしを食らう。さらに2年目からは年会費1575円が必要になる。年間の還元率を一番都合のいいパターンで計算すると、1年目は最初の2ヶ月はキャッシュバックがないから(1000*10)/(15000*12)で5.6%、2年目からは年会費がかかって(1000*12-1575)/(15000*12)で5.8%となる。

これを去年の5月に契約してこれまで運用した結果、計算を間違えて多めに決済してしまったり逆に決済額が足らなかったりして、8ヶ月で154865円決済して還元額は4197円。還元率はほぼ3.0%になった。広告の数字ほどではないけど、決して悪くない数字だと思う。ケータイ代はもちろんこれで、あとプロバイダ代や電気・ガスなど毎月引き落とされるものを15000円分集めてほかっておけばいいので、そういうデッキを作れればあとは楽。

計算を間違えたというのは、クレジットカードへの請求の上がる日が各社によってまちまちなのを知らなくて、これは足りない!と思って30ptになるように買い物→あとから請求が上がってきて意味がなかったというのがあった。2月10日請求の利用明細を見ると、11月30日のNTT東日本から1月6日の東京電力まで幅広い利用日が並んでいる。NTT東日本の請求の上がるのが遅いからこうなるのだが、使いはじめでそういうのを知らなかったから、未確定請求に東京電力がなくて1ヶ月以上前のNTT東日本からのがあれば東京電力の請求が来月回しになってしまったのではないかと焦るのももっともだと思う。「東京電力からのお知らせ」には、クレジットカード会社の締切日と当社の検針日の関係により、2ヶ月分の電気料金があわせてご請求となる場合がございますと書いてあるし……

K-powerのほうは楽ペイ2倍が廃止されず、あまり考えないでこれだけ還元されるカードもなかなかないと思うので、まだ使い続ける予定。

タグ

年収ジェネレータ

昨晩年収ジェネレータというweb5.0的サービスをリリースしまして、そのきっかけをここにまとめようとしていたのですが有志の方がすでにまとめてくださったので特に書くことがありません。まきもとさんのワンライナーを使っているとfuba様に思われたような感じですが、別の方法で実装しています。本当は平均年収がよく出るような実装にしたら良かったのですが、金額生成部分は1行で書いたので、だいたい0円~3400万円が均等に出るようになっています。確認したところ少ない人で4万円程度、検索結果を眺めると1000万から2000万の人が多い印象です。私の場合約250万だったので、正直それほど高めに出てくるとは思いませんでした……ってアルゴリズムを知っている人が読むと白々しいのですが。

アドバイスをいただいたので途中からTwitterにPOSTするアンカーを設けました。それのおかげか、今日未明から昼過ぎくらいまで割と長い間Buzzっていたようです。ありがたいことです。なにがWeb5.0なのか知りませんが、こういうライトなものがどんどん広まっていく様子を観察できるのもTwitterのいいところですよね。ブログだとこんな1行ネタを脊髄反射で投稿してはい次なんてありえないし、まとめて観察もしにくい。気軽にアウトプットできないからこんなに広がらないし、今まで身内程度でしか面白がれなかったのがどんどんまきこめていける。個人ニュースサイトからソーシャルブックマーク、そしてマイクロブログと情報伝達がどんどんボトムアップ型へシフトしていく中で、そのエキサイティングさとエネルギーを久しぶりに実体験できました。

4万円台をはるか下回る結果が出ました。おめでとうございます(謎

井蛙さんの今年の年収は313円でした。

タグ

ブログから性別を判定する研究とかありましたね

ようざかは名前がちさですし、女の子ですよ

@youzaka さんの性別を疑った事はないです。あとトイレさんも

@okadapan はい……

一方@siro_xxは、先日のチームラボ&pixiv合同会社説明会(便宜的に@tksさんのtweetにリンクを貼っているけど、もっとそれっぽいアンカー先募集)で性別詐称に成功していた。

タグ

アンカー生成をブックマークレットで

たいした話でないばかりに書かずにいたら外で参照できずに残念な思いをしたので悔い改めるシリーズ。

IEだったらコンテクストメニュー追加セットでやっていたようなことをOperaではopera exを使ってしていたけど、いま更新停止中だし、よく考えたらブックマークレットで十分なのでブックマークレットを書いた、というか他にそういうことをやっていた人のを参考にした。

ニックネームアドレス
majavascript:window.prompt('','<a\x20href=\x22'+location.href+'\x22\x20title=\x22'+document.title+'\x22>'+document.title+'</a>')
mljavascript:window.prompt('',document.title+'\x0A'+location.href)
mbjavascript:window.prompt('','<blockquote\x20cite=\x22'+location.href+'\x22\x20title=\x22'+document.title+'\x22>\x0A<p>'+window.getSelection()+'</p>\x0A</blockquote>')
mqjavascript:window.prompt('','<q\x20cite=\x22'+location.href+'\x22\x20title=\x22'+document.title+'\x22>'+window.getSelection()+'</q>')

ということで、ウェブサイトを見ていてアンカーを生成したくなったら、Ctrl+lmaEnterCtrl+xEscAlt+TabCtrl+vな感じ。

ただ、window.getSelection()を使うと選択した箇所が単にテキストとして扱われるので、選択箇所にアンカーなどほかの要素がマークアップされていてもそれが反映できない。コンテクストメニュー追加セットは選択範囲の子要素もうまく扱ってくれていたので、Operaでもそこらへんうまくやれるようなやり方を見つけていきたい。

あとはbはてブにPOSTできるようにしたり、lldRにフィードを投げられるようにしたり、tTumblrにPOSTできるようにしたり (Opera版Tomblooください)、mmitterにPOSTできるようにしたりしている。これらはオフィシャルで配布しているブックマークレット。

タグ

解こう!イラストロジック!

イラロジのセンスが無いだけなのかもしれんけど、これ解くのに3ヶ月かかりました。(DL-PDF 30KB

ということでやってみる。

まず、セオリーに従って数字の大きいところに注目、あとはそこからどんどん論理的に考えて塗ったり×印をつけたりすればいい (図1; 茶色は塗るところ、灰色は×印を打つところ)。

図1 これ以上どうする?

図1まで塗り終わったあと手が止まった。これ以上どこを塗れるだろうか。

手詰まりなので、たとえば、[4 3]の列(左から4番目の列)に注目してみる。この列は、[1 1 1 1 1 2]の行(上から7番目の行)に×があって上下2つにわかれている。上部は6マスあり、ここに[4]が来る可能性がある。一方下部は8マスあり、[4]と[3]が両方塗られる可能性がある。なので、現時点ではどちらが塗られるべきマスか確定しないのだが、とりあえず仮定して塗ってみる (図2、図3)。

図2 上6マスに[4]が入ると仮定
図3 下8マスに[4 3]が入ると仮定

このあと塗りすすめていくと、図3の場合、そのうち破綻してしまう (図4)。

図4 図3の状態から論理的に塗りすすめていく

たとえば、[2 2 1 2]の行(上から1番目の行)は、×が打たれていないマスが[2 1 2 2]となっていて明らかにうまく塗れない。[2 2 2]の列(左から1番目の列)も、空いた4マスに残った[2 2]を塗るのは不可能だ。

ということで、図2の状態に戻して再び考え始める。とはいっても、[3 2 1]の行(上から4番目の行)と[2 2 2]の列(左から1番目の列)の交点に×をつけられるくらいだ (図5)。

図5 図2の状態に戻してまた塗り始める

ただ、ここで×がつけられたのが大きい。いま[2 2 2]の列は3つの×で4つに分割されている。連続したマスは最大でも4であり、分割された空間に[2 2]が入ることはない。つまり、分割された4つの連続したマスのうちどれか3つにそれぞれ[2]が塗られることになる。そこで、分割された一番下に注目する。ここは2マスしか空いていないから、ほかの空間が塗られることを仮定するより多くのマスを塗ることができる (図6)。

図6 左下隅のタテ2マスが塗られると仮定

はたしてこれが大正解で、ここまでくると正解まで一気に塗り進めることができる (図7)

図7 完成

ということで、解いていく中で仮塗りを2回しないといけなかった。

個人的には、仮塗りをせずに解けるほうがイラストロジックとして完成度が高いと思う。消しゴムなしで解きたい。

タグ

金やる

金くれについて 軽犯罪法違反だから閉鎖をお薦めしますというメールが来たらしいので、軽犯罪法に問われなくなるソリューションを考えた。

つまり、「金やる」というサービスを開設する。「金やる」は、金をやりたい人がサイトに載っている口座番号に向けて金をやることができる次世代ネット寄付プラットフォームということにする。これなら件のメールをよこした人も大満足だし、昨今上から目線が大人気なので「金やる」なんてサービス名はヒット間違いなし。ほげ。

タグ

やっと神保町二郎に行けた

二郎の中でも評判の高い神田神保町店へ行ってきた。開店30分前に着いたのにすでに20人も並んでいて、結局食券を買うまで1時間かかった。いくら土曜の朝だからって人大杉で、それだけ本当に評価が高いのだろう。普段行く桜台駅前店では、もうきつくなってきたので小豚も頼んでいないのだけど、今日はせっかくの機会なので小豚を頼んで、ニンニクマシマシヤサイとした

まず、なにより豚が柔らかくてかなり美味い! これはすごい。最近桜台では豚の劣化が気になっていて、久しぶりに二郎らしい豚を食べられたと思った。あと、桜台だと客のコールを訊くのは完全に助手の役割になっていて店主はもっぱらラーメン作りだけど、ここは店主に近い客には店主がコールを訊いていたのが好印象だった。もっとも、桜台のイケメン店主に直接訊かれると客としてもすわりが悪いので、そこは店ごとの空気があると思う。

麺は桜台に比べるとやや細めで、カエシが底に沈んでしまっていて上のほうは味があまりなかったけど、本当においしかったし、やっぱり評判通りで来てよかった。店を出ると、行列は大通りの突き当りまで延びていた。どう見ても40人以上いる。さすがにどうかと思った。

二郎はおいしいけどどうみても太るので、せめて隔週にしようとしているのだけど、二郎に行かない週は大に行ってたり、今月はもう3回も行ってしまっている。毒に中っている。それでも3月の健診からは12kg痩せていてやっとBMIが25を切りそうな感じになってきた。バランスを取りつつおいしくいただいていきたい。

タグ

Favotterにcensoredされた

最近Favotterにcensored機能が実装され、Google AdSenseに怒られるような単語が<censored>に置換されて表示されるようになった。私の場合、いわゆる変態クラスタと呼ばれているような方々がなさっているような偽悪的な投稿は行っておらず、このようなフィルタに引っかかるはずはないと思っていたのだが、LLTVのときに投稿した幼女censoredされてしまっていたのに気づいた。なんということでしょう。いや、よくよく検索してみたら何度もそういう投稿をしていたのでそういう投稿をした自分がアレなんだけど、はじめてFavotter上で自分の投稿がcensoredされたのを見てちょっとショックだった

ところで、件の投稿をふぁぼったーで見ると、「幼女」がcensoredされていません。そりゃあそうですよね。「幼女」がcensoredされたことを嘆いているのに、肝心の「幼女」がcensoredされたらFavotter上で見たときに文意がつかめません。これはふぁぼったーが私のそういう気持ちを酌んでくれている、というわけではなくて数値文字参照で書き込んだからです。変換には「使えない文字」のHTML4文字参照フォームを利用し、これにより「幼女」の数値文字参照である&#24188;&#22899;を得ました。この文字列をTwitterに投稿すると、画面上には「幼女」と表示されますが、データとしてはあくまで"&#24188;&#22899;"なので、Favotterの検閲フィルタを突破できたということです。アドセンスの中の人が数値文字参照まで気にするかどうか知りませんが、もし数値文字参照に置換すればオッケーなのだとしたら、機械も人間もWin-Winですね。

ところで、第9回文学フリマで頒布される『つヤ部報vol2』に、ふぁぼったー <censored> 徹底解説 ―これが卑猥語フィルタだ― @ono_matopeという記事が掲載されているようですね。楽しみです。

タグ

研究室の合宿で全自動メシをクソBotに変換しました

今日まで2日間、研究室の合宿でグランビュー熱海というコンドミニアムで開発合宿をしていた。これがとてもよくて、先生が授業で営業を精力的に行ったためとかでまず女子の割合が高い。そしてご飯が自動的に出てくる。屋上に風呂があって見晴らしがいい。すぐ前が海でリアルが爆発している。そんな中OB共は怠惰な時間をすごしておりまったく救いようがない。

たとえば私などはyozbotというクソみたいなBotを作成するのみで、これは私の今までの投稿をなんかマルコフ連鎖? とかいうので再構築しているもので、ていうかまず実装したアルゴリズムがマルコフ連鎖になっているのかも不明で、ただPythonの勉強をしたかっただけだった。

まず、自分のタイムラインは古い分がすでにサーバにあるので、新しい部分を1時間おきくらいにでも取得してくる必要がある。こんな感じ:

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import MySQLdb
import twitter
import time
import calendar

api = twitter.Api()
statuses = api.GetUserTimeline('youzaka')

def tosqltime(created_at):
    return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(calendar.timegm(time.strptime(created_at, '%a %b %d %H:%M:%S +0000 %Y'))))

data = [(s.id, s.text.encode('utf_8'), tosqltime(s.created_at)) for s in statuses]

con = MySQLdb.connect(db='*****', user='*****', passwd='*****')
cur = con.cursor()

for (id, body, date) in data:
    sql = "insert ignore into tweets(id, body, date, treated) values(%s, %s, %s, 0)"
    cur.execute(sql, (id, body, date))
cur.close()

タイムライン取得APIを叩いて、IDと本文と日付を取得して、それをDBにつっこんでいるだけ。tweetsテーブルのidカラムは主キーになっているので、IGNOREをつけておく。15行目で、取得したtextをそのまま使っていたらDBにつっこむところでUnicodeEncodeErrorが出た。Pythonはここら辺が慣れない。

マルコフ連鎖みたいなのが以下:

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import MeCab
import MySQLdb
import re
import random

mecab = MeCab.Tagger('-Owakati')
con = MySQLdb.connect(db='*****', user='*****', passwd='*****')
cur = con.cursor()
sql = 'select id, body from tweets where treated = 0'
cur.execute(sql)
rows = cur.fetchall()
id_list = []
markov = {}

for row in rows:
    id_list.append(row[0])
    body = row[1]
    first = ""
    second = ""
    body = re.sub('https?://[^ ]+', '', body)

    if body == '':
        continue;

    parsed = mecab.parse(body.rstrip()).split(' ')
    parsed.insert(0, '[start]')
    parsed[-1] = '[end]'
    for third in parsed:
        if first and second:
            if (first, second) not in markov:
                markov[(first, second)] = []
            markov[(first, second)].append(third)
        first, second = second, third
cur.close()

cur = con.cursor()
for (first, second), list in markov.items():
    for third in list:
        sql = "insert into markov(first, second, third) values(%s, %s, %s)"
        cur.execute(sql, (first, second, third))
cur.close()

cur = con.cursor()
for id in id_list:
    sql = "update tweets set treated = 1 where id = %s"
    cur.execute(sql, id)
cur.close()

取得したTwitterの書き込みのうち未処理のものについて、URLを削除してMecabに投げて形態素の配列を取得して、先頭と最後にそれぞれ[start]、[end]のマークをつける。で、どんどんDBに投げていく。たとえば、「研究室の合宿で全自動メシをクソBotに変換しました」を形態素分解すると「研究室」「の」「合宿」「で」「全」「自動」「メシ」「を」「クソ」「Bot」「に」「変換」「し」「まし」「た」となる (デフォルトの辞書の場合) ので、この文章から以下のルールを得られる:

startsecondthird
[start]研究
研究
合宿
合宿
合宿
自動
自動メシ
自動メシ
メシクソ
クソBot
クソBot
Bot変換
変換
変換まし
まし
まし[end]

このルールから文章を作成してTwitterに投げるところはこんな感じ:

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import MySQLdb
import re
import twitter

con = MySQLdb.connect(db='*****', user='*****', passwd='*****')
cur = con.cursor()
sql = "select second, third from markov where first='[start]' order by rand() limit 1"
cur.execute(sql)
row = cur.fetchone()

first = row[0]
second = row[1]
cur.close()

if re.search('[a-zA-Z]$', first) and re.search('^[a-zA-Z]', second):
    text = first + ' ' + second
else:
    text = first + second

while True:
    cur = con.cursor()
    sql = "select third from markov where first = %s and second = %s order by rand() limit 1"
    cur.execute(sql, (first, second))
    row = cur.fetchone()
    first, second = second, row[0]

    if second == '[end]':
        break

    if re.search('[a-zA-Z]$', text) and re.search('^[a-zA-Z]', second):
        text = text + ' ' + second
    else:
        text += second

api = twitter.Api('yozbot', '*****')
api.PostUpdate(text.decode('utf_8'))

まず、最初の2語をランダムに選んで、その2語から次の単語をランダムに繋げていって、[end]が出たら終わりという単純なもの。次の単語を選ぶのに前の2語を使っているので、バリエーションの少ない組み合わせだとそのままもとの投稿が出力されるということもありえて、そこらへんは調整したい。「北大」→「クラスタ」の連なりが1例しかなかったので、「北大」が選択された場合は100%「北大クラスタ爆発しろ」になってしまう。

firstsecondthird
むしろクラスタ
特定クラスタ
所属クラスタ以外
所属クラスタごと
からクラスタ
操作クラスタ[end]
関東クラスタ
関東クラスタ
クラスタ解説
クラスタ
北大クラスタ爆発
クラスタ
クラスタ
クラスタ
筑波大クラスタ
筑波大クラスタよく
ないクラスタ
クラスタ
クラスタより
クラスタっぽい
クラスタ
クラスタ
クラスタ
クラスタ
クラスタ
会話クラスタ
クラスタ
京大クラスタ
クラスタ
働くクラスタ
高専クラスタ?
新人クラスタ

これ、「大」→「クラスタ」ってなっているもののほとんどが「静大クラスタ」だったはずで、「京大」「北大」はあるのに「静大」はなくて辞書漏れ悲しいですという話ですね。

タグ

牛丼キングを食べに行った

噂の牛丼キングを食べに新宿南口のすき家へ行った。新宿駅の南口から出てすき家までの道中に吉野家が2軒もあったのが印象的だった。ていうか「牛丼キングを食べに行く会」ということで3人で行ったはずなのに、結局牛丼キングを頼んだのが私だけだったのがちょっとアレだった。

量はメガ牛丼の2倍、並の6倍ということで、普通に半分まで食べた時点でお腹一杯だし、これ以上同じ味というのもきついという感じになったけど、ご飯はこれを残してはならないと育てられたのでがんばって完食した。食器を下げに来た店員が空の丼を見て笑っていたのがアレだった。よもや完食しまいと思っていたな。

関係ないんだけど、近所のラーメン大は野菜の量が「少し多め」、「多め」、「増し」などとあって「マシマシ」は明示されていない。増しでも二郎のそれより十分多いのだけど、マシマシにすると野菜がこれの2倍来る。当然同じ丼には乗らないので、ラーメン+増し分の野菜が乗ったものと野菜だけが乗ったものの2つが供される。あのときは、店員から何も知らずに二郎のつもりでマシマシを頼みやがってという空気を感じたので、当然のように全部食べた。残したら負けだし、そしたら恥ずかしくて大に行けなくなってしまう。隣の人は、メニューにあるのはあくまで「増し」までなので「増し」を頼むとああなるのかと勘違いしたみたいで「少し多め」にしていた。すみません。二郎に比べて丼が小さいし麺も細いので、意外と無理なレベルではない。さすがに昴神角ふじの野菜本増しとか麺香房暖々の野菜多めのレベルになるとごめんなさいだけど。

タグ

光が丘公園でジョギングしている

最近は週末に光が丘公園ジョギングコースでジョギングするようにしている。だいたい3kmのコースになっていて、ヘタレなので20分弱で1周して40分強休憩するみたいなペースで3周くらい走って帰る感じ。コースは緑が豊かで、走っていて気持ちいい。途中バーベキュー場の横を通るところがあって、そこに差し掛かると心が折れそうになる。

タグ

行列をスキップできる権利

らーめん大練馬店が開店して、通常600円のラーメンが100円で食べられるとのことでアホみたいな行列になっている。開店直後でも80人以上並んでいたりする。そりゃ100円は安いけど、高々500円引きですよ。吉野家コピペでは150円引きのために普段来ていない吉野家に家族連れで来ている半可通が揶揄されているが、500円のために数時間並ぶというのも同様に揶揄されてしかるべきだ。

たとえば、行列をスキップしていきなり専用席に座れる権利を500円で用意して、最後尾グループに数枚配布するとする。この権利は自由に売買できて、値段も自由に決められるとする。どう値段が変化してどう売買されるのだろうか。

まず、このチケットを受け取った人は、そもそもラーメンを食べに来ているので早速権利を行使するだろう。しかし、周りの人も同じくラーメンが食べたいはずなので、もしかしたら600円とか1000円とかで買いたいと思うかもしれない。でも良く考えるとそれ買っちゃうと定価より高いし、かといって何時間も並ぶのやだし…みたいな葛藤が飛び交うようになって、観察する分にはおもしろいかもしれない。そこから適切な値段とかも見えてくるだろうしね。実際どうなるんだろう。

タグ

普通にphp-mysqliを使ってCRUDするコード例

普通にMysqliを使ってみます。特別なネタがあるわけではありません。リファレンスが分かりにくいという声を近くで聞いたので、まあだいたいこんな風にすれば良いんじゃない? というのをメモとして残しておきます。

個人ユースのデータベースといえばやはり住所録ということで、これにデータを出し入れする部分だけMysqliを使って書いてみます。住所録のデータスキームはこんな感じ:

create table addresses(
    id int unsigned not null primary key auto_increment,
    name varchar(32) not null,
    kana varchar(64) not null,
    zipcode char(8) not null,
    address1 varchar(64) not null,
    address2 varchar(64) not null,
    tel char(13) not null,
    email varchar(255) not null,
    index(kana)
);

IDを指定してデータを取得する関数は、だいたいこんな感じに書ける (実際はもっとまじめにエラー処理とかすべき):

// インスタンス変数として$con = new Mysqli("ホスト名", "ユーザ名", "パスワード", "データベース名");が存在することを前提としている。以下同様
function getAddressById($id) {
    $result = array();
    $sql = "select name, kana, zipcode, address1, address2, tel, email from addresses where id = ?";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("i", $id);
    $stmt->execute();
    $stmt->bind_result($name, $kana, $zipcode, $address1, $address2, $tel, $email);
    if($stmt->fetch()) {
        $result = compact("id", "name", "kana", "zipcode", "address1", "address2", "tel", "email");
    }
    $stmt->close();
    return $result;
}

1ページに20件表示させるとして、その20件分を持ってくる関数はこんな感じに書ける:

// $page: ページ数。最初のページは1
function getAddresses($page, $limit = 20) {
    $result = array();
    $sql = "select id, name, kana, zipcode, address1, address2, tel, email from addresses order by kana limit ?, ?";
    $offset = ($page - 1) * $limit;
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("ii", $offset, $limit);
    $stmt->execute();
    $stmt->bind_result($id, $name, $kana, $zipcode, $address1, $address2, $tel, $email);
    while($stmt->fetch()) {
        $result[] = compact("id", "name", "kana", "zipcode", "address1", "address2", "tel", "email");
    }
    $stmt->close();
    return $result;
}

指定したカナに前方一致するものを取得したいならこんな感じ:

function getAddressesByKana($kana, $page, $limit = 20) {
    $result = array();
    $sql = "select id, name, kana, zipcode, address1, address2, tel, email from addresses where kana like ? order by kana limit ?, ?";
    $offset = ($page - 1) * $limit;
    $like = "{$kana}%";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("sii", $like, $offset, $limit);
    $stmt->execute();
    $stmt->bind_result($id, $name, $kana, $zipcode, $address1, $address2, $tel, $email);
    while($stmt->fetch()) {
        $result[] = compact("id", "name", "kana", "zipcode", "address1", "address2", "tel", "email");
    }
    $stmt->close();
    return $result;
}

新しいエントリを挿入するのはこんな感じ:

// $addressはnameやkanaなどのキーがある連想配列
function insert($address) {
    $result = 0;
    $sql = "insert into addresses(name, kana, zipcode, address1, address2, tel, email) values(?,?,?,?,?,?,?)";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("sssssss", $name, $kana, $zipcode, $address1, $address2, $tel, $email);
    extract($address, EXTR_OVERWRITE);
    $stmt->execute();
    $result = $this->con->insert_id;
    $stmt->close();
    return $result;
}

まとめて挿入するなら:

// $addressesはnameやkanaなどのキーがある連想配列の配列
function insertAll($addresses) {
    $result = array();
    $sql = "insert into addresses(name, kana, zipcode, address1, address2, tel, email) values(?,?,?,?,?,?,?)";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("sssssss", $name, $kana, $zipcode, $address1, $address2, $tel, $email);
    foreach($addresses as $address) {
        extract($address, EXTR_OVERWRITE);
        $stmt->execute();
        $result[] = $this->con->insert_id;
    }
    $stmt->close();
    return $result;
}

更新するなら:

function update($address) {
    $sql = "update addresses set name = ?, kana = ?, zipcode = ?, address1 = ?, address2 = ?, tel = ?, email = ? where id = ?";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("sssssssi", $name, $kana, $zipcode, $address1, $address2, $tel, $email, $id);
    extract($address, EXTR_OVERWRITE);
    $stmt->execute();
    $stmt->close();
}

ついでに削除:

function delete($id) {
    $sql = "delete from addresses where id = ?";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("i", $id);
    $stmt->execute();
    $stmt->close();
}

function deleteAll($id_list) {
    $sql = "delete from addresses where id = ?";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("i", $id);
    foreach($id_list as $id) {
        $stmt->execute();
    }
    $stmt->close();
}

実際はフレームワークとかでささっとやってしまう部分かもしれませんが、あえてフルスクラッチするならこんな感じでしょうか。

タグ
© 2001-2010 Chisa YOUZAKA. Some rights reserved.