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 で日本語を渡してやると良いそうだ。

というワケで、対処法その1iconv-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 は不要になり、正しく読み上げてくれた。