20210308のNode.jsに関する記事は7件です。

【コピペOK】bashでnodeとyarnのインストールをする方法

こんにちは、くりぱんです。

この記事で実現できること

  • homebrewのインストール
  • nodebrewのインストール
  • Node.jsのインストール
  • yarnのインストール

説明

フロントエンドの環境を構築している際にnode.jsとyarnが必要になったので、nodebrewを使って、node.jsとyarnをインストールしていきます。
※なお、今回はbashで実装していきます。

開発環境

  • macOS Catalina
  • bash

実装の流れ

  • Homebrewのインストール
  • Nodebrewのインストール
  • nodebrewのPATHを通す
  • Node.jsのインストール
  • yarnのインストール

実装

Homebrewのインストール

Homebrewはパッケージ管理システムの一つで、様々なソフトウェアの導入を簡単にしてくれるツールです。
今回はこちらを使用していくので、下記のコマンドを実行して、インストールしてください。

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

公式サイト:https://brew.sh/index_ja

nodebrewのインストール

NodebrewはNode.jsのバージョンを管理するバージョン管理ツールです。今回はこのnodebrewを使用して、Node.jsとyarnを一括管理できるようにしていきます。
下記のコマンドを実行して、先ほどインストールしたHomebrewを使用してnodebrewをインストールしてください。

$ brew install nodebrew

これでnodebrewのインストールは終わりです。

nodebrewのPATHを通す

Nodebrewのコマンドを利用するためにnodebrewにPATHを通していきます。
下記のコマンドを実行してください。

$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile

追加したパスを適用していきます。

$ source ~/.bash_profile

これでNode.jsとyarnをインストールする準備が完了しました。

Node.jsのインストール

それでは、nodebrewを使用して、Node.jsをインストールしていきます。

$ nodebrew install-binary latest

インストールが完了したら、インストールされたNode.jsのバージョンを確認して、そのバージョンを使う設定にしていきます。

$ nodebrew list

v15.11.0などのバージョンが表示されていればOKです。このバージョンは人によって違うので、数字が違くても気にしなくて大丈夫です。

バージョンを確認したら、下記コマンドのバージョンを自分がインストールしたバージョンにしてコマンドを実行してください。

$ nodebrew use v15.11.0

下記コマンドでNode.jsのバージョンを確認してください。

$ node -v
v15.11.0

yarnのインストール

最後にyarnのインストールをして終わりです。

$ npm install -g yarn

バージョン確認もしておきましょう

$ yarn -v
1.22.10

最後に

以上でbashを使用したNode.jsとyarnのインストールは終了です。
各々フロントの開発を楽しんじゃってください!

少しでも役に立った!という時は、LGTMをポチッと、、、笑
1つでもLGTMが付くとその日がハッピーになるんです!
役に立たなかった時は、怒らないでコメント頂けると幸いです笑

Twitterもやってます!
プログラミングや金融知識についてやエンジニアの現実についてつぶやいています!
よかったら見てみてくださいね!
https://twitter.com/sakuslife

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js】Firebaseでエミュレーターを使ってjestのCIをGithub Actionsで

もうエミュレーターでテストすればいいんじゃないか説。

package.json
  "scripts": {
    "jest": "jest",
    "test": "firebase emulators:exec --only functions,firestore \"npm run jest --exit\"",
  }

別のFirebaseプロジェクトを用意する必要もないし、sinonでFirebaseAdminのモックとかも作る必要ないから楽。

Github Actions

jobs:
  ci:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest]
        node: [12]

    steps:
      - name: Checkout ?
        uses: actions/checkout@master

      - name: Setup node env ?
        uses: actions/setup-node@v2.1.2
        with:
          node-version: ${{ matrix.node }}

      - name: Cache node_modules ?
        uses: actions/cache@v2
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install dependencies ??‍?
        run: |
          npm install -g firebase-tools
          npm ci

      - name: Firebase runtime config ?
        uses: w9jds/firebase-action@master
        with:
          args: functions:config:get > .runtimeconfig.json
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
          PROJECT_ID: "default"

      - name: Run linter ?
        run: |
          npm run lint
          npm run test

functions.config()を使ってる場合は、firebase-actionsを使って.runtimeconfig.jsonに吐き出す。
また、エミュレーターの起動はfirebase-actionsを使わないのでnpm install -g firebase-toolsを忘れずに。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js】Firebaseでエミュレーターを使ったjestのCIをGithub Actionsで

