まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

無料のグループウェアを SaaS で提供していたサイボウズ Live が 2019 年4月15日をもってサービスを終了する、というアナウンスがありました:
サイボウズLiveサービス終了のお知らせ

個人的にも仲間内サークル活動の中で使っていたりしていたもので、このニュース自体はとても残念なものでした。が、終了まで1年半も猶予を持ったアナウンスであり、また(上記リンクによると)今後データのエクスポート(CSVファイル?機能?)などを提供する予定もあるらしいです。データを他のサービスに移行するための準備期間としては充分にあるようにも感じました。

データ移行に関しては時間的な猶予もあるので、しばらくは謹製のデータエクスポートが出されるのを待ってもいいとは思っています。が、個人的/技術的な興味もあって自分でも PHP で作ってみました。詳しくは後述しますが、もしご利用になる場合、現時点ではまだ不完全なものだと理解の上で使ってください。なおこのツールではデータを XML ファイルでエクスポートします(掲示板データなど、改行情報を含むデータは CSV に向かないと判断したので):
https://github.com/dotnsf/cbl_export/blob/master/cbl_export.php


実際に使うには、まず PHP 実行環境が必要です(自分自身がテストした範囲では PHP 5.3.3 でも動きました、かなり古いバージョンでも大丈夫だと思います):
(例 CentOS の場合)
# yum install php php-mbstring php-xml php-pear

加えてプログラムの中で OAuth などの外部ライブラリモジュールを使うため、これらのモジュールをあらかじめインストールしておいてください:
(例 pear を使う場合)
# pear install Net_URL2
# pear install HTTP_Request2
# pear install HTTP_OAuth

またこのツールを使う準備としてサイボウズ Developer Center のアカウントが必要です。同アカウントをお持ちでない場合はリンク先の「デベロッパー登録」ボタンから申請してアカウントを作成してください:
2017102901


アカウント作成後にログインし、「Myアプリケーション」の一覧で「アプリケーションを登録する」を選択し、今から動かすアプリケーションの登録を行います:
2017102902


アプリケーションを登録する際のアプリケーション名などは任意に設定いただいていいのですが、「アプリケーションの種類」は「クライアント」、そして「アクセスレベル」は「レベルZ」を選択する点に注意してください(ここを間違えると正しく動かなくなります。なおアプリケーション種類をクライアントにしないとレベルZのアクセスレベルは選択できないはずです):
2017102903


こうしてアプリケーションを登録し、登録後の画面に表示される Consumer Key と、Consumer Secret をメモしておきます(他人に教えてはいけません)。またアプリケーションの種類とアクセスレベルが正しく登録されていることを確認してください:
20171026



ここまでの準備ができたところで、改めて今回作成したエクスポートアプリを使います。こちらのサイトから git clone するなどして cbl_export.php をコピーします:
https://github.com/dotnsf/cbl_export/blob/master/cbl_export.php


cbl_export.php をテキストエディタで開き、一部を編集します:
<?php
//. cbl_export.php
//. Referer: https://developer.cybozulive.com/doc/current/#id1
//. For XAuth(Ones of Level Z)
$consumer_key           = '(CybozuLive Developer Center Consumer Key)';
$consumer_secret        = '(CybozuLive Developer Center Consumer Secret)';
$xauth_access_token_url = 'https://api.cybozulive.com/oauth/token';
 
$params = array(
    'x_auth_username' =--> '(CybozuLive Email)',
    'x_auth_password' => '(CybozuLive Password)',
    'x_auth_mode'     => 'client_auth',
);

:
:

上記の青字部分を編集します。$consumer_key には上記サイボウズ Developer Center でのアプリケーション登録時に確認した Consumer Key を、$consumer_secret には同 Consumer Secret を代入します。また $params 'x_auth_username' には自分のサイボウズ Live ログイン時のメールアドレスを、そして 'x_auth_password' には同パスワードを指定します。


ここまでの準備ができたら php コマンドで cbl_export.php を実行します:
# php -f cbl_export.php

実行が成功するとカレントディレクトリに自分のメールアドレスと同じ名前のフォルダと、自分がサイボウズ Live で所属しているグループごとの ID(X:XXXXX みたいなフォーマットです)のフォルダが作成され、各データがそれぞれのフォルダ内にエクスポートされます。例えば1つのグループにしか所属していない場合であれば、自分のメールアドレスのフォルダが1つと、そのグループのIDのフォルダが1つ生成されます:
2017102904
(↑自分が実行した場合の結果サンプル)


そして個人のデータはメールアドレスのフォルダ内に、要素毎に XML ファイルでエクスポートされています(現在の仕様ではスケジュール、チャット、ToDo、コネクションのデータがエクスポートされます):
2017102905


またグループ ID のフォルダには各グループ毎のメンバー、掲示板、イベント、ToDo、ファイル共有の情報が XML ファイルでエクスポートされています:
2017102906


なお、このツールを開発するにあたり、サイボウズ Live API を使っています。その仕様はこちらです:
サイボウズLive データ API ドキュメント


