2011年12月22日木曜日

Flickr APIを使った画像アップロード

自社でやっているソーシャルファンディングサービスではプロフィール画像をFlickrに保存するようにしています。

理由は自分たちのサーバーに画像をどんどん貯めていくと
管理が面倒だからです。

また、メディアデータは物理HDDを圧迫するのでなるべく貯めたくありません。
将来的にそういうサービスを作りたくなったときは、クラウド使うとか安いHDDをクラスタにした構成にする予定です。

そーたろーさんのHPを参考に実装してみたところ、
フローが若干面倒になってしまいました。
(1)認証⇒(2)連携を承認⇒(3)画像をアップロードという3つのステップが必要だったためです。

改善する方法がないかと思い色々調べてみると、実装した認証方式が古いもので、OAuth認証が用意されていた事がわかりました。
http://www.flickr.com/services/api/auth.oauth.html

実装してみるとTwitter連携などとほぼ全く同じようにできました。
コードが統一的になっていいですね。

認証⇒画像アップロード⇒画像URLのコードを晒しておきます。フレームワークはCakePHPです。

(1)リクエストトークンを取得してリダイレクト
$settings = Configure::read('flickr_settings');
        
$consumer = $this->createConsumerFlickr();
$callback = $settings['callback'];
        
$consumer->getRequestToken('http://www.flickr.com/services/oauth/request_token', $callback);
$this->Session->write('flickr_request_token', $consumer->getToken());
$this->Session->write('flickr_request_token_secret', $consumer->getTokenSecret());

$auth_url = $consumer->getAuthorizeUrl('http://www.flickr.com/services/oauth/authorize');
$this->redirect($auth_url);
exit;
(1)-2 createConsumerFlickr()の内容
function createConsumerFlickr(){
    include_once('HTTP/OAuth/Consumer.php');
    require_once(dirname(__FILE__) . "/../Lib/SfOAuthConsumer.php");
    
    $flickr_settings = Configure::read('flickr_settings');
    $consumer_key = $flickr_settings['api_key'];
    $consumer_secret = $flickr_settings['secret'];
    $consumer = new SfOAuthConsumer($consumer_key, $consumer_secret);
    
    return $consumer;
}
HTTP_OAuth_Consumerを継承させただけの SfOAuthConsumer という独自クラスを作っています。 (理由は後で説明します) (2)コールバック
$consumer = $this->createConsumerFlickr();

$verifier = $_GET['oauth_verifier'];
$consumer->setToken($this->Session->read('flickr_request_token'));
$consumer->setTokenSecret($this->Session->read('flickr_request_token_secret'));
$consumer->getAccessToken('http://www.flickr.com/services/oauth/access_token', $verifier);

$this->Session->write('flickr_access_token', $consumer->getToken());
$this->Session->write('flickr_access_token_secret', $consumer->getTokenSecret());

// Flickr User Idを取得
$params = array();
$params['method'] = 'flickr.test.login';
$params['nojsoncallback'] = '1';
$params['format'] = 'json';

$response = $consumer->sendRequest("http://api.flickr.com/services/rest", $params, "GET");
$login = json_decode($response->getBody(), true);

$this->me['User']['fl_oauth_token'] = $consumer->getToken();
$this->me['User']['fl_oauth_secret'] = $consumer->getTokenSecret();
$this->me['User']['fl_user_id'] = $login['user']['id'];

//DBに保存
$this->User->save($this->me);
$this->Auth->login($this->me);

$this->redirect('/users/uploadPhoto');
exit;
(3)写真をアップロード(フォームから送信ボタンを押したあとのアクション)
$consumer = $this->createConsumerFlickr();
$consumer->setToken($this->me['User']['fl_oauth_token']);
$consumer->setTokenSecret($this->me['User']['fl_oauth_secret']);

$file = $_FILES['user_photo']['tmp_name'];

// 写真を登録
$params = array();
$consumer->addUpload($file);
$response = $consumer->sendRequest("http://api.flickr.com/services/upload/", $params, "POST");
$photoId = FlickrApi::parsePhotoId($response->getBody());

// 写真情報を取得
$params = array();
$params['photo_id'] = $photoId;
$params['method'] = 'flickr.photos.getInfo';
$params['nojsoncallback'] = '1';
$params['format'] = 'json';

$response = $consumer->sendRequest("http://api.flickr.com/services/rest", $params, "GET");
$photoInfo = json_decode($response->getBody(), true);

// 写真URLに変換
$photoUrl = FlickrApi::convertUrl($photoInfo);

// DBに登録する
$this->me['User']['image1'] = $photoUrl;
$this->User->save($this->me);
$this->Auth->login($this->me);

$this->redirect('/users/uploadPhoto');
exit;
(3)-2 FlickrApi::convertUrl()の内容
public static function convertUrl($info){
    // http://farm{farm-id}.staticflickr.com/{server-id}/{id}_{secret}_[mstzb].jpgg
    $farm_id = $info['photo']['farm'];
    $server_id = $info['photo']['server'];
    $id = $info['photo']['id'];
    $secret = $info['photo']['secret'];
    $mstzb = 't';
    
    return "http://farm{$farm_id}.staticflickr.com/{$server_id}/{$id}_{$secret}_{$mstzb}.jpg";
}
(3)-3 FlickrApi::parsePhotoId()の内容
public static function parsePhotoId($str){
    /* $str ='<?xml version="1.0" encoding="utf-8" ?><rsp stat="ok"><photoid>6536716601</photoid></rsp>'; */
    $p = xml_parser_create();
    xml_parse_into_struct($p, $str, $vals, $index);
    
    $photoid = null;
    foreach($vals as $dom){
        if($dom['tag'] == 'PHOTOID'){
            $photoid = $dom['value'];
            break;
        }
    }
    
    return $photoid;
}
写真をアップロードしようとして困ったのですが、 PEAR のHTTP_OAuthには画像を送信する方法が用意されていませんでした。(多分) 実装スピードを重視して今回は継承クラスを書き換え、sendRequest() 内でバイナリを送信できるようにしました。
class SfOAuthConsumer extends HTTP_OAuth_Consumer
{
    public $filename = null;
    
    
    public function addUpload($filename){
        $this->filename = $filename;
    }
    
    public function sendRequest($url, array $additional = array(), $method = 'POST')
    {
        $params = array(
            'oauth_consumer_key'     => $this->key,
            'oauth_signature_method' => $this->getSignatureMethod()
        );

        if ($this->getToken()) {
            $params['oauth_token'] = $this->getToken();
        }

        $params = array_merge($additional, $params);

        $req = clone $this->getOAuthConsumerRequest();

        $req->setUrl($url);
        $req->setMethod($method);
        $req->setSecrets($this->getSecrets());
        $req->setParameters($params);
        
        if(!is_null($this->filename)){
            $req->addUpload('photo', $this->filename);
        }
        
        $this->lastResponse = $req->send();
        $this->lastRequest  = $req;

        return $this->lastResponse;
    }   
}
Flickr が社内で不評だったことの原因の一つに、認証ページが英語しかないことがあると思います。 英語アレルギーの人には嫌われるかもしれないですね。。 早く日本語対応してほしいです。

0 件のコメント:

コメントを投稿