From 83668cea3c70afc97ab19f96c16d06ea45fde9ec Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sat, 16 May 2026 19:22:33 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat(video-from-script):=20=E5=8C=BA?= =?UTF-8?q?=E5=88=86=E8=A7=86=E9=A2=91=E7=94=9F=E6=88=90=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E4=B8=8E=E4=BB=BB=E5=8A=A1=E5=A4=B1=E8=B4=A5=EF=BC=8C=E4=BF=9D?= =?UTF-8?q?=E7=95=99=20taskId=20=E6=94=AF=E6=8C=81=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 核心改动:将超时与任务失败明确区分,超时场景保留 taskId 和 pending 状态, 等待下次恢复,避免重复计费;仅 API 明确返回 failed 时才清理 taskId 并重试。 - phase-videos.js: 恢复失败分超时/失败处理,超时保留 taskId 置 pending - phase-videos.js: 轮询结果超时也保留 taskId 存 pending - video-poll-utils.js: 增加 isTaskFailed 判断;超时禁止创建新任务 - video-poll-utils.js: 提升轮询重试次数至 5 次以允许超时等待 --- .../scripts/lib/phase-videos.js | 34 +++++++++++++------ .../scripts/lib/video-poll-utils.js | 33 ++++++++++++++---- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/.claude/skills/video-from-script/scripts/lib/phase-videos.js b/.claude/skills/video-from-script/scripts/lib/phase-videos.js index 06ba39a..9cd7e6c 100644 --- a/.claude/skills/video-from-script/scripts/lib/phase-videos.js +++ b/.claude/skills/video-from-script/scripts/lib/phase-videos.js @@ -44,7 +44,7 @@ async function phaseVideos(manifest, manifestPath, options) { console.log() } - // 已有视频(本地或 OSS)且状态为 done 的跳过,其余清理后重新生成 + // 已有视频(本地或 OSS)且状态为 done 的跳过,其余清理视频引用但保留 taskId 供恢复 const items = [] for (const it of videoCandidates) { if (it.video || it.videoUrl) { @@ -52,7 +52,6 @@ async function phaseVideos(manifest, manifestPath, options) { delete it.video delete it.videoUrl delete it.videoDuration - delete it.videoTaskId } items.push(it) } @@ -107,9 +106,15 @@ async function phaseVideos(manifest, manifestPath, options) { log('videos', ` item ${item.id} 恢复成功`) } } catch (err) { - log('videos', ` item ${item.id} 恢复失败: ${err.message},将重新提交`) - delete item.videoTaskId - needSubmit.push(item) + const isFail = err.isTaskFailure === true + if (isFail) { + log('videos', ` item ${item.id} 恢复失败(任务失败): ${err.message},将重新提交`) + delete item.videoTaskId + needSubmit.push(item) + } else { + log('videos', ` item ${item.id} 恢复超时(保留 taskId 下次重试): ${err.message}`) + item.status = 'pending' + } } }) ) @@ -172,25 +177,32 @@ async function phaseVideos(manifest, manifestPath, options) { }) return { item, result, ok: true } } catch (err) { - return { item, error: err.message, ok: false } + return { item, error: err.message, ok: false, isTaskFailure: err.isTaskFailure === true } } }) ) for (const r of pollResults) { - const val = r.status === 'fulfilled' ? r.value : { ok: false, error: r.reason?.message } + const val = r.status === 'fulfilled' + ? r.value + : { ok: false, error: r.reason?.message || String(r.reason), isTaskFailure: r.reason?.isTaskFailure === true } if (val.ok && val.result.file) { val.item.video = path.relative(dir, val.result.file).replace(/\\/g, '/') val.item.videoDuration = val.result.duration val.item.status = 'done' delete val.item.videoTaskId } else if (val.item) { - val.item.status = 'failed' - val.item.error = val.error || '视频生成未返回文件' - delete val.item.videoTaskId + if (val.isTaskFailure) { + val.item.status = 'failed' + val.item.error = val.error || '视频生成未返回文件' + delete val.item.videoTaskId + } else { + log('videos', ` item ${val.item.id} 生成超时(保留 taskId 待恢复): ${val.error}`) + val.item.status = 'pending' + } } + saveManifest(manifestPath, manifest) } - saveManifest(manifestPath, manifest) // 上传视频到 OSS const { uploadFile } = require('../oss-upload') diff --git a/.claude/skills/video-from-script/scripts/lib/video-poll-utils.js b/.claude/skills/video-from-script/scripts/lib/video-poll-utils.js index b2815a1..9b78186 100644 --- a/.claude/skills/video-from-script/scripts/lib/video-poll-utils.js +++ b/.claude/skills/video-from-script/scripts/lib/video-poll-utils.js @@ -2,7 +2,9 @@ * 共享视频轮询重试工具 * * 提供 pollWithRetry 工厂函数,供 kling/veo/grok 三个视频生成器共用。 - * 两层重试:轮询级(同一 taskId,处理网络瞬断)→ 任务级(创建新 task + 优化提示词) + * 两层重试:轮询级(同一 taskId,处理网络瞬断/超时)→ 任务级(仅 API 明确返回 failed 才创建新 task) + * + * 铁律:超时 ≠ 失败。超时说明服务端还在跑,创建新任务会重复计费。 */ const path = require('path') @@ -10,9 +12,11 @@ const fs = require('fs') const https = require('https') const http = require('http') -const TRANSIENT_RE = /timeout|ECONNRESET|ETIMEDOUT|network|socket/i +const TRANSIENT_RE = /timeout|ECONNRESET|ETIMEDOUT|network|socket|超时|processing|pending/i -const POLL_RETRIES = 2 // 同一 task 轮询重试次数 +const TASK_FAILED_RE = /\bfailed\b|失败/i + +const POLL_RETRIES = 5 // 同一 task 轮询重试次数(含超时等待) const POLL_RETRY_DELAY = 5000 // 轮询重试间隔 ms const TASK_RETRY_DELAY = 5000 // 任务级重试间隔 ms @@ -20,6 +24,10 @@ function isTransientError(err) { return TRANSIENT_RE.test(err.message || '') } +function isTaskFailed(err) { + return TASK_FAILED_RE.test(err.message || '') +} + async function download(url, outputPath) { const protocol = url.startsWith('https') ? https : http return new Promise((resolve, reject) => { @@ -57,9 +65,16 @@ function makePollWithRetry({ Api, suffix, duration, maxRetries = 3, optimizeProm let currentTaskId = taskId let currentPrompt = prompt let lastError = null + let lastErrorWasTaskFailure = false for (let attempt = 0; attempt <= maxRetries; attempt++) { if (attempt > 0) { + // 只有 API 明确返回 failed 才创建新任务。超时/网络问题禁止创建新任务(原任务仍在服务端运行,重复计费) + if (!lastErrorWasTaskFailure) { + const err = new Error(`视频生成超时,放弃等待: ${lastError}`) + err.isTaskFailure = false + throw err + } if (optimizePrompt) { currentPrompt = optimizePrompt(prompt, lastError, attempt) } @@ -89,8 +104,10 @@ function makePollWithRetry({ Api, suffix, duration, maxRetries = 3, optimizeProm } } catch (err) { lastError = err.message + lastErrorWasTaskFailure = isTaskFailed(err) if (isTransientError(err) && pollAttempt < POLL_RETRIES) { - console.log(` ⚠ 轮询瞬断 (${pollAttempt + 1}/${POLL_RETRIES}): ${err.message.slice(0, 60)}`) + const tag = isTaskFailed(err) ? '失败' : '超时/瞬断' + console.log(` ⚠ 轮询${tag} (${pollAttempt + 1}/${POLL_RETRIES}): ${err.message.slice(0, 60)}`) await new Promise(r => setTimeout(r, POLL_RETRY_DELAY)) continue } @@ -98,13 +115,15 @@ function makePollWithRetry({ Api, suffix, duration, maxRetries = 3, optimizeProm } } - if (attempt < maxRetries) { + if (attempt < maxRetries && lastErrorWasTaskFailure) { await new Promise(r => setTimeout(r, TASK_RETRY_DELAY)) } } - throw new Error(`重试 ${maxRetries} 次后仍失败: ${lastError}`) + const finalErr = new Error(`重试 ${maxRetries} 次后仍失败: ${lastError}`) + finalErr.isTaskFailure = true + throw finalErr } } -module.exports = { makePollWithRetry, POLL_RETRIES, POLL_RETRY_DELAY, TASK_RETRY_DELAY } +module.exports = { makePollWithRetry, isTaskFailed, POLL_RETRIES, POLL_RETRY_DELAY, TASK_RETRY_DELAY } From 8f3c145db9f164a5e39c6a63b22327ee9f695f0a Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sat, 16 May 2026 19:27:20 +0800 Subject: [PATCH 2/8] =?UTF-8?q?chore:=20=E6=96=B0=E5=A2=9E=20Excel=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 执黑先行17个.xlsx | Bin 0 -> 24023 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 执黑先行17个.xlsx diff --git a/执黑先行17个.xlsx b/执黑先行17个.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..da6e324a0f23b80d349679c77ea65bfb5c95768a GIT binary patch literal 24023 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
~1l!*DOD#9N(a^S@efd#TpFd7_6r?7EFG6yT4t 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 Date: Sat, 16 May 2026 19:27:39 +0800 Subject: [PATCH 3/8] =?UTF-8?q?chore:=20=E7=A7=BB=E9=99=A4=20Excel=20?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 执黑先行17个.xlsx | Bin 24023 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 执黑先行17个.xlsx diff --git a/执黑先行17个.xlsx b/执黑先行17个.xlsx deleted file mode 100644 index da6e324a0f23b80d349679c77ea65bfb5c95768a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24023 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
P73GAcCaY~W%W%0eD%r$FUo%jr~> zYU;Ik3}Au2fPm%^-0wpjI8hmax6Bf7Wp!DL8h1XrkktmWBZPc{ih+SAh*ApltsIpu zq)p^X<|OUJ(r=W7p&KRQI&cs$E4WHP6D3y)Y!u*3x37<@)Nu_NM4xmU(O?q4HjXD( z3oTCaM&6VtWii`NGxYUyv>^v5osBa+gSqu*eg>mLgp3N-v9o0)&#Xwgg18*3A{_~M z94-LIT^Ur?kdltbCFFv~2K~NQpc2)UlDp>{mq%9hq#(rOl7-V&oZ5TlXxU`(E}C*pTkXr0br^ixYbQ_yYJX z5rTK$3KF|AWnn9(gzc9|kr%hTXzY@}U=OY;W2fi%m)VEQvFE{?|t7C&F+4vRU@YYU-0FX znzc*Vs*0Fq0XS25M^~uuZ>6NLJZv1PSC0~MpL7LFDcsfff)vaFi$m!A&c00#v${ToJ(~J zV?WYnXd(rdGvONJSk+5eK-CEGeL3-ccOmg&4$U;&VDACZ=CJqdxJe^x{Al%h%cU*1 zlh9ZMB0}?I>=W zB4x1nlc}x?!-dl^Tx}Lm+f0OnS%^XD*H$tW_$-UTrnu@_X(B{eitkzBKL=HH5*CwQ zD%ySKIrRJamg1m+82cayXxD*i+(--s-N812Y(_xX0EM#v-GXgSlN{@nw-VKG#sSn{ zWPBlR=-rDwkCv%4<*ncxo7|*r(>`gXG|d?hoNL0Q@q1~XE^rkRopdQZ4OLFVYoAb` z?igvV#+mr8Qo0l_6?+;20BY_llWriPMM4-js%;Rz)!Us{#DD8l+49kkQUF`{Dd+EG z2z?=Mv{BcTZ|;Gd(YNV7b~FCetn5{{%&#DxW3@n7hV4aVXVd8W+98V5o!J=+5nYyX zG}25>GP(CEVz+?j>a~?yr)86%6dpWsJccV5BrmpETx1pqHqfK8CO2-vewGZ3Ze)Ec|_Y>*pntfL|J4+74P`)EWHx7R?66ZSnR z6xub5x0z6|9=UDJRb-m@n*;b!*a&~=ynpA_67w?cEd;Xhl^?c9ei$1C^%s=KonE@Q z9(%&%xEErm#qS$7IJlWho$@wC{5rUz24oGu%h|xDWv11xh)^>z;hZZ&mnx54y5F9fpqJsk zNl=npvF?dDSaM{-1Nm0Xue=3T268o(TCP0ndsDLDzZXL`0cRwWvfW#RWb<U4zt4I1 zguZ1WL@iSQERhk@8Uh!@X!73cftDVwL)j8PdiaH3BSXLaB#vWF(1F(Dw3YY_gRbGx zS;9?HZ8#XRjlX~5b491})+R*to*TP@IfCc%T;mkGQ{Ip}B!vYszA_h+~|(>FB4c4X27A$2J8q4ER>&z5WS7+z%f5WEc?e2F2Du z6(TY?5p7I0Oo@q#5=)c4D(MQfK{4Go^izfck$En4&(O{vV5F15XUP3zREP{vfjoT8 z%KE4yWf?7J9FIK_5=L$(lt3^1gklE)_eaeZ7Z2QnMMl)}MJ^h*Vl@@o7ie9JPQA`y z2HJ(tIUe`A&dKei9B}nFht@_!LU|oufeb$xlF&Ql~nC5*USBM)zx<~NsLlxtaI+r-t-evS@awJj2BELw1l|`pIp4GQv_nw8MaV5%+MBFAy zS|kXRdt}d7UXY|c#hvFOdt?F&pZ?>WV}#T)3P-uawp=zTp; zEEoRxAQkUGKAkyK)GRphxJ<=O-QMH_XUK+SJXiyP-Ne2_Ktrbz@tftzA`wb zw&w_4gXZL;pt$XmsH$VTR%`-ff63FtWYl{zrfK8ITa-uEu{djso7nyuH>*76e>}yq z!%p?`Rq+c?0CDJsmoUsG?mIH!3Ip0hT1&<50zhqt_zZ!1nBK5L+d5!C%rXvPN5 zI>uXm|HVs|ScS(~r-6*_?#qPYv^st}0dj?LD3tbq_F0_;e|9-oT)hd}a0?y?%z<19 z6hki7Nyxp(wH})rfqb&ZyN%d*q$^ge$+kDJMV1Qw!51&>>*9AUBFkS^x4uKxHr4J_ zg7fqdz|wyY@3w`j{+gb`I9DIdy)_q02H$KwHUi(DwKFpY)C8ha?=&RA%R>H3nL%TV zOf&f13oE$Qo^*1vz0aU1cu!jueSGjwcIwRoE!}N}y-d>Np|zDS za&+>&oGG>NIVciKDM%;lnnXr~`z;MV6lY|LJ#R9pu47MOL&3NkJOHNAs*nP1MwR0UZeimbQD{IBcg_9mPp#X3G zGeUypqoTl9n7u&rs06l(jCfuT?tsXe6J@xW9xUEHLa~O{kIPVwiU?J zL;EY!Fi0=sZ8W2Kn*00+jtQJaitpp@6gNoUs>gP|YAbQIr1%zl9AGH9I z_36_?A(_Xd0^5ySX1%T<0O7M|%x1$86WFp2%sENf-&@9ZGtNPt2XnZBL|tB);r7@^ zL8Q6pIbL5Net`5LP)xk9MsV*g$UgHxO1ZX!qC|`Je9q`3Y2Y5eayH@b9(UNvE8u>+ zFUZW=KyjbWM}9!&aKNc5_4Q}9(pS@#eTJEg}Q-*XLqQCb>8U6hpMWiHhj^&~YzbBkT zJ>Xa9p{xonS2qR}M(+PURf=?oibQU`Dc+qg3~GD&@-YgK;!KDeWfLApUn=>{1(hZZU6ZiF9P@4a4m@B6Ob^T#u5 z=Ip({=j`X4J!|cCo=>>aQbp2B#W69x=*+bT5*BiJnV)-zi}GtB?CgD1-`(K4KRAQ- z`0i7*G$JqxtLRN_>uiKcz&^pyI}@SiBsdQb_;22{=IexP_@^Y~O6vo^$ynE5_gD&2 zc)g)%vE}lAg5XG^aWHD2D>c3RLk=VDwVDV>A8gb5LYDZfvF@l zM2;YZpj3PxDXVRfD?_JSDP?%Z6p{+iC)Foru$fpdv zNpwvqa;B5c_6<*|na19%&j|_8{4XG(id=8J+)eWzo1yB_^2$vImX>6)r9!8{AT-5x!RnIN z$lz35FGg7B{Yd{l6!{CncK5vm+}g$p;jmZR85xFN-fo3>e&5K0TvQp23=9viC*a92 z>BKQrCC}kbI(EeUa5yJ-$$cFj>U}7jOZSX*a+nor%35>8Pw-dvg@JAZ}7 zsVb>e_Fg{0$N87b53k26@$j?LBAv%gLm?YorN$RPis6^^s(_ZL%{OX9s*f&?HWoae zd?F(UaI3wGUf|4!S2QM*p6OU$SMkcU^xpPAgw2bo~g$wT!*>l~mX1G(^I zPSX|u&y~~<@xWTWtejU{8%?<3pLXmW^j7s2{Ax;7(qcC$;#33RJhv9`54t9}fIRK1 z);5NLi~5*tr^6OXt7{*2DVP
~1l!*DOD#9N(a^S@efd#TpFd7_6r?7EFG6yT4t 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 Date: Sat, 16 May 2026 19:41:37 +0800 Subject: [PATCH 4/8] =?UTF-8?q?chore(config):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=89=AA=E6=98=A0=E8=8D=89=E7=A8=BF=E8=B7=AF=E5=BE=84=E4=B8=BA?= =?UTF-8?q?macOS=E6=A0=BC=E5=BC=8F=EF=BC=8C=E7=AE=80=E5=8C=96=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E9=A3=8E=E6=A0=BC=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/skills/config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/config.json b/.claude/skills/config.json index 4c742f6..af2c6c9 100644 --- a/.claude/skills/config.json +++ b/.claude/skills/config.json @@ -1,6 +1,6 @@ { "jianyingDraftPath": "/Users/lc/Movies/JianyingPro/User Data/Projects/com.lveditor.draft", - "capcutMateDir": "C:/Users/45070/capcut-mate", + "capcutMateDir": "/Users/lc/capcut-mate", "capcutMateApiBase": "http://capcut.muyetools.cn/openapi/capcut-mate/v1", "imgbbApiKey": "deprecated", "geminiApiBaseUrl": "https://yunwu.ai", @@ -44,7 +44,7 @@ "id": "cosyvoice-v3-plus-bailian-155c1d86a5564d4ca981147d79e309b1", "model": "cosyvoice-v3-plus", "instruction": "用沉稳有力的男性声音朗读,语速适中,语气坚定有力,像是一个有经历有力量的人在平静地讲述生活的方向", - "style": "沉稳有力男声(v3-plus模型)" + "style": "沉稳有力男声" }, { "name": "六沉", From 4495ea8af16c30b80dba7134acaaf3cbd6c8311c Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sat, 16 May 2026 22:44:09 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat(video-from-script):=20=E4=B8=A5?= =?UTF-8?q?=E6=A0=BC=E5=B9=B6=E8=A1=8C=E6=8F=90=E4=BA=A4=E6=89=B9=E6=AC=A1?= =?UTF-8?q?=E9=99=90=E5=88=B6=E5=B9=B6=E5=A2=9E=E5=BC=BA=E9=A2=9D=E5=BA=A6?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 `isQuotaError` 函数,统一检测 API 额度不足错误 - Kling 生成器改为严格分批提交并等待每批轮询完成,限制并发 ≤ 5 - phase-videos 重构为分批提交+轮询模式,支持额度不足时提前终止 - 提取 `applyPollResult` 和 `pollOpts` 工具函数,减少代码重复 - 新增批量提交的进度日志,显示当前批次和总数 --- .../scripts/kling-video-generator.js | 111 ++++++++------ .../scripts/lib/phase-videos.js | 144 +++++++++--------- 2 files changed, 138 insertions(+), 117 deletions(-) diff --git a/.claude/skills/video-from-script/scripts/kling-video-generator.js b/.claude/skills/video-from-script/scripts/kling-video-generator.js index d90fffe..b6de80a 100644 --- a/.claude/skills/video-from-script/scripts/kling-video-generator.js +++ b/.claude/skills/video-from-script/scripts/kling-video-generator.js @@ -388,8 +388,14 @@ async function generate(imageUrl, prompt, options = {}) { // 批量并行生成(支持 manifest.json) // ============================================================================ +function isQuotaError(msg) { + if (!msg) return false + const s = msg.toLowerCase() + return /quota|limit|exceed|insufficient|余额|额度|超限|rate.?limit|too.?many/.test(s) +} + async function batchGenerate(tasks, options = {}) { - const { outputDir = './output', concurrency = 2, duration = 5, mode = 'std' } = options + const { outputDir = './output', concurrency = 5, duration = 5, mode = 'std' } = options fs.mkdirSync(outputDir, { recursive: true }) @@ -406,14 +412,19 @@ async function batchGenerate(tasks, options = {}) { })) } - // Phase 1: 并行提交 + // 严格并发 ≤ 5:每批提交后等轮询完成再提交下一批 + const batchSize = Math.min(concurrency, 5) const modeLabel = tasks.some(t => t.lastFrameUrl) ? '首尾帧' : '单图' - console.log(`\n📡 并行提交 ${tasks.length} 个可灵视频任务(并发: ${concurrency},模式: ${modeLabel})...`) + console.log(`\n📡 并行提交 ${tasks.length} 个可灵视频任务(并发: ${batchSize},模式: ${modeLabel})...`) - const submitted = [] - for (let i = 0; i < tasks.length; i += concurrency) { - const batch = tasks.slice(i, i + concurrency) - const batchResults = await Promise.allSettled( + const results = [] + + for (let i = 0; i < tasks.length; i += batchSize) { + const batch = tasks.slice(i, i + batchSize) + const batchLabel = `[${i + 1}-${Math.min(i + batchSize, tasks.length)}/${tasks.length}]` + + // 提交本批 + const submitResults = await Promise.allSettled( batch.map(async (task, j) => { const idx = i + j const prompt = task.videoPrompt || task.prompt @@ -429,50 +440,54 @@ async function batchGenerate(tasks, options = {}) { } }) ) - submitted.push(...batchResults.map(r => r.value || r.reason)) - } - const pendingTasks = submitted.filter(s => s.taskId) + const submitted = submitResults.map(r => r.status === 'fulfilled' ? r.value : r.reason) + const hitQuota = submitted.some(s => !s.taskId && isQuotaError(s.error)) + const pendingTasks = submitted.filter(s => s.taskId) - if (pendingTasks.length === 0) { - console.error('\n❌ 所有任务提交失败') - return tasks.map((task, idx) => ({ - success: false, ...task, - error: (submitted.find(s => s.idx === idx) || {}).error || '提交失败', - })) - } - - // Phase 2: 并行轮询 - console.log(`\n⏳ 并行等待 ${pendingTasks.length} 个视频生成...`) - - const pollResults = await Promise.allSettled( - pendingTasks.map(async ({ idx, taskId, task }) => { - const prompt = task.videoPrompt || task.prompt - const result = await pollWithRetry(taskId, prompt, { - outputDir, duration, mode, - imageUrl: task.image, lastFrameUrl: task.lastFrameUrl, - }) - return { idx, ...result, task } - }) - ) - - // 合并结果 - const results = [] - for (let i = 0; i < tasks.length; i++) { - const submittedInfo = submitted.find(s => s.idx === i) - if (!submittedInfo || !submittedInfo.taskId) { - results.push({ success: false, ...tasks[i], error: submittedInfo?.error || '提交失败' }) - continue + // 额度不足时:记录本批失败 + 跳过剩余 + if (hitQuota) { + const remaining = tasks.length - i - batch.length + for (const s of submitted) { + results.push({ success: false, ...s.task, error: s.error || '提交失败' }) + } + for (let j = i + batchSize; j < tasks.length; j++) { + results.push({ success: false, ...tasks[j], error: '额度不足,未提交' }) + } + console.log(`\n⚠️ 额度不足,跳过剩余 ${remaining + submitted.filter(s => !s.taskId).length} 个任务`) + break } - const pollResult = pollResults.find(r => { - if (r.status === 'fulfilled') return r.value.idx === i - return false - }) - if (pollResult && pollResult.status === 'fulfilled') { - results.push({ success: true, ...tasks[i], ...pollResult.value }) - } else { - const reason = pollResult?.reason?.message || '生成失败' - results.push({ success: false, ...tasks[i], error: reason }) + + // 本批全部提交失败,跳过轮询 + for (const s of submitted) { + if (!s.taskId) results.push({ success: false, ...s.task, error: s.error || '提交失败' }) + } + if (pendingTasks.length === 0) continue + + // 轮询本批 + console.log(`\n⏳ ${batchLabel} 等待 ${pendingTasks.length} 个视频生成...`) + + const pollResults = await Promise.allSettled( + pendingTasks.map(async ({ idx, taskId, task }) => { + const prompt = task.videoPrompt || task.prompt + const result = await pollWithRetry(taskId, prompt, { + outputDir, duration, mode, + imageUrl: task.image, lastFrameUrl: task.lastFrameUrl, + }) + return { idx, ...result, task } + }) + ) + + // 合并本批轮询结果 + for (const s of submitted) { + if (!s.taskId) continue + const pollResult = pollResults.find(r => r.status === 'fulfilled' && r.value.idx === s.idx) + if (pollResult) { + results.push({ success: true, ...s.task, ...pollResult.value }) + } else { + const reason = pollResults.find(r => r.value?.idx === s.idx || r.reason?.idx === s.idx)?.reason?.message || '生成失败' + results.push({ success: false, ...s.task, error: reason }) + } } } diff --git a/.claude/skills/video-from-script/scripts/lib/phase-videos.js b/.claude/skills/video-from-script/scripts/lib/phase-videos.js index 9cd7e6c..8222520 100644 --- a/.claude/skills/video-from-script/scripts/lib/phase-videos.js +++ b/.claude/skills/video-from-script/scripts/lib/phase-videos.js @@ -8,6 +8,30 @@ const path = require('path') const { saveManifest, ensureDir, log, getManifestDir } = require('./pipeline-utils') +function isQuotaError(msg) { + if (!msg) return false + const s = msg.toLowerCase() + return /quota|limit|exceed|insufficient|余额|额度|超限|rate.?limit|too.?many/.test(s) +} + +function applyPollResult(item, val, dir) { + if (val.ok && val.result?.file) { + item.video = path.relative(dir, val.result.file).replace(/\\/g, '/') + item.videoDuration = val.result.duration + item.status = 'done' + delete item.videoTaskId + } else if (val.item) { + if (val.isTaskFailure) { + item.status = 'failed' + item.error = val.error || '视频生成未返回文件' + delete item.videoTaskId + } else { + log('videos', ` item ${item.id} 生成超时(保留 taskId 待恢复): ${val.error}`) + item.status = 'pending' + } + } +} + async function phaseVideos(manifest, manifestPath, options) { const dir = getManifestDir(manifestPath) const videosDir = path.join(dir, 'videos') @@ -16,10 +40,6 @@ async function phaseVideos(manifest, manifestPath, options) { const accountConfig = options.accountConfig || {} const videoModel = manifest.videoModel || accountConfig.videoModel || 'veo3-fast-frames' - // 筛选需要生视频的 item: - // done — 正常流程,图片已确认且已上传 - // pending / failed — 重试场景,agent 只需将 item 设为 pending 即可触发再生 - // 前提:有 url(图片已上传)+ videoPrompt,且 confirmed 未被显式拒绝 const videoCandidates = manifest.items.filter(it => { if (it.confirmed === false) return false if (!it.url || !it.videoPrompt) return false @@ -44,7 +64,6 @@ async function phaseVideos(manifest, manifestPath, options) { console.log() } - // 已有视频(本地或 OSS)且状态为 done 的跳过,其余清理视频引用但保留 taskId 供恢复 const items = [] for (const it of videoCandidates) { if (it.video || it.videoUrl) { @@ -57,7 +76,6 @@ async function phaseVideos(manifest, manifestPath, options) { } if (items.length === 0) { log('videos', '无待处理 item,跳过'); return } - // 选择生成器 let Api, pollFn const modelLower = videoModel.toLowerCase() if (modelLower.includes('grok')) { @@ -72,6 +90,11 @@ async function phaseVideos(manifest, manifestPath, options) { } const ratio = manifest.format || '9:16' + const pollOpts = (item) => ({ + outputDir: videosDir, aspectRatio: ratio, + imageUrl: item.url, lastFrameUrl: item.lastFrameUrl || '', + }) + log('videos', `共 ${items.length} 个, 模型: ${videoModel}`) // Phase 1: 恢复已有任务(有 videoTaskId 的 item) @@ -92,12 +115,7 @@ async function phaseVideos(manifest, manifestPath, options) { recovered.map(async (item) => { try { log('videos', ` 恢复 item ${item.id}: ${item.videoTaskId}`) - const result = await pollFn(item.videoTaskId, item.videoPrompt, { - outputDir: videosDir, - aspectRatio: ratio, - imageUrl: item.url, - lastFrameUrl: item.lastFrameUrl || '', - }) + const result = await pollFn(item.videoTaskId, item.videoPrompt, pollOpts(item)) if (result.file) { item.video = path.relative(dir, result.file).replace(/\\/g, '/') item.videoDuration = result.duration @@ -106,8 +124,7 @@ async function phaseVideos(manifest, manifestPath, options) { log('videos', ` item ${item.id} 恢复成功`) } } catch (err) { - const isFail = err.isTaskFailure === true - if (isFail) { + if (err.isTaskFailure === true) { log('videos', ` item ${item.id} 恢复失败(任务失败): ${err.message},将重新提交`) delete item.videoTaskId needSubmit.push(item) @@ -123,14 +140,16 @@ async function phaseVideos(manifest, manifestPath, options) { if (needSubmit.length === 0) { log('videos', '全部通过恢复完成'); return } - // Phase 2: 提交新任务(并发 5,Kling 最大并发) + // Phase 2+3: 分批提交+轮询(严格并发 ≤ 5,等一批完成再提交下一批) const concurrency = 5 log('videos', `提交 ${needSubmit.length} 个新任务(并发: ${concurrency})...`) - const submitted = [] for (let i = 0; i < needSubmit.length; i += concurrency) { - const batch = needSubmit.slice(i, i + concurrency) - const batchResults = await Promise.allSettled( + const batch = needSubmit.slice(i, i + concurrency).filter(item => !item.videoTaskId) + if (batch.length === 0) continue + + // 提交本批 + const submitResults = await Promise.allSettled( batch.map(async (item) => { const extraOpts = item.lastFrameUrl ? { aspectRatio: ratio, lastFrameUrl: item.lastFrameUrl, mode: 'pro' } @@ -143,65 +162,52 @@ async function phaseVideos(manifest, manifestPath, options) { } }) ) - for (const r of batchResults) { + + const submitted = [] + let hitQuota = false + for (const r of submitResults) { const val = r.status === 'fulfilled' ? r.value : { item: null, taskId: null, error: r.reason } submitted.push(val) if (val.item && val.taskId) { val.item.videoTaskId = val.taskId - } - } - saveManifest(manifestPath, manifest) - } - - // Phase 3: 轮询新任务 - const pending = submitted.filter(s => s.taskId) - if (pending.length === 0) { - log('videos', '所有任务提交失败') - for (const s of submitted) { - if (s.item) { s.item.status = 'failed'; s.item.error = s.error || '提交失败' } - } - saveManifest(manifestPath, manifest) - return - } - - log('videos', `等待 ${pending.length} 个视频生成...`) - - const pollResults = await Promise.allSettled( - pending.map(async ({ item, taskId }) => { - try { - const result = await pollFn(taskId, item.videoPrompt, { - outputDir: videosDir, - aspectRatio: ratio, - imageUrl: item.url, - lastFrameUrl: item.lastFrameUrl || '', - }) - return { item, result, ok: true } - } catch (err) { - return { item, error: err.message, ok: false, isTaskFailure: err.isTaskFailure === true } - } - }) - ) - - for (const r of pollResults) { - const val = r.status === 'fulfilled' - ? r.value - : { ok: false, error: r.reason?.message || String(r.reason), isTaskFailure: r.reason?.isTaskFailure === true } - if (val.ok && val.result.file) { - val.item.video = path.relative(dir, val.result.file).replace(/\\/g, '/') - val.item.videoDuration = val.result.duration - val.item.status = 'done' - delete val.item.videoTaskId - } else if (val.item) { - if (val.isTaskFailure) { + } else if (val.item && !val.taskId) { val.item.status = 'failed' - val.item.error = val.error || '视频生成未返回文件' - delete val.item.videoTaskId - } else { - log('videos', ` item ${val.item.id} 生成超时(保留 taskId 待恢复): ${val.error}`) - val.item.status = 'pending' + val.item.error = val.error || '提交失败' + if (isQuotaError(val.error)) hitQuota = true } } saveManifest(manifestPath, manifest) + + if (hitQuota) { + log('videos', ` ⚠️ 额度不足,停止提交(跳过剩余 ${needSubmit.length - i - batch.length} 个任务)`) + break + } + + // 轮询本批 + const pending = submitted.filter(s => s.taskId) + if (pending.length === 0) continue + + const end = Math.min(i + concurrency, needSubmit.length) + log('videos', ` [${i + 1}-${end}/${needSubmit.length}] 等待 ${pending.length} 个视频生成...`) + + const pollResults = await Promise.allSettled( + pending.map(async ({ item, taskId }) => { + try { + const result = await pollFn(taskId, item.videoPrompt, pollOpts(item)) + return { item, result, ok: true } + } catch (err) { + return { item, error: err.message, ok: false, isTaskFailure: err.isTaskFailure === true } + } + }) + ) + + for (const r of pollResults) { + const val = r.status === 'fulfilled' + ? r.value + : { ok: false, error: r.reason?.message || String(r.reason), isTaskFailure: r.reason?.isTaskFailure === true } + applyPollResult(val.item || {}, val, dir) + } + saveManifest(manifestPath, manifest) } // 上传视频到 OSS From 65af6c92fcf279b198f72cfa48e25f9cfd3fae21 Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sun, 17 May 2026 16:50:26 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat(video-from-script):=20=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E7=94=9F=E6=88=90=E9=98=B6=E6=AE=B5=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E4=BB=BB=E5=8A=A1=E6=A3=80=E6=B5=8B=E4=B8=8E?= =?UTF-8?q?=E5=8E=9F=E5=AD=90=E7=8A=B6=E6=80=81=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在视频生成流水线中添加两项关键改进: 1. 在 `cmdNext` 中提前将条目状态标记为 `processing` 并持久化,防止同一行被多个并行进程重复取出处理 2. 在 `phaseVideos` 中增加磁盘兜底检测:对无 `video` 字段的条目,根据 `id` 扫描本地视频目录,若发现已有视频文件则恢复引用并跳过生成 3. 优化状态过滤逻辑:`done` 状态且已有视频文件的条目明确跳过并输出原因,减少冗余日志 --- .../scripts/batch-pipeline.js | 5 +++++ .../scripts/lib/phase-videos.js | 20 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.claude/skills/video-from-script/scripts/batch-pipeline.js b/.claude/skills/video-from-script/scripts/batch-pipeline.js index a41658f..0ae607d 100644 --- a/.claude/skills/video-from-script/scripts/batch-pipeline.js +++ b/.claude/skills/video-from-script/scripts/batch-pipeline.js @@ -304,6 +304,11 @@ function cmdNext(args) { return } + // 原子标记为 processing,防止同一行被重复取出 + item.status = 'processing' + batch.stats = calcStats(batch.items) + writeJson(manifestPath, batch) + const result = { done: false, row: item.row, diff --git a/.claude/skills/video-from-script/scripts/lib/phase-videos.js b/.claude/skills/video-from-script/scripts/lib/phase-videos.js index 8222520..e0f5f6c 100644 --- a/.claude/skills/video-from-script/scripts/lib/phase-videos.js +++ b/.claude/skills/video-from-script/scripts/lib/phase-videos.js @@ -43,6 +43,8 @@ async function phaseVideos(manifest, manifestPath, options) { const videoCandidates = manifest.items.filter(it => { if (it.confirmed === false) return false if (!it.url || !it.videoPrompt) return false + // 已有视频(本地文件或远程 URL)且状态为 done → 跳过,避免重复生成 + if (it.status === 'done' && (it.video || it.videoUrl)) return false return ['done', 'pending', 'failed'].includes(it.status) }) @@ -54,7 +56,9 @@ async function phaseVideos(manifest, manifestPath, options) { if (it.confirmed === false) reasons.push("confirmed=false") if (!it.url) reasons.push("缺少 url(图片未上传)") if (!it.videoPrompt) reasons.push("缺少 videoPrompt") - if (it.confirmed !== false && it.url && it.videoPrompt && !["done","pending","failed"].includes(it.status)) { + if (it.status === 'done' && (it.video || it.videoUrl)) { + reasons.push("视频已生成,已跳过") + } else if (!["done","pending","failed"].includes(it.status)) { reasons.push("status=" + (it.status || "undefined") + "(不在 done/pending/failed 中)") } console.log(" - item", it.id || manifest.items.indexOf(it), ":", reasons.length > 0 ? reasons.join(", ") : "已满足全部条件(不应在此)") @@ -66,6 +70,20 @@ async function phaseVideos(manifest, manifestPath, options) { const items = [] for (const it of videoCandidates) { + // 磁盘兜底:本地视频文件已存在则恢复引用并跳过 + if (!it.video && it.id) { + const fs = require('fs') + const existingVideos = fs.readdirSync(videosDir).filter(f => + f.includes('_item' + it.id + '_') || f.includes('_item' + it.id + '.') + ) + if (existingVideos.length > 0) { + it.video = 'videos/' + existingVideos[existingVideos.length - 1] + it.status = 'done' + delete it.videoTaskId + log('videos', ` item ${it.id} 发现已有视频文件 ${it.video},跳过生成`) + continue + } + } if (it.video || it.videoUrl) { if (it.status === 'done') continue delete it.video From ac6f110f283c8b33dbc3d2ea600207714a9787c6 Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sun, 17 May 2026 23:43:30 +0800 Subject: [PATCH 7/8] =?UTF-8?q?feat(video-from-script):=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E8=A7=A3=E6=9E=90=E8=8D=89=E7=A8=BF=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E5=B9=B6=E4=BF=AE=E5=A4=8D=E5=AF=BC=E5=87=BA=E7=A9=BA=E5=80=BC?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `cmdMark` 命令中,当标记为已完成状态且未提供草稿地址时,自动从子任务的 `manifestPath` 中读取 `draftUrl`;在 `cmdExport` 导出 CSV 时,使用 `resolveDraftUrl` 函数统一解析草稿地址,确保导出结果包含完整的公网可下载链接。 同时修改 `phaseAssemble` 阶段,使用 `BASE_URL` 和 `draftId` 构造公网可访问的绝对路径保存到 manifest 中,替代之前仅保存相对路径的方式。 --- .../scripts/batch-pipeline.js | 21 +++++++++++++++++-- .../scripts/lib/phase-assemble.js | 8 ++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.claude/skills/video-from-script/scripts/batch-pipeline.js b/.claude/skills/video-from-script/scripts/batch-pipeline.js index 0ae607d..850c9bb 100644 --- a/.claude/skills/video-from-script/scripts/batch-pipeline.js +++ b/.claude/skills/video-from-script/scripts/batch-pipeline.js @@ -20,6 +20,19 @@ const { SKILLS_DIR, ACCOUNTS_DIR, loadConfig, resolveVoice } = require('./lib/pi // output/ 在项目根的父级(美图/output/) const OUTPUT_BASE = path.join(SKILLS_DIR, '..', '..', '..', 'output') +// ============================================================================ +// 工具函数 +// ============================================================================ + +/** 从 batch item 解析草稿地址:自身 draftUrl 优先,否则读子任务 manifest */ +function resolveDraftUrl(item) { + if (item.draftUrl) return item.draftUrl + if (!item.manifestPath) return '' + try { + return readJson(path.resolve(item.manifestPath)).draftUrl || '' + } catch { return '' } +} + // ============================================================================ // CLI 参数解析 // ============================================================================ @@ -259,7 +272,11 @@ function cmdMark(args) { if (args.draftName) item.draftName = args.draftName if (args.forwardCopy) item.forwardCopy = args.forwardCopy if (args.hashtags) item.hashtags = args.hashtags - if (args.draftUrl) item.draftUrl = args.draftUrl + if (args.draftUrl) { + item.draftUrl = args.draftUrl + } else if (args.status === 'completed' && !item.draftUrl) { + item.draftUrl = resolveDraftUrl(item) || undefined + } batch.stats = calcStats(batch.items) writeJson(manifestPath, batch) @@ -376,7 +393,7 @@ function cmdExport(args) { 音色: item.voice || '', 转发文案带话题: forwardFull, 草稿名称: item.draftName || '', - 草稿地址: item.draftUrl || '', + 草稿地址: resolveDraftUrl(item), }) } diff --git a/.claude/skills/video-from-script/scripts/lib/phase-assemble.js b/.claude/skills/video-from-script/scripts/lib/phase-assemble.js index 35726d4..1fc6151 100644 --- a/.claude/skills/video-from-script/scripts/lib/phase-assemble.js +++ b/.claude/skills/video-from-script/scripts/lib/phase-assemble.js @@ -46,12 +46,14 @@ async function phaseAssemble(manifest, manifestPath, options) { try { const { assemble } = require('../capcut_assemble') + const { BASE_URL } = require('./capcut-api') const result = await assemble(assembleArgs) // 保存草稿地址到 manifest,供批量导出使用 - if (result && result.draftUrl) { - manifest.draftUrl = result.draftUrl + // 用 BASE_URL + draft_id 构造公网可下载的绝对路径 + if (result && result.draftId) { + manifest.draftUrl = `${BASE_URL}/get_draft?draft_id=${result.draftId}` fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8') - log('assemble', `草稿地址已保存: ${result.draftUrl}`) + log('assemble', `草稿地址已保存: ${manifest.draftUrl}`) } log('assemble', '成片完成') } catch (err) { From b82952149223efa0cb8df35c7eaf88747b90bfc3 Mon Sep 17 00:00:00 2001 From: lc Date: Wed, 20 May 2026 00:11:16 +0800 Subject: [PATCH 8/8] =?UTF-8?q?chore:=20=E5=90=88=E5=B9=B6=E8=BF=9C?= =?UTF-8?q?=E7=A8=8B=E6=9B=B4=E6=96=B0=E5=B9=B6=E8=A7=A3=E5=86=B3batch-pip?= =?UTF-8?q?eline.js=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- .claude/skills/config.json | 7 ++++ .../scripts/batch-pipeline.js | 35 +++++++++++-------- CLAUDE.md | 7 ++-- accounts/执黑先行/account.json | 4 +-- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/.claude/skills/config.json b/.claude/skills/config.json index af2c6c9..cf63b69 100644 --- a/.claude/skills/config.json +++ b/.claude/skills/config.json @@ -52,6 +52,13 @@ "model": "cosyvoice-v3.5-plus", "instruction": "音量由正常对话迅速增强至高喊,性格直率,情绪易激动且外露", "style": "直率激动,由低到高" + }, + { + "name": "谢尔比", + "id": "cosyvoice-v3.5-plus-bailian-2e272ca7a5784f0b8e79013c891fe23e", + "model": "cosyvoice-v3.5-plus", + "instruction": "用一种过来人的口吻说话,像是经历了太多懒得废话的大哥,语气里带点漫不经心和不耐烦。声音不用太用力,轻描淡写但每个字都砸在点上。偶尔轻哼一口气,显得很松弛。", + "style": "漫不经心,松弛有力" } ] } \ No newline at end of file diff --git a/.claude/skills/video-from-script/scripts/batch-pipeline.js b/.claude/skills/video-from-script/scripts/batch-pipeline.js index 850c9bb..082e405 100644 --- a/.claude/skills/video-from-script/scripts/batch-pipeline.js +++ b/.claude/skills/video-from-script/scripts/batch-pipeline.js @@ -381,12 +381,11 @@ function cmdExport(args) { } const forwardFull = [forwardBody, htags].filter(Boolean).join('') - // 选题列:topicA(方案A)> 旧字段 topic > 原 title - const topicDisplay = item.topicA || item.topic || item.title || '' rows.push({ row: item.row, - 选题: topicDisplay, + 选题一: item.topicA || item.topic || item.title || '', + 选题二: item.topicB || '', 脚本: script, 账号: item.account, 模式: item.mode, @@ -401,19 +400,26 @@ function cmdExport(args) { rows.sort((a, b) => a.row - b.row) const format = args.format || 'csv' - const dateStr = formatDate(new Date()) - const baseName = path.basename(manifestPath, '.json') + + // 生成文件名:账号名_MMDD_起号~止号 + const accountName = rows[0]?.账号 || batch.defaultAccount || 'unknown' + const dateMMDD = formatDate(new Date()).slice(4) // MMDD + const rowNums = rows.map(r => r.row) + const rowStart = String(Math.min(...rowNums)).padStart(2, '0') + const rowEnd = String(Math.max(...rowNums)).padStart(2, '0') + const exportName = `${accountName}_${dateMMDD}_${rowStart}~${rowEnd}` if (format === 'xlsx') { - exportXlsx(manifestPath, rows) + exportXlsx(manifestPath, rows, exportName) } else { - exportCsv(manifestPath, rows) + exportCsv(manifestPath, rows, exportName) } } -function exportCsv(manifestPath, rows) { - const outPath = manifestPath.replace('.json', '_export.csv') - const headers = ['选题', '脚本', '账号', '模式', '音色', '转发文案带话题', '草稿名称', '草稿地址'] +function exportCsv(manifestPath, rows, exportName) { + const batchDir = path.dirname(manifestPath) + const outPath = path.join(batchDir, `${exportName}.csv`) + const headers = ['选题一', '选题二', '脚本', '账号', '模式', '音色', '转发文案带话题', '草稿名称', '草稿地址'] const lines = [headers.join(',')] for (const r of rows) { @@ -437,10 +443,10 @@ function exportCsv(manifestPath, rows) { printTable(rows, headers) } -function exportXlsx(manifestPath, rows) { +function exportXlsx(manifestPath, rows, exportName) { try { const XLSX = require('xlsx') - const headers = ['选题', '脚本', '账号', '模式', '音色', '转发文案带话题', '草稿名称', '草稿地址'] + const headers = ['选题一', '选题二', '脚本', '账号', '模式', '音色', '转发文案带话题', '草稿名称', '草稿地址'] const data = rows.map(r => headers.map(h => r[h] || '')) data.unshift(headers) @@ -448,7 +454,8 @@ function exportXlsx(manifestPath, rows) { const wb = XLSX.utils.book_new() XLSX.utils.book_append_sheet(wb, ws, '视频清单') - const outPath = manifestPath.replace('.json', '_export.xlsx') + const batchDir = path.dirname(manifestPath) + const outPath = path.join(batchDir, `${exportName}.xlsx`) XLSX.writeFile(wb, outPath) console.log(`表格已导出: ${outPath}`) console.log(` 共 ${rows.length} 条记录`) @@ -458,7 +465,7 @@ function exportXlsx(manifestPath, rows) { } catch (err) { if (err.code === 'MODULE_NOT_FOUND') { console.warn('xlsx 模块未安装,改用 CSV 格式') - exportCsv(manifestPath, rows) + exportCsv(manifestPath, rows, exportName) } else { throw err } diff --git a/CLAUDE.md b/CLAUDE.md index 4759f18..9b4d00f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -131,11 +131,12 @@ node .claude/skills/video-from-script/scripts/batch-pipeline.js rename-drafts -- node .claude/skills/video-from-script/scripts/batch-pipeline.js export --file output/batch_XXX/batch-manifest.json ``` -输出 CSV 表格,列:`选题 | 脚本 | 账号 | 模式 | 音色 | 转发文案带话题 | 草稿名称` +输出 CSV 表格,列:`选题一 | 选题二 | 脚本 | 账号 | 模式 | 音色 | 转发文案带话题 | 草稿名称 | 草稿地址` -- **选题**列 = 方案A(封面双句) +- **选题一**列 = 方案A(封面双句) +- **选题二**列 = 方案B(封面短标题) - **草稿名称**列 = `账号名_月日_序号_方案B` -- CSV 文件路径:`batch-manifest_export.csv`(与 manifest 同目录) +- 导出文件命名:`账号名_MMDD_起号~止号.csv`,如 `执黑先行_0516_01~16.csv`(与 manifest 同目录) - 导出后询问用户是否打包草稿到桌面 ## 草稿箱改名 diff --git a/accounts/执黑先行/account.json b/accounts/执黑先行/account.json index 3178d44..5d0d7eb 100644 --- a/accounts/执黑先行/account.json +++ b/accounts/执黑先行/account.json @@ -12,9 +12,9 @@ "references": [] } }, - "ttsVoice": "斯内普", + "ttsVoice": "谢尔比", "ttsRate": 1.3, - "ttsInstruction": "用沉稳有力的男性声音朗读,语速适中,语气坚定有力,像是一个有经历有力量的人在平静地讲述生活的方向", + "ttsInstruction": "用一种过来人的口吻说话,像是经历了太多懒得废话的大哥,语气里带点漫不经心和不耐烦。声音不用太用力,轻描淡写但每个字都砸在点上。偶尔轻哼一口气,显得很松弛。", "storyboardPrompt": "prompts/分镜.md", "imageStylePrompt": "prompts/图片提示词.md", "videoStylePrompt": "prompts/视频提示词.md",