冰朔 b924782ef7
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
D140: restore EP01 3D V2 prompt chain
2026-06-22 20:34:23 +08:00

285 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* 光湖视频AI系统 · 无成本体检脚本
* D140 · Codex收口层
*
* 只扫描本地仓库和JZAO外置盘不调用任何视频/图片生成API。
* 目标: 把剧本、分镜、导演编码、产物、注册表、制作线状态对齐成一张报告。
*/
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const ROOT = path.resolve(__dirname, '../..');
const SYS = path.join(ROOT, 'video-ai-system');
const OUT = path.join(SYS, 'outputs');
const SCRIPT_MD = path.join(ROOT, '动态漫:《付费才能修仙?我的宗门全免费》.md');
const PRODUCT_LINES = path.join(SYS, 'config/product-lines.json');
const STORYBOARD = path.join(SYS, 'data/ep01-storyboard.json');
const DIRECTOR_ENCODING = path.join(OUT, '付费修仙-ep01-director-encoding.json');
const DIRECTOR_ENCODING_V2 = path.join(OUT, 'ep01-director-encoding-3d-v2.json');
const PROMPTS_V2 = path.join(OUT, 'ep01-prompts-3d-v2.json');
const VIDEO_REGISTRY = path.join(OUT, 'video-registry.json');
const STATUS = path.join(SYS, 'memory/zai-fu-fei-xiu-xian/STATUS.hdlp');
const JZAO_EP01 = '/Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/视频/zai-fu-fei-xiu-xian/ep01';
const JZAO_EP01_3D = '/Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/视频/zai-fu-fei-xiu-xian/ep01-3D';
function readJson(file, fallback = null) {
try {
return JSON.parse(fs.readFileSync(file, 'utf8'));
} catch (e) {
return fallback;
}
}
function exists(file) {
return fs.existsSync(file);
}
function listFiles(dir, exts) {
if (!exists(dir)) return [];
const out = [];
const stack = [dir];
while (stack.length) {
const current = stack.pop();
for (const name of fs.readdirSync(current)) {
if (name.startsWith('._')) continue;
const p = path.join(current, name);
const stat = fs.statSync(p);
if (stat.isDirectory()) {
stack.push(p);
} else if (exts.includes(path.extname(name).toLowerCase())) {
out.push(p);
}
}
}
return out.sort();
}
function ffprobe(file) {
try {
const raw = execFileSync('ffprobe', [
'-v', 'quiet',
'-print_format', 'json',
'-show_format',
'-show_streams',
file,
], { encoding: 'utf8', timeout: 8000 });
const meta = JSON.parse(raw);
const stream = (meta.streams || []).find(s => s.codec_type === 'video') || {};
return {
duration: Number.parseFloat(meta.format?.duration || stream.duration || 0),
size: Number.parseInt(meta.format?.size || '0', 10),
width: stream.width || null,
height: stream.height || null,
codec: stream.codec_name || null,
};
} catch (e) {
return { error: e.message };
}
}
function summarizeVideos(files) {
return files.map(file => ({
file,
name: path.basename(file),
...ffprobe(file),
}));
}
function activeLines(config) {
const lines = config?.lines || {};
return Object.entries(lines)
.filter(([, v]) => v.active)
.map(([k, v]) => ({ key: k, name: v.name, status: v.status || 'active' }));
}
function detectIssues(data) {
const issues = [];
if (data.activeLines.length !== 1) {
issues.push({
level: 'red',
title: '制作线必须只有一条active',
detail: `当前active数量=${data.activeLines.length}`,
});
}
if (data.activeLines[0]?.key !== '3d') {
issues.push({
level: 'red',
title: '当前主线不是3D漫剧',
detail: '冰朔已决策注销真人线主线必须是3d。',
});
}
if (!data.script.exists) {
issues.push({ level: 'red', title: '剧本MD缺失', detail: SCRIPT_MD });
}
if (!data.storyboard.exists) {
issues.push({ level: 'red', title: '分镜JSON缺失', detail: STORYBOARD });
}
if (!data.directorEncoding.exists && !data.directorEncodingV2.exists) {
issues.push({ level: 'red', title: '导演编码缺失', detail: `${DIRECTOR_ENCODING} / ${DIRECTOR_ENCODING_V2}` });
}
if (data.localFinals.length === 0 && data.jzaoVideos.length === 0 && data.jzao3dVideos.length === 0) {
issues.push({ level: 'red', title: '没有发现任何视频产物', detail: 'outputs和JZAO均为空。' });
}
if (data.statusClaimsInProgress && data.jzaoFound15s) {
issues.push({
level: 'yellow',
title: '状态文件滞后',
detail: 'STATUS仍写生成中但JZAO已发现D136 15秒成品。',
});
}
if (!data.promptsV2.exists) {
issues.push({
level: 'red',
title: '3D主线提示词缺失',
detail: PROMPTS_V2,
});
}
if (data.promptsStillRealistic && !data.promptsV2.exists) {
issues.push({
level: 'yellow',
title: '旧提示词仍是真人写实',
detail: 'ep01-prompts.json属于旧线不能作为3D主线继续生成且尚未发现ep01-prompts-3d-v2.json。',
});
}
if (data.registryMissingExistingFiles.length > 0) {
issues.push({
level: 'yellow',
title: '注册表存在失效文件路径',
detail: `${data.registryMissingExistingFiles.length}条registry路径在当前机器上不存在。`,
});
}
return issues;
}
function main() {
const product = readJson(PRODUCT_LINES, {});
const storyboard = readJson(STORYBOARD, {});
const encoding = readJson(DIRECTOR_ENCODING, {});
const encodingV2 = readJson(DIRECTOR_ENCODING_V2, {});
const promptsV2 = readJson(PROMPTS_V2, {});
const registry = readJson(VIDEO_REGISTRY, { shots: {} });
const promptsText = exists(path.join(SYS, 'data/ep01-prompts.json'))
? fs.readFileSync(path.join(SYS, 'data/ep01-prompts.json'), 'utf8')
: '';
const statusText = exists(STATUS) ? fs.readFileSync(STATUS, 'utf8') : '';
const statusClaimsInProgress = statusText
.split(/\r?\n/)
.some(line => /(\|.*(🔄|生成中|重新生成中))|(\[[ x]\].*(生成中|重新生成中))/.test(line));
const localVideos = summarizeVideos([
...listFiles(path.join(OUT, 'final'), ['.mp4']),
...listFiles(path.join(OUT, 'shots'), ['.mp4']),
...listFiles(OUT, ['.mp4']),
]);
const jzaoVideos = summarizeVideos(listFiles(JZAO_EP01, ['.mp4']));
const jzao3dVideos = summarizeVideos(listFiles(JZAO_EP01_3D, ['.mp4']));
const registryEntries = Object.values(registry.shots || {});
const registryMissingExistingFiles = registryEntries.filter(x => x.filePath && !exists(x.filePath));
const data = {
generatedAt: new Date().toISOString(),
activeLines: activeLines(product),
script: {
exists: exists(SCRIPT_MD),
file: SCRIPT_MD,
size: exists(SCRIPT_MD) ? fs.statSync(SCRIPT_MD).size : 0,
},
storyboard: {
exists: exists(STORYBOARD),
shots: storyboard.shots?.length || 0,
totalDuration: (storyboard.shots || []).reduce((s, x) => s + (Number(x.duration) || 0), 0),
},
directorEncoding: {
exists: exists(DIRECTOR_ENCODING),
shots: encoding.shots?.length || 0,
project: encoding.project,
episode: encoding.episode,
},
directorEncodingV2: {
exists: exists(DIRECTOR_ENCODING_V2),
file: DIRECTOR_ENCODING_V2,
shots: encodingV2.shots?.length || 0,
targetDuration: encodingV2.target_duration || null,
status: encodingV2._meta?.status || null,
},
promptsV2: {
exists: exists(PROMPTS_V2),
file: PROMPTS_V2,
shots: promptsV2.shots?.length || 0,
status: promptsV2._meta?.status || null,
},
localFinals: localVideos.filter(v => v.file.includes('/outputs/final/')),
localVideos,
jzaoVideos,
jzao3dVideos,
legacyOutputPolicy: '旧JZAO视频产物全部废弃只作为踩坑样本和反例参考不进入D140 V2生产线。',
jzaoFound15s: jzaoVideos.some(v => v.name.includes('15s') || v.name.includes('opening')),
registry: {
total: registryEntries.length,
missingFiles: registryMissingExistingFiles.length,
},
registryMissingExistingFiles,
statusClaimsInProgress,
promptsStillRealistic: /真人写实/.test(promptsText),
};
data.issues = detectIssues(data);
const jsonPath = path.join(OUT, 'system-audit-latest.json');
fs.writeFileSync(jsonPath, JSON.stringify(data, null, 2), 'utf8');
const lines = [];
lines.push('# 视频AI系统 · 无成本体检报告');
lines.push('');
lines.push(`生成时间: ${data.generatedAt}`);
lines.push('');
lines.push(`当前active制作线: ${data.activeLines.map(x => `${x.key}(${x.name})`).join(', ') || '无'}`);
lines.push(`剧本MD: ${data.script.exists ? '存在' : '缺失'}`);
lines.push(`分镜: ${data.storyboard.shots}镜 / ${data.storyboard.totalDuration}s`);
lines.push(`导演编码: 旧${data.directorEncoding.shots}镜 / V2 ${data.directorEncodingV2.shots}`);
lines.push(`3D提示词V2: ${data.promptsV2.exists ? `${data.promptsV2.shots}` : '缺失'}`);
lines.push(`本地产物: ${data.localVideos.length}个mp4`);
lines.push(`JZAO ep01产物: ${data.jzaoVideos.length}个mp4`);
lines.push(`JZAO ep01-3D产物: ${data.jzao3dVideos.length}个mp4`);
lines.push(`旧产物裁决: ${data.legacyOutputPolicy}`);
lines.push(`注册表: ${data.registry.total}条 / 失效路径${data.registry.missingFiles}`);
lines.push('');
lines.push('## 问题');
if (data.issues.length === 0) {
lines.push('- 无红黄问题。');
} else {
for (const issue of data.issues) {
lines.push(`- ${issue.level.toUpperCase()} · ${issue.title}: ${issue.detail}`);
}
}
lines.push('');
lines.push('## 推荐下一步');
lines.push('1. 只保留3D漫剧为active制作线。');
lines.push(data.promptsV2.exists
? '2. 旧真人提示词不再继续生成;使用 ep01-prompts-3d-v2.json 作为主线。'
: '2. 旧真人提示词不再继续生成重新生成3D主线提示词。');
lines.push('3. 旧JZAO视频不进入生产线只作为反例经验。');
lines.push(data.directorEncodingV2.exists && data.promptsV2.exists
? '4. 下一步生成/确认CHAR-003、ENV-002、PROP牌匾/广告牌视觉锚点。'
: '4. 按 PIPELINE-3D-MANJU.hdlp 重建 EP01-15S-V2。');
lines.push('5. 状态文件必须由体检报告回写,不再靠人工记忆。');
lines.push('6. 视觉锚点和生成前体检通过后才调用API。');
lines.push('');
const mdPath = path.join(OUT, 'system-audit-latest.md');
fs.writeFileSync(mdPath, lines.join('\n'), 'utf8');
console.log(`体检完成: ${jsonPath}`);
console.log(`报告: ${mdPath}`);
if (data.issues.length) {
console.log('发现问题:');
data.issues.forEach(i => console.log(`- ${i.level}: ${i.title}`));
} else {
console.log('未发现红黄问题。');
}
}
main();