duck3-create Claude Opus 4.6 commited on
Commit
31cd65e
Β·
1 Parent(s): 5036b96

Improve language option and split download buttons

Browse files

- Language: add "μžλ™" (auto) option that tries ko+en, "ν•œκ΅­μ–΄"/"English" now fetch only that language
- Download: replace ambiguous "νŒŒμΌλ³„ λ‹€μš΄λ‘œλ“œ" checkbox with two clear buttons ("톡합 λ‹€μš΄λ‘œλ“œ" / "κ°œλ³„ λ‹€μš΄λ‘œλ“œ")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. main.py +5 -6
  2. static/index.html +50 -39
main.py CHANGED
@@ -99,7 +99,7 @@ except Exception as e:
99
 
100
  class TranscriptRequest(BaseModel):
101
  urls: list[str]
102
- language: str = "ko"
103
  denoise: bool = False
104
  format: str = "text"
105
  keep_newlines: bool = False
@@ -174,11 +174,10 @@ def _format_error(error_msg: str) -> str:
174
 
175
 
176
  def _fetch_transcript(video_id: str, language: str, denoise: bool, fmt: str, keep_newlines: bool = False) -> dict:
177
- languages = [language]
178
- if language == "ko":
179
- languages.append("en")
180
- elif language == "en":
181
- languages.append("ko")
182
 
183
  apis_to_try = [("plain", _yt_api)]
184
  if _yt_api_cookies:
 
99
 
100
  class TranscriptRequest(BaseModel):
101
  urls: list[str]
102
+ language: str = "auto"
103
  denoise: bool = False
104
  format: str = "text"
105
  keep_newlines: bool = False
 
174
 
175
 
176
  def _fetch_transcript(video_id: str, language: str, denoise: bool, fmt: str, keep_newlines: bool = False) -> dict:
177
+ if language == "auto":
178
+ languages = ["ko", "en"]
179
+ else:
180
+ languages = [language]
 
181
 
182
  apis_to_try = [("plain", _yt_api)]
183
  if _yt_api_cookies:
static/index.html CHANGED
@@ -893,7 +893,8 @@
893
  <div class="option-group">
894
  <span class="option-label">μ–Έμ–΄</span>
895
  <div class="toggle-group" data-name="language">
896
- <button class="toggle-btn active" data-value="ko">ν•œκ΅­μ–΄</button>
 
897
  <button class="toggle-btn" data-value="en">English</button>
898
  </div>
899
  </div>
@@ -926,11 +927,6 @@
926
  <span class="checkbox-custom"></span>
927
  <span>μžλ™ λ‹€μš΄λ‘œλ“œ</span>
928
  </label>
929
- <label class="checkbox-wrapper">
930
- <input type="checkbox" id="perFileDownload">
931
- <span class="checkbox-custom"></span>
932
- <span>νŒŒμΌλ³„ λ‹€μš΄λ‘œλ“œ</span>
933
- </label>
934
  </div>
935
  </div>
936
  </div>
@@ -959,7 +955,8 @@
959
  <div class="results-actions">
960
  <button class="btn btn-secondary" id="retryFailedBtn" style="display:none">μ‹€νŒ¨ ν•­λͺ© μž¬μ‹œλ„</button>
961
  <button class="btn btn-secondary" id="copyAllBtn">전체 볡사</button>
962
- <button class="btn btn-secondary" id="downloadAllBtn">전체 λ‹€μš΄λ‘œλ“œ</button>
 
963
  </div>
964
  </div>
965
  <div id="resultsList" class="results-list"></div>
@@ -978,7 +975,7 @@
978
  <script>
