honeydo 0.1.0 → 0.1.2
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 +3 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.js +0 -0
- package/packages/doubao/dist/cli.js +0 -0
- package/packages/gcli/dist/cli.d.ts +262 -6
- package/packages/gcli/dist/cli.js +1504 -73
- package/packages/gcli/dist/cli.js.map +1 -1
- package/packages/lmedia/dist/index.js +532 -132
- package/packages/lmedia/python/turbo_merge.py +239 -0
- package/packages/lmedia/python/video_gen.py +31 -0
- package/packages/minimax/dist/cli.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import * as
|
|
3
|
+
import * as fs7 from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as os from 'os';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
@@ -10,12 +10,16 @@ import { setTimeout } from 'timers/promises';
|
|
|
10
10
|
import { createHash } from 'crypto';
|
|
11
11
|
|
|
12
12
|
var HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
|
|
13
|
+
var IMAGE_SNAPSHOT_REPOS = ["Qwen/Qwen-Image-2512", "Qwen/Qwen-Image-Edit-2511"];
|
|
14
|
+
function imageHubDir() {
|
|
15
|
+
return path.join(os.homedir(), ".cache", "huggingface", "hub");
|
|
16
|
+
}
|
|
17
|
+
function resolveRuntimeRoot() {
|
|
14
18
|
const home = os.homedir();
|
|
15
19
|
let root = process.env.LMEDIA_RUNTIME ?? "";
|
|
16
20
|
if (!root) {
|
|
17
21
|
const link = path.join(home, ".lmedia", "runtime");
|
|
18
|
-
if (
|
|
22
|
+
if (fs7.existsSync(link)) root = fs7.realpathSync(link);
|
|
19
23
|
}
|
|
20
24
|
if (!root) root = path.join(home, "ml", "lb-local-gen");
|
|
21
25
|
return root;
|
|
@@ -28,22 +32,23 @@ function pythonDriverDir() {
|
|
|
28
32
|
// src/lib/runtime.ts(tsx dev)→ <pkg>/python
|
|
29
33
|
];
|
|
30
34
|
for (const c of candidates) {
|
|
31
|
-
if (
|
|
35
|
+
if (fs7.existsSync(path.join(c, "sfx.py"))) return c;
|
|
32
36
|
}
|
|
33
37
|
return candidates[0];
|
|
34
38
|
}
|
|
35
39
|
function resolveVideoRuntime() {
|
|
36
|
-
const root =
|
|
40
|
+
const root = resolveRuntimeRoot();
|
|
37
41
|
return {
|
|
38
42
|
root,
|
|
39
43
|
venvVideo: path.join(root, ".venv-video"),
|
|
40
44
|
pythonVideo: path.join(root, ".venv-video", "bin", "python"),
|
|
41
45
|
mmh3turbo: path.join(root, ".venv-video", "bin", "mmh3turbo"),
|
|
42
|
-
weightsDir: path.join(os.homedir(), ".cache", "mmh3turbo")
|
|
46
|
+
weightsDir: process.env.LMEDIA_WEIGHTS_DIR ?? path.join(os.homedir(), ".cache", "mmh3turbo"),
|
|
47
|
+
pythonDir: pythonDriverDir()
|
|
43
48
|
};
|
|
44
49
|
}
|
|
45
50
|
function resolveAudioRuntime() {
|
|
46
|
-
const root =
|
|
51
|
+
const root = resolveRuntimeRoot();
|
|
47
52
|
return {
|
|
48
53
|
root,
|
|
49
54
|
pythonAudio: path.join(root, ".venv-audio", "bin", "python"),
|
|
@@ -57,28 +62,28 @@ function hfHubCache() {
|
|
|
57
62
|
}
|
|
58
63
|
function hfSnapshot(repo) {
|
|
59
64
|
const dir = path.join(hfHubCache(), `models--${repo.replace("/", "--")}`, "snapshots");
|
|
60
|
-
if (!
|
|
61
|
-
const snaps =
|
|
65
|
+
if (!fs7.existsSync(dir)) return null;
|
|
66
|
+
const snaps = fs7.readdirSync(dir).filter((s) => !s.startsWith("."));
|
|
62
67
|
return snaps.length ? path.join(dir, snaps[0]) : null;
|
|
63
68
|
}
|
|
64
69
|
function resolveRuntime() {
|
|
65
|
-
const root =
|
|
66
|
-
if (!
|
|
70
|
+
const root = resolveRuntimeRoot();
|
|
71
|
+
if (!fs7.existsSync(root)) {
|
|
67
72
|
throw new Error(
|
|
68
73
|
`\u8FD0\u884C\u65F6\u672A\u627E\u5230: ${root}
|
|
69
74
|
\u8BF7\u8BBE\u7F6E LMEDIA_RUNTIME \u6307\u5411\u672C\u5730\u751F\u6210\u6808\u76EE\u5F55\uFF08\u542B .venv/.venv-train\uFF09\uFF0C\u6216\u521B\u5EFA\u8F6F\u94FE: mkdir -p ~/.lmedia && ln -s <\u6808\u76EE\u5F55> ~/.lmedia/runtime`
|
|
70
75
|
);
|
|
71
76
|
}
|
|
72
|
-
const hf =
|
|
77
|
+
const hf = imageHubDir();
|
|
73
78
|
const snapOf = (repo) => {
|
|
74
79
|
const modelDir = path.join(hf, `models--${repo.replace("/", "--")}`);
|
|
75
80
|
const dir = path.join(modelDir, "snapshots");
|
|
76
|
-
if (!
|
|
77
|
-
const snaps =
|
|
81
|
+
if (!fs7.existsSync(dir)) throw new Error(`\u6A21\u578B\u672A\u4E0B\u8F7D: ${repo}\uFF08\u5148\u8FD0\u884C lmedia doctor \u5B89\u88C5\u6307\u5F15\uFF09`);
|
|
82
|
+
const snaps = fs7.readdirSync(dir);
|
|
78
83
|
if (snaps.length === 0) throw new Error(`\u6A21\u578B\u5FEB\u7167\u4E3A\u7A7A: ${repo}`);
|
|
79
84
|
const refFile = path.join(modelDir, "refs", "main");
|
|
80
|
-
if (
|
|
81
|
-
const ref =
|
|
85
|
+
if (fs7.existsSync(refFile)) {
|
|
86
|
+
const ref = fs7.readFileSync(refFile, "utf-8").trim();
|
|
82
87
|
if (snaps.includes(ref)) return path.join(dir, ref);
|
|
83
88
|
}
|
|
84
89
|
return path.join(dir, snaps[0]);
|
|
@@ -87,8 +92,8 @@ function resolveRuntime() {
|
|
|
87
92
|
root,
|
|
88
93
|
pythonGen: path.join(root, ".venv-train", "bin", "python"),
|
|
89
94
|
pythonFast: path.join(root, ".venv", "bin", "python"),
|
|
90
|
-
snapshot: snapOf(
|
|
91
|
-
snapshotEdit: snapOf(
|
|
95
|
+
snapshot: snapOf(IMAGE_SNAPSHOT_REPOS[0]),
|
|
96
|
+
snapshotEdit: snapOf(IMAGE_SNAPSHOT_REPOS[1]),
|
|
92
97
|
pythonDir: pythonDriverDir()
|
|
93
98
|
};
|
|
94
99
|
}
|
|
@@ -98,11 +103,11 @@ function seed() {
|
|
|
98
103
|
return [];
|
|
99
104
|
}
|
|
100
105
|
function loadRegistry() {
|
|
101
|
-
if (!
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
if (!fs7.existsSync(REGISTRY_PATH)) {
|
|
107
|
+
fs7.mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
108
|
+
fs7.writeFileSync(REGISTRY_PATH, JSON.stringify(seed(), null, 2));
|
|
104
109
|
}
|
|
105
|
-
return JSON.parse(
|
|
110
|
+
return JSON.parse(fs7.readFileSync(REGISTRY_PATH, "utf-8"));
|
|
106
111
|
}
|
|
107
112
|
function findLora(name) {
|
|
108
113
|
return loadRegistry().find((l) => l.name === name);
|
|
@@ -110,10 +115,10 @@ function findLora(name) {
|
|
|
110
115
|
function addLora(entry) {
|
|
111
116
|
const list = loadRegistry().filter((l) => l.name !== entry.name);
|
|
112
117
|
list.push(entry);
|
|
113
|
-
|
|
118
|
+
fs7.writeFileSync(REGISTRY_PATH, JSON.stringify(list, null, 2));
|
|
114
119
|
}
|
|
115
120
|
function runPython(python, script, payload, opts = {}) {
|
|
116
|
-
return new Promise((
|
|
121
|
+
return new Promise((resolve8, reject) => {
|
|
117
122
|
const child = spawn(python, [script, JSON.stringify(payload)], {
|
|
118
123
|
env: { ...process.env, HF_HUB_OFFLINE: process.env.HF_HUB_OFFLINE ?? "1" }
|
|
119
124
|
});
|
|
@@ -135,7 +140,7 @@ ${stderrTail}`));
|
|
|
135
140
|
const lines = stdoutBuf.trim().split("\n").filter(Boolean);
|
|
136
141
|
const lastLine = lines[lines.length - 1] ?? "";
|
|
137
142
|
try {
|
|
138
|
-
|
|
143
|
+
resolve8(JSON.parse(lastLine));
|
|
139
144
|
} catch {
|
|
140
145
|
reject(new Error(`\u65E0\u6CD5\u89E3\u6790 python \u8F93\u51FA: ${lastLine}
|
|
141
146
|
${stderrTail}`));
|
|
@@ -161,13 +166,13 @@ function pidAlive(pid) {
|
|
|
161
166
|
}
|
|
162
167
|
function readStatus(m) {
|
|
163
168
|
try {
|
|
164
|
-
return JSON.parse(
|
|
169
|
+
return JSON.parse(fs7.readFileSync(statusPath(m), "utf-8"));
|
|
165
170
|
} catch {
|
|
166
171
|
return null;
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
function requestJob(mode, payload) {
|
|
170
|
-
return new Promise((
|
|
175
|
+
return new Promise((resolve8, reject) => {
|
|
171
176
|
const sock = net.connect({ path: sockPath(mode) });
|
|
172
177
|
sock.setNoDelay(true);
|
|
173
178
|
let buf = "";
|
|
@@ -183,7 +188,7 @@ function requestJob(mode, payload) {
|
|
|
183
188
|
if (!sock.writableEnded && buf === "") {
|
|
184
189
|
settled = true;
|
|
185
190
|
sock.destroy();
|
|
186
|
-
|
|
191
|
+
resolve8(null);
|
|
187
192
|
}
|
|
188
193
|
});
|
|
189
194
|
sock.on("connect", () => {
|
|
@@ -196,7 +201,7 @@ function requestJob(mode, payload) {
|
|
|
196
201
|
if (e.code === "ENOENT" || e.code === "ECONNREFUSED") {
|
|
197
202
|
settled = true;
|
|
198
203
|
sock.destroy();
|
|
199
|
-
|
|
204
|
+
resolve8(null);
|
|
200
205
|
} else fail2(new Error(`daemon \u8FDE\u63A5\u9519\u8BEF: ${e.message}`));
|
|
201
206
|
});
|
|
202
207
|
sock.on("data", (d) => {
|
|
@@ -220,7 +225,7 @@ function requestJob(mode, payload) {
|
|
|
220
225
|
} else if (frame.t === "done") {
|
|
221
226
|
settled = true;
|
|
222
227
|
sock.end();
|
|
223
|
-
if (frame.ok)
|
|
228
|
+
if (frame.ok) resolve8(frame.result);
|
|
224
229
|
else fail2(new Error(`daemon \u4EFB\u52A1\u5931\u8D25: ${frame.error}
|
|
225
230
|
${frame.tb ?? ""}`));
|
|
226
231
|
}
|
|
@@ -237,7 +242,7 @@ async function spawnDaemon(mode, opts = {}) {
|
|
|
237
242
|
const alive = await pingDaemon(mode);
|
|
238
243
|
if (alive) return { pid: alive.pid };
|
|
239
244
|
const rt = resolveRuntime();
|
|
240
|
-
|
|
245
|
+
fs7.mkdirSync(SERVE_DIR, { recursive: true });
|
|
241
246
|
const idle = String(opts.idleTimeoutSec ?? DEFAULT_IDLE_TIMEOUT_SEC);
|
|
242
247
|
const args = [
|
|
243
248
|
path.join(rt.pythonDir, "serve.py"),
|
|
@@ -254,7 +259,7 @@ async function spawnDaemon(mode, opts = {}) {
|
|
|
254
259
|
"--idle-timeout",
|
|
255
260
|
idle
|
|
256
261
|
];
|
|
257
|
-
const logFd =
|
|
262
|
+
const logFd = fs7.openSync(logPath(mode), "a");
|
|
258
263
|
const child = spawn(rt.pythonGen, args, {
|
|
259
264
|
detached: true,
|
|
260
265
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -263,7 +268,7 @@ async function spawnDaemon(mode, opts = {}) {
|
|
|
263
268
|
child.unref();
|
|
264
269
|
const deadline = Date.now() + 2e4;
|
|
265
270
|
while (Date.now() < deadline) {
|
|
266
|
-
if (
|
|
271
|
+
if (fs7.existsSync(sockPath(mode))) return { pid: child.pid ?? -1 };
|
|
267
272
|
await setTimeout(200);
|
|
268
273
|
}
|
|
269
274
|
return null;
|
|
@@ -271,10 +276,10 @@ async function spawnDaemon(mode, opts = {}) {
|
|
|
271
276
|
async function stopDaemon(mode) {
|
|
272
277
|
const st = await pingDaemon(mode);
|
|
273
278
|
if (!st) {
|
|
274
|
-
const had =
|
|
279
|
+
const had = fs7.existsSync(sockPath(mode)) || fs7.existsSync(statusPath(mode));
|
|
275
280
|
for (const p of [sockPath(mode), statusPath(mode)]) {
|
|
276
281
|
try {
|
|
277
|
-
|
|
282
|
+
fs7.rmSync(p);
|
|
278
283
|
} catch {
|
|
279
284
|
}
|
|
280
285
|
}
|
|
@@ -294,7 +299,7 @@ async function stopDaemon(mode) {
|
|
|
294
299
|
await setTimeout(500);
|
|
295
300
|
for (const p of [sockPath(mode), statusPath(mode)]) {
|
|
296
301
|
try {
|
|
297
|
-
|
|
302
|
+
fs7.rmSync(p);
|
|
298
303
|
} catch {
|
|
299
304
|
}
|
|
300
305
|
}
|
|
@@ -312,6 +317,121 @@ async function waitGpuIdle() {
|
|
|
312
317
|
await setTimeout(5e3);
|
|
313
318
|
}
|
|
314
319
|
}
|
|
320
|
+
var ESRGAN_CACHE_PATH = path.join(os.homedir(), ".cache", "lmedia", "RealESRGAN_x2.pth");
|
|
321
|
+
var LEGACY_TMP_PATH = "/tmp/RealESRGAN_x2.pth";
|
|
322
|
+
function resolveEsrganPath() {
|
|
323
|
+
const env = process.env.LMEDIA_REALESRGAN_PATH;
|
|
324
|
+
if (env && fs7.existsSync(env)) return env;
|
|
325
|
+
if (fs7.existsSync(ESRGAN_CACHE_PATH)) return ESRGAN_CACHE_PATH;
|
|
326
|
+
if (fs7.existsSync(LEGACY_TMP_PATH)) return LEGACY_TMP_PATH;
|
|
327
|
+
return process.env.LMEDIA_REALESRGAN_PATH ?? ESRGAN_CACHE_PATH;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/lib/image-state.ts
|
|
331
|
+
var IMAGE_STATE_ENV = "LMEDIA_IMAGE_STATE";
|
|
332
|
+
var IMAGE_GATE_BYPASS_ENV = "LMEDIA_NO_IMAGE_GATE";
|
|
333
|
+
function imageStatePath() {
|
|
334
|
+
const env = process.env[IMAGE_STATE_ENV];
|
|
335
|
+
if (env && env.trim()) return path.resolve(env.trim());
|
|
336
|
+
return path.join(os.homedir(), ".config", "limg", "image-state.json");
|
|
337
|
+
}
|
|
338
|
+
var ImageDisabledError = class extends Error {
|
|
339
|
+
};
|
|
340
|
+
function readImageGate() {
|
|
341
|
+
const file = imageStatePath();
|
|
342
|
+
if (!fs7.existsSync(file)) return { state: "enabled", file, present: false };
|
|
343
|
+
let parsed;
|
|
344
|
+
try {
|
|
345
|
+
parsed = JSON.parse(fs7.readFileSync(file, "utf-8"));
|
|
346
|
+
} catch (e) {
|
|
347
|
+
return { state: "corrupt", file, error: `\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${e.message}\uFF09` };
|
|
348
|
+
}
|
|
349
|
+
const s = parsed;
|
|
350
|
+
if (!s || typeof s !== "object" || Array.isArray(s)) {
|
|
351
|
+
return { state: "corrupt", file, error: "\u6839\u4E0D\u662F\u5BF9\u8C61" };
|
|
352
|
+
}
|
|
353
|
+
if (typeof s.enabled !== "boolean") {
|
|
354
|
+
return { state: "corrupt", file, error: "\u7F3A\u5C11 enabled \u5E03\u5C14\u5B57\u6BB5" };
|
|
355
|
+
}
|
|
356
|
+
return s.enabled === false ? { state: "disabled", file, present: true, disabledAt: s.disabledAt ?? "", reason: s.reason } : { state: "enabled", file, present: true, reason: s.reason };
|
|
357
|
+
}
|
|
358
|
+
function writeImageState(enabled, opts = {}) {
|
|
359
|
+
const file = imageStatePath();
|
|
360
|
+
const state = enabled ? { enabled: true } : { enabled: false, disabledAt: (/* @__PURE__ */ new Date()).toISOString(), ...opts.reason ? { reason: opts.reason } : {} };
|
|
361
|
+
fs7.mkdirSync(path.dirname(file), { recursive: true });
|
|
362
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
363
|
+
fs7.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}
|
|
364
|
+
`, "utf-8");
|
|
365
|
+
fs7.renameSync(tmp, file);
|
|
366
|
+
return file;
|
|
367
|
+
}
|
|
368
|
+
function gateAdvice(g) {
|
|
369
|
+
const head = g.state === "disabled" ? `\u56FE\u50CF\u80FD\u529B\u5DF2\u7981\u7528${g.disabledAt ? `\uFF08${g.disabledAt}\uFF09` : ""}${g.reason ? ` \u539F\u56E0: ${g.reason}` : ""}` : `\u56FE\u50CF\u80FD\u529B\u72B6\u6001\u6587\u4EF6\u635F\u574F\uFF0C\u62D2\u7EDD\u6267\u884C\u56FE\u50CF\u547D\u4EE4\uFF08\u4E0D\u731C\uFF09: ${g.file}
|
|
370
|
+
\u539F\u56E0: ${g.error}`;
|
|
371
|
+
return `${head}
|
|
372
|
+
\u6062\u590D: lmedia image enable${g.state === "corrupt" ? `\uFF08\u7528\u542F\u7528\u72B6\u6001\u8986\u76D6\u91CD\u5199\uFF09
|
|
373
|
+
\u6216 rm ${g.file}\uFF08\u5220\u9664\u540E\u56DE\u5230\u9ED8\u8BA4\u542F\u7528\uFF09` : ""}
|
|
374
|
+
\u67E5\u770B: lmedia image status
|
|
375
|
+
\u4E91\u7AEF\u66FF\u4EE3: honeydo image gen "<prompt>" --engine doubao
|
|
376
|
+
\uFF08\u4E34\u65F6\u7ED5\u8FC7: ${IMAGE_GATE_BYPASS_ENV}=1\uFF09`;
|
|
377
|
+
}
|
|
378
|
+
function assertImageEnabled(what) {
|
|
379
|
+
if (process.env[IMAGE_GATE_BYPASS_ENV] === "1") {
|
|
380
|
+
const g2 = readImageGate();
|
|
381
|
+
if (g2.state !== "enabled") {
|
|
382
|
+
console.error(`\u26A0\uFE0F ${IMAGE_GATE_BYPASS_ENV}=1\uFF1A\u5DF2\u7ED5\u8FC7\u56FE\u50CF\u80FD\u529B gate\uFF08\u5F53\u524D\u4E3A${g2.state === "disabled" ? "\u7981\u7528" : "\u635F\u574F"}\u6001\uFF09`);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const g = readImageGate();
|
|
387
|
+
if (g.state === "enabled") return;
|
|
388
|
+
throw new ImageDisabledError(`${gateAdvice(g)}
|
|
389
|
+
\uFF08\u88AB\u62E6\u622A\u7684\u547D\u4EE4: ${what}\uFF09`);
|
|
390
|
+
}
|
|
391
|
+
var VENV_FIX = "\u56FE\u50CF venv \u672A\u5C31\u7EEA\uFF1A\u6309 packages/lmedia/USAGE.md\u300C\u56FE\u50CF\u300D\u5C0F\u8282\u91CD\u5EFA .venv / .venv-train";
|
|
392
|
+
var snapDir = (repo) => path.join(imageHubDir(), `models--${repo.replace("/", "--")}`, "snapshots");
|
|
393
|
+
function hasSnapshot(repo) {
|
|
394
|
+
try {
|
|
395
|
+
const d = snapDir(repo);
|
|
396
|
+
return fs7.existsSync(d) && fs7.readdirSync(d).length > 0;
|
|
397
|
+
} catch {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function snapshotFix(repo) {
|
|
402
|
+
return `HF_HUB_OFFLINE=0 hf download ${repo}\uFF08~54GB\uFF1B\u7F51\u7EDC\u5DEE\u53EF HF_ENDPOINT=https://hf-mirror.com\uFF09`;
|
|
403
|
+
}
|
|
404
|
+
function imagePreflight() {
|
|
405
|
+
const root = resolveRuntimeRoot();
|
|
406
|
+
return [
|
|
407
|
+
{
|
|
408
|
+
name: "runtime root",
|
|
409
|
+
ok: fs7.existsSync(root),
|
|
410
|
+
fix: `\u8BBE LMEDIA_RUNTIME=${root} \u6307\u5411\u672C\u5730\u751F\u6210\u6808\uFF0C\u6216 ln -s <\u6808\u76EE\u5F55> ~/.lmedia/runtime`
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: "pythonGen (.venv-train)",
|
|
414
|
+
ok: fs7.existsSync(path.join(root, ".venv-train", "bin", "python")),
|
|
415
|
+
fix: VENV_FIX
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: "pythonFast (.venv)",
|
|
419
|
+
ok: fs7.existsSync(path.join(root, ".venv", "bin", "python")),
|
|
420
|
+
fix: VENV_FIX
|
|
421
|
+
},
|
|
422
|
+
// 标签取 repo 短名(Qwen-Image-2512),与 doctor 既有输出逐字一致,不破坏外部匹配
|
|
423
|
+
...IMAGE_SNAPSHOT_REPOS.map((repo) => ({
|
|
424
|
+
name: `[image] ${repo.split("/")[1]} \u5FEB\u7167`,
|
|
425
|
+
ok: hasSnapshot(repo),
|
|
426
|
+
fix: snapshotFix(repo)
|
|
427
|
+
})),
|
|
428
|
+
{
|
|
429
|
+
name: "[image] Real-ESRGAN \u6743\u91CD",
|
|
430
|
+
ok: fs7.existsSync(resolveEsrganPath()),
|
|
431
|
+
fix: "\u7F3A RealESRGAN_x2.pth\uFF08~64MB\uFF09\uFF1A\u89C1 USAGE.md\u300C\u56FE\u50CF\u300D\u5C0F\u8282\uFF0C\u6216 LMEDIA_REALESRGAN_PATH \u6307\u5B9A"
|
|
432
|
+
}
|
|
433
|
+
];
|
|
434
|
+
}
|
|
315
435
|
function parseMode(v) {
|
|
316
436
|
if (v !== "gen" && v !== "edit") {
|
|
317
437
|
console.error("--mode \u5FC5\u987B\u662F gen \u6216 edit");
|
|
@@ -415,13 +535,119 @@ function registerServe(image) {
|
|
|
415
535
|
registerStatus(serve);
|
|
416
536
|
}
|
|
417
537
|
|
|
538
|
+
// src/commands/image-capability.ts
|
|
539
|
+
var MODES = ["gen", "edit"];
|
|
540
|
+
var isBusy = (st) => st.busy || st.queue > 0;
|
|
541
|
+
function registerDisable(image) {
|
|
542
|
+
image.command("disable").description("\u7981\u7528\u56FE\u50CF\u80FD\u529B\uFF1Agen/edit/upscale/serve start \u5168\u90E8\u62E6\u4E0B\uFF08enable/status/serve stop \u59CB\u7EC8\u53EF\u7528\uFF09").option("--reason <text>", "\u8BB0\u5F55\u7981\u7528\u539F\u56E0\uFF08\u5199\u8FDB\u72B6\u6001\u6587\u4EF6\uFF09").action(async (opts) => {
|
|
543
|
+
const g = readImageGate();
|
|
544
|
+
if (g.state === "disabled") {
|
|
545
|
+
console.log(`\xB7 \u5DF2\u662F\u7981\u7528\u6001\uFF08\u672A\u6539\u5199\u72B6\u6001\u6587\u4EF6\uFF09`);
|
|
546
|
+
}
|
|
547
|
+
let stopped = false;
|
|
548
|
+
for (const m of MODES) {
|
|
549
|
+
const st = await pingDaemon(m);
|
|
550
|
+
if (!st) {
|
|
551
|
+
console.log(`\xB7 ${m} daemon \u672A\u8FD0\u884C`);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (isBusy(st)) {
|
|
555
|
+
console.error(
|
|
556
|
+
`\u2717 ${m} daemon \u6B63\u5FD9\uFF08pid ${st.pid}\uFF0Cjobs ${st.jobs}\uFF09\u2014\u2014\u672A\u5199\u72B6\u6001\u3002
|
|
557
|
+
\u7B49\u4EFB\u52A1\u7ED3\u675F\uFF0C\u6216 lmedia image serve stop --mode ${m} \u4E2D\u65AD\u5F53\u524D\u4EFB\u52A1\u540E\u91CD\u8BD5\u3002`
|
|
558
|
+
);
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
const r = await stopDaemon(m);
|
|
562
|
+
console.log(`\u2713 \u5DF2\u505C\u6B62 ${m} daemon\uFF08pid ${st.pid}${r === "killed" ? "\uFF0CSIGKILL" : ""}\uFF09\u2014\u2014\u91CA\u653E\u5E38\u9A7B\u5185\u5B58\u4E0E\u6587\u4EF6\u53E5\u67C4`);
|
|
563
|
+
stopped = true;
|
|
564
|
+
}
|
|
565
|
+
if (g.state === "corrupt") {
|
|
566
|
+
console.error(`\u26A0\uFE0F \u72B6\u6001\u6587\u4EF6\u635F\u574F\uFF0C\u5DF2\u7528\u65B0\u72B6\u6001\u8986\u76D6: ${g.file}\uFF08${g.error}\uFF09`);
|
|
567
|
+
}
|
|
568
|
+
if (g.state !== "disabled") {
|
|
569
|
+
const file = writeImageState(false, { reason: opts.reason });
|
|
570
|
+
console.log(`\u2713 \u56FE\u50CF\u80FD\u529B\u5DF2\u7981\u7528\uFF08${file}\uFF09`);
|
|
571
|
+
}
|
|
572
|
+
console.log(
|
|
573
|
+
` \u6062\u590D: lmedia image enable
|
|
574
|
+
\u4E91\u7AEF\u66FF\u4EE3: honeydo image gen "<prompt>" --engine doubao
|
|
575
|
+
` + (stopped ? "" : ` \u63D0\u793A: \u5220\u9664\u6743\u91CD\u524D\u8BF7\u786E\u8BA4 lmedia image serve status \u65E0 daemon \u5728\u8DD1\uFF08\u6301\u6709\u6587\u4EF6\u53E5\u67C4\u65F6 rm \u4E0D\u91CA\u653E\u7A7A\u95F4\uFF09
|
|
576
|
+
`) + ` \u968F\u540E\u53EF\u624B\u5DE5\u6E05\u7406\uFF08\u5F53\u524D\u5728\u4F4D\u60C5\u51B5\u89C1 lmedia image status\uFF09`
|
|
577
|
+
);
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
function registerEnable(image) {
|
|
581
|
+
image.command("enable").description("\u542F\u7528\u56FE\u50CF\u80FD\u529B\uFF08\u672C\u5730\u6743\u91CD/venv \u82E5\u5DF2\u6E05\u7406\u4F1A\u5217\u51FA\u7F3A\u5931\u9879\u4E0E\u91CD\u65B0\u4E0B\u8F7D\u6307\u5F15\uFF09").action(() => {
|
|
582
|
+
const g = readImageGate();
|
|
583
|
+
if (g.state === "corrupt") {
|
|
584
|
+
console.error(`\u26A0\uFE0F \u72B6\u6001\u6587\u4EF6\u635F\u574F\uFF0C\u5DF2\u7528\u65B0\u72B6\u6001\u8986\u76D6: ${g.file}\uFF08${g.error}\uFF09`);
|
|
585
|
+
}
|
|
586
|
+
const file = writeImageState(true);
|
|
587
|
+
console.log(`\u2713 \u56FE\u50CF\u80FD\u529B\u5DF2\u542F\u7528\uFF08${file}\uFF09`);
|
|
588
|
+
const missing = imagePreflight().filter((i) => !i.ok);
|
|
589
|
+
if (!missing.length) {
|
|
590
|
+
console.log(` \u672C\u5730\u56FE\u50CF\u8D44\u4EA7\u9F50\u5907\uFF0C\u53EF\u76F4\u63A5\u4F7F\u7528\u3002`);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
console.error(`\u26A0\uFE0F \u4F46\u56FE\u50CF\u6808\u4ECD\u7F3A ${missing.length} \u9879\uFF0C\u6682\u4E0D\u53EF\u7528\uFF1A`);
|
|
594
|
+
for (const m of missing) console.error(` \u2717 ${m.name} \u2192 ${m.fix}`);
|
|
595
|
+
console.error(` \u78C1\u76D8\u9700\u9884\u7559\u7EA6 108GB\uFF08\u4E24\u4E2A\u5FEB\u7167\uFF09\uFF1B\u5C31\u7EEA\u540E lmedia doctor \u590D\u6838`);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
function registerStatus2(image) {
|
|
600
|
+
image.command("status").description("\u67E5\u770B\u56FE\u50CF\u80FD\u529B\u5F00\u5173\u72B6\u6001\u4E0E\u672C\u5730\u4F4D\u5728\u4F4D\u60C5\u51B5\uFF080=\u53EF\u8DD1\u56FE\u50CF\u547D\u4EE4\uFF1B\u4E0E image serve status \u770B\u7684\u662F\u4E0D\u540C\u4E1C\u897F\uFF09").action(async () => {
|
|
601
|
+
const g = readImageGate();
|
|
602
|
+
const label = g.state === "disabled" ? "\u5DF2\u7981\u7528" : g.state === "corrupt" ? "\u72B6\u6001\u6587\u4EF6\u635F\u574F" : "\u5DF2\u542F\u7528";
|
|
603
|
+
const when = g.state === "disabled" && g.disabledAt ? `\uFF08${g.disabledAt}\uFF09` : "";
|
|
604
|
+
const why = g.state !== "corrupt" && g.reason ? ` \xB7 ${g.reason}` : "";
|
|
605
|
+
console.log(`\u56FE\u50CF\u80FD\u529B: ${label}${when}${why}`);
|
|
606
|
+
console.log(` \u72B6\u6001\u6587\u4EF6: ${g.file}${g.state === "enabled" && !g.present ? "\uFF08\u4E0D\u5B58\u5728=\u9ED8\u8BA4\u542F\u7528\uFF09" : ""}`);
|
|
607
|
+
if (g.state !== "enabled") console.log(gateAdvice(g).split("\n").map((l) => ` ${l}`).join("\n"));
|
|
608
|
+
const items = imagePreflight();
|
|
609
|
+
for (const i of items) {
|
|
610
|
+
console.log(` ${i.ok ? "\u2713" : "\u2717"} ${i.name}${i.ok ? "" : ` \u2192 ${i.fix}`}`);
|
|
611
|
+
}
|
|
612
|
+
for (const m of MODES) {
|
|
613
|
+
const st = await pingDaemon(m);
|
|
614
|
+
console.log(
|
|
615
|
+
` ${st ? g.state === "disabled" ? "\u2717" : "\u2713" : "\xB7"} daemon ${m}` + (st ? `: ${st.state}\uFF08pid ${st.pid}\uFF0Cjobs ${st.jobs}\uFF09${g.state === "disabled" ? "\u2014\u2014\u80FD\u529B\u5DF2\u7981\u7528\u5374\u4ECD\u5728\u8DD1\uFF0C\u8BF7 lmedia image serve stop" : ""}` : ": \u672A\u8FD0\u884C")
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const usable = g.state === "enabled" && items.every((i) => i.ok);
|
|
619
|
+
process.exit(usable ? 0 : 1);
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
function registerImageCapability(image) {
|
|
623
|
+
registerDisable(image);
|
|
624
|
+
registerEnable(image);
|
|
625
|
+
registerStatus2(image);
|
|
626
|
+
}
|
|
627
|
+
|
|
418
628
|
// src/commands/image.ts
|
|
629
|
+
var IMAGE_GATED = /* @__PURE__ */ new Set(["image gen", "image edit", "image upscale", "image serve start"]);
|
|
630
|
+
function commandPath(cmd) {
|
|
631
|
+
const parts = [];
|
|
632
|
+
for (let c = cmd; c && c.parent; c = c.parent) parts.unshift(c.name());
|
|
633
|
+
return parts.join(" ");
|
|
634
|
+
}
|
|
635
|
+
function gateImage(what) {
|
|
636
|
+
try {
|
|
637
|
+
assertImageEnabled(what);
|
|
638
|
+
} catch (e) {
|
|
639
|
+
if (!(e instanceof ImageDisabledError)) throw e;
|
|
640
|
+
console.error(`\u2717 ${e.message}`);
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
419
644
|
var DEFAULT_NEG = "\u4F4E\u5206\u8FA8\u7387\uFF0C\u4F4E\u753B\u8D28\uFF0C\u80A2\u4F53\u7578\u5F62\uFF0C\u624B\u6307\u7578\u5F62\uFF0C\u753B\u9762\u8FC7\u9971\u548C\uFF0C\u8721\u50CF\u611F\uFF0C\u4EBA\u8138\u65E0\u7EC6\u8282\uFF0C\u8FC7\u5EA6\u5149\u6ED1\uFF0C\u753B\u9762\u5177\u6709AI\u611F\u3002\u6784\u56FE\u6DF7\u4E71\u3002\u6587\u5B57\u6A21\u7CCA\uFF0C\u626D\u66F2\u3002";
|
|
420
645
|
function parseCache(v) {
|
|
421
646
|
if (v === void 0) return void 0;
|
|
422
647
|
return { thresh: v === true ? 0.3 : parseFloat(String(v)) };
|
|
423
648
|
}
|
|
424
649
|
async function runImageJob(rt, args) {
|
|
650
|
+
gateImage(`image ${args.kind}`);
|
|
425
651
|
if (!args.noDaemon && process.env.LMEDIA_NO_DAEMON !== "1") {
|
|
426
652
|
for (const mode of args.daemonModes) {
|
|
427
653
|
const r = await requestJob(mode, { kind: args.kind, ...args.payload });
|
|
@@ -474,7 +700,7 @@ function registerGen(image) {
|
|
|
474
700
|
const trueCfg = opts.cfg !== void 0 ? parseFloat(opts.cfg) : fast ? 1 : 4;
|
|
475
701
|
if (fast && !names.includes("lightning2512")) {
|
|
476
702
|
const lt = findLora("lightning2512");
|
|
477
|
-
if (!lt || !
|
|
703
|
+
if (!lt || !fs7.existsSync(lt.path)) {
|
|
478
704
|
console.error("--fast \u9700\u8981 lightning2512 LoRA\uFF08\u4E0B\u8F7D\u540E: lmedia lora add lightning2512 <path> --kind speed\uFF09");
|
|
479
705
|
process.exit(2);
|
|
480
706
|
}
|
|
@@ -521,7 +747,7 @@ function registerEdit(image) {
|
|
|
521
747
|
if (fast) {
|
|
522
748
|
const wantX4 = fastSteps <= 4;
|
|
523
749
|
const lt = (wantX4 ? findLora("lightningedit2511x4") : void 0) ?? findLora("lightningedit2511");
|
|
524
|
-
if (!lt || !
|
|
750
|
+
if (!lt || !fs7.existsSync(lt.path)) {
|
|
525
751
|
console.error("--fast \u9700\u8981 lightningedit2511 LoRA\uFF08\u4E0B\u8F7D\u540E: lmedia lora add lightningedit2511 <path> --kind speed\uFF09");
|
|
526
752
|
process.exit(2);
|
|
527
753
|
}
|
|
@@ -571,11 +797,16 @@ function registerUpscale(image) {
|
|
|
571
797
|
});
|
|
572
798
|
}
|
|
573
799
|
function registerImage(program2) {
|
|
574
|
-
const image = program2.command("image").description("\u56FE\u50CF\u751F\u4EA7\u80FD\u529B\uFF08\u6587\u751F\u56FE/\u53C2\u8003\u56FE\u7F16\u8F91/\u8D85\u5206/\u5E38\u9A7B daemon\uFF09");
|
|
800
|
+
const image = program2.command("image").description("\u56FE\u50CF\u751F\u4EA7\u80FD\u529B\uFF08\u6587\u751F\u56FE/\u53C2\u8003\u56FE\u7F16\u8F91/\u8D85\u5206/\u5E38\u9A7B daemon\uFF1B\u5F00\u5173\u89C1 image disable|enable|status\uFF09");
|
|
801
|
+
image.hook("preAction", (_thisCommand, actionCommand) => {
|
|
802
|
+
const p = commandPath(actionCommand);
|
|
803
|
+
if (IMAGE_GATED.has(p)) gateImage(p);
|
|
804
|
+
});
|
|
575
805
|
registerGen(image);
|
|
576
806
|
registerEdit(image);
|
|
577
807
|
registerUpscale(image);
|
|
578
808
|
registerServe(image);
|
|
809
|
+
registerImageCapability(image);
|
|
579
810
|
}
|
|
580
811
|
function hasCommand(cmd) {
|
|
581
812
|
return spawnSync("which", [cmd], { stdio: "ignore" }).status === 0;
|
|
@@ -590,11 +821,43 @@ var RES_PRESETS = [
|
|
|
590
821
|
"704p",
|
|
591
822
|
"720p",
|
|
592
823
|
"768p",
|
|
824
|
+
"1080p",
|
|
593
825
|
"square",
|
|
594
826
|
"portrait",
|
|
595
827
|
"vertical"
|
|
596
828
|
// 竖版/方版(绘本竖页用 portrait)
|
|
597
829
|
];
|
|
830
|
+
var TURBO_BUNDLE = "dit-turbo";
|
|
831
|
+
var TURBO_LORA_FILE = "lightx2v_fl2v_turbo_8step_v1.0_768p_bf16.safetensors";
|
|
832
|
+
var TURBO_LORA_URL = `https://hf-mirror.com/lightx2v/Minimax-h3-Turbo/resolve/main/${TURBO_LORA_FILE}`;
|
|
833
|
+
var FAST_STEPS = 8;
|
|
834
|
+
var FAST_SHIFT_VIDEO = 6;
|
|
835
|
+
var PERSON_RE = new RegExp(
|
|
836
|
+
"\u4EBA\u50CF|\u4EBA\u7269|\u5973\u5B69|\u7537\u5B69|\u5973\u4EBA|\u7537\u4EBA|\u5973\u751F|\u7537\u751F|\u7238\u7238|\u5988\u5988|\u5B9D\u5B9D|\u5B69\u5B50|\u5C0F\u5B69|\u513F\u5B50|\u5973\u513F|\u59D0\u59D0|\u5F1F\u5F1F|\u59B9\u59B9|\u54E5\u54E5|\u7237\u7237|\u5976\u5976|\u53D4\u53D4|\u963F\u59E8|\u5BB6\u5EAD|portrait|person|child|baby|girl|boy|woman|man|people|face",
|
|
837
|
+
"i"
|
|
838
|
+
);
|
|
839
|
+
var LOWRES_PRESETS = /* @__PURE__ */ new Set(["256p", "352p", "480p", "576p"]);
|
|
840
|
+
function qualityHints(prompt, o) {
|
|
841
|
+
const hints = [];
|
|
842
|
+
if (!PERSON_RE.test(prompt)) return hints;
|
|
843
|
+
if (o.fast) {
|
|
844
|
+
console.error("\u63D0\u793A: \u4EBA\u7269\u89C6\u9891\u7528 --fast\uFF08\u84B8\u998F\u6863\uFF09\u4F1A\u51FA\u73B0\u7EB9\u7406\u6CB9\u753B\u611F\u4E0E\u8EAB\u4EFD\u6F02\u79FB\u2014\u2014\u8D28\u91CF\u4F18\u5148\u8BF7\u53BB\u6389 --fast \u7528\u57FA\u5EA7 12 \u6B65\uFF0C\u5E76\u628A\u6784\u56FE\u88C1\u5230\u4EBA\u7269\u7279\u5199\uFF08\u4EBA\u8138\u5360\u753B\u9762 1/4 \u4EE5\u4E0A\uFF09\u3002\u914D\u65B9: lmedia video recipes");
|
|
845
|
+
hints.push("person+fast: \u84B8\u998F\u6863\u7EB9\u7406\u8F6F\u5316\u4E14\u65F6\u57DF\u6F02\u79FB\u5927\uFF1B\u4EBA\u7269\u8D28\u91CF\u4F18\u5148\u7528\u57FA\u5EA7 12 \u6B65 + \u4E3B\u4F53\u7279\u5199\u6784\u56FE\uFF08lmedia video recipes\uFF09");
|
|
846
|
+
}
|
|
847
|
+
if (LOWRES_PRESETS.has(o.res)) {
|
|
848
|
+
console.error(`\u63D0\u793A: \u4EBA\u7269\u89C6\u9891\u5206\u8FA8\u7387\u4E0D\u8DB3\uFF08${o.res} \u77ED\u8FB9 < 704\uFF0C\u4EBA\u8138\u50CF\u7D20\u592A\u5C11\u5FC5\u7CCA\uFF09\u2014\u2014\u5EFA\u8BAE -r 720p/square/portrait \u5E76\u8BA9\u4EBA\u7269\u5360\u753B\u9762\u4E3B\u4F53\u3002\u914D\u65B9: lmedia video recipes`);
|
|
849
|
+
hints.push(`person+${o.res}: \u4EBA\u8138\u50CF\u7D20\u4E0D\u8DB3\uFF1B\u7528 720p/square/portrait + \u4E3B\u4F53\u7279\u5199\u6784\u56FE`);
|
|
850
|
+
}
|
|
851
|
+
if (o.firstFrame && !o.lastFrame) {
|
|
852
|
+
console.error("\u63D0\u793A: \u56FE\u751F\u89C6\u9891\u4EC5\u951A\u5B9A\u9996\u5E27\uFF0C\u4EBA\u7269\u52A8\u4F5C\u5927\u65F6\u7ED3\u5C3E\u8EAB\u4EFD\u4F1A\u6F02\u79FB\uFF08\u5B9E\u6D4B\u7ED3\u5C3E\u4EBA\u8138\u76F8\u4F3C\u5EA6\u53EF\u8DCC\u5230 0.3\uFF09\u2014\u2014\u540C\u4E00\u5F20\u56FE\u518D\u52A0 --last-frame \u53CC\u951A\u5B9A\u53EF\u56DE\u73AF\u6291\u5236\uFF08\u5B9E\u6D4B\u62C9\u56DE 0.97\uFF09\u3002\u914D\u65B9: lmedia video recipes");
|
|
853
|
+
hints.push("first-frame only: \u7ED3\u5C3E\u8EAB\u4EFD\u4F1A\u6F02\u79FB\uFF1B\u540C\u56FE --last-frame \u53CC\u951A\u5B9A\u53EF\u5C06\u7ED3\u5C3E\u4EBA\u8138\u76F8\u4F3C\u5EA6 0.3\u21920.97");
|
|
854
|
+
}
|
|
855
|
+
if (o.seconds > 5) {
|
|
856
|
+
console.error("\u63D0\u793A: \u4EBA\u7269\u89C6\u9891\u6F02\u79FB\u968F\u65F6\u957F\u7D2F\u79EF\uFF0C\u5EFA\u8BAE \u22645s\uFF08\u8981\u957F\u955C\u5934\u8BF7\u5206\u591A\u6BB5 + --last-frame \u8854\u63A5\uFF09\u3002");
|
|
857
|
+
hints.push("person+long: \u6F02\u79FB\u968F\u65F6\u957F\u7D2F\u79EF\uFF0C\u5EFA\u8BAE \u22645s \u6216\u5206\u6BB5");
|
|
858
|
+
}
|
|
859
|
+
return hints;
|
|
860
|
+
}
|
|
598
861
|
function runStep(cmd, args) {
|
|
599
862
|
const r = spawnSync(cmd, args, { stdio: "inherit" });
|
|
600
863
|
if (r.status !== 0) {
|
|
@@ -604,16 +867,16 @@ function runStep(cmd, args) {
|
|
|
604
867
|
}
|
|
605
868
|
function patchGgufFilename(venvVideo) {
|
|
606
869
|
const libDir = path.join(venvVideo, "lib");
|
|
607
|
-
if (!
|
|
608
|
-
const pyDir =
|
|
870
|
+
if (!fs7.existsSync(libDir)) return;
|
|
871
|
+
const pyDir = fs7.readdirSync(libDir).find((d) => d.startsWith("python"));
|
|
609
872
|
if (!pyDir) return;
|
|
610
873
|
const w = path.join(libDir, pyDir, "site-packages", "mmh3turbo", "weights.py");
|
|
611
|
-
if (!
|
|
612
|
-
const src =
|
|
874
|
+
if (!fs7.existsSync(w)) return;
|
|
875
|
+
const src = fs7.readFileSync(w, "utf8");
|
|
613
876
|
const OLD = "MiniMax-H3-Qwen3VL-32B-TextEncoder-Q2_K.gguf";
|
|
614
877
|
const NEW = "qwen3vl-32B-MiniMax-H3-Q2_K.gguf";
|
|
615
878
|
if (src.includes(OLD)) {
|
|
616
|
-
|
|
879
|
+
fs7.writeFileSync(w, src.replaceAll(OLD, NEW));
|
|
617
880
|
console.error(`\u5DF2\u4FEE\u8865 mmh3turbo \u4E0A\u6E38 GGUF \u6587\u4EF6\u540D\u6F02\u79FB\uFF08${OLD} \u2192 ${NEW}\uFF09`);
|
|
618
881
|
}
|
|
619
882
|
}
|
|
@@ -636,8 +899,8 @@ function mirrorProvision() {
|
|
|
636
899
|
for (const f of MIRROR_FILES) {
|
|
637
900
|
const dest = f.dest.replace(/^~/, os.homedir());
|
|
638
901
|
const url = f.repo === "Comfy-Org/MiniMax-H3" ? `${MIRROR}/${f.repo}/resolve/main/vae/${path.basename(dest)}` : `${MIRROR}/${f.repo}/resolve/main/${path.basename(dest)}`;
|
|
639
|
-
|
|
640
|
-
if (
|
|
902
|
+
fs7.mkdirSync(path.dirname(dest), { recursive: true });
|
|
903
|
+
if (fs7.existsSync(dest) && fs7.statSync(dest).size === f.bytes) {
|
|
641
904
|
console.error(`\u5DF2\u5C31\u4F4D: ${dest}`);
|
|
642
905
|
continue;
|
|
643
906
|
}
|
|
@@ -657,8 +920,8 @@ function mirrorProvision() {
|
|
|
657
920
|
console.error(`\u4E0B\u8F7D\u4E2D\u65AD\uFF0C5 \u79D2\u540E\u7EED\u4F20\uFF08\u7B2C ${attempt} \u8F6E\uFF09\u2026`);
|
|
658
921
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5e3);
|
|
659
922
|
}
|
|
660
|
-
|
|
661
|
-
const got =
|
|
923
|
+
fs7.renameSync(`${dest}.part`, dest);
|
|
924
|
+
const got = fs7.statSync(dest).size;
|
|
662
925
|
if (expect && got !== expect) {
|
|
663
926
|
console.error(`\u5927\u5C0F\u6821\u9A8C\u5931\u8D25: ${dest} got=${got} expect=${expect}`);
|
|
664
927
|
process.exit(1);
|
|
@@ -669,11 +932,11 @@ function mirrorProvision() {
|
|
|
669
932
|
function registerSetup(video) {
|
|
670
933
|
video.command("setup").description("\u521B\u5EFA .venv-video \u5E76\u5B89\u88C5 mmh3turbo\uFF08MiniMax-H3 MLX \u5F15\u64CE\uFF0CApple Silicon\uFF09").option("--mirror", "\u7528 hf-mirror.com + curl \u9884\u7F6E H3 \u6743\u91CD\uFF08~50GB\uFF0C\u56FD\u5185\u63A8\u8350\uFF1Bhuggingface_hub \u5BA2\u6237\u7AEF\u4E0E\u955C\u50CF\u91CD\u5B9A\u5411\u4E0D\u517C\u5BB9\u65F6\u7684\u6B63\u89E3\uFF09").action((opts) => {
|
|
671
934
|
const rt = resolveVideoRuntime();
|
|
672
|
-
|
|
935
|
+
fs7.mkdirSync(rt.root, { recursive: true });
|
|
673
936
|
if (!hasCommand("ffmpeg")) {
|
|
674
937
|
console.error("\u26A0\uFE0F \u672A\u627E\u5230 ffmpeg\uFF08mp4 \u5C01\u88C5\u5FC5\u9700\uFF09\uFF1Abrew install ffmpeg");
|
|
675
938
|
}
|
|
676
|
-
if (!
|
|
939
|
+
if (!fs7.existsSync(rt.pythonVideo)) {
|
|
677
940
|
console.error(`\u521B\u5EFA venv: ${rt.venvVideo}`);
|
|
678
941
|
if (hasCommand("uv")) runStep("uv", ["venv", "--python", "3.12", rt.venvVideo]);
|
|
679
942
|
else runStep("python3", ["-m", "venv", rt.venvVideo]);
|
|
@@ -694,10 +957,21 @@ function registerSetup(video) {
|
|
|
694
957
|
});
|
|
695
958
|
}
|
|
696
959
|
function registerGen2(video) {
|
|
697
|
-
video.command("gen <prompt>").description("\u6587\u751F\u89C6\u9891/\u9996\u5E27\u56FE\u751F\u89C6\u9891\uFF1AMiniMax-H3 \u672C\u5730\uFF08\u9ED8\u8BA4 480p / 5s / 12 \u6B65\uFF0Cmp4 \u542B\u7ACB\u4F53\u58F0\u97F3\u8F68\uFF09").option("-o, --out <path>", "\u8F93\u51FA mp4 \u8DEF\u5F84", `lmedia-video-${Date.now()}.mp4`).option("-r, --res <preset>", `\u5206\u8FA8\u7387\u6863\uFF1A${RES_PRESETS.join("|")}\uFF08lmedia video list-res \u770B\u5B9E\u9645\u753B\u5E03\uFF09`, "480p").option("--seconds <s>", "\u7247\u6BB5\u65F6\u957F\u79D2\uFF081-15\uFF09", "5").option("--steps <n>",
|
|
960
|
+
video.command("gen <prompt>").description("\u6587\u751F\u89C6\u9891/\u9996\u5E27\u56FE\u751F\u89C6\u9891\uFF1AMiniMax-H3 \u672C\u5730\uFF08\u9ED8\u8BA4 480p / 5s / 12 \u6B65\uFF0Cmp4 \u542B\u7ACB\u4F53\u58F0\u97F3\u8F68\uFF1B\u751F\u4EA7\u914D\u65B9\u89C1 lmedia video recipes\uFF09").option("-o, --out <path>", "\u8F93\u51FA mp4 \u8DEF\u5F84", `lmedia-video-${Date.now()}.mp4`).option("-r, --res <preset>", `\u5206\u8FA8\u7387\u6863\uFF1A${RES_PRESETS.join("|")}\uFF08lmedia video list-res \u770B\u5B9E\u9645\u753B\u5E03\uFF09`, "480p").option("--seconds <s>", "\u7247\u6BB5\u65F6\u957F\u79D2\uFF081-15\uFF09", "5").option("--steps <n>", `\u53BB\u566A\u6B65\u6570\uFF08\u57FA\u5EA7 12 \u6B65\u224820 \u6B65\uFF1B--fast \u9ED8\u8BA4\u53D6 bundle meta \u7684\u63A8\u8350\u6B65\u6570\uFF0C\u7F3A\u5931\u6309 ${FAST_STEPS}\uFF0C4-8 \u4E3A\u84B8\u998F\u6709\u6548\u533A\u95F4\uFF09`).option("--seed <n>", "\u968F\u673A\u79CD\u5B50", "42").option("--first-frame <path>", "\u9996\u5E27\u56FE\uFF08\u56FE\u751F\u89C6\u9891\uFF1B\u63A8\u8350\u8854\u63A5 lmedia image gen \u4EA7\u7269\uFF09").option("--last-frame <path>", "\u5C3E\u5E27\u56FE\uFF08\u9996\u5C3E\u951A\u5B9A\u63D2\u503C\uFF09").option("--fast", `Lightning \u84B8\u998F\u52A0\u901F\u6863\uFF08dit-turbo bundle + ${FAST_STEPS} \u6B65 + shift ${FAST_SHIFT_VIDEO}\uFF1B\u9996\u6B21\u5148 lmedia video turbo-merge\uFF09\u3002\u98CE\u666F/\u9759\u7269/\u7ED8\u672C\u7528\uFF1B\u4EBA\u7269\u89C6\u9891\u614E\u7528\uFF08\u6CB9\u753B\u611F+\u6F02\u79FB\uFF0C\u89C1 lmedia video recipes\uFF09`, false).action(async (prompt, opts) => {
|
|
961
|
+
const rt = resolveVideoRuntime();
|
|
962
|
+
const turboPrefix = path.join(rt.weightsDir, TURBO_BUNDLE);
|
|
963
|
+
const turboMeta = (() => {
|
|
964
|
+
if (!opts.fast) return void 0;
|
|
965
|
+
try {
|
|
966
|
+
return JSON.parse(fs7.readFileSync(`${turboPrefix}.idx`, "utf8"))["__meta__"];
|
|
967
|
+
} catch {
|
|
968
|
+
return void 0;
|
|
969
|
+
}
|
|
970
|
+
})();
|
|
698
971
|
const res = opts.res;
|
|
699
972
|
const seconds = parseFloat(opts.seconds);
|
|
700
|
-
const
|
|
973
|
+
const fast = !!opts.fast;
|
|
974
|
+
const steps = opts.steps ? parseInt(opts.steps, 10) : fast ? Number(turboMeta?.turbo_nfe) || FAST_STEPS : 12;
|
|
701
975
|
const seed2 = parseInt(opts.seed, 10);
|
|
702
976
|
if (!RES_PRESETS.includes(res)) {
|
|
703
977
|
console.error(`--res \u4EC5\u652F\u6301 ${RES_PRESETS.join("|")}\uFF08\u5F53\u524D ${res}\uFF09`);
|
|
@@ -711,16 +985,35 @@ function registerGen2(video) {
|
|
|
711
985
|
console.error(`--steps \u9700\u4E3A 1-50 \u7684\u6574\u6570\uFF08\u5F53\u524D ${opts.steps}\uFF09`);
|
|
712
986
|
process.exit(2);
|
|
713
987
|
}
|
|
988
|
+
if (fast && steps > 8) {
|
|
989
|
+
console.error(`\u63D0\u793A: --fast \u84B8\u998F\u6863\u6B65\u6570 >8 \u65E0\u6536\u76CA\u4E14\u53EF\u80FD\u8FC7\u9510\u5316\uFF08\u84B8\u998F\u914D\u65B9\u63A8\u8350 ${FAST_STEPS}\uFF0C4-8 \u6709\u6548\uFF09`);
|
|
990
|
+
}
|
|
714
991
|
if (!Number.isInteger(seed2)) {
|
|
715
992
|
console.error(`--seed \u9700\u4E3A\u6574\u6570\uFF08\u5F53\u524D ${opts.seed}\uFF09`);
|
|
716
993
|
process.exit(2);
|
|
717
994
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
995
|
+
for (const [label, p] of [["\u9996\u5E27\u56FE", opts.firstFrame], ["\u5C3E\u5E27\u56FE", opts.lastFrame]]) {
|
|
996
|
+
if (p && !fs7.existsSync(p)) {
|
|
997
|
+
console.error(`${label}\u4E0D\u5B58\u5728: ${p}`);
|
|
998
|
+
process.exit(2);
|
|
999
|
+
}
|
|
721
1000
|
}
|
|
722
|
-
const
|
|
723
|
-
|
|
1001
|
+
const hints = qualityHints(prompt, {
|
|
1002
|
+
fast,
|
|
1003
|
+
seconds,
|
|
1004
|
+
res,
|
|
1005
|
+
firstFrame: opts.firstFrame,
|
|
1006
|
+
lastFrame: opts.lastFrame
|
|
1007
|
+
});
|
|
1008
|
+
const turboReady = fs7.existsSync(`${turboPrefix}.bin`);
|
|
1009
|
+
if (fast && !opts.steps && turboReady && !turboMeta?.turbo_nfe) {
|
|
1010
|
+
console.error(`\u63D0\u793A: \u52A0\u901F\u6863 bundle \u672A\u8BB0\u5F55\u63A8\u8350\u6B65\u6570\uFF08turbo_nfe\uFF09\uFF0C\u672C\u6B21\u6309\u515C\u5E95 ${FAST_STEPS} \u6B65\u8DD1\u2014\u2014\u82E5\u5408\u5E76\u7684\u662F 4 \u6B65\u7248 LoRA\uFF0C\u8BF7\u663E\u5F0F --steps 4\uFF08\u6216 turbo-merge --nfe 4 \u91CD\u5199 meta\uFF09`);
|
|
1011
|
+
hints.push(`turbo bundle \u7F3A turbo_nfe\uFF0C\u6B65\u6570\u6309\u515C\u5E95 ${FAST_STEPS}\uFF1B4 \u6B65\u7248 LoRA \u9700\u663E\u5F0F --steps 4`);
|
|
1012
|
+
}
|
|
1013
|
+
if (/[一-鿿]/.test(prompt) && prompt.length < 20) {
|
|
1014
|
+
console.error(`\u63D0\u793A: \u4E2D\u6587 prompt \u5EFA\u8BAE 30-50 \u5B57\uFF08\u5F53\u524D ${prompt.length} \u5B57\uFF09\uFF0C\u592A\u77ED\u6613\u88AB seed \u4E3B\u5BFC\uFF08\u6362 prompt \u753B\u9762\u4E0D\u53D8\uFF09`);
|
|
1015
|
+
}
|
|
1016
|
+
if (!fs7.existsSync(rt.mmh3turbo)) {
|
|
724
1017
|
console.error(`mmh3turbo \u672A\u5B89\u88C5\uFF08${rt.venvVideo}\uFF09\u3002\u5148\u8FD0\u884C: lmedia video setup`);
|
|
725
1018
|
process.exit(1);
|
|
726
1019
|
}
|
|
@@ -728,11 +1021,12 @@ function registerGen2(video) {
|
|
|
728
1021
|
console.error("\u672A\u627E\u5230 ffmpeg\uFF08mp4 \u5C01\u88C5\u5FC5\u9700\uFF09\uFF1Abrew install ffmpeg \u540E\u91CD\u8BD5");
|
|
729
1022
|
process.exit(1);
|
|
730
1023
|
}
|
|
731
|
-
if (
|
|
732
|
-
console.error(`\
|
|
1024
|
+
if (fast && !fs7.existsSync(`${turboPrefix}.bin`)) {
|
|
1025
|
+
console.error(`\u52A0\u901F\u6863 bundle \u672A\u5C31\u7EEA\uFF08${turboPrefix}.bin\uFF09\u3002\u5148\u8FD0\u884C: lmedia video turbo-merge`);
|
|
1026
|
+
process.exit(1);
|
|
733
1027
|
}
|
|
734
1028
|
const runDir = path.join(os.homedir(), ".lmedia", "video-runs", `${Date.now()}`);
|
|
735
|
-
|
|
1029
|
+
fs7.mkdirSync(runDir, { recursive: true });
|
|
736
1030
|
const args = [
|
|
737
1031
|
prompt,
|
|
738
1032
|
"-r",
|
|
@@ -747,40 +1041,54 @@ function registerGen2(video) {
|
|
|
747
1041
|
runDir
|
|
748
1042
|
];
|
|
749
1043
|
if (opts.firstFrame) args.push("--first-frame", path.resolve(opts.firstFrame));
|
|
750
|
-
|
|
1044
|
+
if (opts.lastFrame) args.push("--last-frame", path.resolve(opts.lastFrame));
|
|
1045
|
+
let engineCmd = rt.mmh3turbo;
|
|
1046
|
+
let engineArgs = args;
|
|
1047
|
+
if (fast) {
|
|
1048
|
+
engineCmd = rt.pythonVideo;
|
|
1049
|
+
engineArgs = [
|
|
1050
|
+
path.join(rt.pythonDir, "video_gen.py"),
|
|
1051
|
+
"--shift-video",
|
|
1052
|
+
String(FAST_SHIFT_VIDEO),
|
|
1053
|
+
"--weights",
|
|
1054
|
+
turboPrefix,
|
|
1055
|
+
...args
|
|
1056
|
+
];
|
|
1057
|
+
}
|
|
1058
|
+
console.error(`mmh3turbo${fast ? "(turbo)" : ""} \xB7 ${res} \xB7 ${seconds}s \xB7 ${steps} \u6B65${fast ? ` \xB7 shift ${FAST_SHIFT_VIDEO}` : ""} \xB7 seed ${seed2} \u2192 ${runDir}`);
|
|
751
1059
|
const t0 = Date.now();
|
|
752
1060
|
const env = { ...process.env };
|
|
753
|
-
const weightsReady =
|
|
1061
|
+
const weightsReady = fs7.existsSync(path.join(rt.weightsDir, "dit.bin")) || fast && fs7.existsSync(`${turboPrefix}.bin`);
|
|
754
1062
|
if (!weightsReady && env.HF_HUB_OFFLINE === "1") {
|
|
755
1063
|
console.error("\u63D0\u793A: \u68C0\u6D4B\u5230 HF_HUB_OFFLINE=1 \u4E14 H3 \u6743\u91CD\u672A\u5C31\u7EEA\uFF0C\u672C\u6B21\u4E34\u65F6\u5173\u95ED\u4EE5\u62C9\u53D6\u6743\u91CD\uFF08\u56FD\u5185\u63A8\u8350 lmedia video setup --mirror\uFF09");
|
|
756
1064
|
env.HF_HUB_OFFLINE = "0";
|
|
757
1065
|
}
|
|
758
|
-
const code = await new Promise((
|
|
759
|
-
const child = spawn(
|
|
1066
|
+
const code = await new Promise((resolve8) => {
|
|
1067
|
+
const child = spawn(engineCmd, engineArgs, { env });
|
|
760
1068
|
child.stdout.on("data", (d) => process.stderr.write(d));
|
|
761
1069
|
child.stderr.on("data", (d) => process.stderr.write(d));
|
|
762
1070
|
child.on("error", (e) => {
|
|
763
|
-
console.error(`\u542F\u52A8
|
|
1071
|
+
console.error(`\u542F\u52A8\u5F15\u64CE\u5931\u8D25: ${e.message}`);
|
|
764
1072
|
process.exit(1);
|
|
765
1073
|
});
|
|
766
|
-
child.on("close", (c) =>
|
|
1074
|
+
child.on("close", (c) => resolve8(c ?? -1));
|
|
767
1075
|
});
|
|
768
1076
|
if (code !== 0) {
|
|
769
|
-
console.error(
|
|
1077
|
+
console.error(`\u5F15\u64CE\u9000\u51FA\u7801 ${code}\uFF08\u8FD0\u884C\u76EE\u5F55\u4FDD\u7559\u4F9B\u6392\u67E5: ${runDir}\uFF1B\u5E38\u89C1\u539F\u56E0: \u78C1\u76D8\u7A7A\u95F4\u4E0D\u8DB3\u3001\u6743\u91CD\u4E0B\u8F7D\u4E2D\u65AD\u2014\u2014\u91CD\u8DD1\u5373\u53EF\u7EED\u4F20\uFF09`);
|
|
770
1078
|
process.exit(1);
|
|
771
1079
|
}
|
|
772
1080
|
const produced = path.join(runDir, "video.mp4");
|
|
773
|
-
if (!
|
|
1081
|
+
if (!fs7.existsSync(produced)) {
|
|
774
1082
|
console.error(`\u672A\u627E\u5230\u4EA7\u7269 ${produced}\uFF08\u8FD0\u884C\u76EE\u5F55\u5185\u5BB9\u4FDD\u7559\u4F9B\u6392\u67E5: ${runDir}\uFF09`);
|
|
775
1083
|
process.exit(1);
|
|
776
1084
|
}
|
|
777
1085
|
const out = path.resolve(opts.out);
|
|
778
|
-
|
|
1086
|
+
fs7.mkdirSync(path.dirname(out), { recursive: true });
|
|
779
1087
|
try {
|
|
780
|
-
|
|
1088
|
+
fs7.renameSync(produced, out);
|
|
781
1089
|
} catch {
|
|
782
|
-
|
|
783
|
-
|
|
1090
|
+
fs7.copyFileSync(produced, out);
|
|
1091
|
+
fs7.unlinkSync(produced);
|
|
784
1092
|
}
|
|
785
1093
|
console.log(
|
|
786
1094
|
JSON.stringify(
|
|
@@ -788,12 +1096,15 @@ function registerGen2(video) {
|
|
|
788
1096
|
out,
|
|
789
1097
|
seconds: Number(((Date.now() - t0) / 1e3).toFixed(1)),
|
|
790
1098
|
runDir,
|
|
791
|
-
engine: "mmh3turbo",
|
|
1099
|
+
engine: fast ? "mmh3turbo+turbo-lora" : "mmh3turbo",
|
|
792
1100
|
res,
|
|
793
1101
|
duration: seconds,
|
|
794
1102
|
steps,
|
|
795
1103
|
seed: seed2,
|
|
796
|
-
...
|
|
1104
|
+
...fast ? { fast: true, shiftVideo: FAST_SHIFT_VIDEO, ...turboMeta?.turbo_lora ? { lora: turboMeta.turbo_lora } : {} } : {},
|
|
1105
|
+
...hints.length ? { hints } : {},
|
|
1106
|
+
...opts.firstFrame ? { firstFrame: path.resolve(opts.firstFrame) } : {},
|
|
1107
|
+
...opts.lastFrame ? { lastFrame: path.resolve(opts.lastFrame) } : {}
|
|
797
1108
|
},
|
|
798
1109
|
null,
|
|
799
1110
|
2
|
|
@@ -801,10 +1112,99 @@ function registerGen2(video) {
|
|
|
801
1112
|
);
|
|
802
1113
|
});
|
|
803
1114
|
}
|
|
1115
|
+
function registerTurboMerge(video) {
|
|
1116
|
+
video.command("turbo-merge").description("\u628A few-step \u84B8\u998F LoRA \u5408\u5E76\u8FDB int8 DiT bundle\uFF08--fast \u52A0\u901F\u6863\u7684\u4E00\u6B21\u6027\u51C6\u5907\uFF0C~20GB \u8BFB\u5199\uFF09").option("--lora <path>", "\u84B8\u998F LoRA safetensors\uFF08lightx2v \u547D\u540D\uFF1B\u7F3A\u5931\u65F6\u81EA\u52A8\u4ECE\u955C\u50CF\u4E0B\u8F7D\uFF09").option("--bundle <prefix>", "\u6E90 int8 bundle \u524D\u7F00\uFF08dit.bin/.idx\uFF09").option("--out <prefix>", "\u8F93\u51FA bundle \u524D\u7F00").option("--scale <n>", "LoRA \u5F3A\u5EA6\uFF08\u84B8\u998F\u914D\u65B9\u56FA\u5B9A 1.0\uFF0C\u52FF\u8C03\uFF09", "1.0").option("--nfe <n>", "\u63A8\u8350\u53BB\u566A\u6B65\u6570\uFF08\u5199\u5165 bundle meta\uFF0C--fast \u9ED8\u8BA4\u6B65\u6570\uFF1B8 \u6B65\u7248=8\uFF09").option("--no-download", "LoRA \u7F3A\u5931\u65F6\u4E0D\u81EA\u52A8\u4E0B\u8F7D\uFF0C\u76F4\u63A5\u62A5\u9519").action((opts) => {
|
|
1117
|
+
const rt = resolveVideoRuntime();
|
|
1118
|
+
const bundle = opts.bundle ?? path.join(rt.weightsDir, "dit");
|
|
1119
|
+
const out = opts.out ?? path.join(rt.weightsDir, TURBO_BUNDLE);
|
|
1120
|
+
const loraDir = path.join(rt.weightsDir, "loras");
|
|
1121
|
+
const lora = opts.lora ?? path.join(loraDir, TURBO_LORA_FILE);
|
|
1122
|
+
if (!fs7.existsSync(`${bundle}.bin`) || !fs7.existsSync(`${bundle}.idx`)) {
|
|
1123
|
+
console.error(`\u6E90 int8 bundle \u4E0D\u5B58\u5728: ${bundle}.bin/.idx\u3002\u5148\u8FD0\u884C: lmedia video setup --mirror`);
|
|
1124
|
+
process.exit(1);
|
|
1125
|
+
}
|
|
1126
|
+
if (!fs7.existsSync(rt.pythonVideo)) {
|
|
1127
|
+
console.error(`\u89C6\u9891 venv \u4E0D\u5B58\u5728: ${rt.venvVideo}\u3002\u5148\u8FD0\u884C: lmedia video setup`);
|
|
1128
|
+
process.exit(1);
|
|
1129
|
+
}
|
|
1130
|
+
if (!fs7.existsSync(lora)) {
|
|
1131
|
+
if (!opts.download) {
|
|
1132
|
+
console.error(`LoRA \u4E0D\u5B58\u5728\u4E14\u5DF2\u7981\u7528\u81EA\u52A8\u4E0B\u8F7D: ${lora}`);
|
|
1133
|
+
process.exit(1);
|
|
1134
|
+
}
|
|
1135
|
+
fs7.mkdirSync(loraDir, { recursive: true });
|
|
1136
|
+
console.error(`\u4E0B\u8F7D\u84B8\u998F LoRA\uFF08~1.4GB\uFF09\u2192 ${lora}`);
|
|
1137
|
+
const r2 = spawnSync("curl", ["-L", "-C", "-", "--retry", "8", "--retry-delay", "5", "-o", `${lora}.part`, TURBO_LORA_URL], { stdio: "inherit" });
|
|
1138
|
+
if (r2.status !== 0 || !fs7.existsSync(`${lora}.part`) || fs7.statSync(`${lora}.part`).size < 1e8) {
|
|
1139
|
+
console.error(`LoRA \u4E0B\u8F7D\u5931\u8D25\uFF08\u53EF\u624B\u52A8\u4E0B\u8F7D\u540E\u653E\u81F3 ${lora}\uFF09: ${TURBO_LORA_URL}`);
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
}
|
|
1142
|
+
fs7.renameSync(`${lora}.part`, lora);
|
|
1143
|
+
}
|
|
1144
|
+
console.error(`\u5408\u5E76 ${path.basename(lora)} \u2192 ${out}.bin/.idx\uFF08fp32 \u7CBE\u5EA6\u52A0 delta \u540E\u6309\u884C\u91CD\u91CF\u5316\uFF09`);
|
|
1145
|
+
const mergeArgs = [
|
|
1146
|
+
path.join(rt.pythonDir, "turbo_merge.py"),
|
|
1147
|
+
"--lora",
|
|
1148
|
+
lora,
|
|
1149
|
+
"--bundle",
|
|
1150
|
+
bundle,
|
|
1151
|
+
"--out",
|
|
1152
|
+
out,
|
|
1153
|
+
"--scale",
|
|
1154
|
+
opts.scale
|
|
1155
|
+
];
|
|
1156
|
+
if (opts.nfe) mergeArgs.push("--nfe", opts.nfe);
|
|
1157
|
+
const r = spawnSync(rt.pythonVideo, mergeArgs, { stdio: "inherit" });
|
|
1158
|
+
if (r.status !== 0) {
|
|
1159
|
+
console.error(`turbo_merge \u9000\u51FA\u7801 ${r.status}`);
|
|
1160
|
+
process.exit(r.status ?? 1);
|
|
1161
|
+
}
|
|
1162
|
+
console.log(`\u5C31\u7EEA: lmedia video gen --fast ... \uFF08bundle: ${out}\uFF09`);
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
function registerRecipes(video) {
|
|
1166
|
+
video.command("recipes").description("\u6253\u5370\u5B9E\u6D4B\u9A8C\u6536\u8FC7\u7684\u751F\u4EA7\u914D\u65B9\uFF08\u4EBA\u7269\u5FAE\u52A8/\u98CE\u666F\u7ED8\u672C/\u53CC\u951A\u5B9A\u4E00\u81F4\u6027\uFF09\uFF0C\u590D\u5236\u5373\u7528").action(() => {
|
|
1167
|
+
console.log(`lmedia video \u751F\u4EA7\u914D\u65B9\uFF08M4 Max \u5B9E\u6D4B\u9A8C\u6536\uFF0C2026-09\uFF09
|
|
1168
|
+
|
|
1169
|
+
\u2460 \u4EBA\u7269\u5FAE\u52A8\uFF08\u8D28\u91CF\u4F18\u5148\u2014\u2014\u4EBA\u7269\u89C6\u9891\u7684\u9ED8\u8BA4\u9009\u62E9\uFF09
|
|
1170
|
+
\u8981\u70B9: \u57FA\u5EA7 12 \u6B65\uFF08\u4E0D\u7528 --fast\uFF0C\u84B8\u998F\u6863\u4EBA\u8138\u4F1A\u6CB9\u753B\u5316\uFF09+ \u4E3B\u4F53\u7279\u5199\u6784\u56FE
|
|
1171
|
+
\uFF08\u5148\u88C1\u526A\uFF1A\u8BA9\u4EBA\u8138\u5360\u753B\u9762 1/4 \u4EE5\u4E0A\uFF0C\u8138\u8D8A\u5927\u50CF\u7D20\u8D8A\u8DB3\uFF09+ \u52A8\u4F5C\u6536\u655B\u7684 prompt
|
|
1172
|
+
+ \u65F6\u957F \u22645s\uFF08\u6F02\u79FB\u968F\u65F6\u957F\u7D2F\u79EF\uFF09
|
|
1173
|
+
|
|
1174
|
+
# \u4ECE\u5927\u56FE\u88C1\u4EBA\u7269\u7279\u5199\uFF08\u793A\u4F8B\uFF1A\u4ECE 1808\xB2 \u6E90\u56FE\u53D6\u4E3B\u4F53\u65B9\u6846\uFF09
|
|
1175
|
+
ffmpeg -i photo.jpg -vf "crop=1150:1150:X:Y" closeup.jpg
|
|
1176
|
+
lmedia video gen "\u4EBA\u7269+\u8F7B\u5FAE\u52A8\u4F5C\u63CF\u8FF0\uFF0C\u52A8\u4F5C\u8F7B\u67D4\u3002Audio: ambience, no talking, no voices" \\
|
|
1177
|
+
-r square --seconds 4 --seed 11 --first-frame closeup.jpg -o clip.mp4
|
|
1178
|
+
# ~23min/\u6761\uFF08M4 Max square 768\xB2 4s\uFF09
|
|
1179
|
+
|
|
1180
|
+
\u2461 \u98CE\u666F/\u9759\u7269/\u7ED8\u672C\u5FAE\u52A8\uFF08\u5FEB\uFF09
|
|
1181
|
+
\u8981\u70B9: --fast \u84B8\u998F\u6863\uFF088 \u6B65 shift 6\uFF0C\u9996\u6B21\u5148 lmedia video turbo-merge\uFF09\uFF1B
|
|
1182
|
+
\u84B8\u998F 768p \u7CFB\u5206\u8FA8\u7387\u7EAA\u5F8B\uFF1A\u77ED\u8FB9 \u2265700\uFF0C352p/480p \u4EC5\u94FE\u8DEF\u9A8C\u8BC1
|
|
1183
|
+
|
|
1184
|
+
lmedia video gen "\u6C34\u5F69\u7ED8\u672C\u98CE\u683C\uFF1A\u5C0F\u871C\u8702\u7537\u5B69\u5728\u82B1\u56ED\u91CC\u6325\u624B\uFF0C\u82B1\u74E3\u98D8\u843D\uFF0C\u7EC6\u8282\u4E30\u5BCC" \\
|
|
1185
|
+
--fast -r square --seconds 4 --first-frame page.png -o clip.mp4
|
|
1186
|
+
# ~17min/\u6761\uFF1B\u4EBA\u7269\u614E\u7528\uFF08\u6CB9\u753B\u611F+\u6F02\u79FB\uFF09
|
|
1187
|
+
|
|
1188
|
+
\u2462 \u4EBA\u7269\u4E00\u81F4\u6027\uFF1A\u53CC\u951A\u5B9A\uFF08\u9996\u5C3E\u540C\u56FE\u56DE\u73AF\uFF09
|
|
1189
|
+
\u8981\u70B9: \u56FE\u751F\u89C6\u9891\u53EA\u951A\u9996\u5E27\u65F6\u7ED3\u5C3E\u8EAB\u4EFD\u5FC5\u6F02\uFF08\u5B9E\u6D4B\u4EBA\u8138\u76F8\u4F3C\u5EA6\u8DCC\u5230 0.3\uFF09\uFF1B
|
|
1190
|
+
\u540C\u4E00\u5F20\u56FE\u540C\u65F6\u5582 --first-frame \u548C --last-frame\uFF0C\u52A8\u4F5C\u300C\u8361\u51FA\u53BB\u518D\u8361\u56DE\u6765\u300D\uFF0C
|
|
1191
|
+
ArcFace \u5B9E\u6D4B\u7ED3\u5C3E\u76F8\u4F3C\u5EA6\u62C9\u56DE 0.97\u3002\u8D70\u8DEF\u4F4D\u79FB\u7C7B\u6784\u56FE\u4E0D\u9002\u7528\uFF08\u4F1A\u903C\u51FA\u56DE\u9000\u52A8\u4F5C\uFF09
|
|
1192
|
+
|
|
1193
|
+
lmedia video gen "\u4EBA\u7269+\u8F7B\u5FAE\u52A8\u4F5C\u63CF\u8FF0\uFF0C\u52A8\u4F5C\u8F7B\u67D4\u51E0\u4E4E\u4FDD\u6301\u539F\u59FF\u3002Audio: ..." \\
|
|
1194
|
+
-r square --seconds 4 --seed 11 \\
|
|
1195
|
+
--first-frame closeup.jpg --last-frame closeup.jpg -o clip.mp4
|
|
1196
|
+
|
|
1197
|
+
\u901A\u7528\u7EAA\u5F8B\uFF1A
|
|
1198
|
+
- \u6587\u751F\u89C6\u9891\u4E2D\u6587 prompt 30-50 \u5B57\uFF0C\u5199\u6E05\u52A8\u4F5C\u4E0E\u955C\u5934\uFF0CAudio: \u6BB5\u5199\u73AF\u5883\u97F3\u5E76\u52A0 no talking
|
|
1199
|
+
- \u57FA\u5EA7\u9ED8\u8BA4 12 \u6B65\u5373 20 \u6B65\u89C2\u611F\uFF1B\u4E0D\u8981\u4F4E\u4E8E 8 \u6B65\uFF08\u6389\u8D28\u660E\u663E\uFF09
|
|
1200
|
+
- \u9996\u5E27\u7167\u7247\u4F18\u9009\uFF1A\u6B63\u8138\u3001\u6E05\u6670\u3001\u4EBA\u8138\u5360\u6BD4\u5927\u2014\u2014\u9996\u5E27\u5C31\u662F\u8EAB\u4EFD\u7684\u57FA\u56E0
|
|
1201
|
+
- \u751F\u6210\u7ED3\u679C\u673A\u8BFB hints \u5B57\u6BB5\u4F1A\u5E26\u573A\u666F\u8D28\u91CF\u5EFA\u8BAE\uFF1B\u73AF\u5883\u81EA\u68C0 lmedia doctor`);
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
804
1204
|
function registerListRes(video) {
|
|
805
1205
|
video.command("list-res").description("\u5217\u51FA mmh3turbo \u652F\u6301\u7684\u5206\u8FA8\u7387\u6863\u4F4D\uFF08\u900F\u4F20\uFF09").action(() => {
|
|
806
1206
|
const rt = resolveVideoRuntime();
|
|
807
|
-
if (!
|
|
1207
|
+
if (!fs7.existsSync(rt.mmh3turbo)) {
|
|
808
1208
|
console.error("mmh3turbo \u672A\u5B89\u88C5\u3002\u5148\u8FD0\u884C: lmedia video setup");
|
|
809
1209
|
process.exit(1);
|
|
810
1210
|
}
|
|
@@ -817,16 +1217,18 @@ function registerListRes(video) {
|
|
|
817
1217
|
});
|
|
818
1218
|
}
|
|
819
1219
|
function registerVideo(program2) {
|
|
820
|
-
const video = program2.command("video").description("\u89C6\u9891\u751F\u4EA7\u80FD\u529B\uFF08\u672C\u5730 MiniMax-H3 / mmh3turbo\uFF0C\u96F6 API \u6210\u672C\uFF09");
|
|
1220
|
+
const video = program2.command("video").description("\u89C6\u9891\u751F\u4EA7\u80FD\u529B\uFF08\u672C\u5730 MiniMax-H3 / mmh3turbo\uFF0C\u96F6 API \u6210\u672C\uFF1B--fast Lightning \u84B8\u998F\u52A0\u901F\u6863\uFF1B\u751F\u4EA7\u914D\u65B9 lmedia video recipes\uFF09");
|
|
821
1221
|
registerSetup(video);
|
|
822
1222
|
registerGen2(video);
|
|
1223
|
+
registerTurboMerge(video);
|
|
1224
|
+
registerRecipes(video);
|
|
823
1225
|
registerListRes(video);
|
|
824
1226
|
}
|
|
825
1227
|
function registerLora(program2) {
|
|
826
1228
|
const lora = program2.command("lora").description("LoRA \u6CE8\u518C\u8868\u7BA1\u7406");
|
|
827
1229
|
lora.command("list").description("\u5217\u51FA\u5DF2\u6CE8\u518C LoRA").action(() => {
|
|
828
1230
|
for (const l of loadRegistry()) {
|
|
829
|
-
const exists =
|
|
1231
|
+
const exists = fs7.existsSync(l.path) ? "\u2713" : "\u2717 \u6587\u4EF6\u7F3A\u5931";
|
|
830
1232
|
console.log(
|
|
831
1233
|
`${l.name} [${l.kind}] weight=${l.defaultWeight} trigger="${l.trigger}" ${exists}
|
|
832
1234
|
${l.path}${l.note ? `
|
|
@@ -848,15 +1250,6 @@ function registerLora(program2) {
|
|
|
848
1250
|
}
|
|
849
1251
|
);
|
|
850
1252
|
}
|
|
851
|
-
var ESRGAN_CACHE_PATH = path.join(os.homedir(), ".cache", "lmedia", "RealESRGAN_x2.pth");
|
|
852
|
-
var LEGACY_TMP_PATH = "/tmp/RealESRGAN_x2.pth";
|
|
853
|
-
function resolveEsrganPath() {
|
|
854
|
-
const env = process.env.LMEDIA_REALESRGAN_PATH;
|
|
855
|
-
if (env && fs5.existsSync(env)) return env;
|
|
856
|
-
if (fs5.existsSync(ESRGAN_CACHE_PATH)) return ESRGAN_CACHE_PATH;
|
|
857
|
-
if (fs5.existsSync(LEGACY_TMP_PATH)) return LEGACY_TMP_PATH;
|
|
858
|
-
return process.env.LMEDIA_REALESRGAN_PATH ?? ESRGAN_CACHE_PATH;
|
|
859
|
-
}
|
|
860
1253
|
var ManifestCorruptError = class extends Error {
|
|
861
1254
|
};
|
|
862
1255
|
var InvalidArgsError = class extends Error {
|
|
@@ -881,8 +1274,8 @@ function emptyManifest() {
|
|
|
881
1274
|
}
|
|
882
1275
|
function readManifest(root) {
|
|
883
1276
|
const p = manifestPath(root);
|
|
884
|
-
if (!
|
|
885
|
-
const raw =
|
|
1277
|
+
if (!fs7.existsSync(p)) return emptyManifest();
|
|
1278
|
+
const raw = fs7.readFileSync(p, "utf-8");
|
|
886
1279
|
let parsed;
|
|
887
1280
|
try {
|
|
888
1281
|
parsed = JSON.parse(raw);
|
|
@@ -902,17 +1295,17 @@ function readManifest(root) {
|
|
|
902
1295
|
return { version: 1, generatedAt: m.generatedAt ?? "", items: m.items };
|
|
903
1296
|
}
|
|
904
1297
|
function writeManifest(root, manifest) {
|
|
905
|
-
|
|
1298
|
+
fs7.mkdirSync(root, { recursive: true });
|
|
906
1299
|
const p = manifestPath(root);
|
|
907
1300
|
const tmp = `${p}.${process.pid}.tmp`;
|
|
908
|
-
|
|
1301
|
+
fs7.writeFileSync(tmp, `${JSON.stringify(manifest, null, 1)}
|
|
909
1302
|
`, "utf-8");
|
|
910
|
-
|
|
1303
|
+
fs7.renameSync(tmp, p);
|
|
911
1304
|
}
|
|
912
1305
|
function initLibrary(root) {
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const created = !
|
|
1306
|
+
fs7.mkdirSync(path.join(root, "sfx"), { recursive: true });
|
|
1307
|
+
fs7.mkdirSync(path.join(root, "ambient"), { recursive: true });
|
|
1308
|
+
const created = !fs7.existsSync(manifestPath(root));
|
|
916
1309
|
if (created) writeManifest(root, emptyManifest());
|
|
917
1310
|
return { root: path.resolve(root), manifest: manifestPath(root), created };
|
|
918
1311
|
}
|
|
@@ -922,7 +1315,7 @@ function listEntries(root, filter = {}) {
|
|
|
922
1315
|
);
|
|
923
1316
|
}
|
|
924
1317
|
function sha256File(file) {
|
|
925
|
-
return createHash("sha256").update(
|
|
1318
|
+
return createHash("sha256").update(fs7.readFileSync(file)).digest("hex");
|
|
926
1319
|
}
|
|
927
1320
|
function assertFfmpeg() {
|
|
928
1321
|
for (const cmd of ["ffmpeg", "ffprobe"]) {
|
|
@@ -971,7 +1364,7 @@ function addEntry(root, opts) {
|
|
|
971
1364
|
if (!KEY_RE.test(opts.key)) throw new InvalidArgsError(`key \u4EC5\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD/\u6570\u5B57/\u77ED\u6A2A\u7EBF: ${opts.key}`);
|
|
972
1365
|
const type = opts.type ?? "sfx";
|
|
973
1366
|
if (type !== "sfx" && type !== "ambient") throw new InvalidArgsError(`type \u4EC5\u652F\u6301 sfx|ambient: ${type}`);
|
|
974
|
-
if (!
|
|
1367
|
+
if (!fs7.existsSync(opts.file)) throw new InvalidArgsError(`\u8F93\u5165\u97F3\u9891\u4E0D\u5B58\u5728: ${opts.file}`);
|
|
975
1368
|
const hash = sha256File(opts.file);
|
|
976
1369
|
const dup = readManifest(root).items.find((i) => i.content_hash === hash);
|
|
977
1370
|
if (dup) return { item: dup, duplicated: true };
|
|
@@ -983,7 +1376,7 @@ function addEntry(root, opts) {
|
|
|
983
1376
|
try {
|
|
984
1377
|
probe = probeAudio(dst);
|
|
985
1378
|
} catch (e) {
|
|
986
|
-
|
|
1379
|
+
fs7.rmSync(dst, { force: true });
|
|
987
1380
|
throw e;
|
|
988
1381
|
}
|
|
989
1382
|
const item = {
|
|
@@ -1020,7 +1413,7 @@ function verifyLibrary(root) {
|
|
|
1020
1413
|
const problems = [];
|
|
1021
1414
|
for (const item of items) {
|
|
1022
1415
|
const issues = [];
|
|
1023
|
-
if (!
|
|
1416
|
+
if (!fs7.existsSync(item.path)) {
|
|
1024
1417
|
issues.push(`\u6587\u4EF6\u7F3A\u5931: ${item.path}`);
|
|
1025
1418
|
} else {
|
|
1026
1419
|
const dur = probeAudioDurationSync(item.path);
|
|
@@ -1077,7 +1470,7 @@ function assertMutuallyExclusive(a, b, nameA, nameB) {
|
|
|
1077
1470
|
}
|
|
1078
1471
|
}
|
|
1079
1472
|
function assertFilesExist(files, flagName = "\u8F93\u5165\u6587\u4EF6") {
|
|
1080
|
-
const missing = files.filter((f) => !
|
|
1473
|
+
const missing = files.filter((f) => !fs7.existsSync(f));
|
|
1081
1474
|
if (missing.length) {
|
|
1082
1475
|
throw new InvalidArgsError(`${flagName}\u4E0D\u5B58\u5728: ${missing.join(", ")}`);
|
|
1083
1476
|
}
|
|
@@ -1154,7 +1547,7 @@ function fail(e) {
|
|
|
1154
1547
|
}
|
|
1155
1548
|
function requireAudioRuntime() {
|
|
1156
1549
|
const rt = resolveAudioRuntime();
|
|
1157
|
-
if (!
|
|
1550
|
+
if (!fs7.existsSync(rt.pythonAudio)) {
|
|
1158
1551
|
throw new SfxEnvError(
|
|
1159
1552
|
`\u97F3\u6548 venv \u672A\u627E\u5230: ${rt.pythonAudio}
|
|
1160
1553
|
\u4FEE\u590D: lmedia sfx setup\uFF08\u6216 cd ${rt.root} && uv venv .venv-audio --python 3.11 && uv pip install -p .venv-audio/bin/python torch torchaudio "transformers<5" einops soundfile\uFF09`
|
|
@@ -1184,7 +1577,7 @@ function registerGen3(sfx) {
|
|
|
1184
1577
|
const rt = requireAudioRuntime();
|
|
1185
1578
|
requireFfmpeg();
|
|
1186
1579
|
const out = path.resolve(opts.out ?? "sfx.wav");
|
|
1187
|
-
|
|
1580
|
+
fs7.mkdirSync(path.dirname(out), { recursive: true });
|
|
1188
1581
|
console.error(`\u{1F3AC} sfx gen\uFF08${rolls} \u63B7 + \u8D28\u91CF\u95E8 + \u526A\u88C1\uFF09\u2026`);
|
|
1189
1582
|
const res = await runSfxOp(rt, {
|
|
1190
1583
|
prompt,
|
|
@@ -1206,11 +1599,11 @@ function registerBatch(sfx) {
|
|
|
1206
1599
|
sfx.command("batch").description("\u6E05\u5355\u9A71\u52A8\u6279\u91CF\u751F\u6210\uFF1A\u9010 key \u591A\u63B7 + \u8D28\u91CF\u95E8\u9009\u4F18 \u2192 <dir>/<key>.best.wav + report.json").requiredOption("-m, --manifest <file>", "\u6279\u6E05\u5355 JSON \u8DEF\u5F84\uFF08- \u8BFB stdin\uFF09").requiredOption("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55").option("--rolls <n>", "\u6BCF key \u57FA\u7840\u751F\u6210\u6B21\u6570\uFF081-10\uFF0C\u5168\u5E9F\u81EA\u52A8\u52A0\u63B7\u22642\uFF09", "3").option("--keep-rolls", "\u4FDD\u7559\u5168\u90E8\u63B7\u6B21\u5230\u8F93\u51FA\u76EE\u5F55\uFF08<key>.r<N>.wav\uFF09").option("--full", "\u8DF3\u8FC7\u526A\u88C1\uFF0C\u4FDD\u7559 10s \u539F\u59CB\u751F\u6210").option("--keys <list>", "\u53EA\u8DD1\u5B50\u96C6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u5982 bear,frog\uFF09").action(async (opts) => {
|
|
1207
1600
|
try {
|
|
1208
1601
|
const rolls = parseRolls(opts.rolls);
|
|
1209
|
-
const text = opts.manifest === "-" ?
|
|
1210
|
-
if (!
|
|
1602
|
+
const text = opts.manifest === "-" ? fs7.readFileSync(0, "utf-8") : (() => {
|
|
1603
|
+
if (!fs7.existsSync(opts.manifest)) {
|
|
1211
1604
|
throw new InvalidArgsError(`\u6279\u6E05\u5355\u4E0D\u5B58\u5728: ${opts.manifest}`);
|
|
1212
1605
|
}
|
|
1213
|
-
return
|
|
1606
|
+
return fs7.readFileSync(opts.manifest, "utf-8");
|
|
1214
1607
|
})();
|
|
1215
1608
|
const items = parseBatchManifest(
|
|
1216
1609
|
text,
|
|
@@ -1219,7 +1612,7 @@ function registerBatch(sfx) {
|
|
|
1219
1612
|
const rt = requireAudioRuntime();
|
|
1220
1613
|
requireFfmpeg();
|
|
1221
1614
|
const outDir = path.resolve(opts.out);
|
|
1222
|
-
|
|
1615
|
+
fs7.mkdirSync(outDir, { recursive: true });
|
|
1223
1616
|
console.error(`\u{1F3AC} sfx batch\uFF08${items.length} key \xD7 ${rolls} \u63B7\uFF09\u2192 ${outDir}`);
|
|
1224
1617
|
const res = await runSfxOp(rt, {
|
|
1225
1618
|
op: "batch",
|
|
@@ -1349,13 +1742,13 @@ function registerAccept(sfx) {
|
|
|
1349
1742
|
function registerAb(sfx) {
|
|
1350
1743
|
sfx.command("ab").description("\u751F\u6210 A/B \u8BD5\u542C\u9875\uFF08\u72EC\u7ACB html\uFF0C\u5019\u9009\u97F3\u9891\u62F7\u8D1D\u5230 ab_files/ \u76F8\u5BF9\u8DEF\u5F84\u5F15\u7528\uFF09").requiredOption("-m, --groups <file>", 'groups JSON\uFF1A[{"name":"...","candidates":["a.wav","b.wav"]}]').requiredOption("-o, --out <file>", "\u8F93\u51FA html \u8DEF\u5F84").action(async (opts) => {
|
|
1351
1744
|
try {
|
|
1352
|
-
if (!
|
|
1353
|
-
const groups = parseAbGroups(
|
|
1745
|
+
if (!fs7.existsSync(opts.groups)) throw new InvalidArgsError(`groups \u6587\u4EF6\u4E0D\u5B58\u5728: ${opts.groups}`);
|
|
1746
|
+
const groups = parseAbGroups(fs7.readFileSync(opts.groups, "utf-8"));
|
|
1354
1747
|
const candidates = groups.flatMap((g) => g.candidates);
|
|
1355
1748
|
assertFilesExist(candidates, "\u5019\u9009\u97F3\u9891");
|
|
1356
1749
|
const rt = requireAudioRuntime();
|
|
1357
1750
|
const out = path.resolve(opts.out);
|
|
1358
|
-
|
|
1751
|
+
fs7.mkdirSync(path.dirname(out), { recursive: true });
|
|
1359
1752
|
const res = await runSfxOp(rt, { op: "abpage", groups, out });
|
|
1360
1753
|
console.error(`\xB7 \u5019\u9009\u97F3\u9891\u5DF2\u62F7\u8D1D\u5230 ${path.join(path.dirname(out), "ab_files")}`);
|
|
1361
1754
|
console.log(`\u2713 ${res.out}\uFF08${res.groups} \u7EC4 / ${res.candidates} \u5019\u9009\uFF09`);
|
|
@@ -1372,7 +1765,7 @@ function registerSetup2(sfx) {
|
|
|
1372
1765
|
throw new SfxEnvError("\u7F3A\u5C11 uv\uFF08curl -LsSf https://astral.sh/uv/install.sh | sh\uFF09\uFF0Csfx setup \u4F9D\u8D56");
|
|
1373
1766
|
}
|
|
1374
1767
|
const deps = ["torch", "torchaudio", "transformers<5", "einops", "soundfile"];
|
|
1375
|
-
if (
|
|
1768
|
+
if (fs7.existsSync(rt.pythonAudio)) {
|
|
1376
1769
|
const ok = spawnSync(rt.pythonAudio, ["-c", "import torch, torchaudio, transformers, einops, soundfile"], { stdio: "ignore" }).status === 0;
|
|
1377
1770
|
if (ok) {
|
|
1378
1771
|
console.error(`\u2713 \u97F3\u6548 venv \u5DF2\u5C31\u7EEA: ${rt.pythonAudio}`);
|
|
@@ -1400,7 +1793,7 @@ function registerSfxDoctor(sfx) {
|
|
|
1400
1793
|
const checks = [
|
|
1401
1794
|
[
|
|
1402
1795
|
"\u97F3\u6548 venv",
|
|
1403
|
-
|
|
1796
|
+
fs7.existsSync(rt.pythonAudio),
|
|
1404
1797
|
`\u4FEE\u590D: lmedia sfx setup\uFF08\u5EFA ${rt.root}/.venv-audio\uFF09`
|
|
1405
1798
|
],
|
|
1406
1799
|
[
|
|
@@ -1529,7 +1922,7 @@ function registerSfx(program2) {
|
|
|
1529
1922
|
function sfxDoctorChecks() {
|
|
1530
1923
|
const rt = resolveAudioRuntime();
|
|
1531
1924
|
return [
|
|
1532
|
-
[`[sfx] .venv-audio\uFF08Dasheng-AudioGen\uFF09`,
|
|
1925
|
+
[`[sfx] .venv-audio\uFF08Dasheng-AudioGen\uFF09`, fs7.existsSync(rt.pythonAudio)],
|
|
1533
1926
|
[`[sfx] \u4E3B\u6A21\u578B\u7F13\u5B58 ${MODEL}`, !!hfSnapshot(MODEL)],
|
|
1534
1927
|
[`[sfx] \u5206\u8BCD\u5668\u7F13\u5B58 ${TOKENIZER}`, !!hfSnapshot(TOKENIZER)]
|
|
1535
1928
|
];
|
|
@@ -1537,37 +1930,43 @@ function sfxDoctorChecks() {
|
|
|
1537
1930
|
|
|
1538
1931
|
// src/commands/doctor.ts
|
|
1539
1932
|
function registerDoctor(program2) {
|
|
1540
|
-
program2.command("doctor").description("\u73AF\u5883\u81EA\u68C0\uFF1A\u8FD0\u884C\u65F6/\u6A21\u578B\u5FEB\u7167/LoRA \u6587\u4EF6\u5B8C\u6574\u6027").action(async () => {
|
|
1933
|
+
program2.command("doctor").description("\u73AF\u5883\u81EA\u68C0\uFF1A\u8FD0\u884C\u65F6/\u6A21\u578B\u5FEB\u7167/LoRA \u6587\u4EF6\u5B8C\u6574\u6027\uFF08\u80FD\u529B\u5F00\u5173\u89C1 lmedia image status\uFF09").action(async () => {
|
|
1541
1934
|
const vrt = resolveVideoRuntime();
|
|
1542
1935
|
const videoChecks = [
|
|
1543
|
-
["[video] .venv-video\uFF08mmh3turbo\uFF09",
|
|
1936
|
+
["[video] .venv-video\uFF08mmh3turbo\uFF09", fs7.existsSync(vrt.mmh3turbo)],
|
|
1544
1937
|
["[video] ffmpeg", hasCommand("ffmpeg")],
|
|
1545
|
-
["[video] H3 \u6743\u91CD\uFF08~/.cache/mmh3turbo\uFF0C\u672A\u5C31\u7EEA\u65F6\u9996\u6B21\u751F\u6210\u81EA\u52A8\u4E0B\u8F7D\u6216 setup --mirror\uFF09",
|
|
1938
|
+
["[video] H3 \u6743\u91CD\uFF08~/.cache/mmh3turbo\uFF0C\u672A\u5C31\u7EEA\u65F6\u9996\u6B21\u751F\u6210\u81EA\u52A8\u4E0B\u8F7D\u6216 setup --mirror\uFF09", fs7.existsSync(path.join(vrt.weightsDir, "dit.bin"))],
|
|
1939
|
+
["[video] \u52A0\u901F\u6863 bundle dit-turbo\uFF08--fast \u7528\uFF1B\u7F3A\u5931\u8DD1 lmedia video turbo-merge\uFF09", fs7.existsSync(path.join(vrt.weightsDir, "dit-turbo.bin"))]
|
|
1546
1940
|
];
|
|
1941
|
+
const gate = readImageGate();
|
|
1547
1942
|
let imageOk = true;
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
[
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1943
|
+
if (gate.state === "corrupt") {
|
|
1944
|
+
console.log(`\u2717 [image] \u80FD\u529B\u72B6\u6001\u6587\u4EF6\u635F\u574F\uFF08\u65E0\u6CD5\u89E3\u6790\uFF09\u2192 ${gate.file}`);
|
|
1945
|
+
console.log(` \u539F\u56E0: ${gate.error}`);
|
|
1946
|
+
console.log(` \u4FEE\u590D: lmedia image enable\uFF08\u7528\u542F\u7528\u72B6\u6001\u8986\u76D6\u91CD\u5199\uFF09\uFF0C\u6216 rm ${gate.file}\uFF08\u5220\u9664\u540E\u56DE\u5230\u9ED8\u8BA4\u542F\u7528\uFF09`);
|
|
1947
|
+
imageOk = false;
|
|
1948
|
+
} else if (gate.state === "disabled") {
|
|
1949
|
+
console.log(
|
|
1950
|
+
`\xB7 [image] \u56FE\u50CF\u80FD\u529B\u5DF2\u7981\u7528\uFF08${gate.file}${gate.disabledAt ? `\uFF0C${gate.disabledAt}` : ""}${gate.reason ? `\uFF0C\u539F\u56E0: ${gate.reason}` : ""}\uFF09`
|
|
1951
|
+
);
|
|
1952
|
+
console.log(`\xB7 runtime/venv/\u6A21\u578B\u5FEB\u7167/LoRA \u68C0\u67E5\u5DF2\u8DF3\u8FC7\uFF1B\u6062\u590D: lmedia image enable`);
|
|
1953
|
+
} else {
|
|
1954
|
+
const items = imagePreflight();
|
|
1955
|
+
for (const it of items) console.log(`${it.ok ? "\u2713" : "\u2717"} ${it.name}${it.ok ? "" : ` \u2192 ${it.fix}`}`);
|
|
1559
1956
|
for (const l of loadRegistry()) {
|
|
1560
|
-
console.log(`${
|
|
1957
|
+
console.log(`${fs7.existsSync(l.path) ? "\u2713" : "\u2717"} LoRA: ${l.name}`);
|
|
1561
1958
|
}
|
|
1562
|
-
imageOk =
|
|
1563
|
-
} catch (e) {
|
|
1564
|
-
imageOk = false;
|
|
1565
|
-
console.error(e.message);
|
|
1959
|
+
imageOk = items.every((i) => i.ok);
|
|
1566
1960
|
}
|
|
1567
1961
|
for (const m of ["gen", "edit"]) {
|
|
1568
1962
|
const st = await pingDaemon(m);
|
|
1963
|
+
if (st && gate.state === "disabled") {
|
|
1964
|
+
console.log(`\u2717 [image] daemon ${m} \u4ECD\u5728\u8FD0\u884C\uFF08pid ${st.pid}\uFF09\u2014\u2014\u56FE\u50CF\u80FD\u529B\u5DF2\u7981\u7528\uFF1B\u505C\u6B62: lmedia image serve stop --mode ${m}`);
|
|
1965
|
+
imageOk = false;
|
|
1966
|
+
continue;
|
|
1967
|
+
}
|
|
1569
1968
|
console.log(
|
|
1570
|
-
`${st ? "\u2713" : "\xB7"} [image] daemon ${m}${st ? `: ${st.state}\uFF08pid ${st.pid}\uFF0Cjobs ${st.jobs}\uFF09` : ": \u672A\u8FD0\u884C\uFF08\u53EF\u9009\uFF0Clmedia image serve start --mode " + m + "\uFF09"}`
|
|
1969
|
+
`${st ? "\u2713" : "\xB7"} [image] daemon ${m}${st ? `: ${st.state}\uFF08pid ${st.pid}\uFF0Cjobs ${st.jobs}\uFF09` : gate.state === "disabled" ? ": \u672A\u8FD0\u884C\uFF08\u80FD\u529B\u5DF2\u7981\u7528\uFF0Cstart \u88AB\u62E6\u622A\uFF09" : ": \u672A\u8FD0\u884C\uFF08\u53EF\u9009\uFF0Clmedia image serve start --mode " + m + "\uFF09"}`
|
|
1571
1970
|
);
|
|
1572
1971
|
}
|
|
1573
1972
|
for (const [name, ok] of videoChecks) console.log(`${ok ? "\u2713" : "\u2717"} ${name}`);
|
|
@@ -1575,9 +1974,10 @@ function registerDoctor(program2) {
|
|
|
1575
1974
|
const sfxChecks = sfxDoctorChecks();
|
|
1576
1975
|
for (const [name, ok] of sfxChecks) console.log(`${ok ? "\u2713" : "\u2717"} ${name}`);
|
|
1577
1976
|
const sfxOk = sfxChecks.every(([, ok]) => ok);
|
|
1977
|
+
const imageText = gate.state === "corrupt" ? "\u80FD\u529B\u72B6\u6001\u635F\u574F\uFF08\u4FEE\u590D\u6307\u5F15\u89C1\u4E0A\uFF09" : gate.state === "disabled" ? `\u5DF2\u7981\u7528${imageOk ? "" : "\uFF08\u4F46 daemon \u4ECD\u5728\u8FD0\u884C\uFF0C\u89C1\u4E0A\uFF09"}` : imageOk ? "\u5C31\u7EEA" : "\u672A\u5C31\u7EEA";
|
|
1578
1978
|
console.log(
|
|
1579
1979
|
`
|
|
1580
|
-
\u56FE\u50CF\u6A21\u6001${
|
|
1980
|
+
\u56FE\u50CF\u6A21\u6001${imageText}\u3002\u89C6\u9891\u6A21\u6001\uFF08\u672C\u5730 MiniMax-H3 / mmh3turbo\uFF09\uFF1A${videoOk ? "\u5C31\u7EEA" : "\u672A\u5C31\u7EEA\uFF08lmedia video setup\uFF09"}\u3002\u97F3\u6548\u6A21\u6001\uFF1A${sfxOk ? "\u5C31\u7EEA" : "\u672A\u5C31\u7EEA\uFF08lmedia sfx doctor \u770B\u4FEE\u590D\u6307\u5F15\uFF09"}`
|
|
1581
1981
|
);
|
|
1582
1982
|
if (!imageOk) process.exit(1);
|
|
1583
1983
|
});
|