添加导出功能
Browse files- youtube_sub.js +105 -1
youtube_sub.js
CHANGED
|
@@ -562,6 +562,33 @@ GM_addStyle(`
|
|
| 562 |
debug.error('Failed to update words cache:', e);
|
| 563 |
}
|
| 564 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
|
| 566 |
displayWordSummary(container) {
|
| 567 |
// Clear existing content safely
|
|
@@ -585,7 +612,69 @@ GM_addStyle(`
|
|
| 585 |
`;
|
| 586 |
|
| 587 |
container.appendChild(summaryDiv);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 588 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
// Group words by first letter
|
| 590 |
const grouped = uniqueWords.reduce((acc, word) => {
|
| 591 |
const firstLetter = word[0].toUpperCase();
|
|
@@ -649,7 +738,7 @@ GM_addStyle(`
|
|
| 649 |
section.appendChild(grid);
|
| 650 |
container.appendChild(section);
|
| 651 |
});
|
| 652 |
-
|
| 653 |
// Add new words to cache
|
| 654 |
this.updateNewWordsCache(uniqueWords);
|
| 655 |
|
|
@@ -659,7 +748,22 @@ GM_addStyle(`
|
|
| 659 |
|
| 660 |
// Update summary text with fresh counts
|
| 661 |
summaryDiv.textContent = `Unique: ${uniqueWords.length} | Total: ${totalCachedWords}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
}
|
|
|
|
|
|
|
| 663 |
cleanup() {
|
| 664 |
// Remove existing instance
|
| 665 |
if (this.container) {
|
|
|
|
| 562 |
debug.error('Failed to update words cache:', e);
|
| 563 |
}
|
| 564 |
}
|
| 565 |
+
convertToSRT(subtitles) {
|
| 566 |
+
return subtitles.map((sub, index) => {
|
| 567 |
+
const start = this.formatSRTTime(sub.startTime);
|
| 568 |
+
const end = this.formatSRTTime(sub.endTime);
|
| 569 |
+
const text = sub.words.map(w => w.text).join(' ');
|
| 570 |
+
return `${index + 1}\n${start} --> ${end}\n${text}\n`;
|
| 571 |
+
}).join('\n');
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
convertToMixedSRT(enSubs, cnSubs) {
|
| 575 |
+
return enSubs.map((enSub, index) => {
|
| 576 |
+
const start = this.formatSRTTime(enSub.startTime);
|
| 577 |
+
const end = this.formatSRTTime(enSub.endTime);
|
| 578 |
+
const enText = enSub.words.map(w => w.text).join(' ');
|
| 579 |
+
const cnText = cnSubs[index]?.words.map(w => w.text).join('') || '';
|
| 580 |
+
return `${index + 1}\n${start} --> ${end}\n${enText}\n${cnText}\n`;
|
| 581 |
+
}).join('\n');
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
formatSRTTime(seconds) {
|
| 585 |
+
const pad = n => n.toString().padStart(2, '0');
|
| 586 |
+
const hours = Math.floor(seconds / 3600);
|
| 587 |
+
const minutes = Math.floor((seconds % 3600) / 60);
|
| 588 |
+
const secs = Math.floor(seconds % 60);
|
| 589 |
+
const ms = Math.floor((seconds % 1) * 1000);
|
| 590 |
+
return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${pad(ms)}`;
|
| 591 |
+
}
|
| 592 |
|
| 593 |
displayWordSummary(container) {
|
| 594 |
// Clear existing content safely
|
|
|
|
| 612 |
`;
|
| 613 |
|
| 614 |
container.appendChild(summaryDiv);
|
| 615 |
+
////////////////////////////////////////////////
|
| 616 |
+
// Add export buttons container
|
| 617 |
+
const exportDiv = document.createElement('div');
|
| 618 |
+
exportDiv.style.cssText = `
|
| 619 |
+
display: flex;
|
| 620 |
+
gap: 10px;
|
| 621 |
+
justify-content: center;
|
| 622 |
+
padding: 10px;
|
| 623 |
+
margin-bottom: 20px;
|
| 624 |
+
flex-wrap: wrap;
|
| 625 |
+
`;
|
| 626 |
+
|
| 627 |
+
const createExportButton = (text, onClick) => {
|
| 628 |
+
const btn = document.createElement('button');
|
| 629 |
+
btn.style.cssText = `
|
| 630 |
+
padding: 8px 16px;
|
| 631 |
+
background: #4CAF50;
|
| 632 |
+
color: white;
|
| 633 |
+
border: none;
|
| 634 |
+
border-radius: 4px;
|
| 635 |
+
cursor: pointer;
|
| 636 |
+
font-size: 14px;
|
| 637 |
+
`;
|
| 638 |
+
btn.textContent = text;
|
| 639 |
+
btn.onclick = onClick;
|
| 640 |
+
return btn;
|
| 641 |
+
};
|
| 642 |
+
|
| 643 |
+
// Export cached words
|
| 644 |
+
exportDiv.appendChild(createExportButton('Export All Cached Words', () => {
|
| 645 |
+
const words = [...this.loadNewWordsCache()].sort();
|
| 646 |
+
this.downloadFile('cached_words.txt', words.join('\n'));
|
| 647 |
+
}));
|
| 648 |
|
| 649 |
+
// Export current video words
|
| 650 |
+
exportDiv.appendChild(createExportButton('Export Video Words', () => {
|
| 651 |
+
const words = this.extractUniqueWords();
|
| 652 |
+
this.downloadFile('video_words.txt', words.join('\n'));
|
| 653 |
+
}));
|
| 654 |
+
|
| 655 |
+
// Export English SRT
|
| 656 |
+
exportDiv.appendChild(createExportButton('Export EN SRT', () => {
|
| 657 |
+
const srt = this.convertToSRT(this.subtitles.english);
|
| 658 |
+
this.downloadFile('english.srt', srt);
|
| 659 |
+
}));
|
| 660 |
+
|
| 661 |
+
// Export Chinese SRT
|
| 662 |
+
exportDiv.appendChild(createExportButton('Export CN SRT', () => {
|
| 663 |
+
const srt = this.convertToSRT(this.subtitles.chinese);
|
| 664 |
+
this.downloadFile('chinese.srt', srt);
|
| 665 |
+
}));
|
| 666 |
+
|
| 667 |
+
// Export Mixed SRT
|
| 668 |
+
exportDiv.appendChild(createExportButton('Export Mixed SRT', () => {
|
| 669 |
+
const srt = this.convertToMixedSRT(this.subtitles.english, this.subtitles.chinese);
|
| 670 |
+
this.downloadFile('mixed.srt', srt);
|
| 671 |
+
}));
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
|
| 677 |
+
//////////////////////////////////////////////////////////////////////////////////
|
| 678 |
// Group words by first letter
|
| 679 |
const grouped = uniqueWords.reduce((acc, word) => {
|
| 680 |
const firstLetter = word[0].toUpperCase();
|
|
|
|
| 738 |
section.appendChild(grid);
|
| 739 |
container.appendChild(section);
|
| 740 |
});
|
| 741 |
+
|
| 742 |
// Add new words to cache
|
| 743 |
this.updateNewWordsCache(uniqueWords);
|
| 744 |
|
|
|
|
| 748 |
|
| 749 |
// Update summary text with fresh counts
|
| 750 |
summaryDiv.textContent = `Unique: ${uniqueWords.length} | Total: ${totalCachedWords}`;
|
| 751 |
+
container.insertBefore(exportDiv, container.firstChild.nextSibling);
|
| 752 |
+
}
|
| 753 |
+
// Add helper methods to class
|
| 754 |
+
downloadFile(filename, content) {
|
| 755 |
+
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
| 756 |
+
const url = URL.createObjectURL(blob);
|
| 757 |
+
const link = document.createElement('a');
|
| 758 |
+
link.href = url;
|
| 759 |
+
link.download = filename;
|
| 760 |
+
document.body.appendChild(link);
|
| 761 |
+
link.click();
|
| 762 |
+
document.body.removeChild(link);
|
| 763 |
+
URL.revokeObjectURL(url);
|
| 764 |
}
|
| 765 |
+
|
| 766 |
+
|
| 767 |
cleanup() {
|
| 768 |
// Remove existing instance
|
| 769 |
if (this.container) {
|