FLV::Info で ustream の FLV ファイル情報をとろうとするとエラーになる件
ustream の FLV をダウンロードして音声変換する方法は、typesterさんのとこに詳しく書いてある わけですが、sox に渡すときにサンプルレートを指定する必要があったりします。
で、これを Plagger プラグインで実現するためには、何らかの方法で元ファイルのサンプルレートを取得しないといけないわけで、FLV::Info でできそうだな、と思ったところ
Failed to read FLV file: Tag size is too small (0) at byte 193 (0xc1) at /usr/local/lib/perl5/site_perl/5.8.7/FLV/Tag.pm line 81.
といったエラーが出ます。色々調べてみたところ、ustream の FLV ファイル中に、ボディが 0 のオーディオタグが存在するから、ということがわかったので、パッチを書いて RT に投げておきました。
パッチ自体は数行コメントアウトして1行追加しただけ、という簡単なものなのですが、このパッチを書くために、FLVのファイルフォーマット を調べて、音声フォーマットを取得する Perl スクリプトを自分で書いてみたりと、えらく回り道しました。せっかくなんでスクリプトを載せておきます。スクリプト作成にあたって、nelly2pcm のソースも参考にしています。
#!/usr/bin/perl
use strict;
use warnings;
use Readonly;
Readonly my $FLV_TAG_TYPE_AUDIO => 0x08;
Readonly my $AUDIO_MASK_STEREO => 0x01;
Readonly my $AUDIO_MASK_16bit => 0x02;
Readonly my $AUDIO_MASK_RATE => 0x0c;
Readonly my $AUDIO_RATE_5point5KHZ => 0x00;
Readonly my $AUDIO_RATE_11KHZ => 0x04;
Readonly my $AUDIO_RATE_22KHZ => 0x08;
Readonly my $AUDIO_RATE_44KHZ => 0x0c;
Readonly my $AUDIO_MASK_FORMAT => 0xf0;
Readonly my $AUDIO_FORMAT_UNCOMPRESSED => 0x00;
Readonly my $AUDIO_FORMAT_ADPCM => 0x10;
Readonly my $AUDIO_FORMAT_MP3 => 0x20;
Readonly my $AUDIO_FORMAT_NELLYMOSER_8KHZ_MONO => 0x50;
Readonly my $AUDIO_FORMAT_NELLYMOSER => 0x60;
Readonly my %AUDIO_RATE => (
$AUDIO_RATE_5point5KHZ => 5.5,
$AUDIO_RATE_11KHZ => 11,
$AUDIO_RATE_22KHZ => 22,
$AUDIO_RATE_44KHZ => 44,
);
Readonly my %AUDIO_FORMAT => (
$AUDIO_FORMAT_UNCOMPRESSED => 'Uncompressed',
$AUDIO_FORMAT_ADPCM => 'ADPCM',
$AUDIO_FORMAT_MP3 => 'MP3',
$AUDIO_FORMAT_NELLYMOSER_8KHZ_MONO => 'Nellymoser 8kHz mono',
$AUDIO_FORMAT_NELLYMOSER => 'Nellymoser',
);
my $file = shift;
open my $fh, '<', $file;
my $header = read_header($fh);
while ( ! eof $fh ) {
my $tag = read_tag($fh);
if ( $tag->{type} == $FLV_TAG_TYPE_AUDIO and $tag->{length} ) {
my $first_byte = $tag->{first_byte};
my $stereo = $first_byte & $AUDIO_MASK_STEREO;
my $audio_size = $first_byte & $AUDIO_MASK_16bit ? 16 : 8;
my $audio_rate = $AUDIO_RATE{ $first_byte & $AUDIO_MASK_RATE } || 'unknown';
my $audio_format = $AUDIO_FORMAT{ $first_byte & $AUDIO_MASK_FORMAT } || 'unknown';
print 'audio type: ' . ( $stereo ? 'stereo' : 'mono' ) . "\n";
print "audio size: $audio_size bit\n";
print "audio rate: $audio_rate Hz\n";
print "audio format: $audio_format\n";
last;
}
}
sub read_header {
my $fh = shift;
read $fh, my $sig, 3;
read $fh, my $version, 1;
read $fh, my $flag, 1;
read $fh, my $offset, 4;
read $fh, my $prev_tag, 4;
return {
sig => $sig,
version => unpack('H*', $version),
flag => unpack('H*', $flag),
offset => unpack('H*', $offset),
};
}
sub read_tag {
my $fh = shift;
read $fh, my $type, 1;
read $fh, my $length, 3;
read $fh, my $timestamp, 3;
read $fh, my $timestamp_extended, 1;
read $fh, my $stream_id, 3;
read $fh, my $first_byte, 1;
my $pos = hex unpack 'H*', $length;
seek $fh, $pos - 1, 1;
read $fh, my $prev_tag, 4;
return {
type => unpack('C', $type),
length => $pos,
first_byte => unpack('C', $first_byte),
};
}
こんな感じで FLV ファイルの音声情報を表示します。
$ ./get_flv_info.pl broadcast_35957_1191232400660.flv
audio type: mono
audio size: 16 bit
audio rate: 11 Hz
audio format: Nellymoser
あとは Plagger プラグインを書くだけ、という感じなのですが、リビドーが沸いてこないのでちょっと保留。FFmpeg で Nellymoser に対応する予定がある(既に SVN HEAD には Nellymoser という文字列がコード中にある)ようなので、それまで待ってもいいかなー、と。
音声変換するプラグイン Filter::Audio は coderepos にあげてありますので、俺が対応してやるぜ、という方はお好きなようにいじってください。
Shibuya.pm テクニカルトーク #8 でしゃべってきました
プレゼン途中にバッテリーが切れるという失態をやらかしたため、yusukebeさん言うところの「お口で」のみのプレゼンとなってしまいました。ごめんなさい。会場でお見せできなかった幻の資料はこちらにアップしておきます。
Thinkpad X60sはバッテリー残量表示が1時間になっていても、プレゼンやると2,3分でバッテリー切れますので、みなさんご注意下さい。
Trac の SearchHyperEstraierPlugin を Trac 0.10.x で動かすパッチ
SearchHyperEstraierPlugin が、Trac 0.10.x というか、うちの環境で動かないので、パッチ書いた。初 Python プログラミング。(というほどのものじゃないけど。)
=== searchhyperestraier/searchhyperestraier.py
==================================================================
--- searchhyperestraier/searchhyperestraier.py (revision 892)
+++ searchhyperestraier/searchhyperestraier.py (local)
@@ -2,8 +2,9 @@
# SearchRepositoryWithNamazu plugin
from trac.core import *
-from trac.Search import ISearchSource, query_to_sql, shorten_result
+from trac.Search import ISearchSource, shorten_result
from trac.util import NaivePopen
+from trac.util.text import to_unicode
from StringIO import StringIO
import urllib
import time
@@ -44,9 +45,9 @@
browse_trac = self.env.config.get('searchhyperestraier', 'browse_trac','enabled')
- cmdline = "%s %s %s %s" % (estcmd_path,estcmd_arg,index_path,unicode(query,'utf-8').encode(estcmd_encode))
+ cmdline = "%s %s %s %s" % (estcmd_path,estcmd_arg,index_path,' '.join(query))
self.log.debug('SearchHyperEstraier:%s' % cmdline)
- np = NaivePopen(cmdline)
+ np = NaivePopen( cmdline.encode(estcmd_encode) )
#self.log.debug('Result:%s' % unicode(np.out,'utf-8').encode('mbcs'))
#self.log.debug('Result:%s' % np.out)
if np.errorlevel or np.err:
@@ -74,7 +75,7 @@
attrelem_array = element.getElementsByTagName("attribute")
for attrelem in attrelem_array:
attr_name = attrelem.getAttribute("name")
- attr_value = unicode(attrelem.getAttribute("value")).encode('utf-8')
+ attr_value = to_unicode(attrelem.getAttribute("value"))
#URLとタイトルを生成
if attr_name == "_lreal":
attr_value=attr_value[len(replace_left):].replace("\\","/")
エラーが出るたびに、適当にいじってたら動いた、という感じ。なので、こんなんでいいのかはよく分からない。
Pushmi つかってます & 技術者募集中 at 福岡 3
弊社 は東京と福岡にオフィスがあり、それぞれの拠点に Subversion + Trac 環境を構築し、OpenVPN により VPN 接続してお互いの開発状況を公開しています。サービス絡みの開発は基本的に、東京は東京、福岡は福岡で完結しているので、この方式で問題はないのですが、サーバ管理関連のスクリプトなんかは、東京と福岡で共通するものが多いため、別々の SVN リポジトリで管理されてると不便なんですよね。かといって、どちらかの拠点だけしかリポジトリがないと、VPN の障害発生時に、リポジトリのない拠点からはまったくアクセスができない、という困ったことになってしまいます。
そこで、SVN リポジトリレプリケーションツール Pushmi を導入してみました。詳細は YAPC::Asia での 作者 Cl Kao によるスピーチ動画 を見て頂くとして、ここでは行った設定についてメモを残しておきます。ほとんど perldoc Pushmi の内容と同じですが。
まず当然 Pushmi のインストールが必要です。これの手順は省略。また、より良いアトミックロック実現のために memcached を利用している、ということなのでインストールしておきます。これも手順は省略。
memcached を起動します。
$ sudo /usr/bin/memcached -p 8123 -dP /var/run/memcached.pid -u nobody
/etc/pushmi.conf を設定します。弊社の環境では memcached のポートのみ指定してます。
authproxy_port: 8123
ミラーリポジトリを作成します。
$ pushmi mirror /var/db/my-local-mirror http://master.repository/svn
Mirror initialized.
ミラーリポジトリとマスターリポジトリを同期します。
$ pushmi sync /var/db/my-local-mirror
Retrieving log information from 1 to 62
ミラーリポジトリは、svnadmin create で作成するのと同様なディレクトリ、ファイル構造になってますが、Pushmi 用の pre-commit スクリプトと post-commit スクリプトが置かれている、というところが異なります。
pre-commit では以下の様なコマンドが実行され、ミラーリポジトリにコミットされた内容を、マスターリポジトリにコミットしに行きます。
/usr/bin/pushmi runhook $1 --txnname $2
post-commit では以下の様なコマンドが実行され、ミラーとマスターの整合性を確認しています。
/usr/bin/pushmi verify $1 --revision $2
Apache + WebDAV で SVN リポジトリにアクセスするために、以下の様な設定を Apache で行います。
<Location /svn/server>
PerlSetVar SVNPath /var/db/my-local-mirror
PerlSetVar Pushmi /usr/bin/pushmi
PerlSetVar PushmiConfig /etc/pushmi.conf
<LimitExcept GET PROPFIND OPTIONS REPORT>
AuthName "mirrored private area"
AuthType Basic
Require valid-user
AuthLDAPURL ldap://localhost:389/ou=people,o=paperboy?uid?sub?(objectclass=*)
PerlAuthenHandler Pushmi::Apache::AuthCommit
</LimitExcept>
</Location>
設定見て分かると思いますが、mod_perl を利用していて、Apche2 + mod_perl の環境が必要です。Apache は 2.0 系でも 2.2 系でも大丈夫なようです。弊社では 2.0 系を利用しています。
特にポイントとなるのは、
PerlAuthenHandler Pushmi::Apache::AuthCommit
の部分で、認証で渡されたユーザ名、パスワードをこのモジュールで memcached にキャッシュしておき、マスタリポジトリへのコミット時の認証に利用します。Apache 2.2 系の場合には、以下の様に設定します。
AuthBasicProvider Pushmi::Apache::RelayProvider
この状態では、ミラーへのコミットは即マスターに反映されますが、マスターへのコミットはミラーに反映されませんので、以下の様な cron 設定を行い、5分おきにマスターとミラーを同期するようにします。
*/5 * * * * /usr/bin/pushmi sync --nowait /var/db/my-local-mirror
今のところこれで問題なく動いています。
話変わりまして、弊社福岡支社では、プログラマ と サーバエンジニア を募集しています。Pushmi を実戦で使ってみたい!という方はぜひ。メガネ女子プログラマもいますよ。(東京本社にもいます。人妻ですが。)
FizzBuzz アセンブラ版 for x86/Linux
竹迫さん、Yappo さん に触発されて、FizzBuzz アセンブラ版 for x86/Linux をつくってみた。
20年ほど前に Z80 でアセンブラをちょっとかじった程度の知識しかないので、ベストには程遠いコードだと思います。だれかもっといいコードを教えてください。
最初竹迫さんのコードと同じように書けるかな、と思ったのですが、Windows とちがって、画面に表示するだけで EAX, EBX, ECX, EDX レジスタ使うので、竹迫さんのように BX レジスタを見張り役に、CX レジスタをカウンタに、ってことができませんでした。
また、とりあえず書いただけで疲れたので、コードゴルフにチャレンジする気力はありません。
global _start
_start:
mov si, 0
mawasu:
call space
inc si
mov ax, si
mov di, 3
xor edx, edx
div di
cmp dx, 0
je pfizz
mov ax, si
mov di, 5
xor edx, edx
div di
cmp dx, 0
je pbuzz
call num
cmp si, 100
jb mawasu
pfizz:
call fizz
mov ax, si
mov di, 5
xor edx, edx
div di
cmp dx, 0
je pbuzz
jmp mawasu
pbuzz:
call buzz
jmp mawasu
num:
mov ax, si
mov di, 100
cmp ax, di
jnge num2
jmp end
num2:
mov di, 10
cmp ax, di
jnge num3
xor edx, edx
div di
add ax, 48
call print
num3:
mov ax, si
mov di, 10
xor edx, edx
div di
mov al, dl
add al, 48
call print
ret
fizz:
mov al, 0x46
call print
mov al, 0x69
call print
mov al, 0x7a
call print
call print
ret
buzz:
mov al, 0x42
call print
mov al, 0x75
call print
mov al, 0x7a
call print
call print
ret
space:
mov al, 0x20
call print
ret
print:
push eax
mov eax, 4
mov ebx, 1
mov ecx, esp
mov edx, 1
int 0x80
pop eax
ret
end:
mov al, 0x0a
call print
mov al, 1
mov bl, 0
int 0x80
nasm を使ってこんな感じで動かすことができます。
$ nasm -f elf fizzbuzz.asm
$ ld -s -o fizzbuzz fizzbuzz.o
$ ./fizzbuzz

