暂时OK,可可以高亮
Browse files
youtube_sub.js
CHANGED
|
@@ -86,25 +86,30 @@ GM_addStyle(`
|
|
| 86 |
|
| 87 |
.subtitle-line {
|
| 88 |
padding: 12px 15px;
|
| 89 |
-
margin:
|
| 90 |
cursor: pointer;
|
| 91 |
border-radius: 4px;
|
| 92 |
transition: background-color 0.2s ease, transform 0.1s ease;
|
| 93 |
position: relative;
|
| 94 |
white-space: pre-wrap;
|
| 95 |
word-wrap: break-word;
|
| 96 |
-
line-height: 1.
|
| 97 |
width: 410px;
|
| 98 |
font-size: 14px;
|
| 99 |
}
|
| 100 |
|
| 101 |
-
.subtitle-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
| 104 |
}
|
| 105 |
|
| 106 |
-
.subtitle-
|
| 107 |
-
background: rgba(255, 255, 255, 0.
|
|
|
|
|
|
|
| 108 |
}
|
| 109 |
|
| 110 |
.mixed-line {
|
|
@@ -166,6 +171,20 @@ GM_addStyle(`
|
|
| 166 |
.subtitle-content::-webkit-scrollbar-thumb:hover {
|
| 167 |
background: rgba(255, 255, 255, 0.4);
|
| 168 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
`;
|
| 170 |
|
| 171 |
// Apply main styles
|
|
@@ -367,9 +386,13 @@ GM_addStyle(`
|
|
| 367 |
.map(event => ({
|
| 368 |
startTime: event.tStartMs / 1000,
|
| 369 |
endTime: (event.tStartMs + event.dDurationMs) / 1000,
|
| 370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
}))
|
| 372 |
-
.filter(sub => sub.
|
| 373 |
|
| 374 |
debug.log(`Loaded ${subtitles.length} subtitles`);
|
| 375 |
return subtitles;
|
|
@@ -526,19 +549,30 @@ GM_addStyle(`
|
|
| 526 |
this.container.classList.toggle('collapsed', this.isCollapsed);
|
| 527 |
}
|
| 528 |
|
|
|
|
| 529 |
displaySingleLanguage(container, subtitles) {
|
| 530 |
subtitles.forEach((sub, index) => {
|
| 531 |
const line = document.createElement('div');
|
| 532 |
line.className = 'subtitle-line';
|
| 533 |
|
| 534 |
-
|
| 535 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
|
| 537 |
const timestamp = document.createElement('span');
|
| 538 |
timestamp.className = 'timestamp';
|
| 539 |
timestamp.textContent = this.formatTime(sub.startTime);
|
| 540 |
|
| 541 |
-
line.appendChild(text);
|
| 542 |
line.appendChild(timestamp);
|
| 543 |
line.dataset.time = sub.startTime;
|
| 544 |
line.dataset.index = index;
|
|
@@ -679,6 +713,37 @@ GM_addStyle(`
|
|
| 679 |
return active[0].index;
|
| 680 |
};
|
| 681 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
const handleTimeUpdate = (video) => {
|
| 683 |
const now = performance.now();
|
| 684 |
if (now - lastUpdate < updateThreshold) {
|
|
@@ -693,6 +758,11 @@ GM_addStyle(`
|
|
| 693 |
|
| 694 |
const newIndex = findActiveSubtitle(currentTime, subtitles);
|
| 695 |
if (newIndex === lastActiveIndex) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
rafId = requestAnimationFrame(() => handleTimeUpdate(video));
|
| 697 |
return;
|
| 698 |
}
|
|
@@ -703,12 +773,14 @@ GM_addStyle(`
|
|
| 703 |
const isActive = lineIndex === newIndex;
|
| 704 |
|
| 705 |
line.classList.toggle('active', isActive);
|
| 706 |
-
if (isActive
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
|
|
|
|
|
|
| 712 |
}
|
| 713 |
});
|
| 714 |
|
|
|
|
| 86 |
|
| 87 |
.subtitle-line {
|
| 88 |
padding: 12px 15px;
|
| 89 |
+
margin: 4px 0;
|
| 90 |
cursor: pointer;
|
| 91 |
border-radius: 4px;
|
| 92 |
transition: background-color 0.2s ease, transform 0.1s ease;
|
| 93 |
position: relative;
|
| 94 |
white-space: pre-wrap;
|
| 95 |
word-wrap: break-word;
|
| 96 |
+
line-height: 1.4;
|
| 97 |
width: 410px;
|
| 98 |
font-size: 14px;
|
| 99 |
}
|
| 100 |
|
| 101 |
+
.subtitle-word {
|
| 102 |
+
display: inline;
|
| 103 |
+
padding: 0 1px;
|
| 104 |
+
border-radius: 2px;
|
| 105 |
+
transition: all 0.15s ease;
|
| 106 |
+
opacity: 0.7;
|
| 107 |
}
|
| 108 |
|
| 109 |
+
.subtitle-word.active {
|
| 110 |
+
background: rgba(255, 255, 255, 0.3);
|
| 111 |
+
color: #fff;
|
| 112 |
+
opacity: 1;
|
| 113 |
}
|
| 114 |
|
| 115 |
.mixed-line {
|
|
|
|
| 171 |
.subtitle-content::-webkit-scrollbar-thumb:hover {
|
| 172 |
background: rgba(255, 255, 255, 0.4);
|
| 173 |
}
|
| 174 |
+
|
| 175 |
+
.subtitle-word {
|
| 176 |
+
display: inline-block;
|
| 177 |
+
padding: 0 1px;
|
| 178 |
+
border-radius: 2px;
|
| 179 |
+
transition: all 0.15s ease;
|
| 180 |
+
opacity: 0.7;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
.subtitle-word.active {
|
| 184 |
+
background: rgba(255, 255, 255, 0.3);
|
| 185 |
+
color: #fff;
|
| 186 |
+
opacity: 1;
|
| 187 |
+
}
|
| 188 |
`;
|
| 189 |
|
| 190 |
// Apply main styles
|
|
|
|
| 386 |
.map(event => ({
|
| 387 |
startTime: event.tStartMs / 1000,
|
| 388 |
endTime: (event.tStartMs + event.dDurationMs) / 1000,
|
| 389 |
+
words: event.segs.map(seg => ({
|
| 390 |
+
text: seg.utf8,
|
| 391 |
+
startTime: seg.tOffsetMs ? (event.tStartMs + seg.tOffsetMs) / 1000 : event.tStartMs / 1000,
|
| 392 |
+
endTime: (seg.tOffsetMs ? event.tStartMs + seg.tOffsetMs + (seg.dDurationMs || 0) : event.tStartMs + event.dDurationMs) / 1000
|
| 393 |
+
}))
|
| 394 |
}))
|
| 395 |
+
.filter(sub => sub.words.length);
|
| 396 |
|
| 397 |
debug.log(`Loaded ${subtitles.length} subtitles`);
|
| 398 |
return subtitles;
|
|
|
|
| 549 |
this.container.classList.toggle('collapsed', this.isCollapsed);
|
| 550 |
}
|
| 551 |
|
| 552 |
+
// Update displaySingleLanguage
|
| 553 |
displaySingleLanguage(container, subtitles) {
|
| 554 |
subtitles.forEach((sub, index) => {
|
| 555 |
const line = document.createElement('div');
|
| 556 |
line.className = 'subtitle-line';
|
| 557 |
|
| 558 |
+
sub.words.forEach((word, i) => {
|
| 559 |
+
const wordSpan = document.createElement('span');
|
| 560 |
+
wordSpan.className = 'subtitle-word';
|
| 561 |
+
wordSpan.textContent = word.text.trim();
|
| 562 |
+
wordSpan.dataset.start = word.startTime;
|
| 563 |
+
wordSpan.dataset.end = word.endTime;
|
| 564 |
+
line.appendChild(wordSpan);
|
| 565 |
+
|
| 566 |
+
// Add space between words
|
| 567 |
+
if (i < sub.words.length - 1) {
|
| 568 |
+
line.appendChild(document.createTextNode(' '));
|
| 569 |
+
}
|
| 570 |
+
});
|
| 571 |
|
| 572 |
const timestamp = document.createElement('span');
|
| 573 |
timestamp.className = 'timestamp';
|
| 574 |
timestamp.textContent = this.formatTime(sub.startTime);
|
| 575 |
|
|
|
|
| 576 |
line.appendChild(timestamp);
|
| 577 |
line.dataset.time = sub.startTime;
|
| 578 |
line.dataset.index = index;
|
|
|
|
| 713 |
return active[0].index;
|
| 714 |
};
|
| 715 |
|
| 716 |
+
const highlightWords = (currentTime, activeLine) => {
|
| 717 |
+
if (!activeLine) return;
|
| 718 |
+
|
| 719 |
+
const words = Array.from(activeLine.querySelectorAll('.subtitle-word'));
|
| 720 |
+
|
| 721 |
+
// Sort words by start time
|
| 722 |
+
words.sort((a, b) =>
|
| 723 |
+
parseFloat(a.dataset.start) - parseFloat(b.dataset.start)
|
| 724 |
+
);
|
| 725 |
+
|
| 726 |
+
let hasActiveWord = false;
|
| 727 |
+
|
| 728 |
+
words.forEach((word, index) => {
|
| 729 |
+
const start = parseFloat(word.dataset.start);
|
| 730 |
+
const end = parseFloat(word.dataset.end);
|
| 731 |
+
const isActive = currentTime >= start && currentTime <= end;
|
| 732 |
+
|
| 733 |
+
if (isActive) {
|
| 734 |
+
hasActiveWord = true;
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
// Also highlight previous words when current word is active
|
| 738 |
+
word.classList.toggle('active', hasActiveWord ?
|
| 739 |
+
currentTime >= start || index < words.findIndex(w =>
|
| 740 |
+
currentTime >= parseFloat(w.dataset.start) &&
|
| 741 |
+
currentTime <= parseFloat(w.dataset.end)
|
| 742 |
+
) : false
|
| 743 |
+
);
|
| 744 |
+
});
|
| 745 |
+
};
|
| 746 |
+
|
| 747 |
const handleTimeUpdate = (video) => {
|
| 748 |
const now = performance.now();
|
| 749 |
if (now - lastUpdate < updateThreshold) {
|
|
|
|
| 758 |
|
| 759 |
const newIndex = findActiveSubtitle(currentTime, subtitles);
|
| 760 |
if (newIndex === lastActiveIndex) {
|
| 761 |
+
// Still highlight words even if subtitle hasn't changed
|
| 762 |
+
const activeLine = this.container.querySelector('.subtitle-line.active');
|
| 763 |
+
if (activeLine) {
|
| 764 |
+
highlightWords(currentTime, activeLine);
|
| 765 |
+
}
|
| 766 |
rafId = requestAnimationFrame(() => handleTimeUpdate(video));
|
| 767 |
return;
|
| 768 |
}
|
|
|
|
| 773 |
const isActive = lineIndex === newIndex;
|
| 774 |
|
| 775 |
line.classList.toggle('active', isActive);
|
| 776 |
+
if (isActive) {
|
| 777 |
+
highlightWords(currentTime, line);
|
| 778 |
+
if (lineIndex !== lastActiveIndex) {
|
| 779 |
+
line.scrollIntoView({
|
| 780 |
+
block: 'center',
|
| 781 |
+
behavior: 'smooth'
|
| 782 |
+
});
|
| 783 |
+
}
|
| 784 |
}
|
| 785 |
});
|
| 786 |
|
/345/261/217/345/271/225/346/210/252/345/233/276 2025-01-05 150012.png
ADDED
|