@olegkoval/agent-skills 1.60.0 → 1.61.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/.github/copilot-instructions.md +2 -0
- package/.github/prompts/carousel-maker.prompt.md +102 -0
- package/.github/prompts/shorts-animator.prompt.md +103 -0
- package/.kiro/steering/carousel-maker.md +101 -0
- package/.kiro/steering/shorts-animator.md +102 -0
- package/.windsurf/rules/carousel-maker.md +100 -0
- package/.windsurf/rules/shorts-animator.md +101 -0
- package/README.md +4 -2
- package/adapters/claude/olko-creative/skills/carousel-maker/SKILL.md +112 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
- package/adapters/claude/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/SKILL.md +113 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/references/kit.md +59 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/references/looks.md +32 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/references/writing.md +9 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
- package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
- package/adapters/codex/olko-creative/README.md +2 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/SKILL.md +112 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
- package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/SKILL.md +113 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/references/kit.md +59 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/references/looks.md +32 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/references/writing.md +9 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
- package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/SKILL.md +112 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
- package/adapters/grok/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/SKILL.md +113 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/references/kit.md +59 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/references/looks.md +32 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/references/writing.md +9 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
- package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
- package/adapters/hermes/olko-creative/README.md +2 -0
- package/catalog/skills.json +46 -0
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/skills/carousel-maker/SKILL.md +111 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
- package/plugins/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
- package/plugins/olko-creative/skills/shorts-animator/SKILL.md +112 -0
- package/plugins/olko-creative/skills/shorts-animator/references/kit.md +59 -0
- package/plugins/olko-creative/skills/shorts-animator/references/looks.md +32 -0
- package/plugins/olko-creative/skills/shorts-animator/references/writing.md +9 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
- package/plugins/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
- package/scripts/lib/catalog.mjs +2 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Shorts Animator: a script and a look in, an animated short out.
|
|
4
|
+
*
|
|
5
|
+
* SA new <dir> [--look oil] [--format 9:16] a project: short.json, scenes.js
|
|
6
|
+
* SA looks the looks, moods and voices, one line each
|
|
7
|
+
* SA check <dir> finds what a render would get wrong, in a second, no browser
|
|
8
|
+
* SA voice <dir> narrates every line (cached) and prints the timing
|
|
9
|
+
* SA frames <dir> one painted frame per shot -> frames/sheet.jpg
|
|
10
|
+
* SA still <dir> <shot> [at] one shot, full size -> frames/shot-NN.jpg
|
|
11
|
+
* SA render <dir> the MP4: voice, score, every drawing -> <title>.mp4
|
|
12
|
+
* SA set <dir> key=value... change short.json from the command line (JSON values)
|
|
13
|
+
* SA doctor checks the machine and renders a 3-second test
|
|
14
|
+
*
|
|
15
|
+
* SA = node <skill-dir>/scripts/sa.ts
|
|
16
|
+
*/
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { check, report } from "./domain/check.ts";
|
|
20
|
+
import { resolveLook, pageState } from "./domain/look.ts";
|
|
21
|
+
import { parseAssignment, setPath } from "./domain/patch.ts";
|
|
22
|
+
import { isJson, isMap, parseShort } from "./domain/schema.ts";
|
|
23
|
+
import { timeline } from "./domain/timeline.ts";
|
|
24
|
+
import type { Json, JsonMap, Result, Short, Take, Timeline } from "./domain/types.ts";
|
|
25
|
+
import { fail, ok } from "./domain/types.ts";
|
|
26
|
+
import type { Page } from "./edge/chrome.ts";
|
|
27
|
+
import { doctor } from "./edge/doctor.ts";
|
|
28
|
+
import { encoder, jpeg, mkdir, readJson, stillSheet, videoSheet, writeJson } from "./edge/media.ts";
|
|
29
|
+
import { pageErrors, paintPage } from "./edge/page.ts";
|
|
30
|
+
import { kitNames, probeScenes } from "./edge/probe.ts";
|
|
31
|
+
import { MOODS, soundtrack } from "./edge/sound.ts";
|
|
32
|
+
import { cachedTakes, narrate } from "./edge/voice.ts";
|
|
33
|
+
|
|
34
|
+
const LOOKS = path.join(import.meta.dirname, "looks");
|
|
35
|
+
const VOICES = "bm_george (warm British man, default), am_michael, am_adam, af_heart, af_nova, bf_emma, bf_isabella";
|
|
36
|
+
const NEW_SCENES = `// One function per shot. k is the drawing kit (references/kit.md).
|
|
37
|
+
window.SCENES = {
|
|
38
|
+
opening: (k) => {
|
|
39
|
+
k.sky({ stops: ["#f3ecd2", "#e9dfb7"] });
|
|
40
|
+
k.ridge({ y: 0.52, amp: 0.02, freq: 4, color: "#4f7a3a", shape: "bush" });
|
|
41
|
+
k.ground({ y: 0.52, color: "#8fbf3a", far: "#c3cf5a", stripes: 14 });
|
|
42
|
+
k.person({ x: 0.5, y: 0.7, h: 0.22, pose: "walk", facing: "front" });
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const unwrap = <T>(r: Result<T>): T => {
|
|
48
|
+
if (!r.ok) throw new Error(r.errors.join("\n "));
|
|
49
|
+
return r.value;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const slug = (s: string): string =>
|
|
53
|
+
s.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "short";
|
|
54
|
+
|
|
55
|
+
const twosName = (n: number): string => (n === 1 ? "ones" : n === 2 ? "twos" : `${n}s`);
|
|
56
|
+
|
|
57
|
+
const lookFiles = (): readonly string[] => fs.readdirSync(LOOKS).filter((f) => f.endsWith(".json")).toSorted();
|
|
58
|
+
|
|
59
|
+
const namedLook = (name: string): Result<JsonMap> => {
|
|
60
|
+
const f = path.join(LOOKS, `${name}.json`);
|
|
61
|
+
if (!fs.existsSync(f)) return fail(`no look "${name}" (SA looks lists them)`);
|
|
62
|
+
const v = readJson(f);
|
|
63
|
+
return isMap(v) ? ok(v) : fail(`looks/${name}.json is not an object`);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const shortFile = (dir: string): string => path.join(dir, "short.json");
|
|
67
|
+
|
|
68
|
+
function rawShort(dir: string): Json {
|
|
69
|
+
const f = shortFile(dir);
|
|
70
|
+
if (!fs.existsSync(f)) throw new Error(`no short.json in ${dir} (start one with: SA new ${dir})`);
|
|
71
|
+
const v = readJson(f);
|
|
72
|
+
if (!isJson(v)) throw new Error("short.json is not JSON");
|
|
73
|
+
return v;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const load = (dir: string): Short => unwrap(parseShort(rawShort(dir)));
|
|
77
|
+
|
|
78
|
+
type Project = { readonly dir: string; readonly s: Short; readonly takes: readonly Take[]; readonly tl: Timeline };
|
|
79
|
+
|
|
80
|
+
async function project(dir: string, quiet = false): Promise<Project> {
|
|
81
|
+
const s = load(dir);
|
|
82
|
+
const takes = await narrate(dir, s, quiet);
|
|
83
|
+
return { dir, s, takes, tl: unwrap(timeline(s, takes)) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function withPage<T>({ dir, s, tl }: Project, use: (pg: Page) => Promise<T>): Promise<T> {
|
|
87
|
+
const state = pageState(s, tl, unwrap(resolveLook(s.look, namedLook)));
|
|
88
|
+
const pg = await paintPage(dir, state, tl.W, tl.H);
|
|
89
|
+
try {
|
|
90
|
+
const out = await use(pg);
|
|
91
|
+
const errs = pageErrors(pg);
|
|
92
|
+
if (errs.length) console.log("⚠ the page said:\n " + errs.join("\n "));
|
|
93
|
+
return out;
|
|
94
|
+
} finally {
|
|
95
|
+
pg.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const frameName = (i: number): string => `s${String(i).padStart(3, "0")}.jpg`;
|
|
100
|
+
|
|
101
|
+
/* ------------------------------------------------------------------ commands */
|
|
102
|
+
|
|
103
|
+
type Args = { readonly dir: string | undefined; readonly rest: readonly string[] };
|
|
104
|
+
type Command = (a: Args) => Promise<number>;
|
|
105
|
+
|
|
106
|
+
const need = (dir: string | undefined, usage: string): string => {
|
|
107
|
+
if (!dir) throw new Error(usage);
|
|
108
|
+
return dir;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const looks: Command = async () => {
|
|
112
|
+
lookFiles().forEach((f) => {
|
|
113
|
+
const l = readJson(path.join(LOOKS, f));
|
|
114
|
+
console.log(`${f.replace(".json", "").padEnd(12)} ${isMap(l) && typeof l.about === "string" ? l.about : ""}`);
|
|
115
|
+
});
|
|
116
|
+
console.log(`\nmusic moods: ${MOODS.join(", ")} (or "none", or a path to their own track)`);
|
|
117
|
+
console.log(`voices: ${VOICES}`);
|
|
118
|
+
return 0;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const create: Command = async ({ dir, rest }) => {
|
|
122
|
+
const d = need(dir, "SA new <dir>");
|
|
123
|
+
mkdir(d, "assets");
|
|
124
|
+
const opt = (k: string, dflt: string): string => {
|
|
125
|
+
const i = rest.indexOf(`--${k}`);
|
|
126
|
+
return (i >= 0 ? rest[i + 1] : undefined) ?? dflt;
|
|
127
|
+
};
|
|
128
|
+
if (!fs.existsSync(shortFile(d))) {
|
|
129
|
+
writeJson(shortFile(d), {
|
|
130
|
+
title: "Untitled short", brief: "", format: opt("format", "9:16"), look: opt("look", "oil"),
|
|
131
|
+
voice: { id: "bm_george", speed: 0.92 }, music: { mood: "tender" }, captions: { style: "box" },
|
|
132
|
+
lines: [{ say: "Every story starts somewhere.", scene: "opening", move: "push" }],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (!fs.existsSync(path.join(d, "scenes.js"))) fs.writeFileSync(path.join(d, "scenes.js"), NEW_SCENES);
|
|
136
|
+
console.log(`✓ ${d}\n short.json the script, look, voice, music\n scenes.js the drawings\n assets/ anything they gave you`);
|
|
137
|
+
return 0;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const set: Command = async ({ dir, rest }) => {
|
|
141
|
+
const d = need(dir, "SA set <dir> key=value...");
|
|
142
|
+
const next = rest.map((a) => unwrap(parseAssignment(a))).reduce<Json>((doc, a) => setPath(doc, a.path, a.value), rawShort(d));
|
|
143
|
+
// Placeholder takes are enough to catch what only the timeline knows (an unknown format).
|
|
144
|
+
const s = unwrap(parseShort(next));
|
|
145
|
+
unwrap(timeline(s, s.lines.map(() => ({ file: null, dur: 1 }))));
|
|
146
|
+
writeJson(shortFile(d), next);
|
|
147
|
+
console.log("✓ short.json updated");
|
|
148
|
+
return 0;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const lookKeys = (): readonly string[] =>
|
|
152
|
+
[...new Set(lookFiles().flatMap((f) => {
|
|
153
|
+
const l = readJson(path.join(LOOKS, f));
|
|
154
|
+
return isMap(l) ? Object.keys(l) : [];
|
|
155
|
+
}))];
|
|
156
|
+
|
|
157
|
+
const checkCmd: Command = async ({ dir }) => {
|
|
158
|
+
const d = need(dir, "SA check <dir>");
|
|
159
|
+
const s = load(d);
|
|
160
|
+
const tl = unwrap(timeline(s, cachedTakes(d, s)));
|
|
161
|
+
const findings = check({ short: s, timeline: tl, probes: probeScenes(d, tl), kit: kitNames(), lookKeys: lookKeys() });
|
|
162
|
+
console.log(report(findings));
|
|
163
|
+
return findings.some((f) => f.level === "error") ? 1 : 0;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const voice: Command = async ({ dir }) => {
|
|
167
|
+
const { tl } = await project(need(dir, "SA voice <dir>"));
|
|
168
|
+
tl.beats.forEach((b, i) => console.log(`${String(i + 1).padStart(2)} ${b.start.toFixed(2).padStart(6)}s ${b.dur.toFixed(2)}s ${b.scene.padEnd(16)} ${b.say}`));
|
|
169
|
+
console.log(`total ${tl.duration.toFixed(1)}s, ${tl.beats.length} shots`);
|
|
170
|
+
return 0;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const frames: Command = async ({ dir }) => {
|
|
174
|
+
const p = await project(need(dir, "SA frames <dir>"), true);
|
|
175
|
+
const fdir = mkdir(p.dir, "frames");
|
|
176
|
+
fs.readdirSync(fdir).filter((f) => /^s\d+\.jpg$/.test(f)).forEach((f) => fs.rmSync(path.join(fdir, f)));
|
|
177
|
+
await withPage(p, (pg) =>
|
|
178
|
+
p.tl.beats.reduce<Promise<void>>(async (prev, _b, i) => {
|
|
179
|
+
await prev;
|
|
180
|
+
fs.writeFileSync(path.join(fdir, frameName(i)), jpeg(await pg.evaluate(`window.still(${i}, 0.6)`)));
|
|
181
|
+
}, Promise.resolve()),
|
|
182
|
+
);
|
|
183
|
+
const sheet = path.join(fdir, "sheet.jpg");
|
|
184
|
+
stillSheet(p.tl, path.join(fdir, "s%03d.jpg"), sheet);
|
|
185
|
+
console.log(`✓ ${sheet} (${p.tl.beats.length} shots, ${p.tl.duration.toFixed(1)}s)`);
|
|
186
|
+
return 0;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const still: Command = async ({ dir, rest }) => {
|
|
190
|
+
const p = await project(need(dir, "SA still <dir> <shot> [at]"), true);
|
|
191
|
+
const i = Math.max(0, parseInt(rest[0] ?? "1", 10) - 1);
|
|
192
|
+
const at = parseFloat(rest[1] ?? "0.6");
|
|
193
|
+
if (i >= p.tl.beats.length) throw new Error(`shot ${i + 1}? there are ${p.tl.beats.length}`);
|
|
194
|
+
const f = path.join(mkdir(p.dir, "frames"), `shot-${String(i + 1).padStart(2, "0")}.jpg`);
|
|
195
|
+
await withPage(p, async (pg) => fs.writeFileSync(f, jpeg(await pg.evaluate(`window.still(${i}, ${at})`))));
|
|
196
|
+
console.log(`✓ ${f}`);
|
|
197
|
+
return 0;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/** Next free vN: one past the highest, so deleting an old render never reuses its number. */
|
|
201
|
+
const nextVersion = (rdir: string): number =>
|
|
202
|
+
1 + Math.max(0, ...fs.readdirSync(rdir).flatMap((f) => /^v(\d+)\.mp4$/.exec(f)?.[1] ?? []).map(Number));
|
|
203
|
+
|
|
204
|
+
async function paintAll(pg: Page, write: (b: Buffer) => Promise<void>, drawings: number): Promise<void> {
|
|
205
|
+
const step = async (d: number): Promise<void> => {
|
|
206
|
+
if (d >= drawings) return;
|
|
207
|
+
await write(jpeg(await pg.evaluate(`window.drawing(${d})`)));
|
|
208
|
+
if (d % 60 === 0) process.stdout.write(`\r· painting ${d}/${drawings} drawings`);
|
|
209
|
+
return step(d + 1);
|
|
210
|
+
};
|
|
211
|
+
await step(0);
|
|
212
|
+
process.stdout.write(`\r· painted ${drawings} drawings \n`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const render: Command = async ({ dir }) => {
|
|
216
|
+
const t0 = Date.now();
|
|
217
|
+
const p = await project(need(dir, "SA render <dir>"));
|
|
218
|
+
const { tl, s } = p;
|
|
219
|
+
console.log(`· ${tl.beats.length} shots, ${tl.duration.toFixed(1)}s at ${tl.fps} fps, drawn on ${twosName(tl.twos)}`);
|
|
220
|
+
const audio = soundtrack(p.dir, s, tl, p.takes);
|
|
221
|
+
const rdir = mkdir(p.dir, "renders");
|
|
222
|
+
const n = nextVersion(rdir);
|
|
223
|
+
const outV = path.join(rdir, `v${n}.mp4`);
|
|
224
|
+
const enc = encoder(tl, audio, outV);
|
|
225
|
+
await withPage(p, (pg) => paintAll(pg, enc.write, Math.ceil((tl.duration * tl.fps) / tl.twos)));
|
|
226
|
+
const code = await enc.done();
|
|
227
|
+
if (code !== 0) throw new Error(`ffmpeg exited ${code} while encoding`);
|
|
228
|
+
const final = path.join(p.dir, `${slug(s.title)}.mp4`);
|
|
229
|
+
fs.copyFileSync(outV, final);
|
|
230
|
+
fs.copyFileSync(shortFile(p.dir), path.join(rdir, `v${n}.json`));
|
|
231
|
+
const sheet = path.join(mkdir(p.dir, "qa"), "sheet.jpg");
|
|
232
|
+
videoSheet(tl, final, sheet);
|
|
233
|
+
console.log(`✓ ${final}\n ${tl.duration.toFixed(1)}s · ${tl.W}×${tl.H} · ${((Date.now() - t0) / 1000).toFixed(0)}s to make · check: ${sheet}`);
|
|
234
|
+
return 0;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const COMMANDS: Readonly<Record<string, Command>> = {
|
|
238
|
+
looks, new: create, set, check: checkCmd, voice, frames, still, render,
|
|
239
|
+
doctor: async () => doctor(import.meta.filename),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const usage = (): string =>
|
|
243
|
+
fs.readFileSync(import.meta.filename, "utf8").split("\n").slice(2, 16).join("\n").replace(/^ \* ?/gm, "");
|
|
244
|
+
|
|
245
|
+
// stripTypeScriptTypes is stable in practice but flagged experimental; that one notice is noise to a user.
|
|
246
|
+
process.removeAllListeners("warning");
|
|
247
|
+
process.on("warning", (w) => {
|
|
248
|
+
if (!(w.name === "ExperimentalWarning" && /stripTypeScriptTypes/.test(w.message))) console.error(`${w.name}: ${w.message}`);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const [cmd = "", dirArg, ...rest] = process.argv.slice(2);
|
|
252
|
+
const command = COMMANDS[cmd];
|
|
253
|
+
if (!command) {
|
|
254
|
+
console.log(usage());
|
|
255
|
+
process.exit(cmd ? 1 : 0);
|
|
256
|
+
}
|
|
257
|
+
command({ dir: dirArg ? path.resolve(dirArg) : undefined, rest })
|
|
258
|
+
.then((code) => process.exit(code))
|
|
259
|
+
.catch((e: unknown) => {
|
|
260
|
+
console.error("✗ " + (e instanceof Error ? e.message : String(e)));
|
|
261
|
+
process.exit(1);
|
|
262
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { captionChunks, chunks, words } from "../domain/captions.ts";
|
|
4
|
+
import { check, report, type Facts, type Probe } from "../domain/check.ts";
|
|
5
|
+
import { resolveLook } from "../domain/look.ts";
|
|
6
|
+
import { parseAssignment, setPath } from "../domain/patch.ts";
|
|
7
|
+
import { parseShort } from "../domain/schema.ts";
|
|
8
|
+
import { timeline } from "../domain/timeline.ts";
|
|
9
|
+
import type { Json, Result, Short, Take, Timeline } from "../domain/types.ts";
|
|
10
|
+
import { fail, ok } from "../domain/types.ts";
|
|
11
|
+
|
|
12
|
+
const value = <T>(r: Result<T>): T => {
|
|
13
|
+
if (!r.ok) throw new Error(r.errors.join("; "));
|
|
14
|
+
return r.value;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const RAW = {
|
|
18
|
+
title: "test", voice: "none", music: "none",
|
|
19
|
+
lines: [
|
|
20
|
+
{ say: "A bright and open morning, out on the hills.", scene: "day", move: "push" },
|
|
21
|
+
{ say: "Night comes down over the city.", scene: "night" },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
const short = (patch: object = {}): Short => value(parseShort({ ...RAW, ...patch }));
|
|
25
|
+
const silent = (s: Short): readonly Take[] => s.lines.map(() => ({ file: null, dur: 2 }));
|
|
26
|
+
const tl = (s: Short): Timeline => value(timeline(s, silent(s)));
|
|
27
|
+
|
|
28
|
+
test("words drops the writer's emphasis and pause marks", () => {
|
|
29
|
+
assert.deepEqual(words("a *bright* | morning"), ["a", "bright", "morning"]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("chunks are balanced, not greedy", () => {
|
|
33
|
+
assert.deepEqual(chunks("one two three four five six seven", 5).map((c) => c.split(" ").length), [4, 3]);
|
|
34
|
+
assert.deepEqual(chunks("short line", 5), ["short line"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("a slash in the caption forces the break", () => {
|
|
38
|
+
assert.deepEqual(captionChunks({ say: "Everyone, every pose.", caption: "Everyone, / every pose.", shots: [] }, 5), ["Everyone,", "every pose."]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("parseShort fills defaults and rejects a script with no lines", () => {
|
|
42
|
+
const s = short();
|
|
43
|
+
assert.equal(s.format, "9:16");
|
|
44
|
+
assert.equal(s.fps, 24);
|
|
45
|
+
assert.equal(s.voice.kind, "none");
|
|
46
|
+
assert.equal(parseShort({ title: "x" }).ok, false);
|
|
47
|
+
assert.equal(parseShort([]).ok, false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("timeline lays beats end to end and rejects an unknown format", () => {
|
|
51
|
+
const t = tl(short());
|
|
52
|
+
assert.equal(t.beats.length, 2);
|
|
53
|
+
assert.equal(t.W, 1080);
|
|
54
|
+
const [a, b] = t.beats;
|
|
55
|
+
assert.ok(a && b && Math.abs(a.start + a.dur - b.start) < 1e-9);
|
|
56
|
+
assert.ok(t.duration >= b.start + b.dur);
|
|
57
|
+
const bad = timeline(short({ format: "3:2" }), silent(short()));
|
|
58
|
+
assert.equal(bad.ok, false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("parseAssignment reads JSON values and falls back to text", () => {
|
|
62
|
+
assert.deepEqual(value(parseAssignment("lines.0.hold=0.8")), { path: ["lines", "0", "hold"], value: 0.8 });
|
|
63
|
+
assert.deepEqual(value(parseAssignment("look=watercolor")), { path: ["look"], value: "watercolor" });
|
|
64
|
+
assert.equal(parseAssignment("nokey").ok, false);
|
|
65
|
+
assert.equal(parseAssignment("a..b=1").ok, false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("setPath returns a new document and leaves the old one alone", () => {
|
|
69
|
+
const doc: Json = { lines: [{ say: "hi" }], music: { mood: "tender" } };
|
|
70
|
+
const before = JSON.stringify(doc);
|
|
71
|
+
const next = setPath(setPath(doc, ["lines", "0", "hold"], 1), ["music", "mood"], "night");
|
|
72
|
+
assert.equal(JSON.stringify(doc), before);
|
|
73
|
+
assert.deepEqual(next, { lines: [{ say: "hi", hold: 1 }], music: { mood: "night" } });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("resolveLook merges a named base under the overrides", () => {
|
|
77
|
+
const named = (n: string): Result<{ readonly [k: string]: Json }> => (n === "oil" ? ok({ grain: 0.3, edge: 1 }) : fail(`no look "${n}"`));
|
|
78
|
+
const look = value(resolveLook({ base: "oil", grain: 0.5 }, named));
|
|
79
|
+
assert.equal(look.grain, 0.5);
|
|
80
|
+
assert.equal(look.edge, 1);
|
|
81
|
+
assert.equal(resolveLook("nope", named).ok, false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const facts = (probes: readonly Probe[], s = short()): Facts => ({ short: s, timeline: tl(s), probes, kit: ["sky", "person"], lookKeys: ["grain"] });
|
|
85
|
+
|
|
86
|
+
test("check flags missing scenes, unknown kit calls and throwing scenes as errors", () => {
|
|
87
|
+
const found = check(facts([{ scene: "day", calls: [{ fn: "skyy", opts: {} }] }, { scene: "other", calls: [], error: "boom" }]));
|
|
88
|
+
const errors = found.filter((f) => f.level === "error").map((f) => f.msg);
|
|
89
|
+
assert.ok(errors.some((m) => m.includes('no scene "night"')));
|
|
90
|
+
assert.ok(errors.some((m) => m.includes("k.skyy is not in the kit")));
|
|
91
|
+
assert.ok(errors.some((m) => m.includes("throws: boom")));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("check warns when a person stands in the caption band, unless captions are off", () => {
|
|
95
|
+
const probes: readonly Probe[] = [
|
|
96
|
+
{ scene: "day", calls: [{ fn: "person", opts: { y: 0.82, h: 0.28 } }] },
|
|
97
|
+
{ scene: "night", calls: [{ fn: "person", opts: { y: 0.5, h: 0.2 } }] },
|
|
98
|
+
];
|
|
99
|
+
const band = (s: Short) => check(facts(probes, s)).filter((f) => f.msg.includes("caption line"));
|
|
100
|
+
assert.deepEqual(band(short()).map((f) => f.where), ['scene "day"']);
|
|
101
|
+
assert.deepEqual(band(short({ captions: { style: "none" } })), []);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("report is one line per finding, or clean", () => {
|
|
105
|
+
assert.equal(report([]), "check: clean");
|
|
106
|
+
assert.equal(report([{ level: "error", where: "x", msg: "y" }]), "ERROR x: y");
|
|
107
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2023",
|
|
4
|
+
"lib": ["es2023"],
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"moduleResolution": "nodenext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"erasableSyntaxOnly": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"skipLibCheck": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["*.ts", "domain", "edge", "audio", "test"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""The narrator: Kokoro-82M on this computer, every line of a short in one go.
|
|
2
|
+
|
|
3
|
+
python tts.py <model.onnx> <voices.bin> <voice> <speed> < jobs.json
|
|
4
|
+
|
|
5
|
+
jobs.json is [[text, out.wav], ...]. Loads the model once, writes one WAV per line,
|
|
6
|
+
prints one line per file. Apache-2.0 model, no account, nothing leaves the machine.
|
|
7
|
+
"""
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import soundfile as sf
|
|
12
|
+
from kokoro_onnx import Kokoro
|
|
13
|
+
|
|
14
|
+
model, voices, voice, speed = sys.argv[1], sys.argv[2], sys.argv[3], float(sys.argv[4])
|
|
15
|
+
lang = {"a": "en-us", "b": "en-gb", "e": "es", "f": "fr-fr", "h": "hi", "i": "it", "j": "ja", "p": "pt-br", "z": "cmn"}.get(voice[0], "en-us")
|
|
16
|
+
k = Kokoro(model, voices)
|
|
17
|
+
for text, out in json.load(sys.stdin):
|
|
18
|
+
samples, rate = k.create(text, voice=voice, speed=speed, lang=lang)
|
|
19
|
+
sf.write(out, samples, rate)
|
|
20
|
+
print(out, flush=True)
|
package/scripts/lib/catalog.mjs
CHANGED
|
@@ -45,7 +45,8 @@ export const PLUGIN_ASSIGNMENT = {
|
|
|
45
45
|
},
|
|
46
46
|
'olko-creative': {
|
|
47
47
|
description: 'Creative and personal projects: photo galleries, music players, listings, wiki editing.',
|
|
48
|
-
skills: ['gallery', 'fill-music-player', 'music-to-plex', 'vinted-listing', 'wikipedia-uk-editor'
|
|
48
|
+
skills: ['gallery', 'fill-music-player', 'music-to-plex', 'vinted-listing', 'wikipedia-uk-editor',
|
|
49
|
+
'shorts-animator', 'carousel-maker'],
|
|
49
50
|
},
|
|
50
51
|
'olko-web-ops': {
|
|
51
52
|
description: 'Operate a website: WAF rules, search console audits, analytics bootstrap, docs indexes.',
|