もうエミュレーターでテストすればいいんじゃないか説。

package.json
  "scripts": {
    "jest": "jest",
    "test": "firebase emulators:exec --only functions,firestore \"npm run jest --exit\"",
  }

別のFirebaseプロジェクトを用意する必要もないし、sinonでFirebaseAdminのモックとかも作る必要ないから楽。

Github Actions

jobs:
  ci:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest]
        node: [12]

    steps:
      - name: Checkout ?
        uses: actions/checkout@master

      - name: Setup node env ?
        uses: actions/setup-node@v2.1.2
        with:
          node-version: ${{ matrix.node }}

      - name: Cache node_modules ?
        uses: actions/cache@v2
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install dependencies ??‍?
        run: |
          npm install -g firebase-tools
          npm ci

      - name: Firebase runtime config ?
        uses: w9jds/firebase-action@master
        with:
          args: functions:config:get > .runtimeconfig.json
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
          PROJECT_ID: "default"

      - name: Run linter ?
        run: |
          npm run lint
          npm run test

functions.config()を使ってる場合は、firebase-actionsを使って.runtimeconfig.jsonに吐き出す。
また、エミュレーターの起動はfirebase-actionsを使わないのでnpm install -g firebase-toolsを忘れずに。

Cloud Storageのテスト

FirebaseのStorageは現状エミュレーターのサポート外なので、initializeAppで認証情報を渡してテストする。同じプロジェクトでテストする場合、バケットをテスト用に変更すると楽。(デフォルトで用意されてるstagingのバケットなど)

storage.test.js
import * as admin from 'firebase-admin';
import { config } from 'firebase-functions';
admin.initializeApp({
  credential: admin.credential.cert({
    project_id: config().credential.project_id,
    client_email: config().credential.client_email,
    private_key: config().credential.private_key.replace(/\\n/g, '\n'),
  }),
  storageBucket: 'staging.<PROJECT_ID>.appspot.com', // test用に変更
});

秘密鍵をfunctions.config()から読み込む場合、\n\\nになって
Failed to parse private key: Error: Invalid PEM formatted message.
と、認証でコケるので改行に置換してあげる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

環境構築 OSがMojave以前の場合

1. Command Line Toolsの導入

1-1. Command Line Toolsをインストール

ターミナルに入力

$ xcode-select --install

「インストール」をクリック。
「同意する」をクリック。

2. Homebrewの導入

2-1. Homebrewをインストール

コマンドを順番に1つずつ実行。

$ cd  #ホームディレクトリに移動
$ pwd #ホームディレクトリにいるかどうか確認
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # コマンドを実行

PCのパスワードを入力する。
「Press RETURN to continue or other key to abort」
が表示されたら、エンターキーをおす。

2-2. Homebrewがインストールされているか確認

上のコマンドだけ実行する。
バージョン情報が表示されれば無事にインストールされている。

$ brew -v
Homebrew 2.5.1 # 数値は異なる場合があります

2-3. Homebrewをアップデート

$ brew update

2-4. Homebrewの権限を変更

$ sudo chown -R `whoami`:admin /usr/local/bin

再度パスワードを求められた場合は、先ほどと同じように入力。

3. Rubyをインストール

Webアプリケーションの開発においては専用のRubyをインストールする必要があります。

3-1. rbenv と ruby-buildをインストール

$ brew install rbenv ruby-build

3-2. rbenvをどこからも使用できるようにする

$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

3-3. bash_profileの変更を反映

$ source ~/.bash_profile

3-4. readlineをinstall

ターミナルのirb上で日本語入力を可能にする設定

$ brew install readline

3-5. readlineをどこからも使用できるようにする

$ brew link readline --force

3-6. rbenvを利用してRubyをインストール

$ RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)"
$ rbenv install 2.6.

2.6.5と書いてあるのは今回インストールするRubyのバージョンです。

3-7. 利用するRubyのバージョンを指定

$ rbenv global 2.6.5

デフォルトでPCに入っていたRubyから、先ほどインストールしたRubyを使用するように切り替えることができました。

3-8. rbenvを読み込んで変更を反映

$ rbenv rehash

3-9. Rubyのバージョンを確認

以下のコマンドで最終確認。

$ ruby -v

4. MySQLを用意

4-1. MySQLのインストール

