diff --git a/alexa-api/server.js b/alexa-api/server.js index c533cf4..941b6e1 100644 --- a/alexa-api/server.js +++ b/alexa-api/server.js @@ -130,31 +130,35 @@ app.post('/speak', async function(req, res) { console.log(' -> ' + target.accountName + ' (type=' + target.deviceType + ', serial=' + target.serialNumber + ')'); -// locale: '' はローカルPCでは日本語発話成功(サーバーからは要検証) -// Alexa.SpeakSsml 系は全滅のため Alexa.Speak に戻す -var sequenceObj = { - '@type': 'com.amazon.alexa.behaviors.model.Sequence', - startNode: { - '@type': 'com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode', - type: 'Alexa.Speak', - operationPayload: { - deviceType: target.deviceType, - deviceSerialNumber: target.serialNumber, - customerId: customerId, - locale: '', - textToSpeak: text - }, - }, -}; + // ★ 重要: sequenceJson の non-ASCII(日本語等)を \uXXXX エスケープに変換してから送る + // raw UTF-8 のまま送ると Amazon 側でフィルタリングされ日本語が発話されない(解決済み 2026-03-03) + var sequenceObj = { + '@type': 'com.amazon.alexa.behaviors.model.Sequence', + startNode: { + '@type': 'com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode', + type: 'Alexa.Speak', + operationPayload: { + deviceType: target.deviceType, + deviceSerialNumber: target.serialNumber, + customerId: customerId, + locale: 'ja-JP', + textToSpeak: text + }, + }, + }; + + var rawSequenceJson = JSON.stringify(sequenceObj).replace( + /[\u0080-\uffff]/g, + function(c) { return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); } + ); var bodyStr = JSON.stringify({ behaviorId: 'PREVIEW', - sequenceJson: JSON.stringify(sequenceObj), + sequenceJson: rawSequenceJson, status: 'ENABLED', }); - // リクエストボディをログ出力(認証・パラメータ確認用) - console.log('[DEBUG] sequenceJson:', JSON.stringify(sequenceObj, null, 2)); + console.log('[DEBUG] textToSpeak:', text); var ttsRes = await httpsRequest('/api/behaviors/preview', { method: 'POST', diff --git a/alexa-api/test_tts.js b/alexa-api/test_tts.js index 8f59ece..232ad07 100644 --- a/alexa-api/test_tts.js +++ b/alexa-api/test_tts.js @@ -61,7 +61,7 @@ async function main() { }); // プレハブを探す - const target = devices.find(d => d.serialNumber === 'G0922H08525302K5'); + const target = devices.find(d => d.serialNumber === 'G0922H08525302K5'); // オフィスの右エコー(以前成功したデバイス) console.log('\nTarget device:', target ? `${target.accountName}` : 'NOT FOUND'); if (!target) { process.exit(1); } @@ -72,7 +72,7 @@ async function main() { const customerId = bootstrap.authentication?.customerId; console.log(' Customer ID:', customerId); - // 3. TTSリクエスト + // 3. TTSリクエスト(新Cookie + Alexa.Speak + locale:'ja-JP' + 日本語テキスト) const sequenceObj = { '@type': 'com.amazon.alexa.behaviors.model.Sequence', startNode: { @@ -83,19 +83,30 @@ async function main() { deviceSerialNumber: target.serialNumber, customerId: customerId, locale: 'ja-JP', - textToSpeak: 'テストです。聞こえますか', + textToSpeak: '\u3053\u308c\u306f\u65e5\u672c\u8a9e\u306e\u30c6\u30b9\u30c8\u3067\u3059', // 「これは日本語のテストです」 }, }, }; + // non-ASCII を \uXXXX に強制エスケープ + // Amazon のパーサーが sequenceJson 内の raw UTF-8 を処理できない場合の回避策 + const rawSequenceJson = JSON.stringify(sequenceObj).replace( + /[\u0080-\uffff]/g, + c => '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4) + ); const bodyObj = { behaviorId: 'PREVIEW', - sequenceJson: JSON.stringify(sequenceObj), + sequenceJson: rawSequenceJson, status: 'ENABLED', }; const body = JSON.stringify(bodyObj); console.log('\n[3] TTS送信...'); + // 送信内容確認(textToSpeakの部分が\uXXXXエスケープになっているか) + const ttsIdx = body.indexOf('textToSpeak'); + console.log(' textToSpeak部分:', body.substring(ttsIdx, ttsIdx + 80)); + + const ttsRes = await makeRequest('https://alexa.amazon.co.jp/api/behaviors/preview', { method: 'POST', diff --git a/docs/10_Alexa TTS API 実装記録 (2026-03-02).md b/docs/alexa-api/10_Alexa TTS API 実装記録 (2026-03-02).md similarity index 100% rename from docs/10_Alexa TTS API 実装記録 (2026-03-02).md rename to docs/alexa-api/10_Alexa TTS API 実装記録 (2026-03-02).md diff --git a/docs/shiraou/11_色々やってダメだった.txt b/docs/alexa-api/11_色々やってダメだった.txt similarity index 100% rename from docs/shiraou/11_色々やってダメだった.txt rename to docs/alexa-api/11_色々やってダメだった.txt diff --git a/docs/alexa-api/12_ローカルで試したこと.md b/docs/alexa-api/12_ローカルで試したこと.md new file mode 100644 index 0000000..29437b5 --- /dev/null +++ b/docs/alexa-api/12_ローカルで試したこと.md @@ -0,0 +1,137 @@ +# Alexa 日本語 TTS 問題 試行記録 + +最終更新: 2026-03-03 +担当: akira + AI (Antigravity) + +--- + +## 現在の問題 + +`/api/behaviors/preview` + `Alexa.Speak` を使って日本語テキストを TTSで発話させようとしているが、 +**日本語Unicode文字だけが Amazon 側でフィルタリングされ、発話されない。** +ASCII文字(英語)は正常に発話される。 + +--- + +## 環境 + +- テスト用スクリプト: `alexa-api/test_tts.js`(ローカルPCから直接 alexa.amazon.co.jp を叩く) +- 本番: `alexa-api/server.js`(VPS上のDockerコンテナ) +- テストデバイス: オフィスの右エコー (serial: G0922H08525302K5, type: A4ZXE0RM7LQ7A) +- Alexaアプリでデバイス言語設定: **日本語** に設定済み(確認済み) +- VPS IP: 162.43.33.56(大阪・Xserver Inc. = 日本国内 ✅ ) + +--- + +## 試行ログ(時系列) + +### 【サーバー側での試行】(ChatGPT との会話ログより、2026-03-02〜03) + +#### ❌ `speakType: 'ssml'` を `operationPayload` に追加 +```json +"type": "Alexa.Speak", +"operationPayload": { ..., "speakType": "ssml" } +``` +→ 変化なし。`Alexa.Speak` はSSML非対応のため無効。 + +#### ❌ `type: 'Alexa.SpeakSsml'` に変更 + `textToSpeak` にSSMLなし +```json +"type": "Alexa.SpeakSsml", +"operationPayload": { ..., "textToSpeak": text } +``` +→ 英語も含めて完全無音(LEDも反応なし)。 + +#### ❌ `Alexa.SpeakSsml` + `textToSpeak: ''+text+''` +→ 英語も無音。`Alexa.SpeakSsml` は `textToSpeak` ではなく別キーを要求する模様。 + +#### ❌ `Alexa.SpeakSsml` + `ssml: ssml`(キー名を変更) +→ 英語も発話せず。 + +**ChatGPTの最終見解:** `/api/behaviors/preview` では `Alexa.SpeakSsml` は動作しない(APIの癖)。`Alexa.Speak` に戻すしかない。 + +--- + +### 【ローカルPCでの試行】(2026-03-03 午前) + +#### ❌ `locale: 'ja-JP'` + 日本語テキスト(test_tts.js デフォルト) +```js +locale: 'ja-JP', +textToSpeak: 'テストです。聞こえますか' +``` +→ 「エ」だけ発話(最初の「テ」の母音のみ)。 + +#### ✅ `locale: ''` + ASCII: `'hello'` +→ 「ハロー」と正常発話。英語は問題なし。 + +#### ❌ `locale: ''` + 日本語: `'テストです。聞こえますか'` +→ 「エ」のみ。デバイス言語が英語設定ならこの動作になるが、日本語設定確認済みのため別原因。 + +#### ❌ `locale: 'ja-JP'` + 日本語: `'テストです。これは日本語のテストです'` +→ 「えんえ」のような音のみ(断片的な音)。 + +#### ❌ `locale: 'ja-JP'` + ひらがな: `'あいうえお'` +→ 無音(LEDは点滅 = 通知は届いている)。 + +#### 🔍 `locale: 'ja-JP'` + 混在: `'あいうえおThis is Testあいうえお'` +→ 「ディスイズテスタ」のみ発話。 +**重要: 日本語部分は無音、ASCII部分のみ日本語アクセントで読まれる。** +→ Amazon側で日本語Unicodeを除去している証拠。 + +#### ❌ `locale: 'ja-JP'` + Unicodeエスケープ: `'\u3053\u308c\u306f\u30c6\u30b9\u30c8\u3067\u3059'` +→ 無音。ファイルエンコード問題ではない(Unicodeエスケープ = `これはテストです` と同一)。 +**→ 文字コードの問題ではないことが確定。** + +#### ❌ `type: 'AlexaAnnouncement'` + locale:`'ja-JP'` + content[].speak構造 +```json +"type": "AlexaAnnouncement", +"operationPayload": { + "content": [{ "locale": "ja-JP", "speak": { "type": "text", "value": "日本語のテストです" } }], + "target": { "devices": [...] } +} +``` +→ 「えんえせんと」("AlexaAnnouncement" を日本語発音で読んだもの)。 +コンテンツではなくノード型名が読まれた → このノードタイプは別用途。 + +--- + +## 確定した事実 + +| 事実 | 根拠 | +|------|------| +| 通知自体は届いている | LEDが点滅する | +| 英語ASCIIは正常発話 | "hello" → 「ハロー」、"This is Test" → 「ディスイズテスタ」 | +| 日本語Unicodeのみ除去される | 混在テキストで確認。Unicodeエスケープでも同じ | +| デバイス言語設定は日本語 | Alexaアプリで確認済み | +| サーバーIPは日本(大阪) | ipinfo.io で確認: Xserver Inc., JP | +| 文字コードは問題なし | Unicodeエスケープテストで確定 | +| `Alexa.SpeakSsml` 系は全て失敗 | 英語含め無音 | +| `AlexaAnnouncement` は別用途 | ノード型名が読まれた | + +--- + +## 仮説(現在) + +Amazon の `/api/behaviors/preview` エンドポイントが、 +何らかの理由で `textToSpeak` 内の日本語Unicodeを除去している。 + +考えられる原因: +1. **セッション/Cookie が古くなりJapanese TTS権限が変わった**(Cookie の再生成で解消する可能性) +2. **Amazonが API の挙動を変更した**(非公開APIのためいつでも変更しうる) +3. **別のAPIエンドポイントが必要**(未探索のルートがある可能性) + +--- + +## 次に試すべきこと + +- [ ] `auth4.js` で Cookie を新規取得してテスト(セッションリセット) +- [ ] `/api/behaviors/preview` 以外のエンドポイントを探す(例: `/api/ap/d-notification`など) +- [ ] `Alexa.TextCommand` ノードタイプ(「テキストで命令を送る」別ルート) +- [ ] ローカルブリッジ方式(ローカルPCをプロキシにしてAmazonにリクエストを転送する) +- [ ] alexa-cookie / alexa-remote2 のソースコードから別APIを調査する + +--- + +## 参考 + +- 実装記録: `docs/alexa-api/10_Alexa TTS API 実装記録 (2026-03-02).md` +- 上記ファイルに記録されていた未解決事項がこのファイルに続く