Spaces:
Sleeping
Sleeping
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>
- main.py +5 -6
- 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 = "
|
| 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 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 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="
|
|
|
|
| 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">
|
|
|
|
| 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 = '
|
| 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: '
|
|
|
|
| 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 |
-
|
| 1635 |
-
|
| 1636 |
-
|
| 1637 |
-
|
| 1638 |
-
|
| 1639 |
-
|
| 1640 |
-
var mimeType = currentFormat === 'json' ? 'application/json' : 'text/plain';
|
| 1641 |
-
downloadFile(filename, text, mimeType);
|
| 1642 |
});
|
|
|
|
| 1643 |
} else {
|
| 1644 |
-
|
| 1645 |
-
|
| 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;
|