@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/package.json CHANGED
@@ -1,63 +1,63 @@
1
- {
2
- "name": "@makefully/dutifully",
3
- "version": "1.10.1",
4
- "description": "Client tools for Cheerfully service.",
5
- "main": "./src/index.js",
6
- "bin": {
7
- "cheer": "./cheer.js",
8
- "say": "./say.js"
9
- },
10
- "files": [
11
- "cheer.js",
12
- "say.js",
13
- "src",
14
- "CHANGELOG.md",
15
- "LICENSE",
16
- "README.md"
17
- ],
18
- "scripts": {
19
- "test-allosaurus": "node ./cheer.js -service allosaurus",
20
- "test-elevenlabs": "node ./cheer.js -service elevenlabs",
21
- "test-ffmpeg": "node ./cheer.js -service ffmpeg",
22
- "test-polly": "node ./cheer.js -service polly",
23
- "test-rasterize": "node ./cheer.js -service rasterize",
24
- "test-rhubarb": "node ./cheer.js -service rhubarb",
25
- "test-transcription": "node ./cheer.js -service transcription",
26
- "say": "node ./say.js"
27
- },
28
- "repository": {
29
- "type": "git",
30
- "url": "git+https://github.com/Makefully-Studios/dutifully.git"
31
- },
32
- "bugs": {
33
- "url": "https://github.com/Makefully-Studios/dutifully/issues"
34
- },
35
- "homepage": "https://github.com/Makefully-Studios/dutifully#readme",
36
- "keywords": [
37
- "cheerfully",
38
- "dutifully",
39
- "tts",
40
- "elevenlabs",
41
- "polly",
42
- "lipsync",
43
- "transcription",
44
- "captions"
45
- ],
46
- "author": "Derek Detweiler <derek@makefullystudios.com>",
47
- "contributors": [
48
- "Derek Detweiler <derek@makefullystudios.com>"
49
- ],
50
- "license": "MIT",
51
- "publishConfig": {
52
- "access": "public"
53
- },
54
- "engines": {
55
- "node": ">=18"
56
- },
57
- "dependencies": {
58
- "archiver": "^4.0.1",
59
- "node-id3": "^0.2.6",
60
- "node-web-audio-api": "^2.0.0",
61
- "unzip-stream": "^0.3.0"
62
- }
63
- }
1
+ {
2
+ "name": "@makefully/dutifully",
3
+ "version": "1.11.0",
4
+ "description": "Client tools for Cheerfully service.",
5
+ "main": "./src/index.js",
6
+ "bin": {
7
+ "cheer": "./cheer.js",
8
+ "say": "./say.js"
9
+ },
10
+ "files": [
11
+ "cheer.js",
12
+ "say.js",
13
+ "src",
14
+ "CHANGELOG.md",
15
+ "LICENSE",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "test-allosaurus": "node ./cheer.js -service allosaurus",
20
+ "test-elevenlabs": "node ./cheer.js -service elevenlabs",
21
+ "test-ffmpeg": "node ./cheer.js -service ffmpeg",
22
+ "test-polly": "node ./cheer.js -service polly",
23
+ "test-rasterize": "node ./cheer.js -service rasterize",
24
+ "test-rhubarb": "node ./cheer.js -service rhubarb",
25
+ "test-transcription": "node ./cheer.js -service transcription",
26
+ "say": "node ./say.js"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Makefully-Studios/dutifully.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/Makefully-Studios/dutifully/issues"
34
+ },
35
+ "homepage": "https://github.com/Makefully-Studios/dutifully#readme",
36
+ "keywords": [
37
+ "cheerfully",
38
+ "dutifully",
39
+ "tts",
40
+ "elevenlabs",
41
+ "polly",
42
+ "lipsync",
43
+ "transcription",
44
+ "captions"
45
+ ],
46
+ "author": "Derek Detweiler <derek@makefullystudios.com>",
47
+ "contributors": [
48
+ "Derek Detweiler <derek@makefullystudios.com>"
49
+ ],
50
+ "license": "MIT",
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "engines": {
55
+ "node": ">=18"
56
+ },
57
+ "dependencies": {
58
+ "archiver": "^4.0.1",
59
+ "node-id3": "^0.2.6",
60
+ "node-web-audio-api": "^2.0.0",
61
+ "unzip-stream": "^0.3.0"
62
+ }
63
+ }
package/say.js CHANGED
@@ -1,15 +1,15 @@
1
- #!/usr/bin/env node
2
-
3
- const
4
- say = require('./src/say'),
5
- text = process.argv.slice(2).join(' ').trim();
6
-
7
- if (!text) {
8
- console.error('Usage: say "Hello world"');
9
- process.exit(1);
10
- }
11
-
12
- say(text).catch((err) => {
13
- console.error(err.message ?? err);
14
- process.exit(1);
15
- });
1
+ #!/usr/bin/env node
2
+
3
+ const
4
+ say = require('./src/say'),
5
+ text = process.argv.slice(2).join(' ').trim();
6
+
7
+ if (!text) {
8
+ console.error('Usage: say "Hello world"');
9
+ process.exit(1);
10
+ }
11
+
12
+ say(text).catch((err) => {
13
+ console.error(err.message ?? err);
14
+ process.exit(1);
15
+ });
@@ -1,255 +1,255 @@
1
- const
2
- archiver = require('archiver'),
3
- fs = require('fs'),
4
- getJSON = require('../helpers/getJSON'),
5
- http = require('http'),
6
- https = require('https'),
7
- unzipper = require('unzip-stream'),
8
- cleanPath = (path) => path[path.length - 1] === '/' ? path.substring(0, path.length - 1) : path,
9
- postStream = (url, stream) => new Promise ((resolve, reject) => {
10
- const
11
- protocol = url.startsWith('https') ? https : http;
12
- req = protocol.request(url, {
13
- method: 'post',
14
- headers: {
15
- 'Content-Type': 'application/zip'
16
- }
17
- }, async (res) => {
18
- let data = '';
19
- res.on('data', (chunk) => {
20
- data += chunk;
21
- });
22
- res.on('close', () => {
23
- let json = null;
24
-
25
- try {
26
- json = JSON.parse(data);
27
- } catch (e) {
28
- const
29
- pre = [data.indexOf('<pre>'), data.indexOf('</pre>')],
30
- htmlError = pre[0] > -1 ? data.substring(pre[0] + 5, pre[1]) : data;
31
-
32
- json = {
33
- errors: [htmlError]
34
- };
35
- }
36
- const
37
- {choreId, errors} = json;
38
-
39
- if (errors) {
40
- console.warn(errors.length === 1 ? `Error: ${errors[0]}` : `${errors.length} Errors`, errors);
41
- }
42
-
43
- if (choreId) {
44
- const
45
- shareStatus = (status) => {
46
- if (status !== lastStatus) {
47
- console.log(status);
48
- lastStatus = status;
49
- }
50
- },
51
- checkStatus = async () => {
52
- let response = null;
53
-
54
- try {
55
- response = await fetchStream(`${url}/${choreId}`);
56
- } catch (e) {
57
- console.warn(e);
58
-
59
- setTimeout(checkStatus, 10000);
60
- return;
61
- }
62
-
63
- if (response.json) {
64
- if (response.json.errors) {
65
- console.warn('Error', response.json.errors);
66
- } else if (response.json.status) {
67
- shareStatus(response.json.status);
68
- setTimeout(checkStatus, 10000);
69
- } else {
70
- shareStatus(response.json);
71
- setTimeout(checkStatus, 10000);
72
- }
73
- } else {
74
- resolve(response.stream);
75
- }
76
- };
77
- let lastStatus = '';
78
-
79
- checkStatus();
80
- } else {
81
- reject(errors[0] ?? 'A valid chore id was not returned.');
82
- }
83
- });
84
- });
85
-
86
- req.on('error', reject);
87
-
88
- stream.pipe(req);
89
- }),
90
- fetchStream = (url) => new Promise((resolve, reject) => {
91
- const
92
- protocol = url.startsWith('https') ? https : http;
93
-
94
- protocol.get(url, async (res) => {
95
- if (res.headers['content-type']?.indexOf('application/json') >= 0) {
96
- let data = '';
97
- res.on('data', (chunk) => {
98
- data += chunk;
99
- });
100
- res.on('close', () => {
101
- resolve({
102
- json: JSON.parse(data)
103
- });
104
- });
105
- } else {
106
- resolve({
107
- stream: res
108
- });
109
- }
110
- }).on('error', (err) => reject(err));
111
- }),
112
- archive = async function (parser) {
113
- const
114
- archive = archiver('zip', {
115
- zlib: {
116
- level: 0
117
- }
118
- });
119
-
120
- // good practice to catch warnings (ie stat failures and other non-blocking errors)
121
- archive.on('warning', function (err) {
122
- if (err.code === 'ENOENT') {
123
- console.log(err);
124
- } else {
125
- throw err;
126
- }
127
- });
128
- archive.on('error', function (err) {
129
- throw err;
130
- });
131
- // 'close' event is fired only when a file descriptor is involved
132
- archive.on('close', function () {
133
- console.log('Zipped ' + archive.pointer() + ' total bytes');
134
- });
135
-
136
- await parser(archive);
137
-
138
- archive.finalize();
139
-
140
- return archive;
141
- },
142
- Cheer = class {
143
- constructor ({config, contents}) {
144
- this.config = config;
145
- this.contents = contents;
146
- this.service = contents.service;
147
- }
148
-
149
- prepare ({difference = true, extract = true}) {
150
- if (difference) {
151
- if (!extract) {
152
- console.warn('Warning: Unable to run difference if not extracted: running all.');
153
- } else {
154
- return this.checkDifference();
155
- }
156
- }
157
- }
158
-
159
- async replaceConfigPathWithJSON (pathKey, JSONKey) {
160
- const
161
- {config} = this,
162
- path = config[pathKey];
163
-
164
- if (path) {
165
- delete config[pathKey];
166
- config[JSONKey] = {
167
- ...config[JSONKey] ?? {}, // so if it already exists, we combine.
168
- ...await getJSON(path) ?? {}
169
- };
170
- return true;
171
- }
172
-
173
- return false;
174
- }
175
-
176
- async checkDifference () {
177
- console.warn('A difference check is not implemented for this service: running all.');
178
- }
179
-
180
- async send ({instanceId}) {
181
- const
182
- {config, contents, service} = this,
183
- {accessToken = '', extract = true, output = './output/', server} = {...contents, ...config},
184
- mkdir = await fs.promises.mkdir(output, { recursive: true }),
185
- dst = extract ? unzipper.Extract({
186
- path: output,
187
- concurrency: 1
188
- }) : fs.createWriteStream(`${output}${instanceId}.zip`);
189
-
190
- return new Promise(async (resolve, reject) => {
191
- const
192
- finish = async (handler) => {
193
- try {
194
- await handler.call(this, {instanceId});
195
- resolve();
196
- } catch (e) {
197
- reject(e);
198
- }
199
- };
200
-
201
- if (extract) {
202
- dst.on('close', () => finish(this.afterExport));
203
- } else {
204
- dst.on('finish', () => finish(this.afterWrite));
205
- }
206
- dst.on('error', reject);
207
-
208
- try {
209
- const
210
- archiveStream = await archive((archive) => this.beforeSend(archive)),
211
- data = await postStream(`${cleanPath(server)}/yap/${service}/${accessToken}`, archiveStream);
212
-
213
- // listen for all archive data to be written
214
- archiveStream.on('close', function () {
215
- console.log('completed send');
216
- });
217
-
218
- data.on('error', function (err) {
219
- if (err.code === 'ECONNREFUSED') {
220
- console.warn(`Cannot connect to Cheerfully server "${server}"`);
221
- reject(err);
222
- } else {
223
- reject(err);
224
- }
225
- });
226
-
227
- data.pipe(dst);
228
- } catch (e) {
229
- console.warn(`Error handling "${instanceId}": ${e}`);
230
- reject(e);
231
- }
232
- });
233
- }
234
-
235
- beforeSend (archive) {
236
- const
237
- {config, service} = this;
238
-
239
- archive.append(JSON.stringify(config, null, 4), {name: `${service}.json`});
240
- }
241
-
242
- afterExport ({instanceId}) {
243
- this.onComplete(instanceId);
244
- }
245
-
246
- afterWrite ({instanceId}) {
247
- this.onComplete(instanceId);
248
- }
249
-
250
- onComplete (instanceId) {
251
- console.log(`Cheerfully completed "${instanceId}"`);
252
- }
253
- };
254
-
1
+ const
2
+ archiver = require('archiver'),
3
+ fs = require('fs'),
4
+ getJSON = require('../helpers/getJSON'),
5
+ http = require('http'),
6
+ https = require('https'),
7
+ unzipper = require('unzip-stream'),
8
+ cleanPath = (path) => path[path.length - 1] === '/' ? path.substring(0, path.length - 1) : path,
9
+ postStream = (url, stream) => new Promise ((resolve, reject) => {
10
+ const
11
+ protocol = url.startsWith('https') ? https : http;
12
+ req = protocol.request(url, {
13
+ method: 'post',
14
+ headers: {
15
+ 'Content-Type': 'application/zip'
16
+ }
17
+ }, async (res) => {
18
+ let data = '';
19
+ res.on('data', (chunk) => {
20
+ data += chunk;
21
+ });
22
+ res.on('close', () => {
23
+ let json = null;
24
+
25
+ try {
26
+ json = JSON.parse(data);
27
+ } catch (e) {
28
+ const
29
+ pre = [data.indexOf('<pre>'), data.indexOf('</pre>')],
30
+ htmlError = pre[0] > -1 ? data.substring(pre[0] + 5, pre[1]) : data;
31
+
32
+ json = {
33
+ errors: [htmlError]
34
+ };
35
+ }
36
+ const
37
+ {choreId, errors} = json;
38
+
39
+ if (errors) {
40
+ console.warn(errors.length === 1 ? `Error: ${errors[0]}` : `${errors.length} Errors`, errors);
41
+ }
42
+
43
+ if (choreId) {
44
+ const
45
+ shareStatus = (status) => {
46
+ if (status !== lastStatus) {
47
+ console.log(status);
48
+ lastStatus = status;
49
+ }
50
+ },
51
+ checkStatus = async () => {
52
+ let response = null;
53
+
54
+ try {
55
+ response = await fetchStream(`${url}/${choreId}`);
56
+ } catch (e) {
57
+ console.warn(e);
58
+
59
+ setTimeout(checkStatus, 10000);
60
+ return;
61
+ }
62
+
63
+ if (response.json) {
64
+ if (response.json.errors) {
65
+ console.warn('Error', response.json.errors);
66
+ } else if (response.json.status) {
67
+ shareStatus(response.json.status);
68
+ setTimeout(checkStatus, 10000);
69
+ } else {
70
+ shareStatus(response.json);
71
+ setTimeout(checkStatus, 10000);
72
+ }
73
+ } else {
74
+ resolve(response.stream);
75
+ }
76
+ };
77
+ let lastStatus = '';
78
+
79
+ checkStatus();
80
+ } else {
81
+ reject(errors[0] ?? 'A valid chore id was not returned.');
82
+ }
83
+ });
84
+ });
85
+
86
+ req.on('error', reject);
87
+
88
+ stream.pipe(req);
89
+ }),
90
+ fetchStream = (url) => new Promise((resolve, reject) => {
91
+ const
92
+ protocol = url.startsWith('https') ? https : http;
93
+
94
+ protocol.get(url, async (res) => {
95
+ if (res.headers['content-type']?.indexOf('application/json') >= 0) {
96
+ let data = '';
97
+ res.on('data', (chunk) => {
98
+ data += chunk;
99
+ });
100
+ res.on('close', () => {
101
+ resolve({
102
+ json: JSON.parse(data)
103
+ });
104
+ });
105
+ } else {
106
+ resolve({
107
+ stream: res
108
+ });
109
+ }
110
+ }).on('error', (err) => reject(err));
111
+ }),
112
+ archive = async function (parser) {
113
+ const
114
+ archive = archiver('zip', {
115
+ zlib: {
116
+ level: 0
117
+ }
118
+ });
119
+
120
+ // good practice to catch warnings (ie stat failures and other non-blocking errors)
121
+ archive.on('warning', function (err) {
122
+ if (err.code === 'ENOENT') {
123
+ console.log(err);
124
+ } else {
125
+ throw err;
126
+ }
127
+ });
128
+ archive.on('error', function (err) {
129
+ throw err;
130
+ });
131
+ // 'close' event is fired only when a file descriptor is involved
132
+ archive.on('close', function () {
133
+ console.log('Zipped ' + archive.pointer() + ' total bytes');
134
+ });
135
+
136
+ await parser(archive);
137
+
138
+ archive.finalize();
139
+
140
+ return archive;
141
+ },
142
+ Cheer = class {
143
+ constructor ({config, contents}) {
144
+ this.config = config;
145
+ this.contents = contents;
146
+ this.service = contents.service;
147
+ }
148
+
149
+ prepare ({difference = true, extract = true}) {
150
+ if (difference) {
151
+ if (!extract) {
152
+ console.warn('Warning: Unable to run difference if not extracted: running all.');
153
+ } else {
154
+ return this.checkDifference();
155
+ }
156
+ }
157
+ }
158
+
159
+ async replaceConfigPathWithJSON (pathKey, JSONKey) {
160
+ const
161
+ {config} = this,
162
+ path = config[pathKey];
163
+
164
+ if (path) {
165
+ delete config[pathKey];
166
+ config[JSONKey] = {
167
+ ...config[JSONKey] ?? {}, // so if it already exists, we combine.
168
+ ...await getJSON(path) ?? {}
169
+ };
170
+ return true;
171
+ }
172
+
173
+ return false;
174
+ }
175
+
176
+ async checkDifference () {
177
+ console.warn('A difference check is not implemented for this service: running all.');
178
+ }
179
+
180
+ async send ({instanceId}) {
181
+ const
182
+ {config, contents, service} = this,
183
+ {accessToken = '', extract = true, output = './output/', server} = {...contents, ...config},
184
+ mkdir = await fs.promises.mkdir(output, { recursive: true }),
185
+ dst = extract ? unzipper.Extract({
186
+ path: output,
187
+ concurrency: 1
188
+ }) : fs.createWriteStream(`${output}${instanceId}.zip`);
189
+
190
+ return new Promise(async (resolve, reject) => {
191
+ const
192
+ finish = async (handler) => {
193
+ try {
194
+ await handler.call(this, {instanceId});
195
+ resolve();
196
+ } catch (e) {
197
+ reject(e);
198
+ }
199
+ };
200
+
201
+ if (extract) {
202
+ dst.on('close', () => finish(this.afterExport));
203
+ } else {
204
+ dst.on('finish', () => finish(this.afterWrite));
205
+ }
206
+ dst.on('error', reject);
207
+
208
+ try {
209
+ const
210
+ archiveStream = await archive((archive) => this.beforeSend(archive)),
211
+ data = await postStream(`${cleanPath(server)}/yap/${service}/${accessToken}`, archiveStream);
212
+
213
+ // listen for all archive data to be written
214
+ archiveStream.on('close', function () {
215
+ console.log('completed send');
216
+ });
217
+
218
+ data.on('error', function (err) {
219
+ if (err.code === 'ECONNREFUSED') {
220
+ console.warn(`Cannot connect to Cheerfully server "${server}"`);
221
+ reject(err);
222
+ } else {
223
+ reject(err);
224
+ }
225
+ });
226
+
227
+ data.pipe(dst);
228
+ } catch (e) {
229
+ console.warn(`Error handling "${instanceId}": ${e}`);
230
+ reject(e);
231
+ }
232
+ });
233
+ }
234
+
235
+ beforeSend (archive) {
236
+ const
237
+ {config, service} = this;
238
+
239
+ archive.append(JSON.stringify(config, null, 4), {name: `${service}.json`});
240
+ }
241
+
242
+ afterExport ({instanceId}) {
243
+ this.onComplete(instanceId);
244
+ }
245
+
246
+ afterWrite ({instanceId}) {
247
+ this.onComplete(instanceId);
248
+ }
249
+
250
+ onComplete (instanceId) {
251
+ console.log(`Cheerfully completed "${instanceId}"`);
252
+ }
253
+ };
254
+
255
255
  module.exports = Cheer;