From b16053c46e538b9084626c7cf1a900aa6badaa9b Mon Sep 17 00:00:00 2001 From: "949036910@qq.com" <> Date: Sun, 31 May 2026 23:03:35 +0800 Subject: [PATCH] 111 --- .../workflow/MixCutSettingPanel.vue | 87 +++++++++++-------- src/services/mixCutSettings.js | 58 +++++++------ src/utils/mixCutDefaults.js | 45 ++++++++++ 3 files changed, 127 insertions(+), 63 deletions(-) diff --git a/src/components/workflow/MixCutSettingPanel.vue b/src/components/workflow/MixCutSettingPanel.vue index af4f125..e83ee2b 100644 --- a/src/components/workflow/MixCutSettingPanel.vue +++ b/src/components/workflow/MixCutSettingPanel.vue @@ -18,8 +18,10 @@ import { revealLocalFileInFolder } from "../../services/fileExport.js"; import { createDefaultGlobalDisplay, createDefaultMixCutSettings, + deriveGlobalDisplayFromSettings, MINIMIZED_POSITION_OPTIONS, PIP_POSITION_OPTIONS, + PIP_SCALE_MODE_OPTIONS, SELECTION_MODE_OPTIONS, } from "../../utils/mixCutDefaults.js"; import { formatRecordTimeRange } from "../../utils/srtParser.js"; @@ -52,12 +54,6 @@ const displayModeOptions = [ { label: "画中画", value: "pip" }, ]; -const scaleModeOptions = [ - { label: "适应", value: "fit" }, - { label: "填充", value: "fill" }, - { label: "拉伸", value: "stretch" }, -]; - const shapeOptions = [ { label: "方形", value: "square" }, { label: "圆形", value: "circle" }, @@ -101,10 +97,7 @@ function applyIncomingSettings(raw) { settings.value = { ...createDefaultMixCutSettings(), ...raw }; records.value = Array.isArray(raw.records) ? [...raw.records] : []; subtitleFilePath.value = raw.subtitleFilePath || ""; - globalDisplay.value = { - ...createDefaultGlobalDisplay(), - displayMode: raw.displayMode === "pip" ? "pip" : "fullscreen", - }; + globalDisplay.value = deriveGlobalDisplayFromSettings(raw); } async function loadExistingSubtitleLines() { @@ -185,7 +178,12 @@ function onPickLibraryForLine(lineIndex) { async function onPickLocalForLine(lineIndex) { const path = await pickLocalMixCutMaterial(); if (!path) return; - bindMaterialToSubtitleLine(settings.value, lineIndex, path); + bindMaterialToSubtitleLine( + settings.value, + lineIndex, + path, + globalDisplay.value, + ); showFeedback("success", "素材已选择"); } @@ -203,11 +201,21 @@ function onMaterialSelected(item) { ? Array.from(selectedLineIndices.value) : [target.lineIndex]; if (indices.length > 1) { - bindMaterialToSubtitleLines(settings.value, indices, item.path); + bindMaterialToSubtitleLines( + settings.value, + indices, + item.path, + globalDisplay.value, + ); showFeedback("success", "已为选中的多条字幕绑定同一个素材"); selectedLineIndices.value = new Set(); } else { - bindMaterialToSubtitleLine(settings.value, target.lineIndex, item.path); + bindMaterialToSubtitleLine( + settings.value, + target.lineIndex, + item.path, + globalDisplay.value, + ); showFeedback("success", "素材已选择"); } } else if (target.type === "fixed-segment") { @@ -362,16 +370,19 @@ defineExpose({ />
- 💡 提示:修改上述设置后,点击「应用到所有素材」按钮,将统一应用到固定模式、关键词模式和字幕模式的所有素材配置 + 💡 提示:保存时将自动应用上述设置到所有素材;也可点击「应用到所有素材」立即预览效果
diff --git a/src/services/mixCutSettings.js b/src/services/mixCutSettings.js index 01b8c05..4212598 100644 --- a/src/services/mixCutSettings.js +++ b/src/services/mixCutSettings.js @@ -4,7 +4,9 @@ import { open } from "@tauri-apps/plugin-dialog"; import { listMaterials } from "./materialDb.js"; import { + createDefaultGlobalDisplay, createDefaultMixCutSettings, + createDefaultOriginalVideoMinimized, normalizeMixCutSettings, } from "../utils/mixCutDefaults.js"; import { parseSrtToRecords } from "../utils/srtParser.js"; @@ -346,18 +348,25 @@ export async function listMixCutMaterialLibrary() { } /** - * @param {object} settings * @param {object} globalDisplay */ -export function applyGlobalDisplayToAllMaterials(settings, globalDisplay) { - const g = globalDisplay; - const patch = { +export function createMaterialDisplayPatch(globalDisplay) { + const g = globalDisplay || createDefaultGlobalDisplay(); + return { displayMode: g.displayMode, pipPosition: g.pipPosition, pipSizePercent: g.pipSizePercent, pipScaleMode: g.pipScaleMode, - originalVideoMinimized: { ...g.originalVideoMinimized }, + originalVideoMinimized: { ...(g.originalVideoMinimized || createDefaultOriginalVideoMinimized()) }, }; +} + +/** + * @param {object} settings + * @param {object} globalDisplay + */ +export function applyGlobalDisplayToAllMaterials(settings, globalDisplay) { + const patch = createMaterialDisplayPatch(globalDisplay); ["opening", "middle", "ending"].forEach((key) => { Object.assign(settings.fixedModeConfig[key], patch); @@ -389,8 +398,14 @@ export function findMappingForLine(settings, lineIndex) { * @param {object} settings * @param {number} lineIndex * @param {string} materialPath + * @param {object} [globalDisplay] */ -export function bindMaterialToSubtitleLine(settings, lineIndex, materialPath) { +export function bindMaterialToSubtitleLine( + settings, + lineIndex, + materialPath, + globalDisplay, +) { const mappings = settings.subtitleModeConfig.mappings; const existing = findMappingForLine(settings, lineIndex); const isImage = isImageMaterialPath(materialPath); @@ -410,16 +425,7 @@ export function bindMaterialToSubtitleLine(settings, lineIndex, materialPath) { materialPath, isImage, selectionMode: "specified", - displayMode: settings.displayMode || "fullscreen", - pipPosition: "bottom-right", - pipSizePercent: 30, - pipScaleMode: "fit", - originalVideoMinimized: { - enabled: false, - shape: "square", - position: "bottom-right", - sizePercent: 25, - }, + ...createMaterialDisplayPatch(globalDisplay), }; filtered.push(created); settings.subtitleModeConfig.mappings = filtered; @@ -430,8 +436,14 @@ export function bindMaterialToSubtitleLine(settings, lineIndex, materialPath) { * @param {object} settings * @param {number[]} lineIndices * @param {string} materialPath + * @param {object} [globalDisplay] */ -export function bindMaterialToSubtitleLines(settings, lineIndices, materialPath) { +export function bindMaterialToSubtitleLines( + settings, + lineIndices, + materialPath, + globalDisplay, +) { const indices = [...new Set(lineIndices)].sort((a, b) => a - b); if (!indices.length) return; @@ -445,16 +457,7 @@ export function bindMaterialToSubtitleLines(settings, lineIndices, materialPath) materialPath, isImage, selectionMode: "specified", - displayMode: settings.displayMode || "fullscreen", - pipPosition: "bottom-right", - pipSizePercent: 30, - pipScaleMode: "fit", - originalVideoMinimized: { - enabled: false, - shape: "square", - position: "bottom-right", - sizePercent: 25, - }, + ...createMaterialDisplayPatch(globalDisplay), }); } @@ -472,6 +475,7 @@ export function removeMaterialFromSubtitleLine(settings, lineIndex) { * @param {object} settings */ export function prepareMixCutSettingsForSave(settings, globalDisplay) { + applyGlobalDisplayToAllMaterials(settings, globalDisplay); const next = normalizeMixCutSettings(settings); next.fixedModeConfig.enabled = next.mode === "fixed"; next.keywordModeConfig.enabled = next.mode === "keyword"; diff --git a/src/utils/mixCutDefaults.js b/src/utils/mixCutDefaults.js index 3c741bf..e6ec7c8 100644 --- a/src/utils/mixCutDefaults.js +++ b/src/utils/mixCutDefaults.js @@ -198,6 +198,51 @@ export const PIP_POSITION_OPTIONS = [ { label: "右下角", value: "bottom-right" }, ]; +export const PIP_SCALE_MODE_OPTIONS = [ + { label: "保持比例+黑边", value: "fit" }, + { label: "保持比例+裁剪", value: "fill" }, + { label: "强制拉伸", value: "stretch" }, +]; + +/** + * 从已保存的混剪配置推断全局显示设置(对齐 Electron 加载后 ue 的展示) + * @param {object | null | undefined} settings + */ +export function deriveGlobalDisplayFromSettings(settings) { + const base = createDefaultGlobalDisplay(); + if (!settings || typeof settings !== "object") return base; + + const candidates = [ + settings.fixedModeConfig?.opening, + ...(Array.isArray(settings.keywordModeConfig?.groups) + ? settings.keywordModeConfig.groups + : []), + ...(Array.isArray(settings.subtitleModeConfig?.mappings) + ? settings.subtitleModeConfig.mappings + : []), + ].filter(Boolean); + + const source = candidates.find((c) => c.displayMode) || candidates[0]; + const displayMode = + source?.displayMode || + (settings.displayMode === "pip" ? "pip" : "fullscreen"); + + if (!source) { + return { ...base, displayMode }; + } + + return { + displayMode, + pipPosition: source.pipPosition || base.pipPosition, + pipSizePercent: source.pipSizePercent ?? base.pipSizePercent, + pipScaleMode: source.pipScaleMode || base.pipScaleMode, + originalVideoMinimized: { + ...base.originalVideoMinimized, + ...(source.originalVideoMinimized || {}), + }, + }; +} + export const MINIMIZED_POSITION_OPTIONS = [ { label: "左上角", value: "top-left" }, { label: "右上角", value: "top-right" },