Eagle Eye Networks APIで簡単ビデオ連携 その2 動画取得

#Eagle Eye Networks APIで簡単ビデオ連携 その2 動画取得

[前回の静止画取得](http://qiita.com/c-nova/items/6da5482019b9ca7b7f62)に引き続き、今回は動画の取得をしてみたいと思います。静止画と違い、動画を簡単に取得するのはオンプレではなかなか難しいものです。特にオンプレのVMSからアプリケーション経由で取得しないといけない場合はアプリケーションの操作やSDKによる作り込みが発生したり、取得できたとしても大きい動画を取得するには余計な通信帯域を使用する結果になります。

今回使用するEENの場合、動画は拠点に設置されているブリッジから無操作で、かつ帯域制御をしながらEENのクラウドデータセンターに直接配置されるため、通信帯域の圧迫も防ぎ、クラウドのアプリケーションから直接クラウドのストレージから動画を取得することが可能です。またEENのREST APIを使用することで簡単に動画を取得できるのが大きな特徴です。
今回はこのAPIを使用し、簡単に動画を取得する方法を紹介したいと思います。

##EENで動画を取得できると何かいいのか？？
静止画と異なり、動画に使用範囲は非常に広いですので掻い摘んで。

- 入退室管理でアラートが発生した際に、実際に違反行為を行った人物の動作を見ることができる
- 今までカメラ単位で実施していた画像解析や動画解析を、一元的にクラウド環境で行うことがきる
- 製造装置の異常状態をセンサーから受け取ったときに、実際に周辺環境がどうだったか目視で確認できる。海外の工場であっても、日本から一元的に確認可能

などなど、いろいろな事に応用可能です。

##環境、事前確認
環境条件や事前確認項目は[前回](http://qiita.com/c-nova/items/6da5482019b9ca7b7f62#%E7%92%B0%E5%A2%83)と同様ですので、<-のリンク先を参照して下さい。
EENの機能の詳細、APIについても前回同様以下のサイトを御覧ください。
[EEN製品サイト（日本語）](http://www.iguazu-eagleeye.jp/)
[EEN APIサイト（日本語）](http://een-api-doc-jp.mybluemix.net/)

##Node-REDでインポート

下記のJSON DocをデプロイしたNode-RED上にインポートしてみてください。

- 認証、認可用ノード

```
[{"id":"977ab801.29e488","type":"inject","z":"ba43e00b.410be","name":"","topic":"","payload":"","payloadType":"str","repeat":"3600","crontab":"","once":true,"x":110,"y":400,"wires":[["dc2123d7.9639d"]]},{"id":"dc2123d7.9639d","type":"function","z":"ba43e00b.410be","name":"Get Auth Head","func":"var user = \"<User ID>\";\nvar password = \"<Password>\";\nmsg.payload = 'username=' + user + '&password=' + password;\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":400,"wires":[["a175b66d.8cb478"]]},{"id":"a175b66d.8cb478","type":"http request","z":"ba43e00b.410be","name":"","method":"POST","ret":"obj","url":"https://login.eagleeyenetworks.com/g/aaa/authenticate","x":330,"y":460,"wires":[["df64b846.5dc978"]]},{"id":"df64b846.5dc978","type":"http request","z":"ba43e00b.410be","name":"","method":"POST","ret":"obj","url":"https://login.eagleeyenetworks.com/g/aaa/authorize","x":330,"y":520,"wires":[["73b7e889.bc36f8"]]},{"id":"73b7e889.bc36f8","type":"contrib-json","z":"ba43e00b.410be","engine":"JSONPath","command":"jq","expr":"$.headers.set-cookie[1]","complete":"complete","prop":"payload","name":"Put Cookie","x":650,"y":400,"wires":[["e994bf64.d51cb"]]},{"id":"e994bf64.d51cb","type":"function","z":"ba43e00b.410be","name":"Form Auth Key2","func":"var cookies = msg.payload[0].split(\"; \");\nfor (var i = 0; i < cookies.length; i++) {\n\tvar str = cookies[i].split(\"=\");\n\tif (str[0] === \"auth_key\") {\n\t\tglobal.set(\"auth_key2\", unescape(str[1]));\n\t\tbreak;\n\t}\n}\nmsg.payload = global.get(\"auth_key2\");\nreturn msg;","outputs":1,"noerr":0,"x":660,"y":460,"wires":[["cf84a0d5.fefc5"]]},{"id":"173b1941.36c997","type":"comment","z":"ba43e00b.410be","name":"Generate EEN Auth Key loop #2","info":"","x":190,"y":340,"wires":[]},{"id":"cf84a0d5.fefc5","type":"debug","z":"ba43e00b.410be","name":"","active":false,"console":"false","complete":"false","x":650,"y":520,"wires":[]}]
```

- 動画表示用ノード

```
[{"id":"abdeaf43.8d949","type":"comment","z":"13af9bb0.48d9b4","name":"Get EEN FLV Video","info":"### Usage\nhttp://<My Bluemix Site>.mybluemix.net/eengetflv?camName=<Camera Name>&st=<Start Time>\n\nThis endopoint is play the video from specified range with \"st\"(Start Time) and \"et\"(End Time).\nIf some number of video is detectd, Video Player is playing 'Latest' video.\n\n- camName\n    - Camera Display Name\n\n- st\n  Start time of video serch range. It must be 'Date' and 'Time' format. ex) 2016/02/16 10:00:00 note) Don't enter as EEN Timestump format\n\n- test\n  If 'test' parameter specify to '1', Live video is not shown and return only 'Return code'.\n  This parameter is option .\n\n### Example\nhttp://<My Bluemix Site>.mybluemix.net/eengetflv?camName=EN-CDUM-002a&st=2016/07/29 09:00:00\n\n","x":150,"y":920,"wires":[]},{"id":"243fc437.34d07c","type":"function","z":"13af9bb0.48d9b4","name":"Set Auth & CamName","func":"msg.orgheaders=msg.req.headers;\n\nif (msg.payload.auth_key) {\n    msg.auth_key = msg.payload.auth_key;\n} else {\n    msg.auth_key = global.get(\"auth_key2\");\n}\n\nif (msg.payload.camName) {\n    msg.camName = msg.payload.camName;\n} else {\n    return null;\n}\nif (msg.payload.st) {\n    var st_JST = msg.payload.st;\n} else {\n    var st_JST = \"2016/1/1 00:00:00\";\n}\nif (msg.payload.test) {\n    msg.test = msg.payload.test;\n}\n\nvar st_JST_do = new Date(st_JST);\nmsg.st_JST = [st_JST_do.getFullYear(),\n                ( '0' + (st_JST_do.getMonth() + 1)).slice(-2),\n                ( '0' + st_JST_do.getDate() ).slice(-2),\n                ( '0' + st_JST_do.getHours() ).slice( -2 ),\n                ( '0' + st_JST_do.getMinutes() ).slice( -2 ),\n                ( '0' + st_JST_do.getSeconds() ).slice( -2 )\n                ].join(\"\");\n\nvar st_UTC_do = new Date(st_JST_do.getTime() - 32400000);\nmsg.st_UTC = [st_UTC_do.getFullYear(),\n                ( '0' + (st_UTC_do.getMonth() + 1)).slice(-2),\n                ( '0' + st_UTC_do.getDate() ).slice(-2),\n                ( '0' + st_UTC_do.getHours() ).slice( -2 ),\n                ( '0' + st_UTC_do.getMinutes() ).slice( -2 ),\n                ( '0' + st_UTC_do.getSeconds() ).slice( -2 )\n                ].join(\"\");\n\nreturn msg;","outputs":1,"noerr":0,"x":380,"y":980,"wires":[["38fec407.2dcf9c"]]},{"id":"5f3ddcce.01c604","type":"http in","z":"13af9bb0.48d9b4","name":"GetFLVVideo","url":"/eengetflv","method":"get","swaggerDoc":"","x":130,"y":980,"wires":[["243fc437.34d07c","424b25a8.824b5c"]]},{"id":"38fec407.2dcf9c","type":"http request","z":"13af9bb0.48d9b4","name":"Get Camera ID","method":"GET","ret":"obj","url":"https://login.eagleeyenetworks.com/g/device/list?A={{auth_key}}&t=camera&s=ATTD","x":360,"y":1040,"wires":[["aaba59aa.762a78"]]},{"id":"aaba59aa.762a78","type":"function","z":"13af9bb0.48d9b4","name":"Set CamID & URL","func":"var camLength = msg.payload.length;\n\nfor (itr = 0; itr < camLength; itr++){\n    var camIdx = msg.payload[itr].indexOf(msg.camName);\n    if ( camIdx > 0 ) {\n        msg.camID = msg.payload[itr][camIdx - 1];\n    }\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":370,"y":1100,"wires":[["b52b57fd.0753e8","b959193d.4576b8"]]},{"id":"162f574c.cb1ca9","type":"change","z":"13af9bb0.48d9b4","name":"Restore Header","rules":[{"t":"delete","p":"headers","pt":"msg"},{"t":"set","p":"headers","pt":"msg","to":"orgheaders","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":920,"y":980,"wires":[["9382bf0c.e47d3"]]},{"id":"1dce94ec.dc1e6b","type":"debug","z":"13af9bb0.48d9b4","name":"","active":true,"console":"false","complete":"payload","x":670,"y":1160,"wires":[]},{"id":"9382bf0c.e47d3","type":"template","z":"13af9bb0.48d9b4","name":"Gen Page Vid","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<head>\n  <link href=\"http://vjs.zencdn.net/5.9.2/video-js.css\" rel=\"stylesheet\">\n\n  <!-- If you'd like to support IE8 -->\n  <script src=\"http://vjs.zencdn.net/ie8/1.1.2/videojs-ie8.min.js\"></script>\n</head>\n\n<body>\n    <h2>Start Time: {{vidStartHRJ}} <br> End Time: {{vidEndHRJ}}</h2>\n    <a href=\"http://<My Bluemix Site>.mybluemix.net/eengetflv?camName={{camName}}&st={{nextVideoJ}}&auth_key={{auth_key}}\">次のビデオ</a><br><br>\n  <video id=\"my-video\" class=\"video-js vjs-default-skin\" controls preload=\"auto\" width=\"640\" height=\"480\"\n  data-setup=\"{}\">\n    <source src=\"{{{liveurl}}}\" type='video/x-flv'>\n    <p class=\"vjs-no-js\">\n      To view this video please enable JavaScript, and consider upgrading to a web browser that\n      <a href=\"http://videojs.com/html5-video-support/\" target=\"_blank\">supports HTML5 video</a>\n    </p>\n  </video>\n  <script src=\"http://vjs.zencdn.net/5.9.2/video.js\"></script>\n</body>","x":920,"y":1040,"wires":[["149bdd44.c70793"]]},{"id":"149bdd44.c70793","type":"http response","z":"13af9bb0.48d9b4","name":"","x":1130,"y":1040,"wires":[]},{"id":"b5141a60.a530f8","type":"debug","z":"13af9bb0.48d9b4","name":"","active":true,"console":"false","complete":"liveurl","x":670,"y":1220,"wires":[]},{"id":"b52b57fd.0753e8","type":"debug","z":"13af9bb0.48d9b4","name":"","active":true,"console":"false","complete":"camID","x":670,"y":1100,"wires":[]},{"id":"263468cd.349808","type":"function","z":"13af9bb0.48d9b4","name":"Pull Video Range","func":"delete msg.url;\n\nvar vidLength = msg.payload.length;\n\nif ( msg.payload[0] ) {\n    msg.vidStart = msg.payload[vidLength - 1].s;\n    msg.vidEnd = msg.payload[vidLength - 1].e;\n    msg.liveurl = \"https://login.eagleeyenetworks.com/asset/play/video.flv?c=\" + msg.camID + \";t=\" + msg.vidStart + \";e=\" + msg.vidEnd + \";A=\" + msg.auth_key;\n    var vidStartHR = msg.vidStart.substr(0,4) + \"/\" + msg.vidStart.substr(4,2) + \"/\" + msg.vidStart.substr(6,2) + \" \" + msg.vidStart.substr(8,2) + \":\" + msg.vidStart.substr(10,2) + \":\" + msg.vidStart.substr(12,2);\n    var vidEndHR = msg.vidEnd.substr(0,4) + \"/\" + msg.vidEnd.substr(4,2) + \"/\" + msg.vidEnd.substr(6,2) + \" \" + msg.vidEnd.substr(8,2) + \":\" + msg.vidEnd.substr(10,2) + \":\" + msg.vidEnd.substr(12,2);\n    var st_UTC_do = new Date(vidStartHR);\n    var nv_UTC_do = new Date(vidEndHR);\n    var st_JST_do = new Date(st_UTC_do.getTime() + 32400000);\n    var nv_JST_do = new Date(nv_UTC_do.getTime() + 32400000);\n    var vidStartJ = [st_JST_do.getFullYear() + '/',\n                ( '0' + (st_JST_do.getMonth() + 1)).slice(-2) + '/',\n                ( '0' + st_JST_do.getDate() ).slice(-2) + ' ',\n                ( '0' + st_JST_do.getHours() ).slice( -2 ) + ':',\n                ( '0' + st_JST_do.getMinutes() ).slice( -2 ) + ':',\n                ( '0' + st_JST_do.getSeconds() ).slice( -2 )\n                ].join(\"\");\n    msg.nextVideoJ = [nv_JST_do.getFullYear() + '/',\n                ( '0' + (nv_JST_do.getMonth() + 1)).slice(-2) + '/',\n                ( '0' + nv_JST_do.getDate() ).slice(-2) + ' ',\n                ( '0' + nv_JST_do.getHours() ).slice( -2 ) + ':',\n                ( '0' + nv_JST_do.getMinutes() ).slice( -2 ) + ':',\n                ( '0' + (nv_JST_do.getSeconds() + 1)).slice( -2 )\n                ].join(\"\");\n    msg.vidStartHRJ = st_JST_do.getFullYear() + \"年\" + (st_JST_do.getMonth() + 1) + \"月\" + st_JST_do.getDate() + \"日 \" + st_JST_do.getHours() + \"時\" + st_JST_do.getMinutes() + \"分\" + st_JST_do.getSeconds() + \"秒\";\n    msg.vidEndHRJ = nv_JST_do.getFullYear() + \"年\" + (nv_JST_do.getMonth() + 1) + \"月\" + nv_JST_do.getDate() + \"日 \" + nv_JST_do.getHours() + \"時\" + nv_JST_do.getMinutes() + \"分\" + nv_JST_do.getSeconds() + \"秒\";\n\n} else {\n    msg.statusCode = 400;\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":370,"y":1220,"wires":[["b5141a60.a530f8","46cd4cfe.38e8d4"]]},{"id":"b959193d.4576b8","type":"http request","z":"13af9bb0.48d9b4","name":"Get Video List","method":"GET","ret":"obj","url":"https://login.eagleeyenetworks.com/asset/list/video?start_timestamp={{st_UTC}}.000;id={{camID}};A={{auth_key}};count=1;options=coalesce","tls":"","x":360,"y":1160,"wires":[["263468cd.349808","1dce94ec.dc1e6b"]]},{"id":"46cd4cfe.38e8d4","type":"switch","z":"13af9bb0.48d9b4","name":"Status Check","property":"statusCode","propertyType":"msg","rules":[{"t":"else"},{"t":"eq","v":"400","vt":"str"}],"checkall":"true","outputs":2,"x":680,"y":980,"wires":[["162f574c.cb1ca9"],["13ffa7b9.e6fcb8"]]},{"id":"13ffa7b9.e6fcb8","type":"template","z":"13af9bb0.48d9b4","name":"Invalid range","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<head>\n</head>\n\n<body>\n    <h2>不正な動画呼び出し範囲です</h2>\n</body>","x":910,"y":1100,"wires":[["149bdd44.c70793"]]},{"id":"424b25a8.824b5c","type":"debug","z":"13af9bb0.48d9b4","name":"","active":true,"console":"false","complete":"false","x":130,"y":1040,"wires":[]}]
```

インポートが完了すると、それぞれ下記のように表示されるはずです。
![auth.png](https://qiita-image-store.s3.amazonaws.com/0/106468/a4cd4a4f-7071-2e33-ebf9-7f687a95eb02.png)
![Download.png](https://qiita-image-store.s3.amazonaws.com/0/106468/6508fc24-628b-1c36-5788-49385f4af263.png)

##ノードのカスタマイズ
前回インポート、カスタマイズした方はそのまま認証ノードは使用できますので、この部分は省いて問題ありません。今回始めての方は[前回の変更方法](http://qiita.com/c-nova/items/6da5482019b9ca7b7f62#%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA)を参考に、ご自身のEENユーザーID、パスワードを定義しているノードに記載してください。

今回は再生ページに自分のBluemixのサイト名を指定する必要があります（すいません、相対で呼ぶのを忘れていました）ので、Templateノード内の<My Bluemix Site>という部分をご自身のサイト名に書き換えてください。

##繋げてみよう
これで準備は完了です。
実際にURLにアクセスして確認してみましょう。
接続先のURLは、

http://Bluemixアプリ名+myblemix.net等/eengetflv

です。

この仕組はカメラ名が必要ですので、
?camName=カメラ名
というパラメータを上記のURLに付加します。最終的には、

http://Bluemixアプリ名+myblemix.net等/eengetflv?camName=カメラ名

となります。
このURLでアクセスを行うと、以下のような静止画が表示されます。

![DLPre.png](https://qiita-image-store.s3.amazonaws.com/0/106468/78d2b1b5-0ac0-d12c-6a19-e273e4dc7e5c.png)

画面にカメラの静止画と時間、「次のビデオ」のリンクが表示されます。
この画面の中にある「再生」ボタンを押すことで、動画の再生が開始されます。

![DLPlay.png](https://qiita-image-store.s3.amazonaws.com/0/106468/1b7e8554-0117-2405-a44f-dee7f814101a.png)

動画プレイヤーは[Video.js](http://videojs.com/)を使用していますが、お好みで変更することも可能です。動画再生ノード群の中のTemplateノードを変更することで変更可能です。

「次のビデオ」リンクをクリックすると、以下のように（もしモーション検知設定がなされていれば）次に録画された動画の画面に遷移します。

![DLNext.png](https://qiita-image-store.s3.amazonaws.com/0/106468/82702d4f-6911-f9f1-ea98-234cbc139090.png)

このアプリケーションは時間を指定して動画を取得することも可能です。例えば2016年10月21日午前10時の動画を見たい場合には、

http://Bluemixアプリ名+myblemix.net等/eengetflv?camName=カメラ名&st=2016/10/21%2010:00:00
※"%20"は空白のurlencodeですので、"2016/10/21 10:00:00"と記載してもOKです

と指定することで、その時間以降の直近の動画がポイントされます。

![DLTime.png](https://qiita-image-store.s3.amazonaws.com/0/106468/beb9a097-67eb-9ce2-034c-e4fbf88d4bf7.png)


##最後に

今回は静止画とは違い、動画ということで少し複雑なことができることがわかりました。
動画を直接取得する場合はどうしてもUIを作らなくてはならないのがネックですが、EENの場合はカメラIDとタイムスタンプで簡単にEENのUIを呼び出して動画の再生やダウンロードを行うことが可能です。これは次回ご紹介したいと思います。

その次は動画の保存を予定していますが、もしかすると全く別の内容を書くかも・・・です。

##ライセンス、著作権等

本スクリプト、ノード構成、エクスポート内容についてはMIT Licenseを適用します。
Eagle Eye Networksの商標およびロゴについてはEagle Eye Networks, Inc.に帰属します。
IBMおよびIBM Bluemix、Node-Redの商標およびロゴについてはInternational Business Machines Corporationに帰属します。
node.jsの商標およびロゴについてはNode.js contributorsに帰属します。