« 2006年11月 | メイン | 2007年1月 »

2006年12月 アーカイブ

2006年12月 2日

DBIx::Class::UTF8Columns 便利

DBIx::Class::UTF8Columns - Force UTF8 (Unicode) flag on columns - search.cpan.org

DBIx使って、utf8の値を持ちうるフィールドがある場合、これを使うと自動的にあれこれしてくれるみたいです。例えば、PlaggerからStore::DBICで書き出したDBのEntryテーブルを扱うときにはadd_columnsした後に

__PACKAGE__->load_components(qw/UTF8Columns Core/);
__PACKAGE__->utf8_columns(qw/title author summary body/);

とすれば文字化けが直った。Catalyst使う時はやっぱDBICを選んだほうがよさげだな。

2006年12月 3日

Plagger + Catalyst = Plalyst or PlaCata ? メソッド

PlaggerとCatalystの複合技、PlalystもしくはPlaCataメソッド。 基本的にはPlaggerでアグリゲートしたフィードとエントリーをStore::DBICでDBにして、 それをモデルとしてCatalystで扱う。Erolystはこれで動いちょる。 スパムサイトを作るにはもってこいだw、というのは少々本音がまざった冗談だが、 うまく生かしていろんなコンテンツができそう(社内の情報共有システムとか?)。

ではPlalystもしくはPlaCataメソッドのざっくりとした手順

まずPlaggerでフィードを用意する。その後DBを作る。

sqlite3 plagger.db < ~/lib/plagger/assets/plugins/Store-DBIC/plagger.sqlite.sql

Store::DBICする

  - module: Store::DBIC
    config:
      schema_class: Plagger::Schema::SQLite
      connect_info: [ 'dbi:SQLite:/path/to/plagger.db', ]

いよいよCatalystる

catalyst.pl MyApp

モデルを作る

$ ./script/myapp_create.pl model Plagger DBIC::Schema MyApp::Model::Schema create=static dbi:SQLite:/path/to/plagger.db

あとはビューを作って、コントローラーを書く。

#これでエントリーを持ってきて
my @entries = $c->model('Plagger::Entry')->all;
#stashに渡すとか
$c->stash->{entries} = ¥@entries;

CDTubeの作り方

CDTubeの作り方をソースつきで追ってみる。まずはPlaggerのconfig.yaml

global:
  assets_path: /path/to/assets
  timezone: Asia/Tokyo

plugins:

  - module: Subscription::Config
    config:
      feed:
         - url: http://www.tbs.co.jp/cdtv/cddb/thisweek-j.html

  - module: CustomFeed::Config
  
  - module: Filter::CountdownTube
    config:
      locale: jp
      associate_id: xxxxxxxxxx-22
      developer_token: XXXXXXXXXX

  - module: Filter::ForcePermalink

  - module: Publish::Feed
    config:
      format: RSS
      dir: ../root
      filename: index.xml

  - module: Publish::JavaScript
    config:
      dir: ../root
      filename: index.js
      
  - module: Store::DBIC
    config:
      schema_class: Plagger::Schema::SQLite
      connect_info: [ 'dbi:SQLite:/path/to/plagger.db', ]

CustomFeed::Configのassets内のyaml。authorにアーティスト名、ランキングの順位を無理やりrateプロパティに入れている

match: http://www¥.tbs¥.co¥.jp/cdtv/cddb/thisweek-j¥.html
extract: <td class="t1" align="right">(.*?)¥.</td>.*?songdb/song.*?">(.*?)</a>.*?artistdb/artist.*?">(.*?)</a>
extract_capture: rank track artist
extract_after_hook: |
  $data->{title} = $data->{track};
  $data->{author} = $data->{artist};
  $data->{rate} = $data->{rank};

Filter::CountdownTubeはこんな感じ。titleとauthorを元にAmazonとYouTubeに検索をかけている。mizzyさんが作ったPluginを参考にさせていただきました。後ほどCatalystで使うためにbodyにYouTubeのビデオIDを入れる。

package Plagger::Plugin::Filter::CountdownTube;

use strict;
use base qw( Plagger::Plugin );
use Net::Amazon;
use Net::Amazon::Request::Keyword;
use Encode;
use HTML::Entities;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'update.entry.fixup' => ¥&filter,
    );
}

sub filter {
    my($self, $context, $args) = @_;
    my $e = $args->{entry};

    my $attr;
    $attr->{token}  = $self->conf->{developer_token};
    $attr->{locale} = $self->conf->{locale};
    $attr->{affiliate_id} = $self->conf->{associate_id};

    my $keywords = $e->title . " " . $e->author;

        decode_entities($keywords);

    $context->log( info => "Searching $keywords on Amazon...");
    my $item = search_aws($attr, $keywords, "music-jp");
        my $video_id = search_youtube($self,$context,$keywords);
        if($video_id eq ''){
                $video_id = search_youtube($self,$context,decode_entities($e->title));
        }
        
    if($item){
        $e->link($item->url);
        $e->icon({ url => $item->ImageUrlSmall });
        $e->summary($item->ProductDescription);
                $e->body($video_id);
    }
}