979
  (function () {
980
  var currentFormat = 'text';
981
- var currentLanguage = 'ko';
982
  var currentResults = null;
983
 
984
  var $ = function (sel) { return document.querySelector(sel); };
@@ -992,12 +989,12 @@
992
  var stats = $('#stats');
993
  var copyAllBtn = $('#copyAllBtn');
994
  var downloadAllBtn = $('#downloadAllBtn');
 
995
  var retryFailedBtn = $('#retryFailedBtn');
996
  var denoiseCheckbox = $('#denoise');
997
  var metadataCheckbox = $('#metadata');
998
  var autoCopyCheckbox = $('#autoCopy');
999
  var autoDownloadCheckbox = $('#autoDownload');
1000
- var perFileDownloadCheckbox = $('#perFileDownload');
1001
  var urlCount = $('#urlCount');
1002
 
1003
  // i18n
@@ -1014,14 +1011,17 @@
1014
  optionsLabel: 'μ˜΅μ…˜',
1015
  formatLabel: 'ν˜•μ‹',
1016
  langLabel: 'μ–Έμ–΄',
 
 
 
1017
  denoise: 'λ…Έμ΄μ¦ˆ 제거',
1018
  urlInclude: 'URL 포함',
1019
  autoCopy: 'μžλ™ 볡사',
1020
  autoDownload: 'μžλ™ λ‹€μš΄λ‘œλ“œ',
1021
- perFileDownload: 'νŒŒμΌλ³„ λ‹€μš΄λ‘œλ“œ',
1022
  extractBtn: 'μžλ§‰ μΆ”μΆœ',
1023
  copyAll: '전체 볡사',
1024
- downloadAll: '전체 λ‹€μš΄λ‘œλ“œ',
 
1025
  copy: '볡사',
1026
  download: 'λ‹€μš΄λ‘œλ“œ',
1027
  copied: '볡사됨 \u2713',
@@ -1051,14 +1051,17 @@
1051
  optionsLabel: 'Options',
1052
  formatLabel: 'Format',
1053
  langLabel: 'Language',
 
 
 
1054
  denoise: 'Denoise',
1055
  urlInclude: 'Include URL',
1056
  autoCopy: 'Auto Copy',
1057
  autoDownload: 'Auto Download',
1058
- perFileDownload: 'Per-file DL',
1059
  extractBtn: 'Extract',
1060
  copyAll: 'Copy All',
1061
- downloadAll: 'Download All',
 
1062
  copy: 'Copy',
1063
  download: 'Download',
1064
  copied: 'Copied \u2713',
@@ -1106,19 +1109,28 @@
1106
  if (optLabels[0]) optLabels[0].textContent = t('formatLabel');
1107
  if (optLabels[1]) optLabels[1].textContent = t('langLabel');
1108
 
 
 
 
 
 
 
 
 
 
1109
  // Checkboxes
1110
  var checkboxSpans = $$('.checkbox-wrapper span:last-child');
1111
  if (checkboxSpans[0]) checkboxSpans[0].textContent = t('denoise');
1112
  if (checkboxSpans[1]) checkboxSpans[1].textContent = t('urlInclude');
1113
  if (checkboxSpans[2]) checkboxSpans[2].textContent = t('autoCopy');
1114
  if (checkboxSpans[3]) checkboxSpans[3].textContent = t('autoDownload');
1115
- if (checkboxSpans[4]) checkboxSpans[4].textContent = t('perFileDownload');
1116
 
1117
  // Buttons
1118
  extractBtn.textContent = t('extractBtn');
1119
  retryFailedBtn.textContent = t('retryFailed');
1120
  copyAllBtn.textContent = t('copyAll');
1121
  downloadAllBtn.textContent = t('downloadAll');
 
1122
 
1123
  // Progress text is updated dynamically during extraction
1124
 
@@ -1625,41 +1637,40 @@
1625
  }
1626
  });
1627
 
1628
- // Download all
1629
  function doDownloadAll() {
1630
  if (!currentResults) return;
1631
-
1632
  var successResults = currentResults.results.filter(function (r) { return !r.error; });
1633
-
1634
- if (perFileDownloadCheckbox.checked) {
1635
- // Download each video as separate file
1636
- successResults.forEach(function (r) {
1637
- var text = getResultText(r);
1638
- var ext = currentFormat === 'json' ? 'json' : 'txt';
1639
- var filename = (r.video_id || 'transcript') + '.' + ext;
1640
- var mimeType = currentFormat === 'json' ? 'application/json' : 'text/plain';
1641
- downloadFile(filename, text, mimeType);
1642
  });
 
1643
  } else {
1644
- // Download all in one file
1645
- if (currentFormat === 'json') {
1646
- var includeMetadata = metadataCheckbox.checked;
1647
- var data = successResults.map(function (r) {
1648
- if (includeMetadata) {
1649
- return { video_id: r.video_id, url: r.url, transcript: r.transcript };
1650
- }
1651
- return r.transcript;
1652
- });
1653
- downloadFile('transcripts.json', JSON.stringify(data, null, 2), 'application/json');
1654
- } else {
1655
- var allText = successResults.map(function (r) { return getResultText(r); }).join('\n\n---\n\n');
1656
- downloadFile('transcripts.txt', allText, 'text/plain');
1657
- }
1658
  }
1659
  }
