Node.js から PowerShell の標準入力にテキストを渡す際の文字化け回避方法 (chcp でエンコーディング設定を変える)
以前、PowerShell でテキスト・トゥ・スピーチができるコードを書いた。
Windows 内蔵の Speech Synthesizer という API を呼び出して喋らせたのだが、今回この API を Node.js 経由で呼んでみようと思った。
PowerShell を child_process.spawn()
で呼び出してやれば良いだろう、喋らせるテキストは標準入力で渡せばよかろうと思って、次のようなコードを書いた。
const childProcess = require('node:child_process');
try {
const child = childProcess.spawn('powershell',
[
'Add-Type -AssemblyName System.speech;'
+ '$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer;'
+ '$speak.Speak([Console]::In.ReadToEnd());'
], { shell: true });
// 以下のエンコーディング指定はあってもなくても変わらなかった
child.stdin.setEncoding('utf8');
child.stdout.setEncoding('utf8');
// 標準入力からテキストを渡してやる
child.stdin.end('こんにちは');
child.addListener('exit', (code, signal) => {
if(code == null || signal != null) console.error(`Error : Code [${code}] Signal [${signal}]`);
});
}
catch(error) {
console.error(error);
}
「こんにちは」と書いた部分、「Hello」などの英語 (ASCII 文字) だと正常に読み上げてくれるのだが、日本語を渡すと意味不明な言葉を発話してくる。
多分エンコーディング周りなんだろうなと思って調べたところ、PowerShell には Shift-JIS で日本語を渡してやると良いそうだ。
- 正確には PowerShell 内部は UTF-16 だが、日本語版 Windows 環境のコンソールは Shift-JIS をデフォルトにしており、コンソールと PowerShell との間での Shift-JIS・UTF-16 変換は自動的に行われる、という仕組みらしい
- GitBash などは UTF-8 (US-ASCII) が前提であり、Node.js を呼び出すところでエンコーディングの不一致が生じている
というワケで、対処法その1は iconv-lite
パッケージを使う方法。
$ npm install --save iconv-lite
const iconvLite = require('iconv-lite');
// 標準入力からテキストを渡してやる部分を、次のように Shift-JIS エンコードしてやる
child.stdin.end(iconvLite.encode('こんにちは', 'Shift_JIS'));
コレでも上手く行ったのだが、iconv-lite
のインストールが嫌なので他に回避方法がないか調べたところ、chccp 65001
で UTF-8 に変更してしまう方法を見付けた。
const childProcess = require('node:child_process');
try {
const child = childProcess.spawn('chcp 65001 > NUL & powershell.exe -NonInteractive -NoProfile -Command',
[
'Add-Type -AssemblyName System.speech;'
+ '$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer;'
+ '$speak.Speak([Console]::In.ReadToEnd());'
], { shell: true });
child.stdin.end('こんにちは');
child.addListener('exit', (code, signal) => {
if(code == null || signal != null) console.error(`Error : Code [${code}] Signal [${signal}]`);
});
}
catch(error) {
console.error(error);
}
child_process.spawn()
内で chcp 65001
を呼び、それから PowerShell を起動している。コレにより、child.stdin.end()
で標準入力を注入する部分に iconv-lite
は不要になり、正しく読み上げてくれた。