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

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

2017/10

久しぶりに 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秒後に切り取り
      :





タイトル通りですが、「『じゃじゃじゃじゃーん』をリズムに変換する Node-RED 用ノード」を作ってみました。


ほとんどの人はこの時点でどんな処理をするノードか理解ができてないと思うので、その補足です。このノードを作るきっかけになったのはこちらのまとめサイトでした:
【超能力者】知恵袋「何の曲か分かりますか?てんてててん てんてんてて」→「戦場のメリークリスマスですね」「なんで分かるんだ!?」


要するに「じゃじゃじゃじゃーん」という日本語テキスト(音階なし、正確なリズムもわからない)だけを頼りに「交響曲第5番、『運命』」のように曲を探し出すという(超)能力を持った人がいて、こりゃすげー!と。

なるほど、面白そうだ。最終的にはこれと同じことをシステム化できないかなあ、と考えているわけですが、その開発の途中で「とりあえずは日本語テキストからリズムを取り出す」仕組みを作り、そこから少々脱線して「その仕組を Node-RED のノードにしてみた」のが今回紹介するノード: node-red-contrib-dotnsf-jajajajan です:
https://www.npmjs.com/package/node-red-contrib-dotnsf-jajajajan

2017092300


利用するにはまず Node-RED 環境を(ローカルでも IBM Bluemix でも)用意し、そこからこのノードを導入します。ローカルであれば以下のコマンドでインストールできます:
$ npm install node-red-contrib-dotnsf-jajajajan

IBM Bluemix の Node-RED の場合はメニューからパレット管理機能を使ってインストールします:
2017101401


パレットの管理画面において「ノードを追加」タブを選び、検索文字列に "jajaja" などと入力すると見つかります(私のミスで複数バージョンが登録されてしまいました。最新バージョンのものを指定して「ノードを追加」してください):
2017101402


この状態で Node-RED を起動すると機能カテゴリ内に音符アイコンの "jajajajan" というノードが追加されて、キャンバス上で使えるようになります:
2017092301


このノードの挙動を確認するために以下のようなシンプルなフローを作ってみます。入力カテゴリの inject ノードと、出力カテゴリの debug ノード、そしてこの jajajajan ノードを配置し、以下のように配線します:
2017092302


inject ノードをダブルクリックして、Payload の種類をデフォルトの timestamp から string に変更し、その値を上記で示したような日本語のリズムテキストにします。以下の例ではまさに『運命』の有名な部分である「じゃじゃじゃじゃーん、ララララーン」としています(「じゃ」でも「ラ」でも同じように処理することができることを確認するため、わざと別の表現にしています)。ここで指定したテキストが msg.payload として jajajajan ノードに渡されて処理されます。入力し終わったら「終了」ボタン:
2017092303


これでフローの準備はできました。動作を確認するため「デプロイ」します:
2017092304


デプロイできたら、画面右ペインを "debug" タブに切り替えたことを確認してから、inject ノードの左にあるボタン部分をクリックします。クリックしたタイミングで指定したテキスト(今回の例では「じゃじゃじゃじゃーん、ララララーン」)が msg.payload として jajajajan ノードに渡されます。
2017092305


クリックすると、不定のタイミングで debug タブに文字が表示されてゆくことが確認できます。このタイミングに関してはネットワーク依存があることと、Node.js 上で動く Node-RED が非同期処理を行うことから必ずしも厳密なタイミングではないのですが、なんとなく「じゃ」「じゃ」「じゃ」「じゃーん」「ラ」「ラ」「ラ」「ラーン」のリズムで1行ずつ表示されている(はず)です:
2017092306


出力されている内容を詳しく見てみると、"8" とか "2" とか数字が表示されています。実はこれは音符の長さを表していて、"8" は「8分音符」、"2" は「半音符(2分音符)」を意味して、その長さに合わせて debug 出力も制御されています。現在の仕様では "8" では 0.5 秒後、"2" では 2 秒後に次の音符が表示されるようにしています(なので "8" の次はすぐに表示されますが、"2" の次が表示されるまでには少し時間がかかります)。結果的になんとなく「じゃ」「じゃ」「じゃ」「じゃーん」「ラ」「ラ」「ラ」「ラーン」というリズムに合わせて1行ずつ表示されているように見える・・・のではないかと思っています(苦笑):
2017092307


試しに『メヌエット』の出だし部分である「タンタタタタタンタンタン」で試すとこんな感じです。最初の「タン」が4分音符で、そこから8分音符が4回続いて・・・というリズムで解釈されていることがわかります:
2017092308


