hyper-animator-codex 0.6.0 → 0.7.1
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/README.md +53 -7
- package/bin/hyper-animator-codex.mjs +30 -8
- package/bin/postinstall.mjs +19 -0
- package/lib/install-options.mjs +3 -0
- package/lib/install-skill.mjs +84 -2
- package/package.json +3 -2
- package/skills/hyper-animator-codex/SKILL.md +23 -18
- package/skills/hyper-animator-codex/references/narration-audio-workflow.md +110 -0
- package/skills/hyper-animator-codex/scripts/detect_project_files.mjs +178 -0
- package/skills/hyper-animator-codex/scripts/generate_minimax_tts.mjs +556 -0
- package/skills/hyper-animator-codex/scripts/generate_subtitles.mjs +181 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { resolve, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const OUTLINE_NAMES = new Set(["outline.md", "outline.json", "outline.txt"]);
|
|
6
|
+
const NARRATION_NAMES = new Set(["narration.json", "narration.md", "narration.txt"]);
|
|
7
|
+
const TEXT_EXTENSIONS = new Set([".md", ".txt"]);
|
|
8
|
+
|
|
9
|
+
function requireValue(args, index, flag) {
|
|
10
|
+
const value = args[index + 1];
|
|
11
|
+
if (!value || value.startsWith("--")) {
|
|
12
|
+
throw new Error(`${flag} requires a value`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseArgs(args) {
|
|
18
|
+
const parsed = { dir: process.cwd(), pretty: false };
|
|
19
|
+
|
|
20
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
21
|
+
const arg = args[index];
|
|
22
|
+
if (arg === "--dir") {
|
|
23
|
+
parsed.dir = requireValue(args, index, arg);
|
|
24
|
+
index += 1;
|
|
25
|
+
} else if (arg === "--pretty") {
|
|
26
|
+
parsed.pretty = true;
|
|
27
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
28
|
+
parsed.help = true;
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function extensionOf(name) {
|
|
38
|
+
const dot = name.lastIndexOf(".");
|
|
39
|
+
return dot === -1 ? "" : name.slice(dot).toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readTopLevelFiles(root) {
|
|
43
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
44
|
+
return entries
|
|
45
|
+
.filter((entry) => entry.isFile())
|
|
46
|
+
.map((entry) => ({
|
|
47
|
+
name: entry.name,
|
|
48
|
+
lowerName: entry.name.toLowerCase(),
|
|
49
|
+
path: join(root, entry.name),
|
|
50
|
+
extension: extensionOf(entry.name),
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function classifyJson(file) {
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(await readFile(file.path, "utf8"));
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.scenes)) {
|
|
63
|
+
const hasNarration = parsed.scenes.some((scene) => typeof scene?.narration === "string" && scene.narration.trim());
|
|
64
|
+
if (hasNarration) {
|
|
65
|
+
return { type: "narration", reason: "json-scenes-narration" };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function classifyText(file) {
|
|
73
|
+
if (!TEXT_EXTENSIONS.has(file.extension)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const text = await readFile(file.path, "utf8");
|
|
78
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
79
|
+
const outlineLines = lines.filter((line) => /^#{1,4}\s+\S/.test(line) || /^[-*]\s+\S/.test(line) || /^\d+[.)]\s+\S/.test(line));
|
|
80
|
+
|
|
81
|
+
if (outlineLines.length >= 2 || (lines.length >= 3 && outlineLines.length >= 1)) {
|
|
82
|
+
return { type: "outline", reason: "outline-text-structure" };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function pickSingle(candidates) {
|
|
89
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function detectProjectFiles(rootDir) {
|
|
93
|
+
const root = resolve(rootDir);
|
|
94
|
+
const rootStat = await stat(root);
|
|
95
|
+
if (!rootStat.isDirectory()) {
|
|
96
|
+
throw new Error(`--dir must be a directory: ${root}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const files = await readTopLevelFiles(root);
|
|
100
|
+
const fixedOutlines = files
|
|
101
|
+
.filter((file) => OUTLINE_NAMES.has(file.lowerName))
|
|
102
|
+
.map((file) => ({ path: file.path, name: file.name, reason: "fixed-name" }));
|
|
103
|
+
const fixedNarrations = files
|
|
104
|
+
.filter((file) => NARRATION_NAMES.has(file.lowerName))
|
|
105
|
+
.map((file) => ({ path: file.path, name: file.name, reason: "fixed-name" }));
|
|
106
|
+
|
|
107
|
+
const candidates = {
|
|
108
|
+
outline: [...fixedOutlines],
|
|
109
|
+
narration: [...fixedNarrations],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (fixedOutlines.length === 0 || fixedNarrations.length === 0) {
|
|
113
|
+
for (const file of files) {
|
|
114
|
+
if (OUTLINE_NAMES.has(file.lowerName) || NARRATION_NAMES.has(file.lowerName)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const classification = file.extension === ".json"
|
|
119
|
+
? await classifyJson(file)
|
|
120
|
+
: await classifyText(file);
|
|
121
|
+
|
|
122
|
+
if (!classification) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (classification.type === "outline" && fixedOutlines.length === 0) {
|
|
127
|
+
candidates.outline.push({ path: file.path, name: file.name, reason: classification.reason });
|
|
128
|
+
} else if (classification.type === "narration" && fixedNarrations.length === 0) {
|
|
129
|
+
candidates.narration.push({ path: file.path, name: file.name, reason: classification.reason });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const outline = pickSingle(candidates.outline);
|
|
135
|
+
const narration = pickSingle(candidates.narration);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
ok: true,
|
|
139
|
+
root,
|
|
140
|
+
outline,
|
|
141
|
+
narration,
|
|
142
|
+
candidates,
|
|
143
|
+
ambiguous: candidates.outline.length > 1 || candidates.narration.length > 1,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function printHelp() {
|
|
148
|
+
console.log(`Usage:
|
|
149
|
+
node scripts/detect_project_files.mjs [--dir <path>] [--pretty]
|
|
150
|
+
|
|
151
|
+
Options:
|
|
152
|
+
--dir <path> Project directory to scan, default cwd
|
|
153
|
+
--pretty Pretty-print JSON output
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function main() {
|
|
158
|
+
try {
|
|
159
|
+
const options = parseArgs(process.argv.slice(2));
|
|
160
|
+
if (options.help) {
|
|
161
|
+
printHelp();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const result = await detectProjectFiles(options.dir);
|
|
166
|
+
console.log(JSON.stringify(result, null, options.pretty ? 2 : 0));
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.log(JSON.stringify({
|
|
169
|
+
ok: false,
|
|
170
|
+
error: {
|
|
171
|
+
message: error.message,
|
|
172
|
+
},
|
|
173
|
+
}, null, 2));
|
|
174
|
+
process.exitCode = 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main();
|