$ brew install mysql@5.6

4-2. MySQLの自動起動設定

MySQLは本来であればPC再起動のたびに起動し直す必要がありますが、それは面倒であるため、自動で起動するようにしておく。

$ mkdir ~/Library/LaunchAgents
$ ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist

4-3.mysqlコマンドをどこからでも実行できるようにする

# mysqlのコマンドを実行できるようにする
$ echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile
# mysqlのコマンドが打てるか確認する
$ which mysql
# 以下のように表示されれば成功
/usr/local/opt/mysql@5.6/bin/mysql

4-4. mysqlを起動を確認

# mysqlの状態を確認するコマンドです
$ mysql.server status

# 以下のように表示されれば成功
 SUCCESS! MySQL running

5. Railsの導入

5-1. bundlerをインストール

Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストールする。

$ gem install bundler --version='2.1.4'

5-2. Railsをインストール

$ gem install rails --version='6.0.0'

5-3. rbenvを再読み込み

$ rbenv rehash

5-4. Railsが導入できたか確認

% rails -v
Rails 6.0.0   #「Rails」のあとに続く数字は変わる可能性があります

6. Node.jsの導入

6-1.Node.jsのインストール

$ brew install node@14

Node.jsへのパスを設定

$ echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile

6-2. Node.jsが導入できたか確認

$ node -v
v14.15.3 # 数値は異なる場合があります

7. yarnの導入

7-1. yarnをインストール

$ brew install yarn

7-2. yarnが導入できたか確認

$ yarn -v
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

環境構築 6 Node.js

Node.jsの導入

Railsを動かすためにはNode.jsが必要となり、それをHomebrewを用いてインストールします。

1. Node.jsのインストール

ターミナルに入力

brew install node@14

Node.jsへのパスを設定しましょう

% echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.zshrc
% source ~/.zshrc

2. Node.jsが導入できたか確認

% node -v
v14.15.3 # 数値は異なる場合がある
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINEボットとATOM Echoでボイスメッセージを作る

ATOM Echoには、マイクとスピーカとボタンとLEDが付いています。
ATOM Echoのマイクにしゃべった言葉がLINEメッセージとして通知されるようにするとともに、LINEアプリから応答メッセージを入力したら、ATOM EchoのLEDが点灯し、さらにボタンを押したら応答メッセージが音声でATOM Echoのスピーカから流れるようにします。

image.png

いくつかのサービスを使っています

・録音したWAVEファイルを、Google Cloud Speech APIの音声認識サービスを使ってテキスト文字に起こします。
・LINEボットの機能を使って、メッセージをLINEアプリに通知したり、LINEアプリに入力したメッセージを受信したりします。
・受信したテキストメッセージを、Amazon Pollyの音声合成サービスを使って音声ファイルにします。
・ATOM Echoで、メッセージ通知を検知するために、MQTTでサブスクライブします。

ソースコードもろもろは、以下のGitHubに上げておきました。

poruruba/LinebotCarrier
 https://github.com/poruruba/LinebotCarrier

ATOM Echo側

以下のライブラリを利用しています。

m5stack/M5StickC
 https://github.com/m5stack/M5StickC
ボタンの検出に使っています。

knolleary/PubSubClient
 https://github.com/knolleary/pubsubclient
MQTTサブスクライブに使っています。

bblanchon/ArduinoJson
 https://github.com/bblanchon/ArduinoJson
MQTTサブスクライブで受信するJSONのパースに使っています。

adafruit/Adafruit_NeoPixel
 https://github.com/adafruit/Adafruit_NeoPixel
RGBのLED制御に使っています。

録音は、以下を参考にしました。
 M5StickCとSpeaker HatでAI Chatと会話
 m5stack/M5-ProductExampleCodes

こんな感じです。

