feat: add first version of subtitle language selection (#77)

This commit is contained in:
Jelle Glebbeek
2021-05-24 03:16:27 +02:00
parent 01ec2627b5
commit b717bb06e6
14 changed files with 215 additions and 30 deletions

View File

@@ -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",

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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");

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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

File diff suppressed because one or more lines are too long

2
renderer/lib/select2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -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 {

View File

@@ -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">&times;</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">

View File

@@ -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;