index.mjs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. // lib/index.mjs — fmode-mv 主入口
  2. //
  3. // 版本号三处必须一致:package.json / SKILL.md frontmatter / 本文件 VERSION。
  4. // 用 `fmode-mv version --check` 可自动校验。
  5. export const VERSION = '0.2.0';
  6. export const NAME = 'fmode-mv';
  7. export const SKILL = 'skill-mv';
  8. import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
  9. import { dirname, join, resolve } from 'node:path';
  10. import { fileURLToPath } from 'node:url';
  11. export const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
  12. // 重新导出各模块,脚本统一从 index.mjs 取用
  13. export * from './config.mjs';
  14. export * from './api.mjs';
  15. export * from './providers.mjs';
  16. export * from './ffmpeg.mjs';
  17. export * from './jimeng.mjs';
  18. export * from './sharpness.mjs';
  19. export * from './prompts.mjs';
  20. // ==================== 工作区约定 ====================
  21. //
  22. // work/ —— 本次生产的中间产物(不入库)
  23. // song.json 作曲填词结果
  24. // storyboard.json 分镜脚本
  25. // audio/ 音乐、人声、伴奏、配音
  26. // frames/ 关键帧图片
  27. // clips/ 生成的视频片段
  28. // lipsync/ 唇形驱动结果
  29. // subtitles.srt 字幕
  30. // out/ —— 最终成片
  31. export const WORK = process.env.FMODE_MV_WORK ? resolve(process.env.FMODE_MV_WORK) : join(ROOT, 'work');
  32. export const OUT = process.env.FMODE_MV_OUT ? resolve(process.env.FMODE_MV_OUT) : join(ROOT, 'out');
  33. export const paths = {
  34. work: WORK,
  35. out: OUT,
  36. audio: join(WORK, 'audio'),
  37. frames: join(WORK, 'frames'),
  38. clips: join(WORK, 'clips'),
  39. lipsync: join(WORK, 'lipsync'),
  40. song: join(WORK, 'song.json'),
  41. storyboard: join(WORK, 'storyboard.json'),
  42. script: join(WORK, 'script.json'),
  43. subtitles: join(WORK, 'subtitles.srt'),
  44. };
  45. export function ensureWork() {
  46. for (const d of [paths.work, paths.out, paths.audio, paths.frames, paths.clips, paths.lipsync]) mkdirSync(d, { recursive: true });
  47. return paths.work;
  48. }
  49. // ==================== JSON 读写 ====================
  50. export function writeJSON(file, data) {
  51. mkdirSync(dirname(file), { recursive: true });
  52. writeFileSync(file, JSON.stringify(data, null, 2) + '\n', 'utf8');
  53. return file;
  54. }
  55. export function readJSON(file, fallback = null) {
  56. try {
  57. if (!existsSync(file)) return fallback;
  58. return JSON.parse(readFileSync(file, 'utf8'));
  59. } catch (e) {
  60. throw new Error(`读取 JSON 失败 ${file}: ${e.message}`);
  61. }
  62. }
  63. // ==================== 日志 ====================
  64. const C = {
  65. reset: '\x1b[0m',
  66. dim: '\x1b[2m',
  67. red: '\x1b[31m',
  68. green: '\x1b[32m',
  69. yellow: '\x1b[33m',
  70. blue: '\x1b[34m',
  71. cyan: '\x1b[36m',
  72. bold: '\x1b[1m',
  73. };
  74. const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
  75. const paint = (c, s) => (useColor ? `${c}${s}${C.reset}` : String(s));
  76. export const log = {
  77. step: (s) => console.log(`\n${paint(C.bold + C.blue, '▶ ' + s)}`),
  78. ok: (s) => console.log(` ${paint(C.green, '✓')} ${s}`),
  79. warn: (s) => console.log(` ${paint(C.yellow, '!')} ${s}`),
  80. err: (s) => console.error(` ${paint(C.red, '✗')} ${s}`),
  81. info: (s) => console.log(` ${s}`),
  82. dim: (s) => console.log(paint(C.dim, ` ${s}`)),
  83. raw: (s) => console.log(s),
  84. key: (k, v) => console.log(` ${paint(C.cyan, k.padEnd(14))} ${v}`),
  85. };
  86. /** 打印未开通通道的标准提示(统一格式,保证「不静默失败」) */
  87. export function reportUnavailable(kind, err) {
  88. log.err(`通道「${kind}」不可用:${String(err.message).split('\n')[0]}`);
  89. if (err.hint) {
  90. console.log('');
  91. console.log(paint(C.yellow, ' 可操作方案:'));
  92. for (const line of String(err.hint).split('\n')) console.log(` ${line}`);
  93. }
  94. console.log('');
  95. console.log(paint(C.dim, ` 用 \`fmode-mv providers\` 查看全部通道现状。`));
  96. }
  97. // ==================== 参数解析 ====================
  98. /**
  99. * 极简参数解析:支持 --k v / --k=v / --flag / 位置参数。
  100. * @returns {{_:string[], flags:object}}
  101. */
  102. export function parseArgs(argv = process.argv.slice(2)) {
  103. const _ = [];
  104. const flags = {};
  105. for (let i = 0; i < argv.length; i++) {
  106. const a = argv[i];
  107. if (a.startsWith('--')) {
  108. const eq = a.indexOf('=');
  109. if (eq > -1) {
  110. flags[a.slice(2, eq)] = a.slice(eq + 1);
  111. } else {
  112. const key = a.slice(2);
  113. const next = argv[i + 1];
  114. if (next === undefined || next.startsWith('--')) flags[key] = true;
  115. else {
  116. flags[key] = next;
  117. i++;
  118. }
  119. }
  120. } else if (a.startsWith('-') && a.length > 1) {
  121. flags[a.slice(1)] = true;
  122. } else {
  123. _.push(a);
  124. }
  125. }
  126. return { _, flags };
  127. }
  128. /** 三处版本一致性校验 */
  129. export function checkVersionConsistency() {
  130. const issues = [];
  131. const pkgPath = join(ROOT, 'package.json');
  132. const skillPath = join(ROOT, 'SKILL.md');
  133. let pkgVer = null;
  134. let skillVer = null;
  135. try {
  136. pkgVer = JSON.parse(readFileSync(pkgPath, 'utf8')).version;
  137. } catch (e) {
  138. issues.push(`package.json 读取失败:${e.message}`);
  139. }
  140. try {
  141. const txt = readFileSync(skillPath, 'utf8');
  142. const m = txt.match(/^---([\s\S]*?)---/);
  143. const fm = m ? m[1] : '';
  144. skillVer = (fm.match(/^version:\s*"?([^"\n]+)"?/m) || [])[1]?.trim() || null;
  145. } catch (e) {
  146. issues.push(`SKILL.md 读取失败:${e.message}`);
  147. }
  148. const ok = pkgVer === VERSION && skillVer === VERSION;
  149. if (pkgVer !== VERSION) issues.push(`package.json=${pkgVer} ≠ index.mjs=${VERSION}`);
  150. if (skillVer !== VERSION) issues.push(`SKILL.md=${skillVer} ≠ index.mjs=${VERSION}`);
  151. return { ok, version: VERSION, pkgVersion: pkgVer, skillVersion: skillVer, issues };
  152. }
  153. export default { VERSION, NAME, SKILL, paths, checkVersionConsistency };