Commit 31f3de74 authored by Jeff nan's avatar Jeff nan

feat

parent 2c73270d
......@@ -700,11 +700,13 @@ function initVueApp(savedData) {
// 兼容数据根可能不是数组(深度搜索)
const subtitleList = this.extractArrayFromJson(subtitleJson, 'subtitle');
const translatedList = this.extractArrayFromJson(translatedJson, 'translated');
// 打开编辑前校准:末词 end 与句子 end 不一致时,以句子 end 为准
// 打开编辑器:只对齐「界面上」看到的时间(末词/翻译末段 → 句子 end)
// 真正写回文件要等「覆盖上传并重新生成」
this.calibrateSubtitleWordEndTimes(subtitleList);
this.subtitleEditor.subtitleList = subtitleList || [];
this.subtitleEditor.translatedList = translatedList || [];
this.subtitleEditor.mergedList = this.buildMergedList(this.subtitleEditor.subtitleList, this.subtitleEditor.translatedList);
this.alignEditorViewEndTimes(this.subtitleEditor.mergedList);
} catch (e) {
console.error(e);
this.$message.error('加载字幕/翻译JSON失败');
......@@ -924,12 +926,9 @@ function initVueApp(savedData) {
this.$set(words[0], 'start', this.serializeTimeByFormat(total, fmt));
}
} else if (d.type === 'sentence-end') {
// 句子 end ↔ 末词 end ↔ 翻译末段 end 保持一致
item.end = display;
if (words.length) {
const last = words[words.length - 1];
const fmt = this.detectTimeFormatFromArray([last]);
this.$set(last, 'end', this.serializeTimeByFormat(total, fmt));
}
this.syncSentenceEndToLastSegments(item, total);
} else if (d.type === 'word-start') {
const word = words[d.wordIndex];
if (!word) {
......@@ -947,7 +946,11 @@ function initVueApp(savedData) {
}
const fmt = this.detectTimeFormatFromArray([word]);
this.$set(word, 'end', this.serializeTimeByFormat(total, fmt));
if (d.wordIndex === words.length - 1) item.end = display;
// 改的是最后一个单词:同步句子 end + 翻译末段 end
if (d.wordIndex === words.length - 1) {
item.end = display;
this.syncSentenceEndToLastSegments(item, total, { skipWords: true });
}
} else {
this.$message.error('未知编辑类型');
return;
......@@ -957,7 +960,62 @@ function initVueApp(savedData) {
this.$message.success('时间已更新');
},
// 校准字幕:若句子 end 与最后一个单词 end 不一致,将末词 end 改为句子 end
// 编辑过程中:句子 end 同步到界面上的末词 / 翻译末段
syncSentenceEndToLastSegments(item, endSec, options = {}) {
if (!item || isNaN(endSec)) return;
const skipWords = !!(options && options.skipWords);
const skipTranslated = !!(options && options.skipTranslated);
const words = Array.isArray(item.sWordsRef) ? item.sWordsRef : [];
if (!skipWords && words.length) {
const last = words[words.length - 1];
if (last) {
const fmt = this.detectTimeFormatFromArray([last]);
this.$set(last, 'end', this.serializeTimeByFormat(endSec, fmt));
}
}
if (!skipTranslated) {
const tSegs = Array.isArray(item.tItemsRef) ? item.tItemsRef : [];
if (tSegs.length) {
const lastT = tSegs[tSegs.length - 1];
if (lastT) {
const fmtT = this.detectTimeFormatFromArray([lastT]);
this.$set(lastT, 'end', this.serializeTimeByFormat(endSec, fmtT));
}
}
}
},
// 打开编辑器:按界面句子 end,对齐原文末词 / 翻译末段(仅编辑视图,不落盘)
alignEditorViewEndTimes(mergedList) {
let fixed = 0;
(mergedList || []).forEach((item) => {
if (!item) return;
const endSec = this.parseTimeToSeconds(item.end);
if (isNaN(endSec)) return;
let changed = false;
const words = Array.isArray(item.sWordsRef) ? item.sWordsRef : [];
if (words.length) {
const last = words[words.length - 1];
const wordEndSec = this.parseTimeToSeconds(last && last.end);
if (isNaN(wordEndSec) || Math.abs(wordEndSec - endSec) >= 0.001) changed = true;
}
const tSegs = Array.isArray(item.tItemsRef) ? item.tItemsRef : [];
if (tSegs.length) {
const lastT = tSegs[tSegs.length - 1];
const tEndSec = this.parseTimeToSeconds(lastT && lastT.end);
if (isNaN(tEndSec) || Math.abs(tEndSec - endSec) >= 0.001) changed = true;
}
if (!changed) return;
this.syncSentenceEndToLastSegments(item, endSec);
fixed += 1;
});
if (fixed > 0) {
console.info(`[编辑视图] 已按句子 end 对齐 ${fixed} 句的末词/翻译末段`);
}
return fixed;
},
// 打开编辑器:原文末词 end 对齐到该句字幕 end(界面句子时间的来源)
calibrateSubtitleWordEndTimes(subtitleList) {
let fixed = 0;
(subtitleList || []).forEach((s) => {
......@@ -970,16 +1028,14 @@ function initVueApp(savedData) {
const lastWord = words[words.length - 1];
if (!lastWord) return;
const wordEndSec = this.parseTimeToSeconds(lastWord.end);
// 已一致(允许 1ms 误差)则跳过
if (!isNaN(wordEndSec) && Math.abs(wordEndSec - sentenceEndSec) < 0.001) return;
// 优先沿用末词原有时间格式;若末词无时间字段则回退到句子格式
const fmtSample = (lastWord.end != null || lastWord.start != null) ? [lastWord] : [s];
const fmt = this.detectTimeFormatFromArray(fmtSample);
lastWord.end = this.serializeTimeByFormat(sentenceEndSec, fmt);
fixed += 1;
});
if (fixed > 0) {
console.info(`[字幕校准] 已修正 ${fixed} 处末词结束时间`);
console.info(`[编辑视图] 已修正 ${fixed} 处末词结束时间`);
}
return fixed;
},
......@@ -1346,7 +1402,7 @@ function initVueApp(savedData) {
return inter / uni;
},
// 应用修改回原始数组
// 应用修改回原始数组(保存/重新生成时才真正按句子 end 对齐并写回)
applyEditorChanges() {
const merged = this.subtitleEditor.mergedList || [];
// 关键修复:根据 mergedList 重新构建数组,确保“拆分后的第二句”被真实写回
......@@ -1358,15 +1414,35 @@ function initVueApp(savedData) {
for (let i = 0; i < merged.length; i++) {
const m = merged[i];
if (!m) continue;
// 构建字幕条目
const sWords = Array.isArray(m.sWordsRef) ? m.sWordsRef : [];
// 计算 start/end(优先用分词的边界,否则回退到 m.start/m.end),以秒为内部统一单位
const sWords = Array.isArray(m.sWordsRef) ? m.sWordsRef.slice() : [];
const tSegs = Array.isArray(m.tItemsRef) ? m.tItemsRef.slice() : [];
// 以界面句子时间为准;保存时强制末词/翻译末段与之对齐
let sStartSec = this.parseTimeToSeconds(sWords.length ? sWords[0].start : m.start);
let sEndSec = this.parseTimeToSeconds(sWords.length ? sWords[sWords.length - 1].end : m.end);
// 底限保护
if (isNaN(sStartSec) && !isNaN(this.parseTimeToSeconds(m.start))) sStartSec = this.parseTimeToSeconds(m.start);
if (isNaN(sEndSec) && !isNaN(this.parseTimeToSeconds(m.end))) sEndSec = this.parseTimeToSeconds(m.end);
// 写回时恢复为原始格式
let sEndSec = this.parseTimeToSeconds(m.end);
if (isNaN(sEndSec) && sWords.length) {
sEndSec = this.parseTimeToSeconds(sWords[sWords.length - 1].end);
}
if (isNaN(sStartSec) && !isNaN(this.parseTimeToSeconds(m.start))) {
sStartSec = this.parseTimeToSeconds(m.start);
}
if (!isNaN(sEndSec)) {
if (sWords.length) {
const last = sWords[sWords.length - 1];
const fmt = this.detectTimeFormatFromArray([last]);
sWords[sWords.length - 1] = Object.assign({}, last, {
end: this.serializeTimeByFormat(sEndSec, fmt),
});
}
if (tSegs.length) {
const lastT = tSegs[tSegs.length - 1];
const fmtT = this.detectTimeFormatFromArray([lastT]);
tSegs[tSegs.length - 1] = Object.assign({}, lastT, {
end: this.serializeTimeByFormat(sEndSec, fmtT),
});
}
}
const sStartOut = this.serializeTimeByFormat(sStartSec, sFmt);
const sEndOut = this.serializeTimeByFormat(sEndSec, sFmt);
newSList.push({
......@@ -1375,11 +1451,11 @@ function initVueApp(savedData) {
words: sWords,
text: this.buildSentenceFromWords(sWords),
});
// 构建翻译条目
const tSegs = Array.isArray(m.tItemsRef) ? m.tItemsRef : [];
// 翻译的 start/end 也尽量从分段计算(内部仍以秒为单位)
let tStartSec = this.parseTimeToSeconds(tSegs.length ? tSegs[0].start : sStartSec);
let tEndSec = this.parseTimeToSeconds(tSegs.length ? tSegs[tSegs.length - 1].end : sEndSec);
if (isNaN(tStartSec)) tStartSec = sStartSec;
// 翻译条目 end 与句子 end 实际对齐(同一时间)
const tEndSec = !isNaN(sEndSec) ? sEndSec : this.parseTimeToSeconds(tSegs.length ? tSegs[tSegs.length - 1].end : sEndSec);
const tStartOut = this.serializeTimeByFormat(tStartSec, tFmt);
const tEndOut = this.serializeTimeByFormat(tEndSec, tFmt);
newTList.push({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment