| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- #!/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
- };
|