sub search_aws {
    my($attr, $keywords, $mode) = @_;

    my $ua = Net::Amazon->new(%$attr);

    $keywords = encode("UTF-8", "$keywords");
    my $req = Net::Amazon::Request::Keyword->new(
        keyword => $keywords,
        mode    => $mode,
    );

    my $response = $ua->request($req);
    my $item = ($response->properties())[0];
    return $item;
}

sub search_youtube{
        my ($self,$context,$query) = @_;
    my $url  = URI->new('http://youtube.com/results');
    my $file = $self->cache->path_to('youtube_search_result.html');

    $query = encode('UTF-8', $query) unless $context->conf->{no_decode_utf8};
    $context->log( info => 'Getting YouTube search results for ' . $query );

    my $ua = Plagger::UserAgent->new;
        $url->query_form(
                                         search_type     => 'search_videos',
                                         search_query    => $query,
                                 );
        
        my $res = $ua->mirror( $url->as_string => $file );
        if($res->is_error){
                $context->log( error => $res->status );
                return;
        }
        
        open my $fh, "<:encoding(utf-8)", $file
            or return $context->log(error => "$file: $!");

        my $video_id;
        while (<$fh>) {
                m!<a href="/watch¥?v=(.*?)"!
                        and do{
                                $video_id = $1;
                                $context->log(info => "$video_id get!");
                                last;
                        };
        };
        return $video_id;
}

1;

フィードの準備ができたので、Catalystのアプリを実装。PlaggerからStore-DBICされたSQLiteのdbがモデルになる。DBIC::Schemeのヘルパースクリプトを使ってクラスを自動生成。

$ ./script/cdtube_create.pl model Plagger DBIC::Schema CDTube::Model::Schema create=static dbi:SQLite:/path/to/plagger.db

次にコントローラーの実装。必要に応じてモデルにもメソッドを追加する。コントローラーはRoot.pmのみで済ませた。DBIC::Scheme、初めて使ったけどいい感じです。諸事情のため、reseltsetのpagerは使ってない。

package CDTube::Controller::Root;

use strict;
use warnings;
use base 'Catalyst::Controller';
use Data::Dumper;

__PACKAGE__->config->{namespace} = '';

sub default : Private {
    my ( $self, $c ) = @_;
        my $of = $c->request->arguments->[0] || 1;
        if($of > 100){$of = 1};
        $c->log->debug("of is $of");
        my @entries;
        my $it = $c->model('Plagger::Entry')->search(
                                                                                                {},
                                                                                                    { order_by => 'cast(rate AS INTEGER) ASC',
                                                                                             });
        while(my $e = $it->next ){
                if($e->rate > ($of - 1)){
                        if($e->rate < ( $of + 10)){
                                push(@entries,$e);
                        }
                }
        }
        my @feed = $c->model('Plagger::Feed')->all;
        my $week = $feed[0]->week;

        $c->stash->{week} = $week;
        $c->stash->{of} = $of;
        $c->stash->{entries} = ¥@entries;
        $c->stash->{template} = 'index.tt';
}

sub end : ActionClass('RenderView') {}

1;

後はビューの実装。$entry->bodyでYouTubeのビデオIDがとれるのでそのままembedのタグに入れ込む。 ページング処理がちょっと複雑になったけど、わりとあっさりとできた。と、一通り完成したら、細かい修正加えて、HTMLをMT風にしてスタイルを適用。あとはmod_perlで動くことを確認して、できあがりです。

2006年12月 4日

思いついた、CDTubeをよりそれっぽくする方法

CDTubeをよりそれっぽくする方法、思いついた。 http://www.veena.jp からインスパイヤ。 本当のTVみたいに、100位とか20位とかからカウントダウンしていく。 YouTubeの映像が終わったり、30秒とかたったら自動的にページを切り替えて、上位の曲を再生する。 あいまに「第20位!」とかキャラクターを出したい。もちろんスキップもできる。

で、自動的に切り替えるのはどうするかというと、JavaScriptを使う。 秒数決めうちで30秒とか、うまくサビが終わったあたりでページを切り替えてもいい。 問題は秒数決めうちじゃなくて、映像を全部みてから切り替えたい時なんだけど、YouTube APIを使えば映像の秒数をゲットできるので、その秒数がたったら上位の映像に切り替えるとすればいいじゃん。

