Skip to content

Updated Windows to support exporting (#48) #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -86,7 +87,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
Expand All @@ -100,11 +101,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.
Expand Down
2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
public getInstalledVoices(callback: errorCallback): void;
Expand Down
19 changes: 17 additions & 2 deletions platform/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {}
}
Expand All @@ -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, filename})
var {command, args, pipedData, options} = this.buildExportCommand({text, voice, speed, filename, dataFormatInfo})
} catch (error) {
return setImmediate(() => {
callback(error)
Expand Down
4 changes: 2 additions & 2 deletions platform/darwin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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}
Expand Down
19 changes: 16 additions & 3 deletions platform/win32.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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
Expand All @@ -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 () {
Expand Down