| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- #!/usr/bin/env node
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const { spawnSync } = require('child_process');
- const { VIDEO_RESOURCE_HEADER } = require('./create-video-intake-pack');
- const root = path.resolve(__dirname, '..');
- function main() {
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-video-resource-readiness-'));
- const passCsv = path.join(tmp, 'video-pass.csv');
- const failCsv = path.join(tmp, 'video-fail.csv');
- const passOutput = path.join(tmp, 'pass-output');
- const failOutput = path.join(tmp, 'fail-output');
- writeCsv(passCsv, [
- VIDEO_RESOURCE_HEADER,
- row({
- briefId: 'brief-001',
- role: '参考视频',
- creatorName: '参考博主',
- videoUrl: 'https://example.com/reference.mp4',
- coverUrl: 'https://example.com/reference-cover.jpg',
- asrText: '真实 ASR 文本,描述人群、场景、口播结构和表达方式。',
- frameUrls: 'https://example.com/reference-frame.jpg',
- isReal: '是'
- }),
- row({
- briefId: 'brief-001',
- role: '候选视频',
- creatorName: '候选博主',
- videoUrl: 'https://example.com/candidate.mp4',
- coverUrl: 'https://example.com/candidate-cover.jpg',
- asrText: '候选视频 ASR 文本,能够辅助判断调性是否接近。',
- frameUrls: '',
- isReal: '是'
- })
- ]);
- writeCsv(failCsv, [
- VIDEO_RESOURCE_HEADER,
- row({
- briefId: 'brief-001',
- role: '参考视频',
- creatorName: '参考博主',
- videoUrl: '',
- coverUrl: '',
- asrText: '',
- frameUrls: '',
- isReal: '否'
- })
- ]);
- runAudit(passCsv, passOutput, true);
- runAudit(failCsv, failOutput, false);
- const passSummary = JSON.parse(fs.readFileSync(path.join(passOutput, 'video-resource-readiness-summary.json'), 'utf8'));
- const failSummary = JSON.parse(fs.readFileSync(path.join(failOutput, 'video-resource-readiness-summary.json'), 'utf8'));
- const report = fs.readFileSync(path.join(passOutput, 'video-resource-readiness-report.md'), 'utf8').replace(/^\uFEFF/, '');
- const failReport = fs.readFileSync(path.join(failOutput, 'video-resource-readiness-report.md'), 'utf8').replace(/^\uFEFF/, '');
- const failRepairCsv = path.join(failOutput, 'video-resource-repair-actions.csv');
- assert(passSummary.acceptance.readyForVideoAbPreflight === true, 'complete video resources should pass preflight readiness');
- assert(Array.isArray(passSummary.repairActions) && passSummary.repairActions.length === 0, 'passing sample should have no repair actions');
- assert(passSummary.counts.realReferenceRows === 1, 'passing sample should include one real reference row');
- assert(passSummary.counts.realCandidateRows === 1, 'passing sample should include one real candidate row');
- assert(passSummary.counts.videoUrlRows === 2, 'passing sample should include video URLs');
- assert(failSummary.acceptance.readyForVideoAbPreflight === false, 'missing video resources should fail readiness');
- assert(Array.isArray(failSummary.repairActions) && failSummary.repairActions.length === failSummary.failureCount, 'failing sample should expose repair action for each issue');
- assert(fs.existsSync(failRepairCsv), 'failing audit should write repair actions CSV');
- assert(failSummary.issues.some(item => item.type === 'missing-real-reference'), 'failure should mention missing real reference resource');
- assert(failSummary.issues.some(item => item.type === 'missing-video-url'), 'failure should mention missing video URL');
- assert(report.includes('# 视频资源就绪审计'), 'report should be Chinese');
- assert(failReport.includes('## 修复清单'), 'failing report should include repair action section');
- assert(fs.readFileSync(failRepairCsv, 'utf8').includes('修复动作'), 'video repair action CSV should include action column');
- assert(!/(sk-[A-Za-z0-9_-]{20,}|r:[A-Za-z0-9]{20,}|Authorization\s*[:=]\s*Bearer)/i.test(report), 'report should not contain secrets');
- console.log(JSON.stringify({
- ok: true,
- passOutput,
- failOutput,
- passFailureCount: passSummary.failureCount,
- failFailureCount: failSummary.failureCount
- }, null, 2));
- }
- function runAudit(input, output, shouldPass) {
- const result = spawnSync(process.execPath, [
- path.join(root, 'scripts', 'video-resource-readiness-audit.js'),
- '--input',
- input,
- '--output',
- output,
- '--strict'
- ], { cwd: root, encoding: 'utf8' });
- if (shouldPass && result.status !== 0) {
- process.stderr.write(result.stderr || result.stdout || '');
- throw new Error('passing video resource readiness audit should exit 0');
- }
- if (!shouldPass && result.status === 0) {
- throw new Error('failing video resource readiness audit should exit non-zero in strict mode');
- }
- }
- function row({ briefId, role, creatorName, videoUrl, coverUrl, asrText, frameUrls, isReal }) {
- return [
- briefId,
- '示例项目',
- '小红书',
- role,
- creatorName,
- `https://example.com/${creatorName}`,
- videoUrl,
- coverUrl,
- asrText,
- frameUrls,
- '示例标题',
- '2026-06-01',
- '内容摘要用于辅助判断视频风格。',
- '真实记录感|高质感女性场景',
- '真人出镜、自然光、轻口播',
- '',
- 'smoke fake source',
- isReal
- ];
- }
- function writeCsv(file, rows) {
- fs.writeFileSync(file, `\uFEFF${rows.map(row => row.map(csvCell).join(',')).join('\n')}`, 'utf8');
- }
- function csvCell(value) {
- const text = String(value ?? '');
- return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
- }
- function assert(condition, message) {
- if (!condition) throw new Error(message);
- }
- main();
|