曲と曲のあいだにキャラクターいれてぇな。誰か絵を描いてくれる人いませんかね?ってかあと、ランキングは別にオリコンとかCDTVにこだわらなくてもAmazonとかでいい気がしてきた。Amazonウェブサービスてランキング取得できったけか。ああでも、CDのタイトルとってこれそうだけど、曲名をとってくるとなるとめんどいな。

ん、なんか、オリコンチャート情報サービス「you大樹」こんなのあるぞ。

2006年12月 5日

CDTubeのRSS

CDTubeではPlaggerで生成したRSSをフィードとして使っていたのだけども、仕様上、そうするとlinkがAmazonの商品ページへのURLになっていて、CDTubeのサイトには行かない。それじゃあ面白くないので、link先をCDTubeの映像があるページに移動させるようにした。Catalystのコントローラーでrssを作って吐き出す。XML::Feedを使って、

sub rss : Local {
        my ($self,$c) = @_;

        my $it = $c->model('Plagger::Entry')->search(    {},{ order_by => 'cast(rate AS INTEGER) ASC',
                                                                                                    });

        my $feed = XML::Feed->new('RSS');
        $feed->title( $c->config->{name} . ' RSS Feed' );
        $feed->link( $c->request->base ); 
        $feed->description('Mashup the Sing CD ranking with YouTube'); 
                
        while( my $e = $it->next ) {
                my $feed_entry = XML::Feed::Entry->new('RSS');
                $feed_entry->title("No." . $e->rate . " " . $e->title . " by " . $e->author);
                $feed_entry->link( $c->request->base . $e->rate );
                $feed_entry->author( $e->author );
                $feed->add_entry($feed_entry);
        }
        $c->res->content_type('application/rss+xml');
        $c->res->body( $feed->as_xml );
}

ってな感じ。リンク先はその曲を先頭に以下それより順位が下の曲情報がでてくるという具合です(この辺も改善の余地はあるがとりあえず放置)。 rss.ttが無いとエラる。参考: Catalyst::Manual::Cookbook - Cooking with Catalyst - search.cpan.org

なのでフィードのURLが http://pulpsite.net/cdtube/rss に変更になりました。あ、date入れたいな。 ところで、100位の曲が2つあったり、取得できないエントリーがあったりとバグが結構あるね。

2006年12月 8日

sub search_youtube {...}

お、これでとってこれるじゃん。 検索語からYouTubeのビデオIDをとってくるシンプルな実装。

#!/usr/bin/perl
use strict;
use LWP::UserAgent;
use URI;
use Encode;

my $query = "oasis";
my @vi = search_youtube($query,"video_view_count");
foreach my $i (@vi) {
        print "$i¥n";
}

sub search_youtube{
        my ($query, $search_sort) = @_;
        my $url  = URI->new('http://youtube.com/results');
    $query = encode('UTF-8', $query);
    my $ua = LWP::UserAgent->new;
        $url->query_form(
                                         search_type     => 'search_videos',
                                         search_query    => $query,
                                         search_sort     => $search_sort,
                                 );
        my $response = $ua->get($url);
        unless($response->is_success){
                return;
        }
        my $content = $response->content;
        my @video_id = ();
        while($content =~ m!<div class="vtitle">.*?<a href="/watch¥?v=(.*?)"!gs){
                push(@video_id,$1);
        }
        return @video_id;
}

ノエルとクリスが絡んでる映像みっけ

2006年12月 9日

&autoplay=1

YouTubeのembedで貼り付けたビデオを自動再生するには

http://www.youtube.com/v/video_idxxx

と指定されているFlashへのパスに対して、

http://www.youtube.com/v/video_idxxx&autoplay=1

とパラメータを渡せばOK。

2006年12月16日

P::P::Filter::YouTubeFromTitle

Last.FMとはてなミュージックがはくRSSのエントリーtitleを検索キーにしてYouTubeの映像をとってきてembedのhtmlをbodyにぶち込むというPlaggerのFilter作ってみた。野良Pluginってやつだね。例えば、iTunesで最近聞いた曲に関係するYouTubeの映像が見れる(はず)。 Filter::DegradeYouTubeと組み合わせるといい感じ。config.yamlはこんな

plugins:

  - module: Subscription::Config
     config:
      feed:
#         - url: http://music.hatelabo.jp/username/?type=recent&mode=rss
         - url: http://ws.audioscrobbler.com/1.0/user/username/recenttracks.rss

  - module: Filter::YouTubeFromTitle
     config:
       search_sort: video_view_count # optional

  - module: Filter::DegradeYouTube
     config:
       dev_id: xxxxxxxxxx