1660
 
1661
  downloadAllBtn.addEventListener('click', doDownloadAll);
1662
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1663
  // Retry failed
1664
  retryFailedBtn.addEventListener('click', async function () {
1665
  if (!currentResults) return;
 
893
  <div class="option-group">
894
  <span class="option-label">μ–Έμ–΄</span>
895
  <div class="toggle-group" data-name="language">
896
+ <button class="toggle-btn active" data-value="auto">μžλ™</button>
897
+ <button class="toggle-btn" data-value="ko">ν•œκ΅­μ–΄</button>
898
  <button class="toggle-btn" data-value="en">English</button>
899
  </div>
900
  </div>
 
927
  <span class="checkbox-custom"></span>
928
  <span>μžλ™ λ‹€μš΄λ‘œλ“œ</span>
929
  </label>
 
 
 
 
 
930
  </div>
931
  </div>
932
  </div>
 
955
  <div class="results-actions">
956
  <button class="btn btn-secondary" id="retryFailedBtn" style="display:none">μ‹€νŒ¨ ν•­λͺ© μž¬μ‹œλ„</button>
957
  <button class="btn btn-secondary" id="copyAllBtn">전체 볡사</button>
958
+ <button class="btn btn-secondary" id="downloadAllBtn">톡합 λ‹€μš΄λ‘œλ“œ</button>
959
+ <button class="btn btn-secondary" id="downloadEachBtn">κ°œλ³„ λ‹€μš΄λ‘œλ“œ</button>
960
  </div>
961
  </div>
962
  <div id="resultsList" class="results-list"></div>
 
975
  <script>
976
  (function () {
977
  var currentFormat = 'text';
978
+ var currentLanguage = 'auto';
979
  var currentResults = null;
980
 
981
  var $ = function (sel) { return document.querySelector(sel); };
 
989
  var stats = $('#stats');
990
  var copyAllBtn = $('#copyAllBtn');
991
  var downloadAllBtn = $('#downloadAllBtn');
992
+ var downloadEachBtn = $('#downloadEachBtn');
993
  var retryFailedBtn = $('#retryFailedBtn');
994
  var denoiseCheckbox = $('#denoise');
995
  var metadataCheckbox = $('#metadata');
996
  var autoCopyCheckbox = $('#autoCopy');
997
  var autoDownloadCheckbox = $('#autoDownload');
 
998
  var urlCount = $('#urlCount');
999
 
1000
  // i18n
 
1011
  optionsLabel: 'μ˜΅μ…˜',
1012
  formatLabel: 'ν˜•μ‹',
1013
  langLabel: 'μ–Έμ–΄',
1014
+ langAuto: 'μžλ™',
1015
+ langKo: 'ν•œκ΅­μ–΄',
1016
+ langEn: 'English',
1017
  denoise: 'λ…Έμ΄μ¦ˆ 제거',
1018
  urlInclude: 'URL 포함',
1019
  autoCopy: 'μžλ™ 볡사',
1020
  autoDownload: 'μžλ™ λ‹€μš΄λ‘œλ“œ',
 
1021
  extractBtn: 'μžλ§‰ μΆ”μΆœ',
1022
  copyAll: '전체 볡사',
1023
+ downloadAll: '톡합 λ‹€μš΄λ‘œλ“œ',
1024
+ downloadEach: 'κ°œλ³„ λ‹€μš΄λ‘œλ“œ',
1025
  copy: '볡사',
1026
  download: 'λ‹€μš΄λ‘œλ“œ',
1027
  copied: '볡사됨 \u2713',
 
1051
  optionsLabel: 'Options',
1052
  formatLabel: 'Format',
1053
  langLabel: 'Language',
1054
+ langAuto: 'Auto',
1055
+ langKo: 'ν•œκ΅­μ–΄',
1056
+ langEn: 'English',
1057
  denoise: 'Denoise',
1058
  urlInclude: 'Include URL',
1059
  autoCopy: 'Auto Copy',
1060
  autoDownload: 'Auto Download',
 
1061
  extractBtn: 'Extract',
1062
  copyAll: 'Copy All',
1063
+ downloadAll: 'Combined DL',
1064
+ downloadEach: 'Individual DL',
1065
  copy: 'Copy',
1066
  download: 'Download',
1067
  copied: 'Copied \u2713',
 
1109
  if (optLabels[0]) optLabels[0].textContent = t('formatLabel');
1110
  if (optLabels[1]) optLabels[1].textContent = t('langLabel');
1111
 
1112
+ // Language toggle buttons
1113
+ var langToggleGroup = document.querySelector('.toggle-group[data-name="language"]');
1114
+ if (langToggleGroup) {
1115
+ var langBtns = langToggleGroup.querySelectorAll('.toggle-btn');
1116
+ if (langBtns[0]) langBtns[0].textContent = t('langAuto');
1117
+ if (langBtns[1]) langBtns[1].textContent = t('langKo');
1118
+ if (langBtns[2]) langBtns[2].textContent = t('langEn');
1119
+ }
1120
+
1121
  // Checkboxes
1122
  var checkboxSpans = $$('.checkbox-wrapper span:last-child');
1123
  if (checkboxSpans[0]) checkboxSpans[0].textContent = t('denoise');
1124
  if (checkboxSpans[1]) checkboxSpans[1].textContent = t('urlInclude');
1125
  if (checkboxSpans[2]) checkboxSpans[2].textContent = t('autoCopy');
1126
  if (checkboxSpans[3]) checkboxSpans[3].textContent = t('autoDownload');
 
1127
 
1128
  // Buttons
1129
  extractBtn.textContent = t('extractBtn');
1130
  retryFailedBtn.textContent = t('retryFailed');
1131
  copyAllBtn.textContent = t('copyAll');
1132
  downloadAllBtn.textContent = t('downloadAll');
1133
+ if (downloadEachBtn) downloadEachBtn.textContent = t('downloadEach');
1134
 
1135
  // Progress text is updated dynamically during extraction
1136
 
 
1637
  }
1638
  });
1639
 
1640
+ // Download all (combined)
1641
  function doDownloadAll() {
1642
  if (!currentResults) return;
 
1643
  var successResults = currentResults.results.filter(function (r) { return !r.error; });
1644
+ if (currentFormat === 'json') {
1645
+ var includeMetadata = metadataCheckbox.checked;
1646
+ var data = successResults.map(function (r) {
1647
+ if (includeMetadata) {
1648
+ return { video_id: r.video_id, url: r.url, transcript: r.transcript };
1649
+ }
1650
+ return r.transcript;
 
 
1651
  });
1652
+ downloadFile('transcripts.json', JSON.stringify(data, null, 2), 'application/json');
1653
  } else {
1654
+ var allText = successResults.map(function (r) { return getResultText(r); }).join('\n\n---\n\n');
1655
+ downloadFile('transcripts.txt', allText, 'text/plain');
 
 
 
 
 
 
 
 
 
 
 
 
1656
  }
1657
  }
1658
 
1659
  downloadAllBtn.addEventListener('click', doDownloadAll);
1660
 
1661
+ // Download each (individual files)
1662
+ downloadEachBtn.addEventListener('click', function () {
1663
+ if (!currentResults) return;
1664
+ var successResults = currentResults.results.filter(function (r) { return !r.error; });
1665
+ successResults.forEach(function (r) {
1666
+ var text = getResultText(r);
1667
+ var ext = currentFormat === 'json' ? 'json' : 'txt';
1668
+ var filename = (r.video_id || 'transcript') + '.' + ext;
1669
+ var mimeType = currentFormat === 'json' ? 'application/json' : 'text/plain';
1670
+ downloadFile(filename, text, mimeType);
1671
+ });
1672
+ });
1673
+
1674
  // Retry failed
1675
  retryFailedBtn.addEventListener('click', async function () {
1676
  if (!currentResults) return;