@makefully/dutifully 1.22.0 → 1.23.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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.23.0
6
+
7
+ - Provider umbrellas: `translate` (`amazon` default → `amazontranslate`, or `deepl` / `google` / `microsoft`), `lipsync` (`rhubarb` default / `allosaurus`), and `voiceover` (`elevenlabs` default / `polly`) resolve to Cheerfully concrete services before Yap submit.
8
+ - Concrete MT parsers: `amazontranslate`, `deepl`, `googletranslate`, `microsofttranslate`.
9
+
10
+ ## 1.22.1
11
+
12
+ - Allow translate `format: "catalog"` (Cheerfully flat `{lang}.json` app catalogs) alongside existing loc formats.
13
+
5
14
  ## 1.22.0
6
15
 
7
16
  - Select a subset of service listings via optional job `id` and top-level `only` (`cheer -service packfully -only heroes,ui`).
package/README.md CHANGED
@@ -432,13 +432,22 @@ Script lines may also include an optional `events` array for one-shot timed mark
432
432
 
433
433
  Cheerfully resolves these against caption timings and exports them in parallel using the same caption `format` (`events.json`, `{id}.events.vtt` / `.srt` / `.sami` / `.smi` / `.lrc`, or a separate MP3 SYLT frame with content type `EVENTS` labeled `"events"`).
434
434
 
435
- ### Translate
435
+ ### Provider umbrellas (`translate`, `lipsync`, `voiceover`)
436
436
 
437
- This text-to-text service translates Cheerfully scripts or language-matrix CSV/TSV via Amazon Translate. Set jobs in `cheerfully.json`:
437
+ Dutifully logical services pick a Cheerfully processor via per-job `provider` (stripped before upload):
438
+
439
+ | Dutifully `-service` | `provider` | Default | Cheerfully target |
440
+ | --- | --- | --- | --- |
441
+ | `translate` | `amazon`, `deepl`, `google`, `microsoft` | `amazon` | `amazontranslate`, `deepl`, `googletranslate`, `microsofttranslate` |
442
+ | `lipsync` | `rhubarb`, `allosaurus` | `rhubarb` | same as provider |
443
+ | `voiceover` | `polly`, `elevenlabs` | `elevenlabs` | same as provider |
444
+
445
+ Concrete keys (`deepl`, `polly`, `rhubarb`, …) still work without `provider`.
438
446
 
439
447
  ```javascript
440
448
  {
441
449
  "translate": [{
450
+ "provider": "amazon",
442
451
  "script": "./example/script.json",
443
452
  "language": "en-US",
444
453
  "languages": ["es-ES", "fr-FR"],
@@ -449,19 +458,35 @@ This text-to-text service translates Cheerfully scripts or language-matrix CSV/T
449
458
  "language": "en-US",
450
459
  "languages": ["es-ES", "fr-FR"],
451
460
  "output": "./example/scripts/"
461
+ }],
462
+ "voiceover": [{
463
+ "provider": "elevenlabs",
464
+ "script": "./example/script.json",
465
+ "output": "./example/vo/en/",
466
+ "voice": "21m00Tcm4TlvDq8ikWAM"
467
+ }],
468
+ "lipsync": [{
469
+ "provider": "rhubarb",
470
+ "src": "./example/vo/en/",
471
+ "output": "./example/lipsync/en/"
452
472
  }]
453
473
  }
454
474
  ```
455
475
 
476
+ Translate job fields:
477
+
456
478
  * `script` — path to a Cheerfully `.json` script or a language-matrix `.csv` / `.tsv` (first column = ids, first row = language tags).
457
479
  * `language` — source locale (CSV: source column). Matrix jobs require this; JSON may use `auto`.
458
480
  * `languages` — target locales to produce or fill.
459
481
  * `format` / `exports` — optional. Defaults to the source extension (`json`, `csv`, or `tsv`).
460
482
  * `ignore` — phrases left untranslated.
483
+ * `provider` — MT engine (see table); default `amazon`.
484
+
485
+ JSON sources write `{lang}/{basename}.{ext}` under `output` (`catalog` → `{lang}.json`). CSV/TSV sources write an updated matrix file and leave existing non-empty cells unchanged.
461
486
 
462
- JSON sources write `{lang}/{basename}.{ext}` under `output`. CSV/TSV sources write an updated matrix file and leave existing non-empty cells unchanged.
487
+ Supported formats: `json`, `i18n`, `catalog`, `csv`, `tsv`, `po`, `xliff`, `properties`, `yml` / `yaml`, `strings`, `xml`.
463
488
 
