This commit is contained in:
949036910@qq.com
2026-05-31 02:29:18 +08:00
parent d6fefd6138
commit 620b022af0
3 changed files with 197 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
const fs = require("fs");
const path = require("path");
const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif"]);
function isImageFile(filePath) {
return IMAGE_EXT.has(path.extname(filePath).toLowerCase());
}
function listMediaFiles(folderPath) {
if (!folderPath || !fs.existsSync(folderPath)) return [];
const entries = fs.readdirSync(folderPath, { withFileTypes: true });
const files = [];
for (const entry of entries) {
if (!entry.isFile()) continue;
const ext = path.extname(entry.name).toLowerCase();
if (
[".mp4", ".mov", ".webm", ".mkv", ".m4v", ".avi", ...IMAGE_EXT].includes(ext)
) {
files.push(path.join(folderPath, entry.name));
}
}
return files.sort();
}
function pickFromFolder(selectionMode, folderPath, specifiedPath) {
if (selectionMode === "specified" && specifiedPath) {
return specifiedPath;
}
const files = listMediaFiles(folderPath);
if (!files.length) return null;
if (selectionMode === "sequential") {
return files[0];
}
return files[Math.floor(Math.random() * files.length)];
}
function buildSubtitleModeReplacements(records, subtitleModeConfig, probeVideoDuration) {
const mappings = Array.isArray(subtitleModeConfig?.mappings)
? subtitleModeConfig.mappings
: [];
const replacements = [];
for (const mapping of mappings) {
if (!mapping?.materialPath || !fs.existsSync(mapping.materialPath)) continue;
const indices = Array.isArray(mapping.subtitleIndices)
? mapping.subtitleIndices
: [];
if (!indices.length || !records.length) continue;
let minStart = Infinity;
let maxEnd = -Infinity;
for (const idx of indices) {
const record = records[idx];
if (!record) continue;
const startSec = Number(record.start ?? record.startTime ?? 0) / 1000;
const endSec = Number(record.end ?? record.endTime ?? 0) / 1000;
minStart = Math.min(minStart, startSec);
maxEnd = Math.max(maxEnd, endSec);
}
if (!Number.isFinite(minStart) || !Number.isFinite(maxEnd)) continue;
const duration = maxEnd - minStart;
if (duration <= 0) continue;
const isImage = mapping.isImage ?? isImageFile(mapping.materialPath);
const materialDuration = isImage
? duration
: probeVideoDuration(mapping.materialPath);
replacements.push({
startTime: minStart,
duration,
materialPath: mapping.materialPath,
materialDuration,
materialStartOffset: 0,
displayMode: mapping.displayMode || "fullscreen",
pipPosition: mapping.pipPosition || "bottom-right",
pipSizePercent: mapping.pipSizePercent ?? 30,
pipScaleMode: mapping.pipScaleMode || "fit",
isImage,
originalVideoMinimized: mapping.originalVideoMinimized,
});
}
return replacements.sort((a, b) => a.startTime - b.startTime);
}
function buildFixedModeReplacements(fixedModeConfig, videoDuration, probeVideoDuration) {
const replacements = [];
if (!fixedModeConfig?.enabled) return replacements;
const segments = [
{ key: "opening", reason: "片头" },
{ key: "middle", reason: "片中" },
{ key: "ending", reason: "片尾" },
];
for (const { key, reason } of segments) {
const seg = fixedModeConfig[key];
if (!seg?.enabled) continue;
const min = Number(seg.timeRange?.durationMin ?? 2);
const max = Number(seg.timeRange?.durationMax ?? 5);
let startTime = 0;
let duration = max - min;
if (key === "middle") {
const mid = videoDuration / 2;
startTime = Math.max(0, mid + min);
duration = Math.min(videoDuration, mid + max) - startTime;
} else if (key === "ending") {
duration = max - min;
startTime = Math.max(0, videoDuration - duration);
} else {
startTime = min;
duration = max - min;
}
if (duration <= 0 || startTime >= videoDuration) continue;
const materialPath = pickFromFolder(
seg.selectionMode || "random",
seg.materialFolderPath,
seg.specifiedMaterialPath,
);
if (!materialPath || !fs.existsSync(materialPath)) continue;
const isImage = isImageFile(materialPath);
replacements.push({
startTime,
duration,
materialPath,
materialDuration: isImage ? duration : probeVideoDuration(materialPath),
materialStartOffset: 0,
displayMode: seg.displayMode || "fullscreen",
pipPosition: seg.pipPosition || "bottom-right",
pipSizePercent: seg.pipSizePercent ?? 30,
pipScaleMode: seg.pipScaleMode || "fit",
isImage,
originalVideoMinimized: seg.originalVideoMinimized,
reason,
});
}
return replacements.sort((a, b) => a.startTime - b.startTime);
}
function buildReplacementsFromMixCutSettings(settings, videoDuration, probeVideoDuration) {
if (!settings || typeof settings !== "object" || !settings.enabled) {
return [];
}
const mode = settings.mode || "subtitle";
let replacements = [];
if (mode === "subtitle") {
const records = Array.isArray(settings.records) ? settings.records : [];
replacements = buildSubtitleModeReplacements(
records,
settings.subtitleModeConfig,
probeVideoDuration,
);
} else if (mode === "fixed" && settings.fixedModeConfig?.enabled) {
replacements = buildFixedModeReplacements(
settings.fixedModeConfig,
videoDuration,
probeVideoDuration,
);
}
return replacements;
}
module.exports = {
buildReplacementsFromMixCutSettings,
buildSubtitleModeReplacements,
buildFixedModeReplacements,
};

View File

@@ -422,6 +422,16 @@ globalThis.__nodejsMain = async function main(params) {
srtPath = makeOutputPath("subtitle", "srt"); srtPath = makeOutputPath("subtitle", "srt");
fs.writeFileSync(srtPath, buildSrtContent(records, maxChars), "utf8"); fs.writeFileSync(srtPath, buildSrtContent(records, maxChars), "utf8");
if (p.recognizeOnly) {
pipelineNative.deleteFile(audioPath);
return {
success: true,
records,
srtPath,
message: `字幕识别完成,共 ${records.length}`,
};
}
const { width, height } = probeVideoSize(current); const { width, height } = probeVideoSize(current);
const assPath = makeOutputPath("subtitle", "ass"); const assPath = makeOutputPath("subtitle", "ass");
fs.writeFileSync( fs.writeFileSync(

View File

@@ -8,6 +8,9 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const os = require("os"); const os = require("os");
const { spawnSync } = require("child_process"); const { spawnSync } = require("child_process");
const {
buildReplacementsFromMixCutSettings,
} = require("./mixcut_replacements.js");
const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif"]); const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif"]);
@@ -434,6 +437,14 @@ function normalizeReplacements(raw) {
function pickReplacementsFromSettings(settings, videoDuration) { function pickReplacementsFromSettings(settings, videoDuration) {
if (!settings || typeof settings !== "object") return []; if (!settings || typeof settings !== "object") return [];
const fromMixCut = buildReplacementsFromMixCutSettings(
settings,
videoDuration,
probeVideoDuration,
);
if (fromMixCut.length) {
return normalizeReplacements(fromMixCut);
}
if (Array.isArray(settings.replacements) && settings.replacements.length) { if (Array.isArray(settings.replacements) && settings.replacements.length) {
return normalizeReplacements(settings.replacements); return normalizeReplacements(settings.replacements);
} }