(↓2017/Oct/29 時点での制限事項)
上記のように一応動くものが作れたつもりなのですが、2017/Oct/29 時点ではファイルのダウンロード機能が正しく動いていません。例えばサイボウズ Live のファイル共有機能や掲示板の中でファイルを添付しているケースは珍しくないと思っています。その「どんなファイルが添付されているか?」といった情報は取得できて、その情報は現在取得できるファイルの中にも正しくエクスポートできているのですが、肝心のファイルそのもののダウンロードができていません。具体的にはこの API の実行時にだけ想定外のエラーになってしまい、まだその理由や解決策が分からずにいます:
https://developer.cybozulive.com/doc/current/pub/fileDownload.html


この部分については私のミスなのか、API 側の問題なのかの判別ができず、現在は問い合わせ中です。分かり次第に対応するつもりです。
(↑2017/Oct/29 時点での制限事項)


(↓2017/Nov/06 追記)
サイボウズより返答あり。サイボウズ側では問題なく動いているとのこと。むむむ・・ (--;
(↑2017/Nov/06 追記)


添付ファイルのダウンロード以外についてはある程度動くようになっているつもりです。早めのデータ移行を検討していたり、移行先候補へのテストを早めに実行したい場合に活用ください。

なおサイボウズから謹製のエクスポートデータやエクスポートツールが提供された場合は、(きっと面倒なアカウント登録なども不要になると思うので)そちらの利用をオススメします。 (^^;


久しぶりに PHP を使っていて気付いたことをブログネタにしました。

最近の Web API/REST API は JSON フォーマットで結果を取得できるものが多いですが、中には XML のみサポートされているものもあります(RSS とかね)。そんな XML を PHP で扱う方法について調べました。
2017102700


まず、PHP には simplexml_load_file および simplexml_load_string という便利な関数が用意されています。これらは XML ファイル(のURL)や XML 文字列を指定して、PHP のオブジェクト化するというものです。例えば以下のような ATOM フィードフォーマットの XML ファイル(atom.xml)があったとします:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
  <id>tag:phpxml/</id>
  <title>サンプルタイトル</title>
  <link rel="alternate" type="text/html" href="http://sample.xml.jp/feed/"/>
  <link rel="self" type="application/atom+xml" href="http://sample.xml.jp/feed/atom10.xml"/>
  <entry>
    <id>http://sample.xml.jp/test1.html</id>
    <title>紹介タイトルその1</title>
    <link rel="alternate" type="text/html" href="http://sample.xml.jp/test1.html"/>
    <summary>紹介するエントリの1つです。</summary>
    <dc:updated>Thu, 26 Oct 2017 07:00:00 +0900</dc:updated>
  </entry>
  <entry>
    <id>http://sample.xml.jp/test2.html</id>
    <title>紹介タイトルその2</title>
    <link rel="alternate" type="text/html" href="http://sample.xml.jp/test2.html"/>
    <summary>紹介するエントリの2つめです。</summary>
    <dc:updated>Fri, 27 Oct 2017 07:00:00 +0900</dc:updated>
  </entry>
</feed>

この atom.xml ファイルに対して、例えば以下のようなコード(test1.php)を用意して実行します:
<?php
$feed = simplexml_load_file( "atom.xml" ); echo "id: " . ( $feed->id ) . "\n"; echo "title: " . ( $feed->title ) . "\n";
foreach( $feed->entry as $entry ){ echo "entry_title: " . ( $entry->title ) . "\n"; echo "link_url: " . ( $entry->link->attributes()->href ) . "\n"; }
?>

その実行結果(赤字部分)はこちら:
$ php -f test1.php

id: tag:phpxml/
title: サンプルタイトル
entry_title: 紹介タイトルその1
link_url: http://sample.xml.jp/test1.html
entry_title: 紹介タイトルその2
link_url: http://sample.xml.jp/test2.html

これだけ。simplexml_load_file 関数や simplexml_load_string 関数は XML ファイルや XML 文字列を簡単に PHP のオブジェクト化して、XML 内の各データや属性値にアクセスできるようになるものです。

※上記のように属性値は $entry->link->attributes()->属性名 で取得できます。


さてここまでは普通にできるのですが、実はこの方法では取得が難しいデータがあります。各 entry 内にある <dc:updated> という特殊な名前のデータです。 
      :
      :
    <title>紹介タイトルその1</title>
    <link rel="alternate" type="text/html" href="http://sample.xml.jp/test1.html"/>
    <summary>紹介するエントリの1つです。</summary>
    <dc:updated>Thu, 26 Oct 2017 07:00:00 +0900</dc:updated>
      :
      :

この部分が少し厄介なのです。test1.php と全く同じ要領でこんな test2.php ファイルを作ってみます:
<?php
$feed = simplexml_load_file( "atom.xml" ); echo "id: " . ( $feed->id ) . "\n"; echo "title: " . ( $feed->title ) . "\n";
foreach( $feed->entry as $entry ){ echo "entry_title: " . ( $entry->title ) . "\n"; echo "link_url: " . ( $entry->link->attributes()->href ) . "\n"; echo "dc:updated: " . ( $entry->dc:updated ) . "\n"; }
?>

この test2.php を実行すると・・・ Parse error となってしまいます。PHP の文法ではここに ":" を記述してはいけないんですね。。
$ php -f test2.php

Parse error: syntax error, unexpected ':' in test2.php on line 8

なるほど。これでは文法的にNGということはわかった。が、問題はどうやって回避すればいいのか、です。simplexml_load_file 関数を使わずに別の方法で XML をパースする方法もありますが、それはそれで面倒だったりします。なんとか simplexml_load_file の便利な機能をそのままに回避できないか・・

そこでググって見つけたがこの方法でした。簡単にいうと
 (1) タグ(<から>まで)内の ":" を "_" とかに一括変換する
 (2) ただしプロトコル( http:// とか https:// とか)で使われている部分だけは元に戻す
という方法です。この方法で作り直したのがこちら(test3.php)です:
<?php
//. XML ファイルを文字列変数へ読み込む
$txt = file_get_contents( "atom.xml" );
//. <***:****> -> <***_****>
$txt  = preg_replace( "/<([^>]+?):(.+?)>/", "<$1_$2>", $txt );
//. プロトコルは元に戻す
$txt  = preg_replace( "/_\/\//", "://", $txt );

$feed = simplexml_load_string( $txt );
echo "id: " . ( $feed->id ) . "\n";
echo "title: " . ( $feed->title ) . "\n";
foreach( $feed->entry as $entry ){
  echo "entry_title: " . ( $entry->title ) . "\n";
  echo "link_url: " . ( $entry->link->attributes()->href ) . "\n";
  echo "dc:updated: " . ( $entry->dc_updated ) . "\n";
}
?>

青字部分ではまず普通に atom.xml ファイルを文字列として読み込み、上記 (1) および (2) の処理を行います。そしてその結果を simplexml_load_string 関数でオブジェクト化しました。このようにすると、各 entry 内の <dc:updated> 要素は <dc_updated> と変わっているので、そのまま扱えるようになります。

この test3.php を実行すると、期待通りの結果を得ることができました:
$ php -f test3.php

id: tag:phpxml/
title: サンプルタイトル
entry_title: 紹介タイトルその1
link_url: http://sample.xml.jp/test1.html
dc:updated: Thu, 26 Oct 2017 07:00:00 +0900
entry_title: 紹介タイトルその2
link_url: http://sample.xml.jp/test2.html
dc:updated: Fri, 27 Oct 2017 07:00:00 +0900

とりあえず動くことは動きました。でももうちょっと上手い方法がないもんかな。。

AI やコグニティブエンジンで画像認識機能を使ったり(特にそのためのデータ学習を行おうと)すると、大量の画像データが必要になります。その画像データが手元にある場合はそれらを使えばいいのですが、充分な画像がない場合にどうするか? という問題があります。

ネットから画像検索して見つかるようなものであればそれも方法の1つですが、学習に使えるようなシーンが映った動画があれば、その動画から静止画像を切り取って使う、という方法もあります。この処理をNode.js のプログラムで自動化する方法を紹介します。

まず、このような動画から静止画を切り出すには ffmpeg を使うと便利です。ffmpeg はクロスプラットフォームで動くフリーな動画処理ソフトで、コーデックやライブラリ等も含んでいます。Node.js からであれば fluent-ffmpeg ライブラリをインストールすることで、この ffmpeg のネイティブ機能が使えるようになります(なので ffmpeg をインストールした上で、fluent-ffmpeg を使う必要があります。ちなみに Ubuntu 16.04 では ffmpeg がデフォルトで導入されています)
2017102000


というわけで、まずはライブラリを準備します。以下のサンプルソースで使う path ライブラリと合わせてこんな感じ:
$ npm install path fluent-ffmpeg

そして以下のようなサンプルコードを用意します:
var ffmpeg = require( 'fluent-ffmpeg' );
var path = require( 'path' );

var mpegfile = './abc.mp4';  //. 動画ファイル

var command = ffmpeg( mpegfile );

command.on( 'end', () => {
  console.log( 'ok' );
}).screenshots({
  count: 4,                               //. 20%, 40%, 60%, 80% のタイミングで静止画を取得する
  folder: path.join( __dirname, 'tmp' );  //. ./tmp/ フォルダに静止画を出力
  filename: 'abc-%i:%s.png',              //. ファイル名は abc-(インデックス番号):(秒).png
  size: '320x?'                           //. 画像サイズは横幅 320 px で、縦を可変(同一縮尺)にする
});


このコードでは実行結果の静止画像を ./tmp フォルダ以下に作成するよう指定しています。なので、まずこのフォルダを作成しておきます:
$ mkdir tmp

そして上記のコードを Node.js で実行。成功すると tmp/ 以下に abc-****.png という画像ファイルが4枚出力されます。


上記例では静止画像を取得するタイミングを同間隔で4枚という指定をしましたが、timemarks を指定することで何秒後、という指定も可能です:
      :
  timemarks: [ 0.5, 1, 2 ],             //. 0.5秒後、1秒後、2秒後に切り取り
      :





このページのトップヘ