Arduino\LinebotCarrier\src\main.cpp
// 録音用タスク
void i2sRecordTask(void* arg){
  // 初期化
  recPos = 0;
  memset(soundStorage, 0, sizeof(soundStorage));

  vTaskDelay(100);

  // 録音処理
  while (isRecording) {
    size_t transBytes;

    // I2Sからデータ取得
    i2s_read(I2S_NUM_0, (char*)soundBuffer, BUFFER_LEN, &transBytes, (100 / portTICK_RATE_MS));

    // int16_t(12bit精度)をuint8_tに変換
    for (int i = 0 ; i < transBytes ; i += 2 ) {
      if ( recPos < STORAGE_LEN ) {
        int16_t* val = (int16_t*)&soundBuffer[i];
        soundStorage[recPos] = ( *val + 32768 ) / 256;
        recPos++;
        if( recPos >= sizeof(soundStorage) ){
          isRecording = false;
          break;
        }
      }
    }
//    Serial.printf("transBytes=%d, recPos=%d\n", transBytes, recPos);
    vTaskDelay(1 / portTICK_RATE_MS);
  }

  i2s_driver_uninstall(I2S_NUM_0);

  pixels.setPixelColor(0, pixels.Color(0, 0, 0));
  pixels.show();

  if( recPos > 0 ){
    unsigned long len = sizeof(temp_buffer);
    int ret = doHttpPostFile((base_url + "/linebot-carrier-wav2text").c_str(), soundStorage, recPos, "application/octet-stream", 
                              "upfile", "test.bin", NULL, NULL, temp_buffer, &len);
    if( ret != 0 ){
      Serial.println("/linebot-carrier-wav2text: Error");
    }else{
      Serial.println((char*)temp_buffer);
    }
  }

  // タスク削除
  vTaskDelete(NULL);
}

void i2sRecord(){
  isRecording = true;

  pixels.setPixelColor(0, pixels.Color(0, 100, 0));
  pixels.show();

  i2s_driver_uninstall(I2S_NUM_0);
  i2s_config_t i2s_config = {
      .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
      .sample_rate = SAMPLING_RATE,
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
      .channel_format = I2S_CHANNEL_FMT_ALL_RIGHT,
      .communication_format = I2S_COMM_FORMAT_I2S,
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
      .dma_buf_count = 6,
      .dma_buf_len = 60,
  };

  esp_err_t err = ESP_OK;

  err += i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_pin_config_t tx_pin_config;

  tx_pin_config.bck_io_num = I2S_BCLK;
  tx_pin_config.ws_io_num = I2S_LRC;
  tx_pin_config.data_out_num = I2S_DOUT;
  tx_pin_config.data_in_num = I2S_DIN;

  //Serial.println("Init i2s_set_pin");
  err += i2s_set_pin(I2S_NUM_0, &tx_pin_config);
  //Serial.println("Init i2s_set_clk");
  err += i2s_set_clk(I2S_NUM_0, SAMPLING_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);

  // 録音開始
  xTaskCreatePinnedToCore(i2sRecordTask, "i2sRecordTask", 4096, NULL, 1, NULL, 1);    
}

録音が完了したら、以下の部分で音声の生データをファイルとしてアップロードしています。

Arduino\LinebotCarrier\src\main.cpp
    int ret = doHttpPostFile((base_url + "/linebot-carrier-wav2text").c_str(), soundStorage, recPos, "application/octet-stream", 
                              "upfile", "test.bin", NULL, NULL, temp_buffer, &len);

MP3の再生は、以下を利用させていただきました。

schreibfaul1/ESP32-audioI2S
 https://github.com/schreibfaul1/ESP32-audioI2S

PlatformIOを利用している場合は、zipファイルの中身をlibフォルダに突っ込めばコンパイルに含めてくれます。
ポート番号は、ATOM Echoの配線に合わせています。

ただし、うまく動かないところがあり、いくつか修正しています。(この直し方でよいのか自信がないですが。。。)
GitHubには、修正したファイルだけ上げてあります。

録音と再生を切り替えられるように、Audioのコンストラクターから再セットアップ用の関数Audio:setup()に分離しました。

変更前