464
- Supported formats: `json`, `i18n`, `csv`, `tsv`, `po`, `xliff`, `properties`, `yml` / `yaml`, `strings`, `xml`.
489
+ `catalog` writes flat key → caption JSON as `{lang}.json` (app localization layout). `i18n` uses the same payload under `{lang}/{basename}.i18n.json`.
465
490
 
466
491
  ## Example
467
492
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makefully/dutifully",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "Client tools for Cheerfully service.",
5
5
  "main": "./src/index.js",
6
6
  "bin": {
@@ -52,7 +52,10 @@
52
52
  "atlas",
53
53
  "spritesheet",
54
54
  "classfully",
55
- "translate"
55
+ "translate",
56
+ "deepl",
57
+ "voiceover",
58
+ "lipsync"
56
59
  ],
57
60
  "author": "Derek Detweiler <derek@makefullystudios.com>",
58
61
  "contributors": [
@@ -6,7 +6,7 @@
6
6
  const LYRIC_FORMATS = ['json', 'lrc', 'mp3', 'sami', 'smi', 'srt', 'vtt', 'tsv', 'xml', 'dat'];
7
7
  const TRANSCRIPTION_FORMATS = ['json', 'lrc', 'mp3', 'sami', 'smi', 'srt', 'vtt'];
8
8
  const PACKFULLY_FORMATS = ['texturepacker', 'json-array', 'createjs', 'spine', 'css', 'starling', 'unity', 'godot'];
9
- const TRANSLATE_FORMATS = ['json', 'i18n', 'po', 'xliff', 'properties', 'yml', 'yaml', 'strings', 'xml', 'csv', 'tsv'];
9
+ const TRANSLATE_FORMATS = ['json', 'i18n', 'catalog', 'po', 'xliff', 'properties', 'yml', 'yaml', 'strings', 'xml', 'csv', 'tsv'];
10
10
 
11
11
  const EXPORT_CATALOG = {
12
12
  transcription: {
@@ -28,6 +28,22 @@ const EXPORT_CATALOG = {
28
28
  keys: ['format', 'exports'],
29
29
  formats: TRANSLATE_FORMATS
30
30
  },
31
+ amazontranslate: {
32
+ keys: ['format', 'exports'],
33
+ formats: TRANSLATE_FORMATS
34
+ },
35
+ deepl: {
36
+ keys: ['format', 'exports'],
37
+ formats: TRANSLATE_FORMATS
38
+ },
39
+ googletranslate: {
40
+ keys: ['format', 'exports'],
41
+ formats: TRANSLATE_FORMATS
42
+ },
43
+ microsofttranslate: {
44
+ keys: ['format', 'exports'],
45
+ formats: TRANSLATE_FORMATS
46
+ },
31
47
  packfully: {
32
48
  keys: ['format', 'exports', 'exportFormat'],
33
49
  formats: PACKFULLY_FORMATS
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Map Dutifully umbrella services + provider → Cheerfully concrete service ids.
3
+ */
4
+
5
+ const
6
+ UMBRELLAS = {
7
+ translate: {
8
+ defaultProvider: 'amazon',
9
+ providers: {
10
+ amazon: 'amazontranslate',
11
+ amazontranslate: 'amazontranslate',
12
+ deepl: 'deepl',
13
+ google: 'googletranslate',
14
+ googletranslate: 'googletranslate',
15
+ microsoft: 'microsofttranslate',
16
+ microsofttranslate: 'microsofttranslate'
17
+ }
18
+ },
19
+ lipsync: {
20
+ defaultProvider: 'rhubarb',
21
+ providers: {
22
+ rhubarb: 'rhubarb',
23
+ allosaurus: 'allosaurus'
24
+ }
25
+ },
26
+ voiceover: {
27
+ defaultProvider: 'elevenlabs',
28
+ providers: {
29
+ polly: 'polly',
30
+ elevenlabs: 'elevenlabs'
31
+ }
32
+ }
33
+ },
34
+ isUmbrella = (service) => Boolean(UMBRELLAS[service]),
35
+ /**
36
+ * @returns {{ cheerfullyService: string, provider: string, job: object }}
37
+ */
38
+ resolveProviderService = (umbrella, job = {}) => {
39
+ const
40
+ table = UMBRELLAS[umbrella];
41
+
42
+ if (!table) {
43
+ const
44
+ cleaned = {...job};
45
+
46
+ delete cleaned.provider;
47
+
48
+ return {
49
+ cheerfullyService: umbrella,
50
+ provider: null,
51
+ job: cleaned
52
+ };
53
+ }
54
+
55
+ const
56
+ raw = job.provider == null || job.provider === ''
57
+ ? table.defaultProvider
58
+ : String(job.provider).toLowerCase().trim(),
59
+ cheerfullyService = table.providers[raw];
60
+
61
+ if (!cheerfullyService) {
62
+ const
63
+ allowed = [...new Set(Object.keys(table.providers))].sort().join(', ');
64
+
65
+ throw new Error(
66
+ `Unknown provider "${job.provider}" for "${umbrella}". Use one of: ${allowed}.`
67
+ );
68
+ }
69
+
70
+ const
71
+ cleaned = {...job};
72
+
73
+ delete cleaned.provider;
74
+
75
+ return {
76
+ cheerfullyService,
77
+ provider: raw,
78
+ job: cleaned
79
+ };
80
+ };
81
+
82
+ module.exports = {
83
+ UMBRELLAS,
84
+ isUmbrella,
85
+ resolveProviderService
86
+ };
package/src/index.js CHANGED
@@ -2,11 +2,16 @@
2
2
  /* global console, process, require */
3
3
  const
4
4
  LipSync = require('./classes/LipSync'),
5
+ Translate = require('./classes/Translate'),
5
6
  parsers = {
6
7
  allosaurus: LipSync,
8
+ amazontranslate: Translate,
7
9
  classfully: require('./classes/Classfully'),
10
+ deepl: Translate,
8
11
  elevenlabs: require('./classes/ElevenLabs'),
9
12
  ffmpeg: require('./classes/FFMPEG'),
13
+ googletranslate: Translate,
14
+ microsofttranslate: Translate,
10
15
  packfully: require('./classes/Packfully'),
11
16
  polly: require('./classes/Polly'),
12
17
  rasterize: require('./classes/Rasterize'),
@@ -14,10 +19,12 @@ const
14
19
  sharp: require('./classes/Sharp'),
15
20
  stackfully: require('./classes/Stackfully'),
16
21
  transcription: require('./classes/Transcription'),
17
- translate: require('./classes/Translate')
22
+ // Concrete only — umbrellas (translate / lipsync / voiceover) resolve first.
23
+ translate: Translate
18
24
  },
19
25
  getJSON = require('./helpers/getJSON'),
20
26
  {normalizeOnly, filterJobsById} = require('./helpers/filterJobs'),
27
+ {isUmbrella, resolveProviderService} = require('./helpers/resolveProviderService'),
21
28
  send = async function (contents) {
22
29
  const
23
30
  {id, service} = contents,
@@ -36,11 +43,40 @@ const
36
43
  continue;
37
44
  }
38
45
 
46
+ let
47
+ cheerfullyService = service,
48
+ jobConfig = config;
49
+
50
+ if (isUmbrella(service)) {
51
+ try {
52
+ const
53
+ resolved = resolveProviderService(service, config);
54
+
55
+ cheerfullyService = resolved.cheerfullyService;
56
+ jobConfig = resolved.job;
57
+ } catch (e) {
58
+ console.warn(e.message);
59
+ continue;
60
+ }
61
+ }
62
+
63
+ const
64
+ Parser = parsers[cheerfullyService];
65
+
66
+ if (!Parser) {
67
+ console.warn(`No Dutifully parser for Cheerfully service "${cheerfullyService}".`);
68
+ continue;
69
+ }
70
+
39
71
  const
40
- serviceHandler = new parsers[service]({config, contents});
72
+ contentsForJob = {
73
+ ...contents,
74
+ service: cheerfullyService
75
+ },
76
+ serviceHandler = new Parser({config: jobConfig, contents: contentsForJob});
41
77
 
42
78
  try {
43
- await serviceHandler.prepare({...contents, ...config});
79
+ await serviceHandler.prepare({...contentsForJob, ...jobConfig});
44
80
  } catch (e) {
45
81
  console.warn(e.message);
46
82
  continue;
@@ -48,10 +84,10 @@ const
48
84
 
49
85
  try {
50
86
  await serviceHandler.send({
51
- instanceId: `${id}-${service}${length > 1 ? `-${index}` : ''}`
87
+ instanceId: `${id}-${cheerfullyService}${length > 1 ? `-${index}` : ''}`
52
88
  });
53
89
  } catch (e) {
54
- console.warn(`Error running "${service}" (${index}): ${e.message || e}`);
90
+ console.warn(`Error running "${cheerfullyService}" (${index}): ${e.message || e}`);
55
91
  }
56
92
  }
57
93
  };