ボクココ

個人開発に関するテックブログ

初めての Alexa Skill 開発で経験した挫折

ども、@kimihom です。

f:id:cevid_cpp:20190519151846p:plain

かねてより Amazon Echo を使った Alexa Skill の開発をしてきたわけだが、初めて自前で作った Alexa Skill 開発は失敗という形で終わったので報告しよう。

思いついた革新的な Alexa Skill

さて、私が開発していた Alexa Skill はどんなものかというと、以下のようなものだ。

「CRM に登録された通話のメモや録音を Alexa 上で再生してくれるアプリ」

なんだかんだで CRM に通話のメモや録音URL を保存しても、Web 上で振り返る機会ってあんまないんだよね。やはり録音再生ボタンを押して聞くっていう作業が入ってしまうことが致命的だ。そこで、Alexa で録音内容を再生して簡単に振り返ることができる Alexa Skill があれば、自分も使うだろうなってイメージができたので開発を開始したのだった。

Alexa Account Link

Alexa Skill 開発の中で、「アカウントリンク」っていうとても便利な機能がある。これを使えば、外部の OAuth アプリケーションと連携して、その人限定の情報を Alexa Skill 側で取得することが可能だ。

アカウントリンクとは | Alexa Skills Kit

そこで、 ユーザーの使っている CRM に保存された 録音を取得して読み上げるってところまで設計した。そして「これはいける!」ってことで開発をスタートした。私は普段 HubSpot CRM にコールの記録や録音を保存しているので、まず手始めに HubSpot CRM の OAuth の連携を始めた。

OAuth の設定

Alexa Developer Console を開くと、左のメニューに「アカウントリンク」というのがある。ここで、外部に公開されている OAuth のそれぞれの情報を入力することで、Alexa Skill に OAuth の仕組みを実現できる。

具体的には以下のような設定の流れだ。

Alexa Developer Console 内の設定

HubSpot Developers

  • アプリの作成
  • 顧客ID, 顧客の秘密をそれぞれ Alexa Developer Console へコピペ
  • OAuth の基本機能、コンタクト にチェック

設定がうまくいけば、Amazon Alexa のアプリを開き、メニューの「スキル・ゲーム」>「有効なスキル」> 「開発」で、開発しているアプリを開き、そこでアカウントリンクの OAuth 連携を実行できる。

この OAuth がうまく実行できると、Alexa Skill の ソースコード上で const accessToken = Alexa.getAccountLinkingAccessToken(handlerInput.requestEnvelope); で HubSpot 上のアクセストークンを取ってくることができるようになる。

ここまで実行できたところで、私はこの開発の「勝ち」を確信したものだった。「これは自分の作りたいものを実現できる・・・!」と。

Alexa Skill における録音再生の仕様

さて、今回は HubSpot で保存された録音を再生するのがメインの目的だ。 HubSpot 側で生のオーディオファイルを保存しているのではなく、単に URL を HubSpot 側に保存しているだけの仕様だ。それ自体は何の問題もなくて、単にそのオーディオ URL を再生できれば OK だった。しかし、これがうまくいかなかった。

Alexa での mp3 の再生には ビットレート(48 kbps)に変換する必要があるのだ。保存されている録音のオーディオファイルがこのビットレートではなかったため、音声ファイルを変換しないと Alexa 上で聞き取れないことが判明した。。これがあまりにも痛い仕様だった。

でも、私がこれで諦めるくらい弱い人間ではない。以下の手順でビットレートに変換して Alexa で流させるところまで実装してみた。

  1. HubSpot にある録音URL を取得・AWS Lambda 内にダウンロード
  2. ffmpeg を実行してビットレートを変換
  3. 変換したファイルを S3 へアップロード
  4. アップロードしたファイル URL を最終的に Alexa に読ませる

さて、 Node.js にすれば以下のようなコードになるだろう。コールバック地獄に陥っているが、そこは気にせず。

const execSync = require('child_process').execSync;
const S3BUCKET = "amzn1-ask-skill-~~~~";
const S3PATH = `https://${S3BUCKET}.s3.amazonaws.com/`;
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const request = require('request');
const fs = require('fs');

var audioUrl = "https://~~~/audio.mp3";
var inPath = `/tmp/in-${new Date().getTime()}.mp3`;
var outPath = `/tmp/out-${new Date().getTime()}.mp3`;
var s3File = `tmp/out-${new Date().getTime()}.mp3`;

request.get(audioUrl).on('response', function (res) {
    console.log('statusCode: ', res.statusCode);
    console.log('content-length: ', res.headers['content-length']);
}).pipe(fs.createWriteStream(inPath).on('close', function() {
    execSync("mv ./bin/ffmpeg /tmp");
    execSync("chmod 755 /tmp/ffmpeg");
    execSync(`/tmp/ffmpeg -i ${inPath} -ac 2 -codec:a libmp3lame -b:a 48k -ar 16000 ${outPath}`);

    // upload file
    s3.putObject({ Bucket: S3BUCKET, Key: s3File, Body: fs.readFileSync(outPath) }, function(err, res) {
      if (err) return genResponse(handlerInput, '残念。途中で失敗しちゃいました。');
      speechText += ` <audio src='${S3PATH}${s3File}'/>`;
      return genResponse(handlerInput, speechText);
    });
}));

いつか終わる夢

ここまで実装できて、バグがなく変換されたオーディオファイルに変換することができた。あとは AWS Lambda にデプロイして Alexa に実行させるだけとなった。

・・・。

Alexa Skill の実行時間の上限の壁にぶち当たったのである。Alexa Skill でデフォルトで提供されている AWS Lambda の実行時間の上限が 8秒 だった。上記の実行をするには、音声ファイルの大きさにもよるけど8秒を超えてしまう。

確かに、「Alexa、ハブスポットの録音を再生して」って言った後に、8秒も何の返答もなかったらバグってんじゃないかって誰もが思うことだ。この実行時間は快適な会話を実現するのに必要不可欠な仕様であろう。この Alexa の仕様を受け入れたことで、私の夢は終わったのである。

終わりに

初めての Alexa Skill 開発は頓挫してしまって、この夢に関しては終わりを迎えた。しかし、私の夢はこれからも出てきて、その新しい夢を実現させれば良いだけなんだ。

今回の開発のおかげで Alexa Skill の概要をかなり理解できたので、またアイディア出しと開発を休日にコツコツやっていく。

「Alexa、私のスキル開発が完成するまで待ってて」