"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: "未配置阿里云 OSS(bucket / 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 ); };