Files
dsh_mcn_agent/profiles/web/node_modules/dsh-plugin-mcn/lib/video-tools.js
T
admin 856e632807 baseline: dsh 工作区基线快照(2026-09-10,全新历史起点)
本提交为 dsh_mcn_workshop 仓库的首个提交(旧仓 dsh_MCNProject 已停用),
完整固化 C:/Users/Administrator/.dsh 当前磁盘状态。

1) 临时数据清理:移出 sessions/(81M)、logs/、skill-backup-20260906/(14M)、
   scripts/__pycache__、storages/workspace.json.bak —— 合计 94M(.dsh 162M → 68M)
2) 凭据脱敏:.credentials.yaml(含明文 DEEPSEEK_API_KEY)移出 git 跟踪并加入
   .gitignore;mcn-data-insight/scripts/setup_redfox_key.py 中真实 key 示例改为占位符
3) .gitignore 增补 logs/、skill-backup-*/ 规则
4) 技能结构以当前磁盘状态为准:skills/ = mcn-short-video / impeccable / taste-skill /
   dsh-multi-user-migration;旧技能目录(mcn-dou-analysis、storyboard-prompt、
   short-video-script、browser-harness、mcn-data-insight)与
   profiles/web/node_modules/dsh-vision-router 本地已不存在,本提交记为删除
2026-09-10 22:31:45 +08:00

108 lines
3.6 KiB
JavaScript
Raw 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.
// dsh-plugin-mcn 视频/账号匹配工具(纯函数,拆分自 index.js)
import { homedir } from "node:os";
import { join } from "node:path";
import { readFileSync, existsSync } from "node:fs";
function normTitle(s) {
return String(s || "").toLowerCase().replace(/[\s#,。!?、,.!?·\-_~~::"“”()()【】]+/g, "");
}
function extractTopic(content) {
const s = String(content || "");
if (!s.trim()) return "";
const lines = s.replace(/\r\n/g, "\n").split("\n");
let start = -1;
for (let i = 0; i < lines.length; i++) {
const t = lines[i].trim();
if (/^#{1,4}\s*选题\s*$/.test(t)) { start = i + 1; break; }
}
if (start < 0) return "";
const out = [];
for (let i = start; i < lines.length; i++) {
const t = lines[i].trim();
if (/^#{1,6}\s/.test(t) && !/^#{1,6}\s*选题\s*$/.test(t)) break;
if (t) out.push(lines[i].trim());
}
return out.join("\n").trim();
}
function titleDistance(a, b) {
const m = a.length, n = b.length;
if (!m) return n;
if (!n) return m;
let prev = new Array(n + 1), curr = new Array(n + 1);
for (let j = 0; j <= n; j++) prev[j] = j;
for (let i = 1; i <= m; i++) {
curr[0] = i;
for (let j = 1; j <= n; j++) {
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
}
[prev, curr] = [curr, prev];
}
return prev[n];
}
function findVideoByFolder(d, accountId, vDir, vName) {
const rows = d.prepare(`SELECT id, aweme_id, video_title FROM account_videos WHERE account_id=?`).all(accountId);
const pick = (title) => {
const t = String(title || "").trim();
if (!t) return undefined;
const exact = rows.find((r) => String(r.video_title || "") === t);
if (exact) return exact;
const tn = normTitle(t);
if (!tn) return undefined;
let best = null;
for (const r of rows) {
const cn = normTitle(r.video_title);
if (!cn || cn.length < 6 || tn.length < 6) continue;
if (tn.includes(cn) || cn.includes(tn)) {
if (!best || tn.length > best.len) best = { id: r.id, aweme_id: r.aweme_id, len: tn.length };
}
}
if (best) return best;
let bestSim = null;
for (const r of rows) {
const cn = normTitle(r.video_title);
if (!cn) continue;
const ratio = 1 - titleDistance(tn, cn) / Math.max(tn.length, cn.length, 1);
if (ratio > 0.8 && (!bestSim || ratio > bestSim.ratio)) bestSim = { id: r.id, aweme_id: r.aweme_id, ratio };
}
return bestSim ? bestSim : undefined;
};
for (const jf of ["原视频解析.json", "content.json"]) {
const jp = join(vDir, jf);
if (!existsSync(jp)) continue;
try {
const jd = JSON.parse(readFileSync(jp, "utf8"));
const hit = pick(jd.title);
if (hit) return hit;
} catch (e) { /* 单个 json 解析失败不影响后续匹配 */ }
}
const first = String(vName || "").split("_")[0].trim();
if (first) {
const hit = pick(first);
if (hit) return hit;
}
return undefined;
}
function resolveAccountDirs(acc) {
const out = [];
if (acc && typeof acc.source_dir === "string" && acc.source_dir && existsSync(acc.source_dir)) out.push(acc.source_dir);
const legacy = acc && acc.account_name ? join(homedir(), "Desktop", "账号对标", acc.account_name) : null;
if (legacy && existsSync(legacy)) out.push(legacy);
return out;
}
function resolveAccountDir(acc) {
return resolveAccountDirs(acc)[0] || null;
}
/** 数值化(非法输入返回 0) */
function num(v) {
const n = Number(v);
return Number.isFinite(n) ? n : 0;
}
export { num, normTitle, extractTopic, titleDistance, findVideoByFolder, resolveAccountDirs, resolveAccountDir };