理系学生日記

おまえはいつまで学生気分なのか

AtomPub でスクリーンキャプチャから Hatena fotolife へのアップロードを自動化する

昨今の blog サービスで画像を貼り付けられないところはありません。ない。たぶんない。
もちろん我らがはてなダイアリーも、画像を余裕で貼り付けることができます。とてもよかった。これで老後も多い日も安心です。

はてなダイアリーははてなフォトライフと連動しており、はてなフォトライフにアップロードした画像は、はてな記法で記述することにより、クソ簡単にはてなダイアリーから参照することができます。とっても簡単だ。人生もこのくらい簡単だったら良いのにね!!!!!

f:id:kiririmode:20100407141723p:image

しかし、はてなフォトライフへのアップロードはメンドい。フォトライフのページを開いて、いろいろクリックして、アップロードする画像を選択して、アップロードボタンを押す必要がある。これは苦行です。あまりにもメンドい。人生と同じくらいメンドい。さらに、スクリーンショットを撮ってフォトライフにアップロードしようと思うと、そのメンドくささはスゴみを増す。こわい。もう戦えない。

なんとかしないと負け組です。人生と同じだ。なんとかしなければならない。なんか API でもないものかと思ってたら、そういえば AtomPub の API があった。これを使えばいいです。これを使えば全ての苦しみから解放される。

以上、ぼくの人生からわずかでも苦しみを解放するために、スクリーンキャプチャ→はてなフォトライフへのアップロードを半自動化する capturelife っていうスクリプトを組んだ。
fotmat を指定して (指定しなければ jpeg)、キャプチャーモードを指定して(指定しなければ、ウィンドウキャプチャを実行)、あとはフォトライフ上のフォルダとタイトルを指定する(指定なしでも良い)と、フォトライフ上に自動でアップロードされる。

[kiririmode@mbp(job:0)]% capturelife -h
Usage:
      capturelife --area 
      capturelife --format jpg
      capturelife --format jpg --name title
      capturelife --format jpg --name title --dir dir

実際にキャプチャしてみると以下のような感じ。これはウィンドウモードでのキャプチャになる。これで下記の画像のように、「スクリーンショット…」というような画像ファイルがデスクトップに出現することもなくなる。

$ capturelife


キャプチャは、スクリプト内部で screencapture コマンドを起動することで実行するので、Mac 限定。
http://github.com/kiririmode/capturelife

#!/usr/bin/perl
# -*- cperl -*-
use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;
use File::Temp;
use IPC::Cmd;
use XML::Atom::Client;
use XML::Atom::Entry;
use Config::Pit;

# Hatena Fotolife Post URI
my $post_uri = 'http://f.hatena.ne.jp/atom/post';

# allowed formats for Hatena Fotolife
my $re_format = qr/jpe?g|gif|png|bmp/;

GetOptions(\my %opts,
           'monitor|m', 'area|a', 'window|w',
           'format|f=s', 'name|n=s', 'dir|d=s', 'help'
) or pod2usage(2);
run(\%opts);

sub run {
    my $opts = shift;

    if ( $opts->{help} ) {
        pod2usage(0);
    }
    check_options($opts);
    
    my $file = capture($opts);

    my $conf = pit_get( 'hatena.ne.jp', require => {
        user => 'hatena user id',
        pass => 'hatena password',
    });
    upload($file, $conf, $opts);
}

sub check_options {
    my $opts = shift;

    # capture mode check
    my $modes = grep defined, @{$opts}{qw/monitor area window/};
    if ($modes > 1) {
        die "-monitor, -area, and -window options are mutually exclusive.\n";
    }

    # format check
    $opts->{format} ||= 'jpeg';
    $opts->{format}   = 'jpeg' if $opts->{format} eq 'jpg';
    $opts->{format} =~ s/^\.//;
    unless ( $opts->{format} =~ $re_format ) {
        die "allowed formats are jpeg, gif, png, and bmp\n";
    }
}

sub capture {
    my $opts = shift;

    my $mode =   defined($opts->{monitor})? "-m"
               : defined($opts->{area})?    "-s"
               : defined($opts->{windows})? "-w"
               :                            "-w";

    my $path;
    unless ( $path = IPC::Cmd::can_run( 'screencapture' ) ) {
        die "screencapture is not installed\n";
    }
    my $file = File::Temp->new( SUFFIX => ".$opts->{format}" );
    $file->unlink_on_destroy( 0 );
    my ($ok, $msg) = IPC::Cmd::run( command => "$path $mode $file" );
    unless( $ok ) {
        die "$msg\n";
    }

    return $file->filename;
}

sub upload {
    my ($filename, $conf, $opts) = @_;
    
    my $api = XML::Atom::Client->new;
    $api->username( $conf->{user} );
    $api->password( $conf->{pass} );

    open my $fh, '<', $filename or die $!;
    binmode $fh;
    my $image = do { local $/; <$fh> };
    my $entry = XML::Atom::Entry->new;

    $entry->content( $image );
    $entry->content->type('image/' . $opts->{format});
    $entry->title( $opts->{name} );

    if ($opts{dir}) {
        my $dc = XML::Atom::Namespace->new(dc => 'http://purl.org/dc/elements/1.1/');
        $entry->set($dc, 'subject', $opts{dir});
    }

    $api->createEntry( $post_uri, $entry ) or die $api->errstr;
}
    
    
__END__

=head1 NAME

capturelife - captures a screenshot, and uploads it to Hatena Fotolife

=head1 SYNOPSIS

  capturelife --area 
  capturelife --format jpg
  capturelife --format jpg --name file
  capturelife --format jpg --name file --dir dir

=head1 DESCRIPTION

See L<README> for details.

=head1 AUTHOR

Yuichi Kiri (kiririmode)

=cut