|
@@ -153,6 +153,12 @@ function computeContentHash(content: string): string {
|
|
|
* 自动检测:当某个叶节点音频完成后,检查其所属1级章节下所有叶节点
|
|
* 自动检测:当某个叶节点音频完成后,检查其所属1级章节下所有叶节点
|
|
|
* 是否都已就绪,若是则自动触发音频合并到该章节。
|
|
* 是否都已就绪,若是则自动触发音频合并到该章节。
|
|
|
*/
|
|
*/
|
|
|
|
|
+// 正在合并中的章节集合(内存锁)。
|
|
|
|
|
+// 防止 AudioScanner 每 30s 对每个叶节点重复调用 tryAutoMerge 时,
|
|
|
|
|
+// 同一章节在上一次合并(异步 ffmpeg 数十秒)未完成前被并发重复触发,
|
|
|
|
|
+// 造成 ffmpeg 进程堆积、CPU 打满、临时文件撑爆磁盘。
|
|
|
|
|
+const mergingChapters = new Set<number>();
|
|
|
|
|
+
|
|
|
export async function tryAutoMerge(
|
|
export async function tryAutoMerge(
|
|
|
completedLeafId: number,
|
|
completedLeafId: number,
|
|
|
bookId: number | null,
|
|
bookId: number | null,
|
|
@@ -266,16 +272,25 @@ export async function tryAutoMerge(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 6. 触发合并
|
|
|
|
|
|
|
+ // 6. 触发合并(加内存锁,防止同一章节并发重复合并)
|
|
|
|
|
+ if (mergingChapters.has(chapterId)) {
|
|
|
|
|
+ // 该章节已有合并任务在进行中,跳过本次触发
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ mergingChapters.add(chapterId);
|
|
|
console.log(
|
|
console.log(
|
|
|
`[AutoMerge] 🎵 章节${chapterId}下所有${leafNodesUnderChapter.length}个叶节点音频已就绪,开始自动合并...`,
|
|
`[AutoMerge] 🎵 章节${chapterId}下所有${leafNodesUnderChapter.length}个叶节点音频已就绪,开始自动合并...`,
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- const mergedUrl = await mergeChapterAudios(chapterId);
|
|
|
|
|
- if (mergedUrl) {
|
|
|
|
|
- console.log(`[AutoMerge] ✅ 章节${chapterId}音频自动合并完成: ${mergedUrl}`);
|
|
|
|
|
- } else {
|
|
|
|
|
- console.warn(`[AutoMerge] ⚠️ 章节${chapterId}合并返回空结果`);
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ const mergedUrl = await mergeChapterAudios(chapterId);
|
|
|
|
|
+ if (mergedUrl) {
|
|
|
|
|
+ console.log(`[AutoMerge] ✅ 章节${chapterId}音频自动合并完成: ${mergedUrl}`);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.warn(`[AutoMerge] ⚠️ 章节${chapterId}合并返回空结果`);
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ mergingChapters.delete(chapterId);
|
|
|
}
|
|
}
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error(`[AutoMerge] 自动合并检测失败:`, err);
|
|
console.error(`[AutoMerge] 自动合并检测失败:`, err);
|