This commit is contained in:
fengchuanhn@gmail.com
2026-05-17 12:54:13 +08:00
parent 1b8ca10ec9
commit 3db327d93b
38 changed files with 2820 additions and 14 deletions

View File

@@ -0,0 +1,225 @@
"use strict";
/**
* douyin_pipeline.js — Node 完整仿写流水线
* 对齐 quickjs/douyin_pipeline.js 与 Electron VideoRewriteIntegration.processDouyinShareComplete
*
* 由后端 `/api/v1/nodejs-scripts` 下发Tauri 调用 run_nodejs_script → __nodejsMain(params)
*/
const { DouyinContentParser } = require("./douyin_content_parser.js");
const { DouyinVideoDownloader } = require("./douyin_video_downloader.js");
const { VideoRewriteOptimizer } = require("./video_rewrite_optimizer.js");
const pipelineNative = require("./pipeline_native.js");
const desktopConfig = require("./desktop_config.js");
const log = (level, msg, fields) =>
globalThis.__native.log(level, msg, fields == null ? null : fields);
const progress = (s) => {
try {
globalThis.__native.emitProgress(s);
} catch (_) {
/* ignore */
}
};
async function rewriteWithAI(content, modelConfig, rewriteConfig, customPrompt) {
if (!modelConfig?.apiUrl || !modelConfig?.apiKey) {
log("error", "rewrite.missingConfig");
return null;
}
const prompt = VideoRewriteOptimizer.buildRewritePrompt(
content,
rewriteConfig || {},
customPrompt
);
const result = await pipelineNative.openaiChat({
apiUrl: modelConfig.apiUrl,
apiKey: modelConfig.apiKey,
model: modelConfig.modelId,
messages: [
{ role: "system", content: "你是专业的短视频文案改写助手。" },
{ role: "user", content: prompt },
],
temperature:
typeof modelConfig.temperature === "number" ? modelConfig.temperature : 0.8,
max_tokens: modelConfig.maxTokens || 2000,
});
if (!result?.success) {
log("error", "rewrite.failed", { error: result?.error });
return null;
}
return result.content;
}
/**
* 完整流程:解析 → Puppeteer/直链下载 → ffmpeg 提音频 → ASR → AI 仿写
*/
async function processDouyinShareComplete(
shareText,
modelConfig,
rewriteConfig,
customPrompt
) {
let videoPath = null;
let audioPath = null;
try {
log("info", "pipeline.start", {
inputLength: shareText?.length,
asrMode: modelConfig?.asrMode,
});
progress("正在解析视频URL...");
const parseResult = DouyinContentParser.parseContent(shareText);
if (!parseResult.success || !parseResult.content) {
return { success: false, error: parseResult.error || "内容解析失败" };
}
const { videoUrl, hashtags } = parseResult.content;
log("info", "pipeline.urlExtracted", { videoUrl });
progress("正在下载视频...");
const downloader = new DouyinVideoDownloader();
const downloadResult = DouyinContentParser.isDirectVideoUrl(videoUrl)
? await downloader.downloadDirectVideo(videoUrl)
: await downloader.downloadDouyinVideo(videoUrl);
if (!downloadResult.success || !downloadResult.videoPath) {
return {
success: false,
error: downloadResult.error || "视频下载失败",
};
}
videoPath = downloadResult.videoPath;
log("info", "pipeline.videoDownloaded", { videoPath });
progress("视频下载完成,正在提取音频...");
try {
audioPath = await pipelineNative.ffmpegExtractAudio(videoPath);
} catch (e) {
return {
success: false,
error: `音频提取失败: ${e.message || e}`,
};
}
log("info", "pipeline.audioExtracted", { audioPath });
const asrMode = modelConfig?.asrMode || "online";
let recognizedText = "";
if (asrMode === "online") {
progress("音频提取完成正在上传到OSS...");
const ossConfig = modelConfig.ossConfig;
if (!ossConfig?.bucket) {
return { success: false, error: "未配置阿里云 OSSbucket / region / key" };
}
const upload = await pipelineNative.aliyunOssUpload(audioPath, ossConfig);
if (!upload?.success || !upload.url) {
return {
success: false,
error: `音频上传失败: ${upload?.error || "unknown"}`,
};
}
log("info", "pipeline.ossOk", { audioUrl: upload.url.slice(0, 80) });
progress("音频上传完成,正在识别文案(在线模式)...");
const asr = await pipelineNative.aliyunAsrFiletrans(upload.url, {
apiKey: modelConfig.aliyunApiKey,
model: "qwen3-asr-flash-filetrans",
});
if (!asr?.success) {
return {
success: false,
error: `语音识别失败(在线模式): ${asr?.error || "unknown"}`,
};
}
recognizedText = asr.text || "";
} else {
progress("音频提取完成,正在识别文案(本地模式)...");
const info = modelConfig.asrServerInfo;
if (!info) {
return {
success: false,
error: "未配置本地语音识别服务器,请在 .env 中配置 VITE_ASR_SERVER_URL",
};
}
const asr = await pipelineNative.localAsr(audioPath, info);
if (!asr?.success) {
return {
success: false,
error: `语音识别失败(本地模式): ${asr?.error || "unknown"}`,
};
}
recognizedText = asr.text || "";
}
if (!modelConfig?.apiUrl || !modelConfig?.apiKey) {
return { success: false, error: "未配置AI模型请先在 .env 中配置" };
}
progress("文案识别完成,正在改写...");
const rewrittenDescription = await rewriteWithAI(
recognizedText,
modelConfig,
rewriteConfig,
customPrompt
);
if (!rewrittenDescription) {
return {
success: false,
error: "AI仿写失败请检查模型配置和网络连接",
};
}
const rewrittenTitle =
DouyinContentParser.generateTitleFromDescription(rewrittenDescription);
const keepTags = rewriteConfig?.keepHashtags !== false;
const fullContent =
keepTags && hashtags?.length
? `${rewrittenDescription}\n\n${hashtags.join(" ")}`
: rewrittenDescription;
progress("完成!");
log("info", "pipeline.success", {
originalLength: recognizedText.length,
rewrittenLength: rewrittenDescription.length,
});
return {
success: true,
original: {
videoUrl,
description: recognizedText,
hashtags: hashtags || [],
title: "视频音频识别结果",
},
rewritten: {
description: rewrittenDescription,
title: rewrittenTitle,
fullContent,
},
};
} catch (e) {
const msg = (e && (e.stack || e.message)) || String(e);
log("error", "pipeline.error", { message: msg });
return { success: false, error: msg };
} finally {
if (videoPath) pipelineNative.deleteFile(videoPath);
if (audioPath) pipelineNative.deleteFile(audioPath);
}
}
globalThis.__nodejsMain = async function (params) {
const p = params || {};
const modelConfig = desktopConfig.buildModelConfig();
const check = desktopConfig.validateModelConfig(modelConfig);
if (!check.ok) {
return { success: false, error: check.error };
}
return processDouyinShareComplete(
p.shareText || p.share_text || "",
modelConfig,
p.rewriteConfig,
p.customPrompt || null
);
};