From 848ed732e24bceeead2efced9d12e5d43b8c0a33 Mon Sep 17 00:00:00 2001 From: Stephen Spencer Date: Mon, 11 Dec 2017 17:16:22 +0000 Subject: [PATCH] Updated Windows to support exporting (#48) Adds dataFormat parameter Also fixed macOS exporting (#71) --- README.md | 12 ++++-------- index.d.ts | 2 +- platform/base.js | 19 +++++++++++++++++-- platform/darwin.js | 4 ++-- platform/win32.js | 19 ++++++++++++++++--- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e496f4b..acc92b6 100644 --- a/README.md +++ b/README.md @@ -59,11 +59,12 @@ say.speak(text, voice || null, speed || null, callback || null) #### Export Audio: -* MacOS Only +* MacOS & Windows Only (Windows ignores endian and data type parts of the dataFormat, can only output sample size to 8 or 16 bit) * Speed: 1 = 100%, 0.5 = 50%, 2 = 200%, etc +* dataFormat: As per [macOS say](https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/say.1.html) e.g. "BEF16@22100" is big endian, floating point, 16bit, 22100kHz, defaults to "LEF32@32000" if unspecified ```javascript -say.export(text, voice || null, speed || null, filename, callback || null) +say.export(text, voice || null, speed || null, filename, callback || null, dataFormat || null) ``` #### Stop Speaking: @@ -80,7 +81,7 @@ Platform | Speak | Export | Stop | Speed | Voice ---------|-------|--------|------|-------|------ macOS | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: Linux | :white_check_mark: | :no_entry_sign: | :white_check_mark: | :white_check_mark: | :white_check_mark: -Windows | :white_check_mark: | :no_entry_sign: | :white_check_mark: | :white_check_mark: | :white_check_mark: +Windows | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: ## macOS Notes @@ -94,11 +95,6 @@ say -v "?" As an example, the default voice is `Alex` and the voice used by Siri is `Samantha`. -## Windows Notes - -The `.export()` method is not available. - - ## Linux Notes Linux support requires [Festival](http://www.cstr.ed.ac.uk/projects/festival/), which uses less friendly names for its voices. Voices for Festival sometimes need to be installed separately. You can check which voices are available by running `festival`, typing `(voice_`, and pressing Tab. Then take the name of the voice you'd like to try, minus the parentheses, and pass it in to say.js. diff --git a/index.d.ts b/index.d.ts index 7e46011..ad6d234 100644 --- a/index.d.ts +++ b/index.d.ts @@ -5,7 +5,7 @@ declare module 'say' { type errorCallback = (err: string) => void; class Say { - public export(text: string, voice?: string, speed?: number, filePath?: string, callback?: errorCallback): void; + public export(text: string, voice?: string, speed?: number, filePath?: string, callback?: errorCallback, dataFormat?: string): void; public speak(text: string, voice?: string, speed?: number, callback?: errorCallback): void; public stop(): void; } diff --git a/platform/base.js b/platform/base.js index e0cc4ca..4039a10 100644 --- a/platform/base.js +++ b/platform/base.js @@ -63,8 +63,9 @@ class SayPlatformBase { * @param {number|null} speed Speed of text (e.g. 1.0 for normal, 0.5 half, 2.0 double) * @param {string} filename Path to file to write audio to, e.g. "greeting.wav" * @param {Function|null} callback A callback of type function(err) to return. + * @param {string} dataFormat A dataFormat string as per macOS say e.g. "BEF16@22100" */ - export (text, voice, speed, filename, callback) { + export (text, voice, speed, filename, callback, dataFormat) { if (typeof callback !== 'function') { callback = () => {} } @@ -83,8 +84,22 @@ class SayPlatformBase { }) } + if (!dataFormat) { + dataFormat = 'LEF32@32000' + } + const dataFormatParts = /(BE|LE)(F|I|UI)(\d+)@(\d+)/.exec(dataFormat) + if (!dataFormatParts) { + throw new Error('Invalid dataFormat') + } + const dataFormatInfo = { + endian: dataFormatParts[1], + dataType: dataFormatParts[2], + sampleSize: parseInt(dataFormatParts[3], 10), + sampleRate: parseInt(dataFormatParts[4], 10) + } + try { - var {command, args, pipedData, options} = this.buildExportCommand({text, voice, speed}) + var {command, args, pipedData, options} = this.buildExportCommand({text, voice, speed, filename, dataFormatInfo}) } catch (error) { return setImmediate(() => { callback(error) diff --git a/platform/darwin.js b/platform/darwin.js index 70ca6e7..5a55509 100644 --- a/platform/darwin.js +++ b/platform/darwin.js @@ -27,7 +27,7 @@ class SayPlatformDarwin extends SayPlatformBase { return {command: COMMAND, args, pipedData, options} } - buildExportCommand ({text, voice, speed, filename}) { + buildExportCommand ({text, voice, speed, filename, dataFormatInfo}) { let args = [] let pipedData = '' let options = {} @@ -43,7 +43,7 @@ class SayPlatformDarwin extends SayPlatformBase { } if (filename) { - args.push('-o', filename, '--data-format=LEF32@32000') + args.push('-o', filename, `--data-format=${dataFormatInfo.endian}${dataFormatInfo.dataType}${dataFormatInfo.sampleSize}@${dataFormatInfo.sampleRate}`) } return {command: COMMAND, args, pipedData, options} diff --git a/platform/win32.js b/platform/win32.js index 7ab22f7..fb95962 100644 --- a/platform/win32.js +++ b/platform/win32.js @@ -11,7 +11,7 @@ class SayPlatformWin32 extends SayPlatformBase { this.baseSpeed = BASE_SPEED } - buildSpeakCommand ({text, voice, speed}) { + buildCommand ({text, voice, speed, filename, dataFormatInfo}) { let args = [] let pipedData = '' let options = {} @@ -27,6 +27,15 @@ class SayPlatformWin32 extends SayPlatformBase { psCommand += `$speak.Rate = ${adjustedSpeed};` } + if (filename) { + const audioBitsPerSample = (dataFormatInfo.sampleSize <= 8) ? 'Eight' : 'Sixteen' + const escapedFilename = filename.replace(/\\/g, '\\\\').replace(/"/g, '\\"\\"').replace(/`/g, '``') + psCommand += `$formatSampleSize = [System.Speech.AudioFormat.AudioBitsPerSample]::${audioBitsPerSample};` + psCommand += `$formatChannels = [System.Speech.AudioFormat.AudioChannel]::Mono;` + psCommand += `$format = New-Object System.Speech.AudioFormat.SpeechAudioFormatInfo ${dataFormatInfo.sampleRate}, $formatSampleSize, $formatChannels;` + psCommand += `$speak.SetOutputToWaveFile(\\"${escapedFilename}\\", $format);` + } + psCommand += `$speak.Speak([Console]::In.ReadToEnd())` pipedData += text @@ -36,8 +45,12 @@ class SayPlatformWin32 extends SayPlatformBase { return {command: COMMAND, args, pipedData, options} } - buildExportCommand ({text, voice, speed, filename}) { - throw new Error(`say.export(): does not support platform ${this.platform}`) + buildSpeakCommand ({text, voice, speed}) { + return this.buildCommand({text, voice, speed}) + } + + buildExportCommand ({text, voice, speed, filename, dataFormatInfo}) { + return this.buildCommand({text, voice, speed, filename, dataFormatInfo}) } runStopCommand () {