以下、Plagger::Plugin::Filter::YouTubeFromTitle。文字コード関係と正規表現にてこずった。汚いコードだと思うが、一応動いちょる。pmファイルも、ここに置いとく。

package Plagger::Plugin::Filter::YouTubeFromTitle;

use strict;
use base qw( Plagger::Plugin );
use Encode;
use HTML::Entities;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'update.entry.fixup' => \&filter,
    );
}

sub filter {
    my($self, $context, $args) = @_;
    my $e = $args->{entry};
        my ($keywords, $title, $artist,$video_id);

        $title = $e->title;
        $title = encode('UTF-8',$title);
        
        #hatena music
        if($title =~ /(.*?)\s-\s.*?\s-\s(.*?)\Z/){
                $title = decode('UTF-8',$1);
                $artist = decode('UTF-8',$2);
        }
        #Last.FM/audioscrobbler
        elsif($title =~ /(.*?)\s\342\200\223\s(.*?)\Z/){
                $artist = decode('UTF-8',$1);
                $title = decode('UTF-8',$2);
        }
        $keywords = $artist . " " . $title;
        
        $video_id = search_youtube($self,$context,$keywords);
        unless($video_id eq ''){
                $e->title( $title . " by " . $artist );
        }else{
                $video_id = search_youtube($self,$context,$artist);
                $e->title($artist);
        }
        
        unless($video_id eq ''){
                $e->link("http://www.youtube.com/watch?v=$video_id");
                $e->body(embed($video_id));
        }

}

sub embed{
        my ($video_id) = @_;
        my $html = '<object width="425" height="350"><param name="movie" value="http://www.youtube.com/v/%s"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/%s" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"></embed></object>';
        $html = sprintf($html, $video_id);
        return $html;
}

sub search_youtube{
        my ($self,$context,$query) = @_;
#        my $query = decode_entities($query);
    my $url  = URI->new('http://youtube.com/results');
    my $file = $self->cache->path_to('youtube_search_result.html');

    $query = encode('UTF-8', $query) unless $context->conf->{no_decode_utf8};
    $context->log( info => 'Getting YouTube search results for ' . $query );

    my $ua = Plagger::UserAgent->new;
        $url->query_form(
                                         search_type     => 'search_videos',
                                         search_query    => $query,
                                         search_sort     => $self->conf->{search_sort} || 'video_view_count',
                                 );
        
        my $res = $ua->mirror( $url->as_string => $file );
        if($res->is_error){
                $context->log( error => $res->status );
                return;
        }
        
        open my $fh, "<:encoding(utf-8)", $file
            or return $context->log(error => "$file: $!");

        my $video_id;
        while (<$fh>) {
                if(m!<a href="/watch\?v=(.*?)"!){
                        $video_id = $1;
                        $context->log(info => "Found YouTube video: $video_id");
                        last;
                }
        }
        return $video_id;
}

1;
__END__

=head1 NAME

Plagger::Plugin::Filter::YouTubeFromTitle - get YouTube movie related entry title

=head1 SYNOPSIS

  - module: Filter::YouTubeFromTitle
    config:
      search_sort: video_view_count

=head1 CONFIG

=over 4

=item search_sort

Sort method. Optional.

=back

=head1 AUTHOR

Yusuke Wada

=head1 SEE ALSO

L<Plagger>

=cut

と、これを使えば、Last.FM及びはてなミュージックとYouTubeのマッシュアップサイトができるかもしれないが(ダイナミックに動かそうと思ってyappoさんのCatalyt::Plugin::Plaggerを使ってみたが動かなかったYO)、もうすでに「I Love Music Video」というすごいサイトがあるんだな。ま、これはPlaggerなんでいろいろ応用利きそう(そいえばLast.FMというかaudioscrobblerとはてなのRSSにはCCのライセンスがついてるよ)。

2006年12月29日

たまったプロセスを一気にkillする

いつもscreenを使っていると、正常に終了できない場合があってプロセスが残るときがある。 screen側で対処する方法があると思われるが、とりあえず一気にkillするときには以下のコマンドでいける。

$ ps ax | grep SCREEN | awk '{ print $1 }' | xargs kill

参考: Linux コマンド ポケットリファレンス P347

追記

コメントいただきました。

pkill SCREEN

でいけるみたい!うほ!知らなかった。コメントくださった方ありがとうございます。

2006年12月30日

XML::Simpleを使うときは

XML::Simpleを使うときには、配列のデリファレンスを忘れないようにしよう。

@{ $data->{Items}->{Item}->{Tracks}->{Disc} }

About 2006年12月

2006年12月にブログ「Yusukebe::Tech」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2006年11月です。

次のアーカイブは2007年1月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。


ブログSEO対策:track word seo