#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const VIDEO_RESOURCE_HEADER = [ 'brief编号', '项目名称', '平台', '资源角色', '博主名称', '主页链接', '视频链接', '封面链接', '字幕或ASR文本', '帧图链接', '标题', '发布时间', '内容摘要', '风格调性标签', '画面人设场景信号', '风险提示', '来源接口或备注', '是否真实资源' ]; function main() { const args = parseArgs(process.argv.slice(2)); const output = path.resolve(args.output || path.join(__dirname, '..', 'outputs', `video-intake-pack-${Date.now()}`)); fs.mkdirSync(output, { recursive: true }); const videoCsv = path.join(output, 'video-resource-template.csv'); const readme = path.join(output, 'README.md'); writeCsv(videoCsv, [ VIDEO_RESOURCE_HEADER, [ 'brief-001', '示例项目', '小红书', '参考视频', '示例博主', 'https://example.com/profile', 'https://example.com/video.mp4', 'https://example.com/cover.jpg', '示例 ASR 文本,真实填写时不要写 token 或鉴权头。', 'https://example.com/frame1.jpg|https://example.com/frame2.jpg', '示例标题', '2026-06-01', '说明视频内容、人群、场景、表达结构。', '真实记录感|高质感女性场景|轻讲解', '室内自然光、真人出镜、护肤前后对比、口播节奏稳定', '需复核广告合规表达和报价档期。', 'VOC social/人工补充均可,写来源但不要写密钥。', '是' ], [ 'brief-001', '示例项目', '小红书', '候选视频', '候选博主', 'https://example.com/candidate-profile', '', 'https://example.com/candidate-cover.jpg', '', '', '候选视频标题', '2026-06-02', '暂时只有封面和标题时,只能作为待补视频证据。', '待确认', '封面偏生活方式,缺 ASR 和帧图。', '缺真实视频链接,不得声明视频分析完成。', '人工待补', '否' ] ]); fs.writeFileSync(readme, withBom(renderReadme()), 'utf8'); console.log(JSON.stringify({ output, files: { videoCsv, readme } }, null, 2)); } function renderReadme() { return [ '# 提号视频资源收集包', '', '用途:为 `acceptance:video-ab` 和真实视频风格分析准备可审计资源,避免只凭接口 200 或占位提示判断视频能力有效。', '', '## 文件', '', '- `video-resource-template.csv`:收集参考视频和候选视频的真实资源。', '', '## 填写规则', '', '- `资源角色` 填 `参考视频` 或 `候选视频`。', '- `视频链接` 优先填写真实可访问的视频 URL;没有视频 URL 时,必须至少补 `封面链接`、`字幕或ASR文本`、`帧图链接` 其中之一。', '- `帧图链接` 多个链接用 `|` 分隔。', '- `是否真实资源` 只能填 `是` 或 `否`;没有真实视频 URL、封面、字幕、ASR、帧图或正文证据时必须填 `否`。', '- 不要写 Parse sessionToken、模型 token、鉴权头、npm token、报价密钥或内部鉴权参数。', '', '## 字段填写验收表', '', '| 字段 | 必填规则 | 常见退回原因 |', '| --- | --- | --- |', '| 资源角色 | 只能填 `参考视频` 或 `候选视频`。 | 写成参考账号、达人主页、素材等非固定值。 |', '| 博主名称/主页链接 | 填真实博主和主页链接。 | 只有昵称、主页缺失、example.com。 |', '| 视频链接 | 优先填真实视频 URL。 | 只填主页链接,或链接不可判断为视频资源。 |', '| 封面链接 | 没有视频 URL 时至少补封面、ASR、帧图或正文证据之一。 | 真实资源行没有任何可审计证据。 |', '| 字幕或ASR文本 | 至少 12 字,能反映表达结构或卖点。 | 只写“无”“待补”“看视频”。 |', '| 帧图链接 | 多张图用 `|` 分隔。 | 多个链接用中文逗号导致解析困难。 |', '| 内容摘要/风格调性标签/画面人设场景信号 | 描述人群、场景、内容结构、画面质感和风险。 | 只写“不错”“高质感”但没有具体信号。 |', '| 是否真实资源 | 有真实 URL 或证据时填 `是`,占位或待补填 `否`。 | 把占位行标成真实资源。 |', '', '## 先跑预审', '', '填完后先跑:', '', '```powershell', 'npm run video:resource-readiness -- --input outputs\\video-intake-pack-latest\\video-resource-template.csv --output outputs\\video-resource-readiness-latest --strict', '```', '', '视频资源预审通过只代表 A/B 前置资源齐备,不代表视频分析已经提升命中率。', '', '## 与视频 A/B 验收的关系', '', '真实视频 A/B 通过前,至少需要证明:', '', '- 真实参考资源已加载。', '- 真实视频 URL 已加载。', '- 封面、ASR 或帧图至少一类已加载。', '- 视频分析 provider 状态为 ok。', '- 证据卡已生成,且不是待补占位。', '- Top 10 证据命中提升且分数不下降。', '- 强推荐数量不下降。', '', '## 命令', '', '```powershell', 'npm run video:intake-template -- --output outputs\\video-intake-pack-latest', 'npm run acceptance:video-ab', 'npm run evidence:index -- --output outputs\\evidence-index-latest', '```', '', '## 边界', '', '- 模板生成本身不证明视频分析完成。', '- 只有真实视频 URL、封面、字幕、ASR、帧图或正文证据被加载,并且 `acceptance:video-ab` 通过后,才能声明视频分析链路有效。', '- 没有真实客户复核或客户选择数据时,仍不能声明视频分析提升了客户命中率。' ].join('\n'); } function writeCsv(file, rows) { fs.writeFileSync(file, withBom(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 parseArgs(argv) { const args = {}; for (let index = 0; index < argv.length; index += 1) { const raw = argv[index]; if (!raw.startsWith('--')) continue; const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase()); const next = argv[index + 1]; if (!next || next.startsWith('--')) args[key] = true; else { args[key] = next; index += 1; } } return args; } function withBom(text) { return `\uFEFF${text}`; } if (require.main === module) main(); module.exports = { VIDEO_RESOURCE_HEADER, renderReadme };