ドキュメント作成の準備
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Alexa TTS API 実装記録 (2026-03-02)</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Alexa TTS API 実装記録 (2026-03-02)</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<p>Alexa TTS API マスタードキュメント</p>
|
||||
<p><strong>最終更新</strong>: 2026-03-03 <strong>状態</strong>: サーバーからの日本語TTS未解決(調査中)
|
||||
<br>------------
|
||||
<br>2026/03/03 10:24 akira記録
|
||||
<br>akiraが下記の変更をしましたので、内容を読んでください。
|
||||
<br>
|
||||
<br>1) 構成とサーバーへのファイル受け渡し方法を変更しました
|
||||
<br>/home/claude/windmill_workflow
|
||||
<br>に、https://gitea.keinafarm.net/akira/windmill_workflow.gitをcloneしました
|
||||
<br>これにより、
|
||||
<br>C:\Users\akira\Develop\windmill_workflow
|
||||
<br>とのやり取りはgiteaを使って出来るようになります。
|
||||
<br>
|
||||
<br>2) docker compose up -dで、「 Docker 内部の IP アドレスが変わり、Traefik のルーティングが古い IP
|
||||
を参照したまま 502/504 エラーになる」のは、原因があるはずです。(他のコンテナで、問題になっていないので)
|
||||
<br>調査して、Traefik 再起動が不必要になるようにしたいです
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>------------</p>
|
||||
<hr>
|
||||
<h2>概要</h2>
|
||||
<p>Windmill から家中の Echo デバイスに任意のテキストを読み上げさせる API サーバー。Node.js + Express で実装し、Docker
|
||||
コンテナとして Windmill サーバー上で稼働する。</p>
|
||||
<p><strong>⚠ 現在の問題</strong>: ローカル PC からは日本語TTS動作確認済み。しかしサーバー(keinafarm.net)のコンテナからリクエストすると日本語文字が
|
||||
Amazon 側で除去されて発話されない。原因は Amazon の IP ベースフィルタリング(海外IPからは日本語 textToSpeak
|
||||
を無視する模様)。調査継続中。</p>
|
||||
<h2>ファイル構成</h2>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ファイル</th>
|
||||
<th>場所</th>
|
||||
<th>役割</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>server.js</td>
|
||||
<td>alexa-api/(リポジトリ)</td>
|
||||
<td>Express API サーバー本体</td>
|
||||
<td>本番コード。変更したらビルド・再デプロイが必要</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dockerfile</td>
|
||||
<td>alexa-api/(リポジトリ)</td>
|
||||
<td>Docker イメージ定義</td>
|
||||
<td>node:20-alpine ベース。server.js と package*.json をコピー</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>docker-compose.yml</td>
|
||||
<td>alexa-api/(リポジトリ)</td>
|
||||
<td>コンテナ起動設定</td>
|
||||
<td>windmill_windmill-internal ネットワーク接続。外部ポート非公開</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>package.json / package-lock.json</td>
|
||||
<td>alexa-api/(リポジトリ)</td>
|
||||
<td>npm 依存関係</td>
|
||||
<td>本番: express のみ。devDeps に alexa-remote2(不使用)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>.env.example</td>
|
||||
<td>alexa-api/(リポジトリ)</td>
|
||||
<td>環境変数テンプレート</td>
|
||||
<td>ALEXA_COOKIE=xxx の形式</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>.env</td>
|
||||
<td>alexa-api/(リポジトリ、.gitignore 対象)</td>
|
||||
<td>実際の Cookie 保管</td>
|
||||
<td>Git にコミットしない。ローカル作業後に scp でサーバーへ転送</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>auth4.js</td>
|
||||
<td>alexa-api/(リポジトリ)</td>
|
||||
<td>Amazon 認証・Cookie 取得スクリプト</td>
|
||||
<td>ローカルのみで実行(Windows PC)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>auth.js / auth2.js / auth3.js</td>
|
||||
<td>alexa-api/(リポジトリ)</td>
|
||||
<td>auth4.js の旧バージョン</td>
|
||||
<td>参考用。実際は auth4.js を使う</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>test_tts.js</td>
|
||||
<td>alexa-api/(リポジトリ)</td>
|
||||
<td>ローカルテスト用スクリプト</td>
|
||||
<td>直接 alexa.amazon.co.jp を叩いて動作確認</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p><strong>サーバー上のファイル場所</strong>: <code>/home/claude/alexa-api/</code>(git
|
||||
リポジトリとは別にコピーして管理)</p>
|
||||
<h2>サーバーへのデプロイ手順</h2>
|
||||
<p>server.js や Dockerfile、package.json を変更した場合は以下の手順でサーバーに反映する。</p>
|
||||
<h3>Step 1: ローカルでファイルを編集</h3>
|
||||
<p>リポジトリ(c:\Users\akira\Develop\windmill_workflow\alexa-api\)でファイルを編集する。</p>
|
||||
<h3>Step 2: scp でサーバーに転送</h3>
|
||||
<p>変更したファイルをサーバーに scp で転送する:</p>
|
||||
<p># server.js を変更した場合 scp alexa-api/server.js keinafarm-claude:/home/claude/alexa-api/server.js
|
||||
# Dockerfile や package.json を変更した場合 scp alexa-api/Dockerfile keinafarm-claude:/home/claude/alexa-api/Dockerfile
|
||||
scp alexa-api/package.json keinafarm-claude:/home/claude/alexa-api/package.json
|
||||
scp alexa-api/package-lock.json keinafarm-claude:/home/claude/alexa-api/package-lock.json
|
||||
# .env を更新した場合(Cookie 更新時など) scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env</p>
|
||||
<h3>Step 3: サーバーでビルドして再起動</h3>
|
||||
<p><strong>⚠ 重要</strong>: <code>docker compose restart</code> はイメージをリビルドしない。server.js
|
||||
等を変更した場合は必ず <code>build + up -d</code> を実行すること。</p>
|
||||
<p># SSH でサーバーに接続してビルド+起動 ssh keinafarm-claude cd /home/claude/alexa-api
|
||||
# イメージをビルド(server.js 等の変更を反映) sudo docker compose build # コンテナを再作成して起動
|
||||
sudo docker compose up -d # Traefik を再起動(コンテナ再作成後は必須) sudo docker restart
|
||||
traefik</p>
|
||||
<h3>Step 4: 動作確認</h3>
|
||||
<p># ヘルスチェック(Windmill ワーカーコンテナ内から) curl http://alexa_api:3500/health # ログ確認
|
||||
sudo docker logs alexa_api -f # デバイス一覧確認 curl http://alexa_api:3500/devices</p>
|
||||
<h3>Cookie だけ更新する場合(server.js 変更なし)</h3>
|
||||
<p># .env をサーバーに転送 scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env
|
||||
# コンテナを再起動(restart で OK → イメージのリビルド不要) ssh keinafarm-claude 'sudo docker
|
||||
compose -f /home/claude/alexa-api/docker-compose.yml restart' # ※ Traefik
|
||||
の再起動は不要(コンテナ再作成しないため)</p>
|
||||
<h2>Traefik 再起動が必要な理由</h2>
|
||||
<p>docker compose up -d はコンテナを「再作成」する(docker compose restart は既存コンテナを再起動するだけ)。コンテナが再作成されると
|
||||
Docker 内部の IP アドレスが変わり、Traefik のルーティングが古い IP を参照したまま 502/504 エラーになる。</p>
|
||||
<p><strong>対処</strong>: <code>sudo docker restart traefik</code> で Traefik
|
||||
に新しい IP を再検出させる。</p>
|
||||
<p>この問題は Traefik の設定で <code>watch: true</code> にすれば自動解消できるが、現状はコンテナ再作成のたびに手動で
|
||||
Traefik を再起動する運用としている。</p>
|
||||
<h2>docker-compose.yml の内容</h2>
|
||||
<p>services: alexa-api: build: . container_name:
|
||||
alexa_api restart: unless-stopped env_file:
|
||||
- .env environment: - PORT=3500
|
||||
networks: - windmill_windmill-internal
|
||||
# 外部には公開しない(Windmill ワーカーから内部ネットワーク経由でのみアクセス) #
|
||||
デバッグ時は以下のコメントを外す: # ports: # - "127.0.0.1:3500:3500"
|
||||
networks: windmill_windmill-internal: external: true</p>
|
||||
<h2>認証方法(auth4.js)</h2>
|
||||
<p>Amazon Japan OpenID フローを自前で実装。ローカル PC(Windows)でのみ実行する:</p>
|
||||
<p># ローカルPC の alexa-api ディレクトリで実行 cd alexa-api AMAZON_EMAIL="メールアドレス" AMAZON_PASSWORD="パスワード"
|
||||
node auth4.js</p>
|
||||
<p>成功すると alexa-api/.env が生成または更新される。</p>
|
||||
<p>ログインフローの概要:</p>
|
||||
<ol>
|
||||
<li>GET https://www.amazon.co.jp/ap/signin?openid.assoc_handle=amzn_dp_project_dee_jp</li>
|
||||
<li>hidden フィールド(anti-csrftoken-a2z, appActionToken, workflowState 等)を抽出</li>
|
||||
<li>POST でメール/パスワードを送信</li>
|
||||
<li>alexa.amazon.co.jp/api/apps/v1/token へのリダイレクトをたどる</li>
|
||||
<li>取得した Cookie(at-acbjp, sess-at-acbjp, sst-acbjp, session-token 等)を .env
|
||||
に保存</li>
|
||||
</ol>
|
||||
<h2>TTS の仕組み(server.js)</h2>
|
||||
<p>alexa-remote2 は使わない直接 API 実装。Endpoints:</p>
|
||||
<ul>
|
||||
<li>POST /speak — { device: "デバイス名 or serial", text: "しゃべる内容" }</li>
|
||||
<li>GET /devices — デバイス一覧</li>
|
||||
<li>GET /health — ヘルスチェック</li>
|
||||
</ul>
|
||||
<p>内部の API 呼び出し順序:</p>
|
||||
<ol>
|
||||
<li>GET /api/language → Set-Cookie: csrf=XXXXX を取得(毎リクエストごと)</li>
|
||||
<li>GET /api/bootstrap → customerId を取得(キャッシュ: 永続)(A1AE8HXD8IJ61L)</li>
|
||||
<li>GET /api/devices-v2/device → デバイス一覧(5分キャッシュ)</li>
|
||||
<li>POST /api/behaviors/preview にシーケンス JSON を送信</li>
|
||||
</ol>
|
||||
<p>POST /api/behaviors/preview のボディ構造:</p>
|
||||
<p>{ behaviorId: "PREVIEW", sequenceJson: JSON.stringify({
|
||||
"@type": "com.amazon.alexa.behaviors.model.Sequence", startNode:
|
||||
{ "@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode",
|
||||
type: "Alexa.Speak", operationPayload:
|
||||
{ deviceType: "...", deviceSerialNumber:
|
||||
"...", customerId: "A1AE8HXD8IJ61L",
|
||||
locale: "ja-JP", // ← 重要(下記参照)
|
||||
textToSpeak: "発話内容" } }
|
||||
}), status: "ENABLED" }</p>
|
||||
<p>ヘッダーに <code>csrf: XXXXX</code> と Cookie に <code>csrf=XXXXX</code> の両方が必要。Content-Length
|
||||
は Buffer.byteLength で計算(マルチバイト文字対応)。</p>
|
||||
<h2>⚠ locale パラメータについて(重要・未解決)</h2>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>locale 値</th>
|
||||
<th>ローカル PC から</th>
|
||||
<th>サーバー(keinafarm.net)から</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>""(空文字)</td>
|
||||
<td>✅ 日本語・英語・漢字全て発話</td>
|
||||
<td>❌ 英語TTSになり日本語部分が発話されない</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"ja-JP"</td>
|
||||
<td>❌ 一瞬音が出るだけ(失敗)</td>
|
||||
<td>❌ 日本語文字が Amazon 側で除去され英字のみ発話</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p>現在 server.js では <code>locale: "ja-JP"</code> に設定している。</p>
|
||||
<p><strong>仮説</strong>: Amazon が海外IP(keinafarm.net = 非日本IP)からのリクエストを IP ベースでフィルタリングし、textToSpeak
|
||||
の日本語文字を除去している。Alexa.TextCommand は同じ問題がない(異なる API パス)。</p>
|
||||
<p><strong>確認済み事実</strong>: alexa_api の server.js ログには日本語テキストが正しく届いている。除去は
|
||||
Amazon サーバー側で発生。</p>
|
||||
<p><strong>次の調査候補</strong>:</p>
|
||||
<ul>
|
||||
<li>SSML の <lang xml:lang="ja-JP"> タグで強制的に日本語 TTS を指定できるか</li>
|
||||
<li>Alexa.TextCommand で「次を読み上げて:{text}」形式が使えるか</li>
|
||||
<li>ローカルブリッジ方式(ユーザーのローカル PC で小さなプロキシサーバーを動かし、クラウドサーバーからローカル経由で alexa.amazon.co.jp
|
||||
を叩く)</li>
|
||||
</ul>
|
||||
<h2>デバイス一覧(Echo デバイスのみ)</h2>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名前</th>
|
||||
<th>deviceType</th>
|
||||
<th>serialNumber</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>プレハブ</td>
|
||||
<td>A4ZXE0RM7LQ7A</td>
|
||||
<td>G0922H085165007R</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>リビングエコー1</td>
|
||||
<td>ASQZWP4GPYUT7</td>
|
||||
<td>G8M2DB08522600RL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>リビングエコー2</td>
|
||||
<td>ASQZWP4GPYUT7</td>
|
||||
<td>G8M2DB08522503WF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>オフィスの右エコー</td>
|
||||
<td>A4ZXE0RM7LQ7A</td>
|
||||
<td>G0922H08525302K5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>オフィスの左エコー</td>
|
||||
<td>A4ZXE0RM7LQ7A</td>
|
||||
<td>G0922H08525302J9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>寝室のエコー</td>
|
||||
<td>ASQZWP4GPYUT7</td>
|
||||
<td>G8M2HN08534302XH</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Windmill スクリプト(u/admin/alexa_speak)</h2>
|
||||
<p>export async function main(device: string, text: string) { const
|
||||
res = await fetch("http://alexa_api:3500/speak", { method:
|
||||
"POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ device, text }), }); if (!res.ok)
|
||||
throw new Error("alexa-api error " + res.status); return res.json();
|
||||
}</p>
|
||||
<p>device はデバイス名(日本語)またはシリアル番号で指定可能。Windmill ワーカーから <code>http://alexa_api:3500</code> でアクセス(windmill_windmill-internal
|
||||
ネットワーク経由)。</p>
|
||||
<h2>Cookie の更新手順</h2>
|
||||
<p>Cookie は数日〜数週間で期限切れ。切れたら:</p>
|
||||
<p># 1. ローカル PC で Cookie を取得 cd alexa-api AMAZON_EMAIL="メールアドレス" AMAZON_PASSWORD="パスワード"
|
||||
node auth4.js # → alexa-api/.env が更新される # 2. サーバーに .env を転送 scp alexa-api/.env
|
||||
keinafarm-claude:/home/claude/alexa-api/.env # 3. コンテナを再起動(restart で OK、リビルド不要)
|
||||
ssh keinafarm-claude 'sudo docker compose -f /home/claude/alexa-api/docker-compose.yml
|
||||
restart' # ※ Traefik 再起動は不要(コンテナ再作成なし)</p>
|
||||
<h2>既知の問題・落とし穴</h2>
|
||||
<ul>
|
||||
<li><strong>docker compose restart ≠ リビルド</strong>: server.js を変更しても restart
|
||||
ではコンテナ内のコードは古いまま。build + up -d が必要。</li>
|
||||
<li><strong>コンテナ再作成後は Traefik 再起動必須</strong>: up -d でコンテナ再作成すると Docker 内部
|
||||
IP が変わり Traefik が 502/504 を返す。<code>sudo docker restart traefik</code> で解消。</li>
|
||||
<li><strong>alexa-remote2 は使えない</strong>: 取得した Cookie 文字列を受け付けない(内部で再認証しようとして失敗)。直接
|
||||
API 実装が必要。</li>
|
||||
<li><strong>CSRF トークンは Cookie と ヘッダーの両方に必要</strong>: csrf ヘッダーだけ、または Cookie
|
||||
だけでは認証失敗。</li>
|
||||
<li><strong>operationPayload に customerId 必須</strong>: ないと 400 エラー。</li>
|
||||
<li><strong>レート制限</strong>: 短時間に連続リクエストすると HTTP 429 または 200 で音が出ない。通常の通知用途では問題なし。</li>
|
||||
<li><strong>git push がブロックされる</strong>: Gitea の pre-receive フック(<code>remote: Gitea: User permission denied for writing</code>)で
|
||||
push が失敗する。根本原因は未調査。ファイル転送は scp で行っている。</li>
|
||||
<li><strong>firstRunCompleted: false はデバイス設定の未完了フラグ</strong>: TTS には直接影響しない(root
|
||||
cause ではなかった)。</li>
|
||||
</ul>
|
||||
<h2>サーバー上の運用コマンド一覧</h2>
|
||||
<p># コンテナ状態確認 sudo docker ps | grep alexa # リアルタイムログ確認 sudo docker logs alexa_api
|
||||
-f # コンテナ停止 sudo docker compose -f /home/claude/alexa-api/docker-compose.yml
|
||||
stop # ビルド+起動(コード変更後) cd /home/claude/alexa-api sudo docker compose build
|
||||
sudo docker compose up -d sudo docker restart traefik # Cookie 更新時(再起動のみ)
|
||||
sudo docker compose -f /home/claude/alexa-api/docker-compose.yml restart</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user