Arduino\LinebotCarrier\lib\ESP32-audioI2S-master\src\Audio.cpp
Audio::Audio() {
   clientsecure.setInsecure();  // if that can't be resolved update to ESP32 Arduino version 1.0.5-rc05 or higher
    //i2s configuration
    m_i2s_num = I2S_NUM_0; // i2s port number
    m_i2s_config.mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
    m_i2s_config.sample_rate          = 16000;
    m_i2s_config.bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT;
    m_i2s_config.channel_format       = I2S_CHANNEL_FMT_RIGHT_LEFT;
    m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
    m_i2s_config.intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1; // high interrupt priority
    m_i2s_config.dma_buf_count        = 8;      // max buffers
    m_i2s_config.dma_buf_len          = 1024;   // max value
    m_i2s_config.use_apll             = APLL_ENABLE;
    m_i2s_config.tx_desc_auto_clear   = true;   // new in V1.0.1
    m_i2s_config.fixed_mclk           = I2S_PIN_NO_CHANGE;

    i2s_driver_install((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL);

    m_f_forceMono = false;

    m_filter[LEFTCHANNEL].a0  = 1;
    m_filter[LEFTCHANNEL].a1  = 0;
    m_filter[LEFTCHANNEL].a2  = 0;
    m_filter[LEFTCHANNEL].b1  = 0;
    m_filter[LEFTCHANNEL].b2  = 0;
    m_filter[RIGHTCHANNEL].a0 = 1;
    m_filter[RIGHTCHANNEL].a1 = 0;
    m_filter[RIGHTCHANNEL].a2 = 0;
    m_filter[RIGHTCHANNEL].b1 = 0;
    m_filter[RIGHTCHANNEL].b2 = 0;

}

変更後

Arduino\LinebotCarrier\lib\ESP32-audioI2S-master\src\Audio.cpp
Audio::Audio() {
   clientsecure.setInsecure();  // if that can't be resolved update to ESP32 Arduino version 1.0.5-rc05 or higher
}

void Audio::setup(){
    i2s_driver_uninstall(I2S_NUM_0);

    //i2s configuration
    m_i2s_num = I2S_NUM_0; // i2s port number
    m_i2s_config.mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
    m_i2s_config.sample_rate          = 16000;
    m_i2s_config.bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT;
    m_i2s_config.channel_format       = I2S_CHANNEL_FMT_RIGHT_LEFT;
    m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
    m_i2s_config.intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1; // high interrupt priority
    m_i2s_config.dma_buf_count        = 8;      // max buffers
    m_i2s_config.dma_buf_len          = 1024;   // max value
    m_i2s_config.use_apll             = APLL_ENABLE;
    m_i2s_config.tx_desc_auto_clear   = true;   // new in V1.0.1
    m_i2s_config.fixed_mclk           = I2S_PIN_NO_CHANGE;

    i2s_driver_install((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL);

    m_f_forceMono = false;

    m_filter[LEFTCHANNEL].a0  = 1;
    m_filter[LEFTCHANNEL].a1  = 0;
    m_filter[LEFTCHANNEL].a2  = 0;
    m_filter[LEFTCHANNEL].b1  = 0;
    m_filter[LEFTCHANNEL].b2  = 0;
    m_filter[RIGHTCHANNEL].a0 = 1;
    m_filter[RIGHTCHANNEL].a1 = 0;
    m_filter[RIGHTCHANNEL].a2 = 0;
    m_filter[RIGHTCHANNEL].b1 = 0;
    m_filter[RIGHTCHANNEL].b2 = 0;

}

MP3ファイルが小さすぎると、再生されませんでしたので、ファイルサイズの下限を下げました。

変更前

Arduino\LinebotCarrier\lib\ESP32-audioI2S-master\src\Audio.cpp
        if((InBuff.bufferFilled() > 6000 && !m_f_psram) || (InBuff.bufferFilled() > 80000 && m_f_psram)) {

変更後

Arduino\LinebotCarrier\lib\ESP32-audioI2S-master\src\Audio.cpp
        if((InBuff.bufferFilled() > 1500 && !m_f_psram) || (InBuff.bufferFilled() > 80000 && m_f_psram)) {

なぜか、MP3の最後あたりの音が切れてしまいましたので、ウェイトを入れました。

変更前

Arduino\LinebotCarrier\lib\ESP32-audioI2S-master\src\Audio.cpp
        if(m_f_webfile && (byteCounter >= m_contentlength - 10) && (InBuff.bufferFilled() < maxFrameSize)) {
            // it is stream from fileserver with known content-length? and
            // everything is received?  and
            // the buff is almost empty?, issue #66 then comes to an end
            playI2Sremains();
            stopSong(); // Correct close when play known length sound #74 and before callback #112
            sprintf(chbuf, "End of webstream: \"%s\"", m_lastHost);
            if(audio_info) audio_info(chbuf);
            if(audio_eof_stream) audio_eof_stream(m_lastHost);
        }

変更後

Arduino\LinebotCarrier\lib\ESP32-audioI2S-master\src\Audio.cpp
        if(m_f_webfile && (byteCounter >= m_contentlength - 10) && (InBuff.bufferFilled() < maxFrameSize)) {
            // it is stream from fileserver with known content-length? and
            // everything is received?  and
            // the buff is almost empty?, issue #66 then comes to an end
            while(!playI2Sremains()) {
                ;
            }
            delay(500);
            stopSong(); // Correct close when play known length sound #74 and before callback #112
            sprintf(chbuf, "End of webstream: \"%s\"", m_lastHost);
            if(audio_info) audio_info(chbuf);
            if(audio_eof_stream) audio_eof_stream(m_lastHost);
        }

後はこんな感じで呼び出すだけです。引数として、MP3を置いたURLを指定します。

Arduino\LinebotCarrier\src\main.cpp
void i2sPlayUrl(const char *url){
  if( audio.isRunning() )
    audio.stopSong();

  audio.setup();
  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT, I2S_DIN);
  audio.setVolume(I2S_VOLUME); // 0...21

  audio.connecttohost(url);
}

WAVEファイルのアップロードは以下を参考にしました。
 ESP32でバイナリファイルのダウンロード・アップロード

MQTTサブスクライブは以下を参考にしました。
 ESP32で作るBeebotteダッシュボード

Node.js側

ざっと、以下のnpmモジュールを使っています。

・line/line-bot-sdk-nodejs
 https://github.com/line/line-bot-sdk-nodejs

・googleapis/nodejs-speech
 https://github.com/googleapis/nodejs-speech

・mqttjs/MQTT.js
 https://github.com/mqttjs/MQTT.js

・aws/aws-sdk-js
 https://github.com/aws/aws-sdk-js

Node.js\api\controllers\linebot-carrier\index.js
const line = require('@line/bot-sdk');

const speech = require('@google-cloud/speech');
const client = new speech.SpeechClient();

const mqtt = require('mqtt');

const AWS = require('aws-sdk');
const polly = new AWS.Polly({
  apiVersion: '2016-06-10',
  region: 'ap-northeast-1'
});

以下の2つのエンドポイントを立ち上げています。

・/linebot-carrier
LINEボットのWebhookであり、LINEメッセージを受信すると呼び出されます。

・/linebot-carrier-wav2text
ATOM Echoからの、WAVファイルのアップロードを受け付けます。

説明が面倒なので、ソースをそのまま載せています。すみません。

Node.js\api\controllers\linebot-carrier\index.js
'use strict';

const config = {
  channelAccessToken: '【LINEチャネルアクセストークン(長期)】',
  channelSecret: '【LINEチャネルシークレット】',
};

const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/';
const Response = require(HELPER_BASE + 'response');

var line_usr_id = '【LINEユーザID】';

const LineUtils = require(HELPER_BASE + 'line-utils');
const line = require('@line/bot-sdk');
const app = new LineUtils(line, config);

const speech = require('@google-cloud/speech');
const client = new speech.SpeechClient();

const mqtt = require('mqtt');
const MQTT_HOST = process.env.MQTT_HOST || '【MQTTサーバのURL(例:mqtt://hostname:1883)】';
const MQTT_CLIENT_ID = 'linebot-carrier';
const MQTT_TOPIC_TO_ATOM = 'linebot_to_atom';

const THIS_BASE_PATH = process.env.THIS_BASE_PATH;
const MESSAGE_MP3_FNAME = THIS_BASE_PATH + '/public/message.mp3';

const fs = require('fs');

const AWS = require('aws-sdk');
const polly = new AWS.Polly({
  apiVersion: '2016-06-10',
  region: 'ap-northeast-1'
});

const mqtt_client = mqtt.connect(MQTT_HOST, { clientId: MQTT_CLIENT_ID });
mqtt_client.on('connect', () => {
  console.log("mqtt connected");
});

app.follow(async (event, client) =>{
  console.log("app.follow: " + event.source.userId );
//  line_usr_id = event.source.userId;
});

app.message(async (event, client) =>{
    console.log("linebot: app.message");

  var buffer = await speech_to_wave(event.message.text);
  fs.writeFileSync(MESSAGE_MP3_FNAME, buffer);

  var json = {
    message: event.message.text
  };
  mqtt_client.publish(MQTT_TOPIC_TO_ATOM, JSON.stringify(json));

    var message = {
        type: 'text',
        text: '$',
        emojis: [
            {
                index: 0,
                productId: "5ac1de17040ab15980c9b438",
                emojiId: 120
            }
        ]
    };
  return client.replyMessage(event.replyToken, message);
});

exports.fulfillment = app.lambda();

exports.handler = async (event, context, callback) => {
  if( event.path == '/linebot-carrier-wav2text' ){
//    console.log(new Uint8Array(event.files['upfile'][0].buffer));
        var norm = normalize_wave8(new Uint8Array(event.files['upfile'][0].buffer));

        // 音声認識
        var result = await speech_recognize(norm);
        if( result.length < 1 )
            throw 'recognition failed';

    var text = result[0];
    console.log(text);
    app.client.pushMessage(line_usr_id, app.createSimpleResponse(text));
    return new Response({ message: text });
  }
};

function normalize_wave8(wav, out_bitlen = 16){
    var sum = 0;
    var max = 0;
    var min = 256;
    for( var i = 0 ; i < wav.length ; i++ ){
        var val = wav[i];
        if( val > max ) max = val;
        if( val < min ) min = val;
        sum += val;
    }

    var average = sum / wav.length;
    var amplitude = Math.max(max - average, average - min);
/*
    console.log('sum=' + sum);
    console.log('avg=' + average);
    console.log('amp=' + amplitude);
    console.log('max=' + max);
    console.log('min=' + min);
*/
    if( out_bitlen == 8 ){
        const norm = Buffer.alloc(wav.length);
        for( var i = 0 ; i < wav.length ; i++ ){
            var value = (wav[i] - average) / amplitude * (127 * 0.8) + 128;
            norm[i] = Math.floor(value);
        }
        return norm;
    }else{
        const norm = Buffer.alloc(wav.length * 2);
        for( var i = 0 ; i < wav.length ; i++ ){
            var value = (wav[i] - average) / amplitude * (32767 * 0.8);
            norm.writeInt16LE(Math.floor(value), i * 2);
        }
        return norm;
    }
}

async function speech_recognize(wav){
    const config = {
        encoding: 'LINEAR16',
        sampleRateHertz: 8192,
        languageCode: 'ja-JP',
    };
    const audio = {
        content: wav.toString('base64')
    };

    const request = {
        config: config,
        audio: audio,
    };  
    return client.recognize(request)
    .then(response =>{
        const transcription = [];
        for( var i = 0 ; i < response[0].results.length ; i++ )
            transcription.push(response[0].results[i].alternatives[0].transcript);

            return transcription;
    });
}

async function speech_to_wave(message, voiceid = 'Mizuki', samplerate = 16000 ){
    const pollyParams = {
    OutputFormat: 'mp3',
        Text: message,
        VoiceId: voiceid,
        TextType: 'text',
        SampleRate : String(samplerate),
    };

    return new Promise((resolve, reject) =>{
        polly.synthesizeSpeech(pollyParams, (err, data) =>{
            if( err ){
                console.log(err);
                return reject(err);
            }
            var buffer = Buffer.from(data.AudioStream);
            return resolve(buffer);
        });
    });
}

ファイルアップロードを受信するところは、以下を参考にしてください。
 バイナリファイルのアップロード・ダウンロードをする

WAVファイルから音声認識をするところ、テキスト文字列を音声ファイルに変換するところは、以下を参考にしてください。
 M5StickCとSpeaker HatでAI Chatと会話

LINEボットにより、メッセージ受信をトリガするには以下を参考にしてください。
 LINEボットを立ち上げるまで。LINEビーコンも。

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ElectronのexecuteJavascriptでError: Script failed to executeが出る件

はじめに

初投稿です...
ElectronのBrowserViewでexecuteJavascriptを使った際、一見正しそうなスクリプトがエラーを吐く問題で少々悩んだので備忘録として。

問題のソースコード

要素の取得に失敗したらfalse、成功したら真を返したい

      const result = await browserView.webContents.executeJavaScript(`
        const anko = document.getElementById("anko"); 
        return anko == null ? false : true;
      `)

正しいソースコード

どうやらreturnで返しちゃだめらしいです。(そんなん知らんよ…)
最後に評価された値が返却されるらしい。

      const result = await browserView.webContents.executeJavaScript(`
     let val = true;
        const anko = document.getElementById("anko"); 
        anko == null ? false : true;
      `)

現在作っているアプリ

Youtube Live用のコメントビュワーです(Electron製)(ベータ版)
配信する方はぜひどうぞ
- https://tubug.netlify.app

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む