と、またこんなくっだらないノードを作ってしまった。。どうしても使ってみたいという変わった人がいたら使ってください。ちなみにソースコードはこちらです(日本語処理の部分はかなりハードコードに依存しているので、他の言語用に移植するのが難しいかも・・):
https://github.com/dotnsf/node-red-contrib-dotnsf-jajajajan


脱線した話を元に戻すと、このノードで実現している仕組みを使ってテキストをリズムに変えて、取り出したリズムの情報をあらかじめ楽譜を元にリズムだけを取り出した楽曲のデータベースから曖昧検索して近いものを探す、という方法で冒頭で紹介したような超能力者みたいな仕組みをシステム化できないか、と考えています。こちらも諦めているわけではなく、ある程度は動くようになっている(楽譜そのものと、楽譜から主旋律を探す仕組みに苦戦している)ので、人前に出せるようになったら公開するかもしれません。まあ、そうそう簡単なものではないと分かっている上に自分が音楽や楽器が得意ではないので、まだ躓いてすらいない落とし穴が待っているかもしれませんが・・・

複数の楽器向けに書かれている楽譜から主旋律(というのかな?人間がよく知ってるパート)を探す方法を知っている人がいたら教えていただけるとうれしいです。

Ubuntu (や CentOS/RedHat Enterprise Linux)をデスクトップとして使っている人はあまり多くないかもしれませんが、自分はその1人です。知る人ぞ知る GPD Pocket も Ubuntu モデルを購入しました。

個人的には最近の Linux デスクトップは非常に便利だと思ってますが、ただ使っていて「あれ??」と思うことが無いわけでもありません。例えば今回紹介するのは MPEG4 動画の再生なんですが、そのコーデックは標準で充分にインストールされているわけではないので、そのままでは再生できないことがあります。その Ubuntu 環境での導入方法を調べたのでメモ目的も兼ねてブログにします。


まず、MPEG4 AAC コーデックなどの拡張メディア機能を Ubuntu に導入するには ubuntu-restricted-extras というパッケージをあらかじめ導入しておく必要があります。以下のコマンドで導入します:
$ sudo apt-get install ubuntu-restricted-extras

その後、必要なメディアパッケージを導入します。今回の MPEG4 であれば以下のコマンドでインストールします:
$ sudo apt-get install libav-tools ffmpeg


これで MPEG4 AAC や H.264 デコーダーが導入されて、Ubuntu デスクトップからも再生可能になります。





Java のアプリ開発をしていて、こんな実行時エラーに遭遇することがあります:
java.lang.UnsatisfiedLinkError: C:\IBM\Notes\nlsxbe.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform


最近は Windows といえば 64bit 版が普通になってきました(よね?)。自分の開発環境も 64bit 版 Windows で、Java 開発環境も(JDK も JRE も Eclipse も)64bit 版を使っています。ここまでは珍しくないと思っています。

しかし開発するアプリケーションによっては外部ライブラリ(JAR ファイル)を指定する必要があり、その JAR ファイルが 32bit のネイティブライブラリを使っていたりするなど、64bit の JDK で 32bit 前提のコードを実行しなければならなくなった場合に上記のようなエラーが発生するのでした。

典型例としては、IBM Notes の Java API を使ったアプリケーション開発が挙げられます。2017/Oct/01 現在、Windows 版の IBM Notes は 32bit 版しか存在していません。Java で IBM Notes の API を実行するには IBM Notes 同梱の Notes.jar 他をインポートして使う必要がありますが、この Notes.jar は 32bit の DLL を参照するため、64bit Java から実行すると上記のエラーが発生します。


エラーの原因が分かったところで、どのように回避すればいいでしょうか? これも解決策としてはシンプルで 32bit Java(JRE) から実行すればよいことになります。32bit JRE をダウンロードして実行してもいいですが、上記のような IBM Notes の JAR を使った場合であれば、IBM Notes が使っている Java をそのまま指定して実行すればよいことになります。

ちとややこしいのが Eclipse のような IDE を使っている場合の指定方法です。まず Java - Installed JREs の中に Standard VM として Notes Java を追加しておく必要があります。Notes Java は(ノーツをインストールしたディレクトリ)/jvm にあるので、このディレクトリを指定して追加します:
2017100101


そして Eclipse の Java プロジェクトの中で JRE System Library として、上記で設定した Notes Java を指定します。これでこのプロジェクト内で作成したアプリを実行する際には Notes Java 内の java.exe が利用されるようになるので、上記のエラーが回避できるようになります:
2017100102



 

このページのトップヘ