Skip to content

Commit 848ed73

Browse files
committed
Updated Windows to support exporting (#48)
Adds dataFormat parameter Also fixed macOS exporting (#71)
1 parent 9de2097 commit 848ed73

File tree

5 files changed

+40
-16
lines changed

5 files changed

+40
-16
lines changed

README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ say.speak(text, voice || null, speed || null, callback || null)
5959

6060
#### Export Audio:
6161

62-
* MacOS Only
62+
* MacOS & Windows Only (Windows ignores endian and data type parts of the dataFormat, can only output sample size to 8 or 16 bit)
6363
* Speed: 1 = 100%, 0.5 = 50%, 2 = 200%, etc
64+
* 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
6465

6566
```javascript
66-
say.export(text, voice || null, speed || null, filename, callback || null)
67+
say.export(text, voice || null, speed || null, filename, callback || null, dataFormat || null)
6768
```
6869

6970
#### Stop Speaking:
@@ -80,7 +81,7 @@ Platform | Speak | Export | Stop | Speed | Voice
8081
---------|-------|--------|------|-------|------
8182
macOS | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark:
8283
Linux | :white_check_mark: | :no_entry_sign: | :white_check_mark: | :white_check_mark: | :white_check_mark:
83-
Windows | :white_check_mark: | :no_entry_sign: | :white_check_mark: | :white_check_mark: | :white_check_mark:
84+
Windows | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark:
8485

8586

8687
## macOS Notes
@@ -94,11 +95,6 @@ say -v "?"
9495
As an example, the default voice is `Alex` and the voice used by Siri is `Samantha`.
9596

9697

97-
## Windows Notes
98-
99-
The `.export()` method is not available.
100-
101-
10298
## Linux Notes
10399

104100
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.

index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ declare module 'say' {
55
type errorCallback = (err: string) => void;
66

77
class Say {
8-
public export(text: string, voice?: string, speed?: number, filePath?: string, callback?: errorCallback): void;
8+
public export(text: string, voice?: string, speed?: number, filePath?: string, callback?: errorCallback, dataFormat?: string): void;
99
public speak(text: string, voice?: string, speed?: number, callback?: errorCallback): void;
1010
public stop(): void;
1111
}

platform/base.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ class SayPlatformBase {
6363
* @param {number|null} speed Speed of text (e.g. 1.0 for normal, 0.5 half, 2.0 double)
6464
* @param {string} filename Path to file to write audio to, e.g. "greeting.wav"
6565
* @param {Function|null} callback A callback of type function(err) to return.
66+
* @param {string} dataFormat A dataFormat string as per macOS say e.g. "BEF16@22100"
6667
*/
67-
export (text, voice, speed, filename, callback) {
68+
export (text, voice, speed, filename, callback, dataFormat) {
6869
if (typeof callback !== 'function') {
6970
callback = () => {}
7071
}
@@ -83,8 +84,22 @@ class SayPlatformBase {
8384
})
8485
}
8586

87+
if (!dataFormat) {
88+
dataFormat = 'LEF32@32000'
89+
}
90+
const dataFormatParts = /(BE|LE)(F|I|UI)(\d+)@(\d+)/.exec(dataFormat)
91+
if (!dataFormatParts) {
92+
throw new Error('Invalid dataFormat')
93+
}
94+
const dataFormatInfo = {
95+
endian: dataFormatParts[1],
96+
dataType: dataFormatParts[2],
97+
sampleSize: parseInt(dataFormatParts[3], 10),
98+
sampleRate: parseInt(dataFormatParts[4], 10)
99+
}
100+
86101
try {
87-
var {command, args, pipedData, options} = this.buildExportCommand({text, voice, speed})
102+
var {command, args, pipedData, options} = this.buildExportCommand({text, voice, speed, filename, dataFormatInfo})
88103
} catch (error) {
89104
return setImmediate(() => {
90105
callback(error)

platform/darwin.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class SayPlatformDarwin extends SayPlatformBase {
2727
return {command: COMMAND, args, pipedData, options}
2828
}
2929

30-
buildExportCommand ({text, voice, speed, filename}) {
30+
buildExportCommand ({text, voice, speed, filename, dataFormatInfo}) {
3131
let args = []
3232
let pipedData = ''
3333
let options = {}
@@ -43,7 +43,7 @@ class SayPlatformDarwin extends SayPlatformBase {
4343
}
4444

4545
if (filename) {
46-
args.push('-o', filename, '--data-format=LEF32@32000')
46+
args.push('-o', filename, `--data-format=${dataFormatInfo.endian}${dataFormatInfo.dataType}${dataFormatInfo.sampleSize}@${dataFormatInfo.sampleRate}`)
4747
}
4848

4949
return {command: COMMAND, args, pipedData, options}

platform/win32.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class SayPlatformWin32 extends SayPlatformBase {
1111
this.baseSpeed = BASE_SPEED
1212
}
1313

14-
buildSpeakCommand ({text, voice, speed}) {
14+
buildCommand ({text, voice, speed, filename, dataFormatInfo}) {
1515
let args = []
1616
let pipedData = ''
1717
let options = {}
@@ -27,6 +27,15 @@ class SayPlatformWin32 extends SayPlatformBase {
2727
psCommand += `$speak.Rate = ${adjustedSpeed};`
2828
}
2929

30+
if (filename) {
31+
const audioBitsPerSample = (dataFormatInfo.sampleSize <= 8) ? 'Eight' : 'Sixteen'
32+
const escapedFilename = filename.replace(/\\/g, '\\\\').replace(/"/g, '\\"\\"').replace(/`/g, '``')
33+
psCommand += `$formatSampleSize = [System.Speech.AudioFormat.AudioBitsPerSample]::${audioBitsPerSample};`
34+
psCommand += `$formatChannels = [System.Speech.AudioFormat.AudioChannel]::Mono;`
35+
psCommand += `$format = New-Object System.Speech.AudioFormat.SpeechAudioFormatInfo ${dataFormatInfo.sampleRate}, $formatSampleSize, $formatChannels;`
36+
psCommand += `$speak.SetOutputToWaveFile(\\"${escapedFilename}\\", $format);`
37+
}
38+
3039
psCommand += `$speak.Speak([Console]::In.ReadToEnd())`
3140

3241
pipedData += text
@@ -36,8 +45,12 @@ class SayPlatformWin32 extends SayPlatformBase {
3645
return {command: COMMAND, args, pipedData, options}
3746
}
3847

39-
buildExportCommand ({text, voice, speed, filename}) {
40-
throw new Error(`say.export(): does not support platform ${this.platform}`)
48+
buildSpeakCommand ({text, voice, speed}) {
49+
return this.buildCommand({text, voice, speed})
50+
}
51+
52+
buildExportCommand ({text, voice, speed, filename, dataFormatInfo}) {
53+
return this.buildCommand({text, voice, speed, filename, dataFormatInfo})
4154
}
4255

4356
runStopCommand () {

0 commit comments

Comments
 (0)