create-video-intake-pack.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const VIDEO_RESOURCE_HEADER = [
  5. 'brief编号',
  6. '项目名称',
  7. '平台',
  8. '资源角色',
  9. '博主名称',
  10. '主页链接',
  11. '视频链接',
  12. '封面链接',
  13. '字幕或ASR文本',
  14. '帧图链接',
  15. '标题',
  16. '发布时间',
  17. '内容摘要',
  18. '风格调性标签',
  19. '画面人设场景信号',
  20. '风险提示',
  21. '来源接口或备注',
  22. '是否真实资源'
  23. ];
  24. function main() {
  25. const args = parseArgs(process.argv.slice(2));
  26. const output = path.resolve(args.output || path.join(__dirname, '..', 'outputs', `video-intake-pack-${Date.now()}`));
  27. fs.mkdirSync(output, { recursive: true });
  28. const videoCsv = path.join(output, 'video-resource-template.csv');
  29. const readme = path.join(output, 'README.md');
  30. writeCsv(videoCsv, [
  31. VIDEO_RESOURCE_HEADER,
  32. [
  33. 'brief-001',
  34. '示例项目',
  35. '小红书',
  36. '参考视频',
  37. '示例博主',
  38. 'https://example.com/profile',
  39. 'https://example.com/video.mp4',
  40. 'https://example.com/cover.jpg',
  41. '示例 ASR 文本,真实填写时不要写 token 或鉴权头。',
  42. 'https://example.com/frame1.jpg|https://example.com/frame2.jpg',
  43. '示例标题',
  44. '2026-06-01',
  45. '说明视频内容、人群、场景、表达结构。',
  46. '真实记录感|高质感女性场景|轻讲解',
  47. '室内自然光、真人出镜、护肤前后对比、口播节奏稳定',
  48. '需复核广告合规表达和报价档期。',
  49. 'VOC social/人工补充均可,写来源但不要写密钥。',
  50. '是'
  51. ],
  52. [
  53. 'brief-001',
  54. '示例项目',
  55. '小红书',
  56. '候选视频',
  57. '候选博主',
  58. 'https://example.com/candidate-profile',
  59. '',
  60. 'https://example.com/candidate-cover.jpg',
  61. '',
  62. '',
  63. '候选视频标题',
  64. '2026-06-02',
  65. '暂时只有封面和标题时,只能作为待补视频证据。',
  66. '待确认',
  67. '封面偏生活方式,缺 ASR 和帧图。',
  68. '缺真实视频链接,不得声明视频分析完成。',
  69. '人工待补',
  70. '否'
  71. ]
  72. ]);
  73. fs.writeFileSync(readme, withBom(renderReadme()), 'utf8');
  74. console.log(JSON.stringify({ output, files: { videoCsv, readme } }, null, 2));
  75. }
  76. function renderReadme() {
  77. return [
  78. '# 提号视频资源收集包',
  79. '',
  80. '用途:为 `acceptance:video-ab` 和真实视频风格分析准备可审计资源,避免只凭接口 200 或占位提示判断视频能力有效。',
  81. '',
  82. '## 文件',
  83. '',
  84. '- `video-resource-template.csv`:收集参考视频和候选视频的真实资源。',
  85. '',
  86. '## 填写规则',
  87. '',
  88. '- `资源角色` 填 `参考视频` 或 `候选视频`。',
  89. '- `视频链接` 优先填写真实可访问的视频 URL;没有视频 URL 时,必须至少补 `封面链接`、`字幕或ASR文本`、`帧图链接` 其中之一。',
  90. '- `帧图链接` 多个链接用 `|` 分隔。',
  91. '- `是否真实资源` 只能填 `是` 或 `否`;没有真实视频 URL、封面、字幕、ASR、帧图或正文证据时必须填 `否`。',
  92. '- 不要写 Parse sessionToken、模型 token、鉴权头、npm token、报价密钥或内部鉴权参数。',
  93. '',
  94. '## 字段填写验收表',
  95. '',
  96. '| 字段 | 必填规则 | 常见退回原因 |',
  97. '| --- | --- | --- |',
  98. '| 资源角色 | 只能填 `参考视频` 或 `候选视频`。 | 写成参考账号、达人主页、素材等非固定值。 |',
  99. '| 博主名称/主页链接 | 填真实博主和主页链接。 | 只有昵称、主页缺失、example.com。 |',
  100. '| 视频链接 | 优先填真实视频 URL。 | 只填主页链接,或链接不可判断为视频资源。 |',
  101. '| 封面链接 | 没有视频 URL 时至少补封面、ASR、帧图或正文证据之一。 | 真实资源行没有任何可审计证据。 |',
  102. '| 字幕或ASR文本 | 至少 12 字,能反映表达结构或卖点。 | 只写“无”“待补”“看视频”。 |',
  103. '| 帧图链接 | 多张图用 `|` 分隔。 | 多个链接用中文逗号导致解析困难。 |',
  104. '| 内容摘要/风格调性标签/画面人设场景信号 | 描述人群、场景、内容结构、画面质感和风险。 | 只写“不错”“高质感”但没有具体信号。 |',
  105. '| 是否真实资源 | 有真实 URL 或证据时填 `是`,占位或待补填 `否`。 | 把占位行标成真实资源。 |',
  106. '',
  107. '## 先跑预审',
  108. '',
  109. '填完后先跑:',
  110. '',
  111. '```powershell',
  112. 'npm run video:resource-readiness -- --input outputs\\video-intake-pack-latest\\video-resource-template.csv --output outputs\\video-resource-readiness-latest --strict',
  113. '```',
  114. '',
  115. '视频资源预审通过只代表 A/B 前置资源齐备,不代表视频分析已经提升命中率。',
  116. '',
  117. '## 与视频 A/B 验收的关系',
  118. '',
  119. '真实视频 A/B 通过前,至少需要证明:',
  120. '',
  121. '- 真实参考资源已加载。',
  122. '- 真实视频 URL 已加载。',
  123. '- 封面、ASR 或帧图至少一类已加载。',
  124. '- 视频分析 provider 状态为 ok。',
  125. '- 证据卡已生成,且不是待补占位。',
  126. '- Top 10 证据命中提升且分数不下降。',
  127. '- 强推荐数量不下降。',
  128. '',
  129. '## 命令',
  130. '',
  131. '```powershell',
  132. 'npm run video:intake-template -- --output outputs\\video-intake-pack-latest',
  133. 'npm run acceptance:video-ab',
  134. 'npm run evidence:index -- --output outputs\\evidence-index-latest',
  135. '```',
  136. '',
  137. '## 边界',
  138. '',
  139. '- 模板生成本身不证明视频分析完成。',
  140. '- 只有真实视频 URL、封面、字幕、ASR、帧图或正文证据被加载,并且 `acceptance:video-ab` 通过后,才能声明视频分析链路有效。',
  141. '- 没有真实客户复核或客户选择数据时,仍不能声明视频分析提升了客户命中率。'
  142. ].join('\n');
  143. }
  144. function writeCsv(file, rows) {
  145. fs.writeFileSync(file, withBom(rows.map(row => row.map(csvCell).join(',')).join('\n')), 'utf8');
  146. }
  147. function csvCell(value) {
  148. const text = String(value ?? '');
  149. return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
  150. }
  151. function parseArgs(argv) {
  152. const args = {};
  153. for (let index = 0; index < argv.length; index += 1) {
  154. const raw = argv[index];
  155. if (!raw.startsWith('--')) continue;
  156. const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
  157. const next = argv[index + 1];
  158. if (!next || next.startsWith('--')) args[key] = true;
  159. else {
  160. args[key] = next;
  161. index += 1;
  162. }
  163. }
  164. return args;
  165. }
  166. function withBom(text) {
  167. return `\uFEFF${text}`;
  168. }
  169. if (require.main === module) main();
  170. module.exports = {
  171. VIDEO_RESOURCE_HEADER,
  172. renderReadme
  173. };