sonygod commited on
Commit
5ed0d22
·
1 Parent(s): cfea4fd

暂时OK,可可以高亮

Browse files
youtube_sub.js CHANGED
@@ -86,25 +86,30 @@ GM_addStyle(`
86
 
87
  .subtitle-line {
88
  padding: 12px 15px;
89
- margin: 6px 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.5;
97
  width: 410px;
98
  font-size: 14px;
99
  }
100
 
101
- .subtitle-line:hover {
102
- background: rgba(255, 255, 255, 0.1);
103
- transform: translateX(5px);
 
 
 
104
  }
105
 
106
- .subtitle-line.active {
107
- background: rgba(255, 255, 255, 0.2);
 
 
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
- text: event.segs.map(seg => seg.utf8).join('').trim()
 
 
 
 
371
  }))
372
- .filter(sub => sub.text);
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
- const text = document.createElement('span');
535
- text.textContent = sub.text;
 
 
 
 
 
 
 
 
 
 
 
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 && lineIndex !== lastActiveIndex) {
707
- // Smooth scroll for new active subtitle
708
- line.scrollIntoView({
709
- block: 'center',
710
- behavior: 'smooth'
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