mirror of
https://github.com/jely2002/youtube-dl-gui.git
synced 2021-11-01 22:46:21 +03:00
feat: add first version of subtitle language selection (#77)
This commit is contained in:
@@ -171,7 +171,7 @@ module.exports = {
|
||||
"no-restricted-imports": "error",
|
||||
"no-restricted-properties": "error",
|
||||
"no-restricted-syntax": "error",
|
||||
"no-return-assign": "error",
|
||||
"no-return-assign": "off",
|
||||
"no-return-await": "off",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
|
||||
6
main.js
6
main.js
@@ -106,6 +106,10 @@ function startCriticalHandlers(env) {
|
||||
env.doneAction = args.action;
|
||||
})
|
||||
|
||||
ipcMain.handle('getSubtitles', (event, args) => {
|
||||
return queryManager.getAvailableSubtitles(args.identifier);
|
||||
})
|
||||
|
||||
ipcMain.handle('videoAction', async (event, args) => {
|
||||
switch (args.action) {
|
||||
case "stop":
|
||||
@@ -134,7 +138,7 @@ function startCriticalHandlers(env) {
|
||||
case "getSize":
|
||||
return await queryManager.getSize(args.identifier, args.formatLabel, args.audioOnly, args.videoOnly, args.clicked);
|
||||
case "setSubtitles":
|
||||
queryManager.setSubtitle(args.value, args.identifier);
|
||||
queryManager.setSubtitle(args);
|
||||
break;
|
||||
case "globalSubtitles":
|
||||
queryManager.setGlobalSubtitle(args.value);
|
||||
|
||||
@@ -354,6 +354,23 @@ class QueryManager {
|
||||
});
|
||||
}
|
||||
|
||||
getAvailableSubtitles(identifier) {
|
||||
const video = this.getVideo(identifier);
|
||||
let subs = [];
|
||||
let autoGen = [];
|
||||
if(video.subtitles != null && video.subtitles.length !== 0) {
|
||||
subs = Object.keys(video.subtitles).map(sub => {
|
||||
return {iso: sub, name: Utils.getNameFromISO(sub)};
|
||||
})
|
||||
}
|
||||
if(video.autoCaptions != null && video.autoCaptions.length !== 0 && this.environment.settings.autoGenSubs) {
|
||||
autoGen = Object.keys(video.autoCaptions).map(sub => {
|
||||
return {iso: sub, name: Utils.getNameFromISO(sub)};
|
||||
})
|
||||
}
|
||||
return [subs.sort(), autoGen.sort()];
|
||||
}
|
||||
|
||||
showInfo(identifier) {
|
||||
let video = this.getVideo(identifier);
|
||||
let args = {
|
||||
@@ -407,9 +424,10 @@ class QueryManager {
|
||||
this.window.webContents.send("updateGlobalButtons", videos);
|
||||
}
|
||||
|
||||
setSubtitle(value, identifier) {
|
||||
const video = this.getVideo(identifier);
|
||||
video.downloadSubs = value;
|
||||
setSubtitle(args) {
|
||||
const video = this.getVideo(args.identifier);
|
||||
video.downloadSubs = args.enabled;
|
||||
video.subLanguages = args.langs;
|
||||
}
|
||||
|
||||
setGlobalSubtitle(value) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const Format = require("./types/Format");
|
||||
const crypto = require('crypto');
|
||||
const ISO6392 = require('iso-639-2');
|
||||
const channelRegex = /(?:https|http):\/\/(?:[\w]+\.)?youtube\.com\/(?:c\/|channel\/|user\/)([a-zA-Z0-9-]{1,})/;
|
||||
|
||||
class Utils {
|
||||
@@ -71,6 +72,24 @@ class Utils {
|
||||
return [urls, alreadyDone]
|
||||
}
|
||||
|
||||
static getNameFromISO(sub) {
|
||||
if(sub === "iw") return "Hebrew";
|
||||
if(sub === "zh-Hans") return "Chinese (Simplified)";
|
||||
if(sub === "zh-Hant") return "Chinese (Traditional)";
|
||||
const iso6391 = ISO6392.find(lang => {
|
||||
return lang.iso6391 === sub
|
||||
})
|
||||
if(iso6391 == null) {
|
||||
const iso6392 = ISO6392.find(lang => {
|
||||
return lang.iso6392B === sub;
|
||||
});
|
||||
if(iso6392 == null) return sub;
|
||||
return iso6392.name.split(";")[0].split(",")[0];
|
||||
} else {
|
||||
return iso6391.name.split(";")[0].split(",")[0];
|
||||
}
|
||||
}
|
||||
|
||||
static detectInfoType(infoQueryResult) {
|
||||
if(infoQueryResult == null) return infoQueryResult;
|
||||
if(Object.keys(infoQueryResult).length === 0) return infoQueryResult;
|
||||
|
||||
@@ -60,11 +60,14 @@ class DownloadQuery extends Query {
|
||||
}
|
||||
if (this.video.downloadSubs) {
|
||||
this.progressBar.setInitial("Downloading subtitles");
|
||||
args.push("--all-subs");
|
||||
args.push("--write-sub");
|
||||
args.push("--write-auto-sub");
|
||||
args.push("--embed-subs");
|
||||
if(this.environment.settings.autoGenSubs) {
|
||||
args.push("--write-auto-sub");
|
||||
}
|
||||
args.push("--sub-lang");
|
||||
let langs = "";
|
||||
this.video.subLanguages.forEach(lang => langs += lang + ",")
|
||||
args.push(langs.slice(0, -1));
|
||||
console.log(langs.slice(0, -1));
|
||||
}
|
||||
if (this.environment.settings.outputFormat !== "none") {
|
||||
args.push("--merge-output-format");
|
||||
|
||||
@@ -10,6 +10,7 @@ class Video {
|
||||
this.videoOnly = environment.mainVideoOnly;
|
||||
this.videoOnlySizeCache = [];
|
||||
this.downloadSubs = environment.mainDownloadSubs;
|
||||
this.subLanguages = [];
|
||||
this.downloadingAudio = false;
|
||||
this.webpage_url = this.url;
|
||||
this.hasMetadata = false;
|
||||
@@ -73,6 +74,8 @@ class Video {
|
||||
this.title = metadata.title;
|
||||
this.description = metadata.description;
|
||||
this.tags = metadata.tags;
|
||||
this.subtitles = metadata.subtitles;
|
||||
this.autoCaptions = metadata.automatic_captions;
|
||||
|
||||
this.duration = metadata.duration;
|
||||
if(metadata.duration != null) this.duration = new Date(metadata.duration * 1000)
|
||||
|
||||
23
package-lock.json
generated
23
package-lock.json
generated
@@ -14,7 +14,7 @@
|
||||
"bottleneck": "^2.19.5",
|
||||
"electron-updater": "^4.3.8",
|
||||
"execa": "^4.1.0",
|
||||
"iso-639-1": "^2.1.9",
|
||||
"iso-639-2": "^2.0.0",
|
||||
"jquery": "^3.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"popper.js": "^1.16.1",
|
||||
@@ -4851,12 +4851,13 @@
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"node_modules/iso-639-1": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.9.tgz",
|
||||
"integrity": "sha512-owRu9up+Cpx/hwSzm83j6G8PtC7U99UCtPVItsafefNfEgMl+pi8KBwhXwJkJfp6IouyYWFxj8n24SvCWpKZEQ==",
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
"node_modules/iso-639-2": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/iso-639-2/-/iso-639-2-2.0.0.tgz",
|
||||
"integrity": "sha512-rpjnAwiShaa/7VqVc1wpraOesD2qSz9hhUTzOQKHNvD0dV5Q/6tiJhy3raMdUl4HkzNrcL/wZh18tAmIs9Ljow==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/isobject": {
|
||||
@@ -13076,10 +13077,10 @@
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"iso-639-1": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.9.tgz",
|
||||
"integrity": "sha512-owRu9up+Cpx/hwSzm83j6G8PtC7U99UCtPVItsafefNfEgMl+pi8KBwhXwJkJfp6IouyYWFxj8n24SvCWpKZEQ=="
|
||||
"iso-639-2": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/iso-639-2/-/iso-639-2-2.0.0.tgz",
|
||||
"integrity": "sha512-rpjnAwiShaa/7VqVc1wpraOesD2qSz9hhUTzOQKHNvD0dV5Q/6tiJhy3raMdUl4HkzNrcL/wZh18tAmIs9Ljow=="
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"bottleneck": "^2.19.5",
|
||||
"electron-updater": "^4.3.8",
|
||||
"execa": "^4.1.0",
|
||||
"iso-639-1": "^2.1.9",
|
||||
"iso-639-2": "^2.0.0",
|
||||
"jquery": "^3.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"popper.js": "^1.16.1",
|
||||
|
||||
@@ -20,7 +20,8 @@ contextBridge.exposeInMainWorld(
|
||||
"theme",
|
||||
"restoreTaskList",
|
||||
"getDoneActions",
|
||||
"setDoneAction"
|
||||
"setDoneAction",
|
||||
"getSubtitles"
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
return await ipcRenderer.invoke(channel, data);
|
||||
|
||||
1
renderer/lib/select2.min.css
vendored
Normal file
1
renderer/lib/select2.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
renderer/lib/select2.min.js
vendored
Normal file
2
renderer/lib/select2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -573,6 +573,57 @@ i.img-overlay:hover, img.info-img:hover ~ i.img-overlay {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
/* SELECT2 OVERRIDES */
|
||||
|
||||
.select2-dropdown {
|
||||
background-color: var(--secondary-bg-color) !important;
|
||||
border: 1px solid var(--tertiary-bg-color) !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-results__option--selected {
|
||||
background-color: var(--tertiary-bg-color) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||
background-color: var(--bg-color) !important;
|
||||
}
|
||||
|
||||
.select2-container--default.select2-container--focus .select2-selection--multiple {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__rendered {
|
||||
background-color: var(--secondary-bg-color) !important;
|
||||
}
|
||||
|
||||
.select2-search__field {
|
||||
height: 1.5rem !important;
|
||||
margin-left: .5rem !important;
|
||||
line-height: 1.5rem !important;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji" !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice {
|
||||
background-color: var(--bg-color) !important;
|
||||
border: 1px solid var(--tertiary-bg-color) !important;
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||
background-color: var(--bg-color) !important;
|
||||
color: var(--font-color) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
background-color: var(--secondary-bg-color) !important;
|
||||
border-radius: 3px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* FILESIZE SPINNER */
|
||||
.lds-dual-ring {
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
<title>YouTube Downloader GUI</title>
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap-icons/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="lib/select2.min.css">
|
||||
<link rel="stylesheet" href="renderer.css">
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../node_modules/popper.js/dist/umd/popper.min.js"></script>
|
||||
<script src="lib/select2.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -176,6 +178,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="subsModal" tabindex="-1" aria-labelledby="subsModalLabel" aria-hidden="true">
|
||||
<div class="modal-xl modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="subsModalLabel">Subtitle settings</h5>
|
||||
<button type="button" class="close dismiss" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<p>Select the subtitle languages you want to download.</p>
|
||||
<div class="mb-1">
|
||||
<input class="check-input" type="checkbox" value="" id="enableSubs">
|
||||
<label class="check-label" for="enableSubs">Download subtitles</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="subsLang" class="form-label d-block">Subtitles:</label>
|
||||
<select class="w-75 mb-2" id="subsLang" multiple></select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="autoGenSubsLang" class="form-label d-block">Auto-generated subtitles:</label>
|
||||
<select class="w-75 mb-2" id="autoGenSubsLang" multiple></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn subsSave btn-dark" >Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="authModal" tabindex="-1" aria-labelledby="authModalLabel" aria-hidden="true">
|
||||
<div class="modal-xl modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
||||
@@ -75,6 +75,10 @@ async function init() {
|
||||
animation: true
|
||||
})
|
||||
|
||||
//Initialize select2
|
||||
$("#subsLang").select2({width: '75%', placeholder: "Select subtitles", language: {noResults: () => "No subtitles found"}});
|
||||
$("#autoGenSubsLang").select2({width: '75%', placeholder: "Select auto-generated subtitles", language: {noResults: () => "No subtitles found"}} );
|
||||
|
||||
//Add url when user presses enter, but prevent default behavior
|
||||
$(document).on("keydown", "form", function(event) {
|
||||
if(event.key == "Enter") {
|
||||
@@ -125,6 +129,18 @@ async function init() {
|
||||
$('#authModal').modal("hide");
|
||||
});
|
||||
|
||||
$('#subsModal .dismiss').on('click', () => {
|
||||
$('#subsModal').modal("hide");
|
||||
});
|
||||
|
||||
$('#subsModal .subsSave').on('click', () => {
|
||||
$('#subsModal').modal("hide");
|
||||
});
|
||||
|
||||
$('#subsModal').on('hide.bs.modal', () => {
|
||||
saveSubtitlesModal();
|
||||
})
|
||||
|
||||
$('#settingsModal .dismiss').on('click', () => {
|
||||
$('#settingsModal').modal("hide");
|
||||
});
|
||||
@@ -490,10 +506,7 @@ function addVideo(args) {
|
||||
});
|
||||
|
||||
$(template).find('.subtitle-btn').on('click', () => {
|
||||
let state = $(template).find('.subtitle-btn i').hasClass("bi-card-text-strike")
|
||||
window.main.invoke("videoAction", {action: "setSubtitles", identifier: args.identifier, value: state});
|
||||
if(state) $(template).find('.subtitle-btn i').removeClass("bi-card-text-strike").addClass("bi-card-text").attr("title", "Subtitles enabled")
|
||||
else $(template).find('.subtitle-btn i').removeClass("bi-card-text").addClass("bi-card-text-strike").attr("title", "Subtitles disabled")
|
||||
showSubtitleModal(args.identifier, template);
|
||||
});
|
||||
|
||||
$(template).find('.info-btn').on('click', () => {
|
||||
@@ -584,10 +597,7 @@ function setUnifiedPlaylist(args) {
|
||||
$(card).find('.download-btn i, .download-btn, .subtitle-btn, .subtitle-btn i, .remove-btn i, .remove-btn').removeClass("disabled");
|
||||
|
||||
$(card).find('.subtitle-btn').on('click', () => {
|
||||
let state = $(card).find('.subtitle-btn i').hasClass("bi-card-text-strike")
|
||||
window.main.invoke("videoAction", {action: "setSubtitles", identifier: args.identifier, value: state});
|
||||
if(state) $(card).find('.subtitle-btn i').removeClass("bi-card-text-strike").addClass("bi-card-text").attr("title", "Subtitles enabled")
|
||||
else $(card).find('.subtitle-btn i').removeClass("bi-card-text").addClass("bi-card-text-strike").attr("title", "Subtitles disabled")
|
||||
showSubtitleModal(args.identifier, card);
|
||||
});
|
||||
|
||||
$(card).find('.custom-select.download-type').on('change', function () {
|
||||
@@ -787,6 +797,45 @@ async function updateTotalSize() {
|
||||
else $('#totalProgress small').html('Ready to download!');
|
||||
}
|
||||
|
||||
function saveSubtitlesModal() {
|
||||
const modal = $('#subsModal');
|
||||
const identifier = $(modal).find('.identifier').val();
|
||||
const card = getCard(identifier);
|
||||
const langs = $('#subsLang').select2('data').map((option => option.id));
|
||||
langs.concat($('#autoGenSubsLang').select2('data').map(option => option.id));
|
||||
console.log(langs);
|
||||
if($(modal).find('#enableSubs').is(":checked")) $(card).find('.subtitle-btn i').removeClass("bi-card-text-strike").addClass("bi-card-text").attr("title", "Subtitles enabled");
|
||||
else $(card).find('.subtitle-btn i').removeClass("bi-card-text").addClass("bi-card-text-strike").attr("title", "Subtitles disabled");
|
||||
window.main.invoke("videoAction", {action: "setSubtitles", identifier: identifier, langs: langs, enabled: $(modal).find('#enableSubs').prop('checked')});
|
||||
}
|
||||
|
||||
async function showSubtitleModal(identifier, card) {
|
||||
const modal = $('#subsModal');
|
||||
const availableLangs = await window.main.invoke("getSubtitles", {identifier: identifier});
|
||||
if($(modal).find('.identifier').length) {
|
||||
$(modal).find('.identifier').val(identifier);
|
||||
} else {
|
||||
$(modal).append(`<input type="hidden" class="identifier" value="${identifier}">`);
|
||||
}
|
||||
$(modal).find('#autoGenSubsLang').empty();
|
||||
for(const lang of availableLangs[0]) {
|
||||
let option = new Option(lang.name, lang.iso);
|
||||
$(modal).find('#subsLang').append(option);
|
||||
}
|
||||
$(modal).find('#autoGenSubsLang').empty();
|
||||
if(availableLangs[1].length === 0) {
|
||||
$(modal).find('#autoGenSubsLang').closest("div").css("display", "none");
|
||||
} else {
|
||||
$(modal).find('#autoGenSubsLang').closest("div").css("display", "initial");
|
||||
for(const lang of availableLangs[1]) {
|
||||
let option = new Option(lang.name, lang.iso);
|
||||
$(modal).find('#autoGenSubsLang').append(option);
|
||||
}
|
||||
}
|
||||
$(modal).find('#enableSubs').prop('checked', !$(card).find('.subtitle-btn i').hasClass("bi-card-text-strike"));
|
||||
modal.modal("show");
|
||||
}
|
||||
|
||||
function showInfoModal(info, identifier) {
|
||||
let modal = $('#infoModal');
|
||||
let data = info;
|
||||
|
||||
Reference in New Issue
Block a user