@makefully/dutifully 1.10.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -5
- package/LICENSE +21 -21
- package/README.md +216 -214
- package/cheer.js +36 -36
- package/package.json +63 -63
- package/say.js +15 -15
- package/src/classes/Cheer.js +254 -254
- package/src/classes/ElevenLabs.js +16 -16
- package/src/classes/FFMPEG.js +12 -12
- package/src/classes/LipSync.js +132 -132
- package/src/classes/Polly.js +9 -9
- package/src/classes/Rasterize.js +12 -12
- package/src/classes/TextToSpeech.js +204 -204
- package/src/classes/Transcript.js +72 -68
- package/src/classes/Transcription.js +162 -162
- package/src/formats/File.js +26 -26
- package/src/formats/JSONFile.js +39 -39
- package/src/formats/LRC.js +37 -37
- package/src/formats/MP3.js +59 -59
- package/src/formats/SAMI.js +58 -58
- package/src/formats/SRT.js +39 -39
- package/src/formats/VTT.js +37 -37
- package/src/helpers/getCaptions.js +43 -43
- package/src/helpers/getFileData.js +32 -32
- package/src/helpers/getJSON.js +32 -32
- package/src/index.js +58 -58
- package/src/say.js +141 -141
package/src/say.js
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
/* eslint-disable no-sync */
|
|
2
|
-
/* global console, process, require */
|
|
3
|
-
const
|
|
4
|
-
fs = require('fs'),
|
|
5
|
-
fsp = require('fs').promises,
|
|
6
|
-
path = require('path'),
|
|
7
|
-
{createHash} = require('crypto'),
|
|
8
|
-
{AudioContext} = require('node-web-audio-api'),
|
|
9
|
-
ElevenLabs = require('./classes/ElevenLabs'),
|
|
10
|
-
getJSON = require('./helpers/getJSON'),
|
|
11
|
-
CACHE_DIR = '.cache/tts',
|
|
12
|
-
resolveVoiceId = (voice) => {
|
|
13
|
-
if (!voice) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (typeof voice === 'string') {
|
|
18
|
-
return voice;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return voice.default ?? Object.values(voice)[0] ?? null;
|
|
22
|
-
},
|
|
23
|
-
cacheKey = ({voice, text}) => createHash('sha256')
|
|
24
|
-
.update(JSON.stringify({
|
|
25
|
-
version: 1,
|
|
26
|
-
voice,
|
|
27
|
-
text
|
|
28
|
-
}))
|
|
29
|
-
.digest('hex'),
|
|
30
|
-
playMp3 = async (file) => {
|
|
31
|
-
const
|
|
32
|
-
audioContext = new AudioContext(),
|
|
33
|
-
data = await fsp.readFile(file),
|
|
34
|
-
arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength),
|
|
35
|
-
buffer = await audioContext.decodeAudioData(arrayBuffer),
|
|
36
|
-
source = audioContext.createBufferSource();
|
|
37
|
-
|
|
38
|
-
source.buffer = buffer;
|
|
39
|
-
source.connect(audioContext.destination);
|
|
40
|
-
|
|
41
|
-
await new Promise((resolve, reject) => {
|
|
42
|
-
source.onended = resolve;
|
|
43
|
-
try {
|
|
44
|
-
source.start();
|
|
45
|
-
} catch (e) {
|
|
46
|
-
reject(e);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
await audioContext.close();
|
|
51
|
-
},
|
|
52
|
-
generate = async ({text, voice, key, contents, elevenlabsConfig}) => {
|
|
53
|
-
const
|
|
54
|
-
output = `${CACHE_DIR}/`,
|
|
55
|
-
config = {
|
|
56
|
-
...elevenlabsConfig,
|
|
57
|
-
files: {
|
|
58
|
-
[key]: text
|
|
59
|
-
},
|
|
60
|
-
path: '',
|
|
61
|
-
voice,
|
|
62
|
-
output
|
|
63
|
-
},
|
|
64
|
-
handler = new ElevenLabs({
|
|
65
|
-
config,
|
|
66
|
-
contents: {
|
|
67
|
-
...contents,
|
|
68
|
-
service: 'elevenlabs'
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
delete config.script;
|
|
73
|
-
|
|
74
|
-
await handler.prepare({
|
|
75
|
-
difference: false,
|
|
76
|
-
extract: true
|
|
77
|
-
});
|
|
78
|
-
await handler.send({
|
|
79
|
-
instanceId: `${contents.id}-say`
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
return path.join(CACHE_DIR, `${key}.mp3`);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
module.exports = async (textOrArgs = {}) => {
|
|
86
|
-
const
|
|
87
|
-
isString = typeof textOrArgs === 'string',
|
|
88
|
-
cmdArgs = isString ? {} : (textOrArgs ?? {}),
|
|
89
|
-
text = (isString ? textOrArgs : (cmdArgs.text ?? '')).trim();
|
|
90
|
-
|
|
91
|
-
if (!text) {
|
|
92
|
-
throw new Error('Usage: say "Hello world"');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
packageJson = await getJSON('./package.json') ?? {},
|
|
97
|
-
cheerfully = await getJSON('./cheerfully.json') ?? {},
|
|
98
|
-
env = await getJSON('./env-cheerfully.json') ?? {},
|
|
99
|
-
contents = {
|
|
100
|
-
id: `${packageJson.name ?? 'cheerfully'}-${packageJson.version ?? '0'}`,
|
|
101
|
-
package: packageJson,
|
|
102
|
-
...cheerfully,
|
|
103
|
-
...env,
|
|
104
|
-
...cmdArgs
|
|
105
|
-
},
|
|
106
|
-
elevenlabsConfig = Array.isArray(contents.elevenlabs)
|
|
107
|
-
? (contents.elevenlabs[0] ?? {})
|
|
108
|
-
: (contents.elevenlabs ?? {}),
|
|
109
|
-
voice = elevenlabsConfig.voice,
|
|
110
|
-
voiceId = resolveVoiceId(voice);
|
|
111
|
-
|
|
112
|
-
if (!voiceId) {
|
|
113
|
-
throw new Error('No ElevenLabs voice configured. Set elevenlabs[0].voice in cheerfully.json.');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!contents.server || !contents.accessToken) {
|
|
117
|
-
throw new Error('Missing server/accessToken. Set them in env-cheerfully.json.');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
await fsp.mkdir(CACHE_DIR, {recursive: true});
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
key = cacheKey({
|
|
124
|
-
voice: voiceId,
|
|
125
|
-
text
|
|
126
|
-
}),
|
|
127
|
-
mp3File = path.join(CACHE_DIR, `${key}.mp3`);
|
|
128
|
-
|
|
129
|
-
if (!fs.existsSync(mp3File)) {
|
|
130
|
-
console.log('Generating speech...');
|
|
131
|
-
await generate({
|
|
132
|
-
text,
|
|
133
|
-
voice,
|
|
134
|
-
key,
|
|
135
|
-
contents,
|
|
136
|
-
elevenlabsConfig
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
await playMp3(mp3File);
|
|
141
|
-
};
|
|
1
|
+
/* eslint-disable no-sync */
|
|
2
|
+
/* global console, process, require */
|
|
3
|
+
const
|
|
4
|
+
fs = require('fs'),
|
|
5
|
+
fsp = require('fs').promises,
|
|
6
|
+
path = require('path'),
|
|
7
|
+
{createHash} = require('crypto'),
|
|
8
|
+
{AudioContext} = require('node-web-audio-api'),
|
|
9
|
+
ElevenLabs = require('./classes/ElevenLabs'),
|
|
10
|
+
getJSON = require('./helpers/getJSON'),
|
|
11
|
+
CACHE_DIR = '.cache/tts',
|
|
12
|
+
resolveVoiceId = (voice) => {
|
|
13
|
+
if (!voice) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (typeof voice === 'string') {
|
|
18
|
+
return voice;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return voice.default ?? Object.values(voice)[0] ?? null;
|
|
22
|
+
},
|
|
23
|
+
cacheKey = ({voice, text}) => createHash('sha256')
|
|
24
|
+
.update(JSON.stringify({
|
|
25
|
+
version: 1,
|
|
26
|
+
voice,
|
|
27
|
+
text
|
|
28
|
+
}))
|
|
29
|
+
.digest('hex'),
|
|
30
|
+
playMp3 = async (file) => {
|
|
31
|
+
const
|
|
32
|
+
audioContext = new AudioContext(),
|
|
33
|
+
data = await fsp.readFile(file),
|
|
34
|
+
arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength),
|
|
35
|
+
buffer = await audioContext.decodeAudioData(arrayBuffer),
|
|
36
|
+
source = audioContext.createBufferSource();
|
|
37
|
+
|
|
38
|
+
source.buffer = buffer;
|
|
39
|
+
source.connect(audioContext.destination);
|
|
40
|
+
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
source.onended = resolve;
|
|
43
|
+
try {
|
|
44
|
+
source.start();
|
|
45
|
+
} catch (e) {
|
|
46
|
+
reject(e);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await audioContext.close();
|
|
51
|
+
},
|
|
52
|
+
generate = async ({text, voice, key, contents, elevenlabsConfig}) => {
|
|
53
|
+
const
|
|
54
|
+
output = `${CACHE_DIR}/`,
|
|
55
|
+
config = {
|
|
56
|
+
...elevenlabsConfig,
|
|
57
|
+
files: {
|
|
58
|
+
[key]: text
|
|
59
|
+
},
|
|
60
|
+
path: '',
|
|
61
|
+
voice,
|
|
62
|
+
output
|
|
63
|
+
},
|
|
64
|
+
handler = new ElevenLabs({
|
|
65
|
+
config,
|
|
66
|
+
contents: {
|
|
67
|
+
...contents,
|
|
68
|
+
service: 'elevenlabs'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
delete config.script;
|
|
73
|
+
|
|
74
|
+
await handler.prepare({
|
|
75
|
+
difference: false,
|
|
76
|
+
extract: true
|
|
77
|
+
});
|
|
78
|
+
await handler.send({
|
|
79
|
+
instanceId: `${contents.id}-say`
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return path.join(CACHE_DIR, `${key}.mp3`);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
module.exports = async (textOrArgs = {}) => {
|
|
86
|
+
const
|
|
87
|
+
isString = typeof textOrArgs === 'string',
|
|
88
|
+
cmdArgs = isString ? {} : (textOrArgs ?? {}),
|
|
89
|
+
text = (isString ? textOrArgs : (cmdArgs.text ?? '')).trim();
|
|
90
|
+
|
|
91
|
+
if (!text) {
|
|
92
|
+
throw new Error('Usage: say "Hello world"');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const
|
|
96
|
+
packageJson = await getJSON('./package.json') ?? {},
|
|
97
|
+
cheerfully = await getJSON('./cheerfully.json') ?? {},
|
|
98
|
+
env = await getJSON('./env-cheerfully.json') ?? {},
|
|
99
|
+
contents = {
|
|
100
|
+
id: `${packageJson.name ?? 'cheerfully'}-${packageJson.version ?? '0'}`,
|
|
101
|
+
package: packageJson,
|
|
102
|
+
...cheerfully,
|
|
103
|
+
...env,
|
|
104
|
+
...cmdArgs
|
|
105
|
+
},
|
|
106
|
+
elevenlabsConfig = Array.isArray(contents.elevenlabs)
|
|
107
|
+
? (contents.elevenlabs[0] ?? {})
|
|
108
|
+
: (contents.elevenlabs ?? {}),
|
|
109
|
+
voice = elevenlabsConfig.voice,
|
|
110
|
+
voiceId = resolveVoiceId(voice);
|
|
111
|
+
|
|
112
|
+
if (!voiceId) {
|
|
113
|
+
throw new Error('No ElevenLabs voice configured. Set elevenlabs[0].voice in cheerfully.json.');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!contents.server || !contents.accessToken) {
|
|
117
|
+
throw new Error('Missing server/accessToken. Set them in env-cheerfully.json.');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await fsp.mkdir(CACHE_DIR, {recursive: true});
|
|
121
|
+
|
|
122
|
+
const
|
|
123
|
+
key = cacheKey({
|
|
124
|
+
voice: voiceId,
|
|
125
|
+
text
|
|
126
|
+
}),
|
|
127
|
+
mp3File = path.join(CACHE_DIR, `${key}.mp3`);
|
|
128
|
+
|
|
129
|
+
if (!fs.existsSync(mp3File)) {
|
|
130
|
+
console.log('Generating speech...');
|
|
131
|
+
await generate({
|
|
132
|
+
text,
|
|
133
|
+
voice,
|
|
134
|
+
key,
|
|
135
|
+
contents,
|
|
136
|
+
elevenlabsConfig
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await playMp3(mp3File);
|
|
141
|
+
};
|