書いていてアセンブラの TMTOWTDI っぷりは Perl の比じゃないと思った。
Re: DBICとDBIx::Class::Schema::Loader 僕のいろいろな勘違い 2
ブログが続かないわけ | DBICとDBIx::Class::Schema::Loader 僕のいろいろな勘違い にて、
とはいえ、僕の稼働中のアプリはすでに手動型のSchema で動いている。スキーマを作り直したら、リレーションの設定を全てしなおさなければならないので、それは現実的じゃない。inflate, deflate の指定は、やっぱりすべてのSchema にかかなきゃだめそうだ。
とあったので、これに関して少し楽ができる方法をコメントしようと思ったけれど、コメント欄ではうまく伝えられる自信がないので、こちらで書いてみることにしました。
load_components で 読み込む方法
DBIx::Class::Schema::Loader ではなく DBIx::Class::Schema を継承したスキーマの場合には、各スキーマファイルに以下の様に書いてあげれば OK です。(既にご存知かもしれませんが。)
package My::Schema::Table;
__PACKAGE__->load_components(
"InflateColumn::DateTime",
"PK::Auto",
"Core",
);
すべてのスキーマファイルに書かなきゃいけないことには変わりありませんが、各カラムに inflate/deflate を設定するよりははるかに楽だと思います。
make_schema_at を使う方法
make_schema_at でスキーマファイルを生成しているのであれば、こんな感じでリレーションだけ定義した ./tmp/lib/My/Schema/Table.pm をまず用意します。
package My::Schema::Table;
__PACKAGE__->belongs_to(
realation => 'My::Schema::OtherTable
{ 'foreign.id' => 'self.other_table_id' },
);
1;
でもって、こんなスクリプトを実行します。
#!/usr/bin/perl
use strict;
use warnings;
use lib qw( ./tmp/lib );
use Carp;
use DBIx::Class::Schema::Loader qw( make_schema_at dump_to_dir:lib dump_overwrite );
make_schema_at(
'My::Schema::Table',
{
components => [qw/ ResultSetManager UTF8Columns InflateColumn::DateTime /],
dump_overwrite => 1,
},
['dbi:mysql:dbname' ,'user', 'password'],
);
そうすると、lib/My/Schema/Table.pm に以下の内容を吐き出してくれます。
package My::Schema::Table;
# Created by DBIx::Class::Schema::Loader v0.03009 @ 2007-04-26 18:21:23
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components(
"ResultSetManager",
"UTF8Columns",
"InflateColumn::DateTime",
"PK::Auto",
"Core",
);
__PACKAGE__->table("table");
__PACKAGE__->add_columns(
"id",
{ data_type => "INT", default_value => undef, is_nullable => 0, size => 11 },
"other_table_id",
{ data_type => "INT", default_value => 0, is_nullable => 0, size => 11 },
"date",
{ data_type => "DATE", default_value => undef, is_nullable => 1, size => 10 },
__PACKAGE__->set_primary_key("id");
# These lines loaded from user-supplied external file:
package My::Schema::Table;
__PACKAGE__->belongs_to(
employees => 'My::Schema::Table',
{ 'foreign.id' => 'self.other_table_id' },
);
1;
# End of lines loaded from user-supplied external file
この方法を使うと、スキーマを作り直しても、リレーションの再設定をしなくて済みます。
手動でやるのが楽なのか、上の方法のいずれかを使うのが楽なのかは、状況にもよると思いますが、こんな方法もありますよ、ということで、ご参考になれば幸いです。
SVN::TracWiki #2
SVN::TracWiki (svn repos) をアップデート。以下の機能を実現するパッチを、Assurer の開発にも参加してくれている franck が送ってくれました。
- Plugin::Extract::Pod で Perl プログラムのコミット時に、pod を自動で抽出して Wiki にアップ。
- xmlrpc プラグインがなくても Wiki ポストができる。
Trac はソースコード自体の検索ができないので、当然 pod の内容も検索することができないのですが、このプラグインにより、pod の検索ができるようになります。
また、検索だけではなく、こんな感じ で pod を Trac の Wiki フォーマットに変換してキレイに表示してくれます。
試しに手元にあった XML::Atom をつっこんでみると、こんな感じ になります。
あと、File::Extract 内部で利用している File::MMagic::XS が謎の Segmentation Fault で落ちるので、File::MMagic を使うように強引に書き換えたり。
SVN::TracWiki #1
SVN::TracWiki (svn repos) について、ブクマコメントで miyagawa さんから「File::Extract?」というアドバイス?を頂きましたので、テキスト抽出部分を File::Extract ベースに書き換えてみました。
おかげでプラグインまわりの処理が少しすっきりした上に、File::Extract で対応しているファイルフォーマットであれば、SVN::TracWiki 用のプラグインを書かなくてもテキスト抽出ができるようになりました。(Excel で試しましたが、ばっちり動作しました。)
SVN::TracWiki #0
SVN::TracWiki というツールをつくってみました。svn repos はこちら。
何をするものかというと、Subversion の post-commit スクリプトとして動作して、コミットされたファイルからテキストを抽出、そのファイルの実体へのリンクを付加して、Trac の Wiki へ自動ポストするというもの。
具体的な例としては、PowerPoint ファイルをコミットしたら、そのファイルからテキストのみを抽出して Wiki へ自動ポスト。こんな感じで。
これで何がうれしいかというと、Subversion で管理している PowerPoint ファイルを、Trac 上で検索ができるようになります。こんな感じですね。元ファイルへのリンクもあるので、検索して元ファイルを開いて読む、ってことが簡単にできます。
例によって YAMLで設定 and プラガブル。ファイルからテキストを抽出する部分がプラグインになっていて、簡単に拡張できるようにしています。
現在は PowerPoint 用フィルタプラグインしかないのですが、以下の様なコードになっていて、フィルタ対象ファイルの MIME タイプを register() で指定、テキスト抽出ルーチンを filter() に記述、という感じで書きます。
package SVN::TracWiki::Plugin::Filter::PowerPoint;
use strict;
use warnings;
use base qw( SVN::TracWiki::Plugin::Filter );
use Encode;
sub register {
my $self = shift;
$self->register_mime_types( qw! application/vnd.ms-powerpoint !);
}
sub filter {
my ( $self, $file ) = @_;
my $html = `/usr/local/bin/ppthtml $file`;
my $text = $self->strip_html($html);
$text = Encode::decode('utf8', $text);
$text = Encode::encode('utf8', $text);
return $text;
}
1;
とりあえず動くようになっただけで、いけてない部分盛りだくさんですが、こんなのつくってみました、ってことで。
html-tt-mode を sgml-mode のマイナーモードで動くようにしてみた
Clouder::Blogger: html-tt - emacsのTemplate Toolkit用のmode を html-helper-mode ではなく、sgml-mode のマイナーモードで動くようにしたパッチ。(置換しただけ。)
別に sgml-mode に思い入れはなく、Meadow でデフォルトで動いてるからそのまま使ってるだけなので、html-helper-mode にしてもいいんだけど、どうも Meadow でうまく色づけされないので、その原因追求するよりも、sgml-mode で動くようにした方がはやかったので。
本当はどっちでも動くようにするのがいいんだろうけど、それはまた今度。
=== html-tt.el
==================================================================
--- html-tt.el (revision 193)
+++ html-tt.el (local)
@@ -66,7 +66,7 @@
;; Code:
(provide 'html-tt)
-(require 'html-helper-mode)
+(require 'sgml-mode)
(require 'tempo)
(require 'font-lock)
@@ -217,38 +217,38 @@
(defun html-tt-load-hook ()
(interactive)
;; define key bind
- ;(define-key html-helper-mode-map "\C-cs"
+ ;(define-key sgml-mode-map "\C-cs"
; 'html-tt-insert-sequence)
- (define-key html-helper-mode-map "\C-cs"
+ (define-key sgml-mode-map "\C-cs"
'tempo-template-html-tt-insert-sequence)
- (define-key html-helper-mode-map "\C-cd"
+ (define-key sgml-mode-map "\C-cd"
'html-tt-insert-directive)
- (define-key html-helper-mode-map "\C-cn"
+ (define-key sgml-mode-map "\C-cn"
'tempo-template-html-tt-insert-directive)
- (define-key html-helper-mode-map "\C-ci"
+ (define-key sgml-mode-map "\C-ci"
'tempo-template-html-tt-insert-if)
- (define-key html-helper-mode-map "\C-cl"
+ (define-key sgml-mode-map "\C-cl"
'tempo-template-html-tt-insert-elsif)
- (define-key html-helper-mode-map "\C-ce"
+ (define-key sgml-mode-map "\C-ce"
'tempo-template-html-tt-insert-else)
- (define-key html-helper-mode-map "\C-cf"
+ (define-key sgml-mode-map "\C-cf"
'tempo-template-html-tt-insert-foreach)
- (define-key html-helper-mode-map "\C-cw"
+ (define-key sgml-mode-map "\C-cw"
'tempo-template-html-tt-insert-while)
- (define-key html-helper-mode-map "\C-cm"
+ (define-key sgml-mode-map "\C-cm"
'tempo-template-html-tt-insert-switch)
- (define-key html-helper-mode-map "\C-cn"
+ (define-key sgml-mode-map "\C-cn"
'tempo-template-html-tt-insert-include)
;; add hilit-set-mode-pattern, if use hilit19.
(if (featurep 'hilit19)
- (hilit-add-pattern "\\[%" "%\\]" 'midnightblue 'html-helper-mode)
+ (hilit-add-pattern "\\[%" "%\\]" 'midnightblue 'sgml-mode)
)
;; set font-lock
(make-local-variable 'font-lock-defaults)
(setq html-tt-font-lock-keywords
- (append html-helper-font-lock-keywords html-tt-font-lock-keywords))
+ (append sgml-font-lock-keywords html-tt-font-lock-keywords))
(setq font-lock-defaults '(html-tt-font-lock-keywords t t))
)
.emacs での設定はこんな感じで。
(require 'html-tt) (add-hook 'sgml-mode-hook 'html-tt-load-hook)