let platform;
let linkCopied = false;
let progressCooldown = [];
let sizeCooldown = [];
let sizeCache = [];
let logUpdateTask;
(function () { init(); })();
async function init() {
//Get platform
platform = await window.main.invoke('platform');
//Initialize titlebar
if(platform === "darwin") {
new window.windowbar({'style':'mac', 'dblClickable':false, 'fixed':true, 'title':document.title,'dark':true})
.appendTo(document.body)
$('.windowbar-title').css("left", "50%").css("top", "14px");
$('.windowbar-controls').css("display", "none");
} else {
new window.windowbar({'style':'win', 'dblClickable':false, 'fixed':true, 'title':document.title,'dark':true})
.appendTo(document.body)
$('.windowbar').prepend("
")
$('.windowbar-title').css("left", "45px")
}
$('.windowbar-minimize').on('click', () => {
window.main.invoke('titlebarClick', "minimize")
})
$('.windowbar-close').on('click', () => {
window.main.invoke('titlebarClick', "close")
})
$('.windowbar-maximize').on('click', () => {
window.main.invoke('titlebarClick', "maximize")
})
//Updates the placeholder to a copied link
window.main.receive("updateLinkPlaceholder", (args) => {
$('#add-url').prop("placeholder", args.text);
linkCopied = args.copied;
});
//Verify the url when the addShortcut gets used
window.main.receive("addShortcut", (link) => {
verifyURL(link);
})
window.main.receive("downloadShortcut", () => {
$('#downloadBtn').click();
})
//Init draggable cards
const dragArea = document.querySelector(".video-cards");
new Sortable(dragArea, {
animation: 200,
sort: true,
draggable: ".video-card",
handle: ".handle"
});
//Init the when done dropdown
$('.dropdown-toggle').dropdown();
const availableOptions = await window.main.invoke('getDoneActions');
for(const option of availableOptions) {
$('#whenDoneOptions').append('
').append(`${option}`)
}
$('.dropdown-item').on('click', function() {
$('#whenDoneOptions').find('.dropdown-selected').removeClass('dropdown-selected');
$(this).addClass('dropdown-selected');
window.main.invoke("setDoneAction", {action: $(this).text()});
})
//Set the selected theme (dark | light)
const startupTheme = await window.main.invoke('theme');
toggleWhiteMode(startupTheme);
$('.video-cards').each(function() {
let sel = this;
new MutationObserver(function() {
//If the queue is completely empty show the empty text
if ($('.video-cards').is(':empty')) {
$('.empty').show();
resetTotalProgress();
$('#downloadBtn, #clearBtn').prop("disabled", true);
updateGlobalDownloadQuality();
} else {
$('.empty').hide();
}
}).observe(sel, {childList: true, subtree: true});
});
//Configures the update toast
$('#update').toast({
autohide: false,
animation: true
})
//Configures the restore toast
$('#task-list').toast({
autohide: false,
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"}} );
$("#sponsorblockMark").select2({width: '75%', placeholder: "Select sections of a video to mark"});
$("#sponsorblockRemove").select2({width: '75%', placeholder: "Select sections of a video to remove"});
//Add url when user presses enter, but prevent default behavior
$(document).on("keydown", "form", function(event) {
if(event.key == "Enter") {
verifyURL($('#add-url').val());
return false;
}
return true
});
//Add url when user press on the + button
$('#add-url-btn').on('click', () => {
verifyURL($('#add-url').val());
});
$('body').on('click', '#install-btn', () => {
window.main.invoke("installUpdate");
}).on('click', '#tasklist-btn', () => {
window.main.invoke("restoreTaskList");
}).on('click', '.video-card .metadata.right button', function() {
const card = $(this).closest('.video-card');
updateSize($(card).prop('id'), true);
}).on('change', '.custom-select.download-quality', function() {
const card = $(this).closest('.video-card');
updateSize($(card).prop('id'), false);
}).on('change', '.custom-select.download-encoding', function() {
const card = $(this).closest('.video-card');
updateSize($(card).prop('id'), false);
});
$('#download-quality').on('change', () => updateAllVideoSettings());
$('#download-type').on('change', async () => {
updateAllVideoSettings();
await getSettings();
sendSettings();
});
$('#infoModal .img-overlay, #infoModal .info-img').on('click', () => {
window.main.invoke("videoAction", {action: "downloadThumb", url: $('#infoModal .info-img').attr("src")});
}).on('mouseover', () => {
$('#infoModal .info-img').addClass("darken");
}).on('mouseout', () => {
$('#infoModal .info-img').removeClass("darken");
});
$('#infoModal .dismiss').on('click', () => {
$('#infoModal').modal("hide");
});
$('#authModal .dismiss').on('click', () => {
$('#authModal').modal("hide");
});
$('#logModal .dismiss').on('click', () => {
$('#logModal').modal("hide");
clearInterval(logUpdateTask);
});
$('#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");
});
$('#settingsModal .apply').on('click', () => {
$('#settingsModal').modal("hide");
sendSettings();
});
$('#maxConcurrent').on('input', () => {
$('#concurrentLabel').html(`Max concurrent jobs (${$('#maxConcurrent').val()})`);
})
$('#nameFormat').on('change', function() {
const value = this.selectedOptions[0].value
if(value !== "custom") {
$('#nameFormatCustom').val(value).prop("disabled", true)
} else {
$('#nameFormatCustom').val(window.settings.nameFormat).prop("disabled", false)
}
})
$('#settingsBtn').on('click', async () => {
await getSettings();
$('#settingsModal').modal("show");
});
$('#defaultConcurrent').on('click', () => {
window.main.invoke("settingsAction", {action: "get"}).then((settings) => {
$('#concurrentLabel').html(`Max concurrent jobs (${settings.defaultConcurrent})`);
$('#maxConcurrent').val(settings.defaultConcurrent);
});
})
$('#authBtn').on('click', () => {
$('#authModal').modal("show");
})
$('#fileInput').on('click', (event) => {
event.preventDefault();
window.main.invoke('cookieFile', false).then((path) => {
if(path != null) {
$('#fileInputLabel').html(path);
$('#fileInput').attr("title", path);
}
});
});
window.main.invoke('cookieFile', "get").then((path) => {
if(path != null) {
$('#fileInputLabel').html(path);
$('#fileInput').attr("title", path);
}
});
$('.removeCookies').on('click', () => {
window.main.invoke('cookieFile', true);
$('#fileInputLabel').html("Click to select cookies.txt");
$('#fileInput').attr("title", "No file selected");
})
$('#infoModal .json').on('click', () => {
window.main.invoke('videoAction', {action: "downloadInfo", identifier: $('#infoModal .identifier').html()});
});
$('#logModal .save').on('click', () => {
window.main.invoke('saveLog', $('#logModal .identifier').html());
});
$('#clearBtn').on('click', () => {
$('.video-cards').children().each(function () {
let identifier = this.id;
$(getCard(identifier)).remove();
window.main.invoke("videoAction", {action: "stop", identifier: identifier});
})
$('#totalProgress .progress-bar').remove();
$('#totalProgress').prepend('')
window.main.invoke("iconProgress", -1);
})
$('#locationBtn').on('click', () => {
window.main.invoke("downloadFolder");
});
$('#subtitleBtn').on('click', () => {
let globalState = $('#subtitleBtn i').hasClass("bi-card-text-strike");
$('.video-cards').children().each(function () {
let state = $(this).find('.subtitle-btn i').hasClass("bi-card-text-strike");
if(globalState === state) {
if(state) $(this).find('.subtitle-btn i').removeClass("bi-card-text-strike").addClass("bi-card-text").attr("title", "Subtitles enabled");
else $(this).find('.subtitle-btn i').removeClass("bi-card-text").addClass("bi-card-text-strike").attr("title", "Subtitles disabled");
}
})
window.main.invoke("videoAction", {action: "globalSubtitles", value: globalState});
if(globalState) $('#subtitleBtn i').removeClass("bi-card-text-strike").addClass("bi-card-text").attr("title", "Subtitles enabled");
else $('#subtitleBtn i').removeClass("bi-card-text").addClass("bi-card-text-strike").attr("title", "Subtitles disabled");
})
$('#downloadBtn').on('click', async () => {
let videos = []
let videoCards = $('.video-cards').children();
for(const card of videoCards) {
let isDownloadable = await window.main.invoke("videoAction", {action: "downloadable", identifier: card.id})
if(isDownloadable) {
if($(card).hasClass("unified")) {
videos.push({
identifier: card.id,
url: $(card).find('.url').val(),
format: $(card).find('.custom-select.download-quality').val(),
encoding: $(card).find('.custom-select.download-encoding').val(),
audioEncoding: $(card).find('.custom-select.download-audio-encoding').val(),
type: $(card).find('.custom-select.download-type').val(),
downloadSubs: !$(card).find('.subtitle-btn i').hasClass("bi-card-text-strike")
})
} else {
videos.push({
identifier: card.id,
format: $(card).find('.custom-select.download-quality').val(),
encoding: $(card).find('.custom-select.download-encoding').val(),
audioEncoding: $(card).find('.custom-select.download-audio-encoding').val(),
type: $(card).find('.custom-select.download-type').val(),
downloadSubs: !$(card).find('.subtitle-btn i').hasClass("bi-card-text-strike")
})
}
$(card).find('.progress').addClass("d-flex");
$(card).find('.metadata.left').html('Speed: ' + "0.00MiB/s").show();
$(card).find('.metadata.right').html('ETA: ' + "Unknown").show();
$(card).find('.options').addClass("d-flex");
$(card).find('select').addClass("d-none");
$(card).find('.download-btn, .download-btn i, .subtitle-btn, .subtitle-btn i').addClass("disabled");
changeDownloadIconToLog(card);
if($(card).hasClass("unified")) {
$(card).find('.metadata.left, .metadata.right').empty();
$(card).find('.info').addClass("d-flex").removeClass("d-none");
$(card).find('.metadata.info').html('Downloading playlist...');
$(card).find('select').addClass("d-none");
}
}
}
let args = {
action: "download",
downloadType: "all",
videos: videos
}
window.main.invoke('videoAction', args)
$('#downloadBtn, #clearBtn').prop("disabled", true);
updateGlobalDownloadQuality();
$('#totalProgress .progress-bar').remove();
$('#totalProgress').prepend('')
$('#totalProgress small').html(`Downloading - item 0 of ${videos.length} completed`);
});
//Enables the main process to show logs/errors in the renderer dev console
window.main.receive("log", (arg) => {
if(arg.isErr) console.error(arg.log);
else console.log(arg.log);
} );
//Enables the main process to show and update toasts.
window.main.receive("toast", (arg) => showToast(arg));
//Passes an error to the setError method
window.main.receive("error", (arg) => setError(arg.error.code, arg.error.description, arg.unexpected, arg.identifier, arg.url));
//Updates the windowbar icon when the app gets maximized/unmaximized
window.main.receive("maximized", (maximized) => {
if(maximized) $('.windowbar').addClass("fullscreen");
else $('.windowbar').removeClass("fullscreen");
});
window.main.receive("updateGlobalButtons", (arg) => {
updateButtons(arg);
updateGlobalDownloadQuality(arg);
});
window.main.receive("binaryLock", (args) => {
if(args.lock === true) {
$('#add-url').attr("placeholder", args.placeholder).prop("disabled", true);
} else {
$('#add-url').attr("placeholder", "Enter a video/playlist URL to add to the queue").prop("disabled", false);
}
})
//Receive calls from main process and dispatch them to the right function
window.main.receive("videoAction", (arg) => {
switch(arg.action) {
case "add":
addVideo(arg);
break;
case "remove":
$(getCard(arg.identifier)).remove();
sizeCache = sizeCache.filter(item => item[0] !== arg.identifier)
updateTotalSize();
break;
case "progress":
updateProgress(arg);
break;
case "totalProgress":
updateTotalProgress(arg);
break;
case "info":
showInfoModal(arg.metadata, arg.identifier);
break;
case "setUnified":
setUnifiedPlaylist(arg);
break;
case "setDownloadType":
$('#download-type').val(arg.type);
updateAllVideoSettings();
break;
}
});
//Opens the input menu (copy/paste) when an editable object gets right clicked.
document.body.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
let node = e.target;
while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
window.main.invoke('openInputMenu');
break;
} else if (node.nodeName.match(/^(a)$/i)) {
window.main.invoke('openCopyMenu', $(node).prop('href'));
break;
}
node = node.parentNode;
}
});
}
function verifyURL(value) {
if (linkCopied && (value == null || value.length === 0)) {
parseURL($('#add-url').prop('placeholder'));
$('#url-form').trigger('reset');
} else if ($('#url-form')[0].checkValidity()) {
if (value != null && value.length > 0) {
parseURL(value);
$('#url-form').trigger('reset');
}
}
}
function toggleWhiteMode(setting) {
const value = setting === "light";
$('body').toggleClass("white-mode", value);
$('.windowbar-minimize, .windowbar-maximize, .windowbar-close > svg').toggleClass("invert", value);
$('.windowbar > img').attr('src', "img/icon-titlebar-" + (value ? "light" : "dark") + ".png");
$('#downloadBtn').toggleClass("desaturate", value);
$('#subtitleBtn > i').toggleClass("light-icon", value);
}
function parseURL(data) {
if(data.includes(',')) {
let urls = data.replaceAll(" ", "").split(",");
for(const url of urls) {
window.main.invoke('videoAction', {action: "entry", url: url});
}
} else {
window.main.invoke('videoAction', {action: "entry", url: data});
}
}
function showToast(toastInfo) {
if(toastInfo.title != null) {
$(`.${toastInfo.type}-title`).html(toastInfo.title);
}
if(toastInfo.body != null) {
$(`.${toastInfo.type}-body`).html(toastInfo.body);
}
if($(`#${toastInfo.type}`).is(':visible')) $(`#${toastInfo.type}`).toast('show').css('visibility', 'visible');
}
function updateEncodingDropdown(enabled) {
$('.video-cards').children().each(function() {
$(this).find('.metadata').toggle(!enabled);
$(this).find('.custom-select.download-encoding, .custom-select.download-audio-encoding').toggle(enabled);
})
}
function updateGlobalDownloadQuality() {
const formats = [];
const currentFormats = [];
$('.video-cards').children().each(function() {
if($(this).find('.download-btn i').hasClass("disabled")) return;
if($(this).find('.custom-select.download-type').val() !== "audio") {
$(this).find('.custom-select.download-quality option.video').each(function () {
formats.push($(this).val());
})
}
})
const sortedFormats = [...new Set(formats)];
sortedFormats.sort((a, b) => {
const aParsed = parseFormatString(a);
const bParsed = parseFormatString(b);
return parseInt(bParsed.height, 10) - parseInt(aParsed.height, 10) || parseInt(bParsed.fps, 10) - parseInt(aParsed.fps, 10);
});
$('#download-quality option.video').each(function() {
currentFormats.push(this.value);
if(!sortedFormats.includes(this.value)) {
$(this).remove();
}
})
for(const format of sortedFormats) {
const option = new Option(format, format);
if(!currentFormats.includes(format)) {
$('#download-quality').append(option);
$(option).addClass("video");
}
}
}
function parseFormatString(string) {
let split = string.split("p");
let height = split[0];
let fps = split[1];
if(fps === "") fps = null;
return {
fps: fps,
height: height
};
}
async function addVideo(args) {
await getSettings();
let template = $('.template.video-card').clone();
$(template).removeClass('template');
$(template).prop('id', args.identifier);
if(args.type === "single") {
$(template).find('.card-title')
.html(args.title)
.prop('title', args.title);
$(template).find('.progress-bar')
.addClass('progress-bar-striped')
.addClass('progress-bar-animated')
.width("100%");
if(args.subtitles) $(template).find('.subtitle-btn i').removeClass("bi-card-text-strike").addClass("bi-card-text").attr("title", "Subtitles enabled");
$(template).find('img').prop("src", args.thumbnail);
$(template).find('.info').addClass("d-none");
$(template).find('.progress small').html("Setting up environment")
$(template).find('.metadata.left').html('Duration: ' + ((args.duration == null) ? "Unknown" : args.duration));
if(window.settings.enableEncoding) {
$(template).find('.metadata').hide();
} else {
$(template).find('.custom-select.download-encoding, .custom-select.download-audio-encoding').hide();
}
if(!args.hasFilesizes) {
$(template).find('.metadata.right').html('Size: Unknown');
} else if(args.loadSize) {
$(template).find('.metadata.right').html('Size: ');
} else {
$(template).find('.metadata.right').html('Size: ')
}
$(template).find('.custom-select.download-type').on('change', function () {
let isAudio = this.selectedOptions[0].value === "audio";
disableEncodingDropdowns(this.selectedOptions[0].value, template);
for(const elem of $(template).find('option')) {
if($(elem).hasClass("video")) {
$(elem).toggle(!isAudio)
} else if($(elem).hasClass("audio")) {
$(elem).toggle(isAudio)
}
}
if (args.formats.length > 0) {
$(template).find('.custom-select.download-quality').val(isAudio ? "best" : args.formats[args.selected_format_index].display_name).change();
}
});
if(args.formats.length === 0) {
$(template).find('.custom-select.download-quality').append(new Option("No formats", "", true)).prop("disabled", true);
$(template).find('.custom-select.download-type').prop("disabled", true);
$(template).find('.subtitle-btn, .subtitle-btn i').addClass("disabled");
}
setCodecs(template, args.audioCodecs, args.formats);
$(template).find('.custom-select.download-quality').on('change', function () {
updateCodecs(template, this.value);
});
$(template).find('.custom-select.download-type').change();
//Initialize remove video popover
$(template).find('.remove-btn').popover();
$(document).click(function(event) {
let target = $(event.target);
if(!target.closest('.remove-btn').length) {
$('.remove-btn').removeClass("clicked").popover("hide");
}
});
$(template).find('.remove-btn').on('click', () => removeVideo(getCard(args.identifier)));
$(template).find('.download-btn').on('click', () => {
let downloadArgs = {
action: "download",
url: args.url,
identifier: args.identifier,
format: $(template).find('.custom-select.download-quality').val(),
encoding: $(template).find('.custom-select.download-encoding').val(),
audioEncoding: $(template).find('.custom-select.download-audio-encoding').val(),
type: $(template).find('.custom-select.download-type').val(),
downloadType: "single"
}
window.main.invoke("videoAction", downloadArgs)
$('#downloadBtn, #clearBtn').prop("disabled", true);
updateGlobalDownloadQuality();
$(template).find('.progress').addClass("d-flex");
$(template).find('.metadata.left').html('Speed: ' + "0.00MiB/s").show();
$(template).find('.metadata.right').html('ETA: ' + "Unknown").show();
$(template).find('.options').addClass("d-flex");
$(template).find('select').addClass("d-none");
$(template).find('.download-btn i, .download-btn, .subtitle-btn, .subtitle-btn i').addClass("disabled");
if(!$(template).hasClass("unified")) {
changeDownloadIconToLog(template);
}
});
$(template).find('.subtitle-btn').on('click', () => {
showSubtitleModal(args.identifier, template);
});
$(template).find('.info-btn').on('click', () => {
window.main.invoke("videoAction", {action: "info", identifier: args.identifier});
});
$(template).find('.open .folder').on('click', () => {
window.main.invoke("videoAction", {action: "open", identifier: args.identifier, type: "folder"});
});
$(template).find('.open .item').on('click', () => {
window.main.invoke("videoAction", {action: "open", identifier: args.identifier, type: "item"});
});
} else if(args.type === "metadata") {
$(template).find('.card-title')
.html(args.url)
.prop('title', args.url);
$(template).find('.progress-bar')
.addClass('progress-bar-striped')
.addClass('progress-bar-animated')
.width("100%")
.prop("aria-valuenow", "indefinite");
$(template).find('.progress').addClass("d-flex");
$(template).find('.options').addClass("d-none");
$(template).find('.metadata.info').html('Downloading metadata...');
$(template).find('.buttons').children().each(function() { $(this).find('i').addClass("disabled"); $(this).addClass("disabled"); });
$(template).find('.remove-btn').on('click', () => removeVideo(getCard(args.identifier)));
} else if(args.type === "playlist") {
$(template).find('.card-title')
.html(args.url)
.prop('title', args.url);
$(template).find('.progress small')
.html('Setting up environment')
$(template).find('.progress-bar')
.addClass('progress-bar-striped')
.addClass('progress-bar-animated')
.width("100%")
.prop("aria-valuenow", "indefinite");
$(template).find('.progress').addClass("d-flex");
$(template).find('.options').addClass("d-none");
$(template).find('.metadata.info').html('Fetching video metadata...');
$(template).find('.buttons').children().each(function() { $(this).find('i').addClass("disabled"); $(this).addClass("disabled"); });
$(template).find('.remove-btn').on('click', () => removeVideo(getCard(args.identifier)));
}
new Promise((resolve) => {
$(template).find('img').on('load error', () => resolve());
}).then(() => {
$('.video-cards').prepend(template);
if(args.type === "single") updateVideoSettings(args.identifier);
});
}
function removeVideo(card) {
const btn = $(card).find('.remove-btn')
if(btn.hasClass("clicked") || $(card).find(".custom-select.download-type").is(":visible") || $(card).find(".btn.btn-dark.folder").is(":visible") || $(card).find(".row.error.d-none").is(":visible") || $(card).find(".url").length) {
$(btn).popover('hide');
$(card).remove();
window.main.invoke("videoAction", {action: "stop", identifier: $(card).prop("id")});
} else {
$(btn).popover('show');
$(btn).addClass("clicked");
}
}
async function setUnifiedPlaylist(args) {
await getSettings();
const card = getCard(args.identifier);
$(card).addClass("unified");
$(card).append(``);
$(card).find('.progress').addClass("d-none").removeClass("d-flex");
$(card).find('.options').addClass("d-flex");
$(card).find('.info').addClass("d-none").removeClass("d-flex");
$(card).find('.metadata.right').html('Playlist size: ' + args.length);
$(card).find('.metadata.left').html('Uploader: ' + (args.uploader == null ? "Unknown" : args.uploader));
if(window.settings.enableEncoding) {
$(card).find('.metadata').hide();
} else {
$(card).find('.custom-select.download-encoding, .custom-select.download-audio-encoding').hide();
}
if(args.subtitles) $(card).find('.subtitle-btn i').removeClass("bi-card-text-strike").addClass("bi-card-text").attr("title", "Subtitles enabled");
$(card).find('img').prop("src", args.thumb);
$(card).find('.card-title')
.html(args.title)
.prop('title', args.title);
$(card).find('.progress-bar')
.addClass('progress-bar-striped')
.addClass('progress-bar-animated')
.width("100%")
.prop("aria-valuenow", "indefinite");
$(card).find('.progress small').html('Setting up environment');
$(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', () => {
showSubtitleModal(args.identifier, card);
});
$(card).find('.custom-select.download-type').on('change', function () {
disableEncodingDropdowns(this.selectedOptions[0].value, card);
let isAudio = this.selectedOptions[0].value === "audio";
for(const elem of $(card).find('option')) {
if($(elem).hasClass("video")) {
$(elem).toggle(!isAudio)
} else if($(elem).hasClass("audio")) {
$(elem).toggle(isAudio)
}
}
$(card).find('.custom-select.download-quality').val(isAudio ? "best" : args.formats[0].display_name).change();
});
$(card).find('.download-btn').on('click', () => {
let downloadArgs = {
action: "download",
identifier: args.identifier,
format: $(card).find('.custom-select.download-quality').val(),
type: $(card).find('.custom-select.download-type').val(),
encoding: $(card).find('.custom-select.download-encoding').val(),
audioEncoding: $(card).find('.custom-select.download-audio-encoding').val(),
downloadType: "unified"
}
window.main.invoke("videoAction", downloadArgs);
$('#downloadBtn, #clearBtn').prop("disabled", true);
updateGlobalDownloadQuality();
$(card).find('.progress').addClass("d-flex");
$(card).find('.metadata.left, .metadata.right').empty();
$(card).find('.info').addClass("d-flex").removeClass("d-none");
$(card).find('.metadata.info').html('Downloading playlist...');
$(card).find('select').addClass("d-none");
$(card).find('.download-btn i, .download-btn, .subtitle-btn, .subtitle-btn i').addClass("disabled");
});
setCodecs(card, args.audioCodecs, args.formats);
$(card).find('.custom-select.download-quality').on('change', function () {
updateCodecs(card, this.value);
});
$(card).find('.custom-select.download-type').change();
$(card).find('.open .folder').on('click', () => {
window.main.invoke("videoAction", {action: "open", identifier: args.identifier, type: "folder"});
});
updateVideoSettings(args.identifier);
}
function setCodecs(card, audioCodecs, formats) {
//Add the audio encoding
for(const audioCodec of audioCodecs) {
let codecOption = new Option(audioCodec, audioCodec);
$(card).find('.custom-select.download-audio-encoding').append(codecOption);
}
for(const format of formats) {
//Add the quality (1080p60)
let option = new Option(format.display_name, format.display_name);
$(card).find('.custom-select.download-quality').append(option);
$(option).addClass("video");
//Add the encoding (vp9) associated with the quality (1080p60)
for(const encoding of format.encodings) {
let encodingOption = new Option(encoding, encoding);
$(card).find('.custom-select.download-encoding').append(encodingOption);
$(encodingOption).addClass(format.display_name);
}
}
}
function updateCodecs(card, newValue) {
const encodingValue = $(card).find('.custom-select.download-encoding :selected').val();
for(const elem of $(card).find('.custom-select.download-encoding option')) {
if($(elem).hasClass(newValue)) {
$(elem).show();
} else if(!$(elem).hasClass("none")) {
$(elem).hide();
}
}
let foundElement = false;
for(const elem of $(card).find('.custom-select.download-encoding option')) {
if($(elem).val() === encodingValue && $(elem).hasClass(newValue)) {
$(elem).attr('selected','selected');
foundElement = true;
break;
}
}
if(!foundElement) {
$(card).find('.custom-select.download-encoding').val("none");
}
}
function updateProgress(args) {
let card = getCard(args.identifier);
if(args.progress.reset != null && args.progress.reset) {
resetProgress($(card).find('.progress-bar')[0], card);
return;
}
if(args.progress.initial != null && args.progress.initial) {
$(card).find('.progress small').html(args.progress.message);
return;
}
if(args.progress.finished != null && args.progress.finished) {
if(args.progress.isPlaylist) {
$(card).find('.progress small').html("Playlist downloaded - 100%");
$(card).find('.progress-bar').attr('aria-valuenow', 100).css('width', "100%");
$(card).find('.options').addClass("d-none").removeClass("d-flex");
$(card).find('.info').addClass("d-none").removeClass("d-flex");
$(card).find('.progress-bar').removeClass("progress-bar-striped")
$(card).find('.open .item').addClass("d-none");
$(card).find('.open .folder').html("Show files in folder");
$(card).find('.open').addClass("d-flex");
} else {
if(args.progress.isAudio == null) $(card).find('.progress small').html("Item downloaded - 100%");
else $(card).find('.progress small').html((args.progress.isAudio ? "Audio" : "Video") + " downloaded - 100%");
$(card).find('.progress-bar').attr('aria-valuenow', 100).css('width', "100%");
$(card).find('.options').addClass("d-none").removeClass("d-flex");
$(card).find('.progress-bar').removeClass("progress-bar-striped")
$(card).find('.open').addClass("d-flex");
if(window.settings.nameFormatMode === "custom") $(card).find('.open .item').prop("disabled", true)
}
changeSubsToRetry(args.url, card);
return;
}
if(args.progress.done != null && args.progress.total != null) {
if($(card).find('.progress-bar').hasClass("progress-bar-striped")) {
resetProgress($(card).find('.progress-bar')[0], card);
}
$(card).find('.progress-bar').attr('aria-valuenow', args.progress.percentage.slice(0,-1)).css('width', args.progress.percentage);
$(card).find('.progress small').html(`${args.progress.percentage} - ${args.progress.done} of ${args.progress.total} `);
} else if(args.progress.percentage != null) {
if(parseFloat(args.progress.percentage.slice(0, -1)) > parseFloat($(card).find('.progress-bar').attr("aria-valuenow"))) {
$(card).find('.progress-bar').attr('aria-valuenow', args.progress.percentage.slice(0,-1)).css('width', args.progress.percentage);
if(args.progress.percentage.slice(0, -1) === "100.0") {
$(card).find('.progress-bar').addClass("progress-bar-striped")
$(card).find('.progress small').html("Converting with FFmpeg");
} else {
if (args.progress.isAudio == null) $(card).find('.progress small').html("Downloading item - " + args.progress.percentage);
else $(card).find('.progress small').html((args.progress.isAudio ? "Downloading audio" : "Downloading video") + " - " + args.progress.percentage);
}
if(!progressCooldown.includes(args.identifier)) {
progressCooldown.push(args.identifier);
$(card).find('.metadata.right').html('ETA: ' + args.progress.eta).show();
$(card).find('.metadata.left').html('Speed: ' + args.progress.speed).show();
setTimeout(() => {
progressCooldown = progressCooldown.filter(item => item !== args.identifier);
}, 200);
}
}
}
}
function updateTotalProgress(args) {
if(args.progress.resetTotal != null && args.progress.resetTotal) {
resetTotalProgress();
return;
}
$('#totalProgress small').html(`Downloading - item ${args.progress.done} of ${args.progress.total} completed`);
$('#totalProgress .progress-bar').css("width", args.progress.percentage).attr("aria-valuenow", args.progress.percentage.slice(0,-1));
const ratio = parseFloat(args.progress.percentage.slice(0,-1));
window.main.invoke("iconProgress", ratio / 100);
}
function updateSize(identifier, clicked) {
if(sizeCooldown.includes(identifier)) return;
sizeCooldown.push(identifier)
const card = getCard(identifier);
if($(card).hasClass('unified')) {
sizeCooldown = sizeCooldown.filter(item => item !== identifier);
return;
}
if($(card).find('.custom-select.download-quality').prop("disabled") === true) {
sizeCooldown = sizeCooldown.filter(item => item !== identifier);
$(card).find('.metadata.right').html('Size: ' + "Unknown");
return;
}
const formatLabel = $(card).find('.custom-select.download-quality').val();
$(card).find('.metadata.right').html('Size: ');
window.main.invoke("videoAction", {
action: "getSize",
identifier: identifier,
formatLabel: formatLabel,
encoding: $(card).find('.custom-select.download-encoding').val(),
audioEncoding: $(card).find('.custom-select.download-audio-encoding').val(),
audioOnly: $(card).find('.custom-select.download-type').val() === "audio",
videoOnly: $(card).find('.custom-select.download-type').val() === "videoOnly",
clicked: clicked
}).then((size) => {
if(size != null && size === "Unknown") {
$(card).find('.metadata.right').html('Size: ' + "Unknown");
} else if(size != null) {
if($(card).find('.custom-select.download-quality').val() === formatLabel) {
sizeCache = sizeCache.filter(item => item[0] !== identifier)
sizeCache.push([identifier, size]);
$(card).find('.metadata.right').html('Size: ' + convertBytes(size));
}
} else {
$(card).find('.metadata.right').html('Size: ');
}
sizeCooldown = sizeCooldown.filter(item => item !== identifier)
updateTotalSize();
});
}
async function updateVideoSettings(identifier) {
const card = getCard(identifier);
const qualityValue = $('#download-quality').val();
const typeValue = $('#download-type').val();
const oldQuality = $(card).find('.custom-select.download-quality');
const oldType = $(card).find('.custom-select.download-type').val();
$(card).find('.custom-select.download-type').val(typeValue);
const classValue = typeValue === "videoOnly" ? "video" : typeValue;
let isAudio = typeValue === "audio";
if(qualityValue === "best") {
$(card).find('.custom-select.download-quality').val($(card).find(`.custom-select.download-quality option.${classValue}:first`).val());
} else if(qualityValue === "worst") {
if(isAudio) {
$(card).find('.custom-select.download-quality').val("worst");
} else {
$(card).find('.custom-select.download-quality').val($(card).find(`.custom-select.download-quality option.${classValue}:last`).val());
}
} else if(!isAudio) {
const formats = [];
$(card).find('.custom-select.download-quality option.video').each(function() {
formats.push(this.value);
});
if(formats.includes(qualityValue)) {
$(card).find('.custom-select.download-quality').val(qualityValue);
} else {
const search = parseFormatString(qualityValue);
const closest = formats.reduce((a, b) => {
const parsedA = parseFormatString(a);
const parsedB = parseFormatString(b);
return Math.abs(parsedB.height - search.height) < Math.abs(parsedA.height - search.height) ? b : a;
});
$(card).find('.custom-select.download-quality').val(closest);
}
} else if(isAudio) {
$('#download-quality').val("best");
$(card).find('.custom-select.download-quality').val($(card).find(`.custom-select.download-quality option.${classValue}:first`).val());
}
disableEncodingDropdowns(typeValue, card);
for(const elem of $(card).find('option')) {
if($(elem).hasClass("video")) {
$(elem).toggle(!isAudio)
} else if($(elem).hasClass("audio")) {
$(elem).toggle(isAudio)
}
}
updateCodecs(card, $(card).find('.custom-select.download-quality').val())
if($(card).hasClass("unified")) return;
await getSettings();
if(oldQuality != null && oldType != null && (oldQuality !== $(card).find('.custom-select.download-quality').val() || oldType !== $(card).find('.custom-select.download-type').val())) {
updateSize(identifier, false);
} else if(window.settings.sizeMode === "full") {
updateSize(identifier, false);
}
}
function disableEncodingDropdowns(typeValue, card) {
let isAudio = typeValue === "audio";
let isVideoOnly = typeValue === "videoOnly";
$(card).find(".custom-select.download-encoding").prop("disabled", isAudio && !isVideoOnly);
$(card).find(".custom-select.download-audio-encoding").prop("disabled", !isAudio && isVideoOnly);
}
function updateAllVideoSettings() {
let isAudio = $('#download-type').val() === "audio";
for(const elem of $('#download-quality option')) {
if($(elem).hasClass("video")) {
$(elem).toggle(!isAudio)
}
}
$('.video-cards').children().each(function () {
updateVideoSettings($(this).prop("id"));
});
}
async function getSettings() {
const settings = await window.main.invoke("settingsAction", {action: "get"});
$('#updateBinary').prop('checked', settings.updateBinary);
$('#updateApplication').prop('checked', settings.updateApplication);
$('#userAgent').val(settings.userAgent);
$('#validateCertificate').prop('checked', settings.validateCertificate);
$('#enableEncoding').prop('checked', settings.enableEncoding);
$('#taskList').prop('checked', settings.taskList);
$('#autoFillClipboard').prop('checked', settings.autoFillClipboard);
$('#noPlaylist').prop('checked', settings.noPlaylist);
$('#globalShortcut').prop('checked', settings.globalShortcut);
$('#ratelimitSetting').val(settings.rateLimit);
$('#proxySetting').val(settings.proxy);
$('#nameFormatCustom').val(settings.nameFormat).prop("disabled", settings.nameFormatMode === "custom");
$('#nameFormat').val(settings.nameFormatMode);
$('#outputFormat').val(settings.outputFormat);
$('#audioOutputFormat').val(settings.audioOutputFormat);
$('#sponsorblockMark').val(settings.sponsorblockMark.split(",")).change();
$('#sponsorblockRemove').val(settings.sponsorblockRemove.split(",")).change();
$('#sponsorblockApi').val(settings.sponsorblockApi);
$('#downloadMetadata').prop('checked', settings.downloadMetadata);
$('#downloadJsonMetadata').prop('checked', settings.downloadJsonMetadata);
$('#downloadThumbnail').prop('checked', settings.downloadThumbnail);
$('#keepUnmerged').prop('checked', settings.keepUnmerged);
$('#calculateTotalSize').prop('checked', settings.calculateTotalSize);
$('#maxConcurrent').val(settings.maxConcurrent);
$('#concurrentLabel').html(`Max concurrent jobs (${settings.maxConcurrent})`);
$('#sizeSetting').val(settings.sizeMode);
$('#splitMode').val(settings.splitMode);
$('#theme').val(settings.theme);
$('#version').html("Version: " + settings.version);
window.settings = settings;
}
function sendSettings() {
let settings = {
updateBinary: $('#updateBinary').prop('checked'),
updateApplication: $('#updateApplication').prop('checked'),
autoFillClipboard: $('#autoFillClipboard').prop('checked'),
noPlaylist: $('#noPlaylist').prop('checked'),
globalShortcut: $('#globalShortcut').prop('checked'),
outputFormat: $('#outputFormat').val(),
audioOutputFormat: $('#audioOutputFormat').val(),
proxy: $('#proxySetting').val(),
userAgent: $('#userAgent').val(),
validateCertificate: $('#validateCertificate').prop('checked'),
enableEncoding: $('#enableEncoding').prop('checked'),
taskList: $('#taskList').prop('checked'),
nameFormatMode: $('#nameFormat').val(),
nameFormat: $('#nameFormatCustom').val(),
sponsorblockMark: $('#sponsorblockMark').val().join(","),
sponsorblockRemove: $('#sponsorblockRemove').val().join(","),
sponsorblockApi: $('#sponsorblockApi').val(),
downloadMetadata: $('#downloadMetadata').prop('checked'),
downloadJsonMetadata: $('#downloadJsonMetadata').prop('checked'),
downloadThumbnail: $('#downloadThumbnail').prop('checked'),
keepUnmerged: $('#keepUnmerged').prop('checked'),
calculateTotalSize: $('#calculateTotalSize').prop('checked'),
sizeMode: $('#sizeSetting').val(),
splitMode: $('#splitMode').val(),
rateLimit: $('#ratelimitSetting').val(),
maxConcurrent: parseInt($('#maxConcurrent').val()),
downloadType: $('#download-type').val(),
theme: $('#theme').val()
}
window.settings = settings;
window.main.invoke("settingsAction", {action: "save", settings});
updateEncodingDropdown(settings.enableEncoding);
toggleWhiteMode(settings.theme);
}
async function updateTotalSize() {
await getSettings();
if(!window.settings.calculateTotalSize) return;
let total = 0;
for(const elem of sizeCache) {
total += elem[1];
}
if(total > 0) $('#totalProgress small').html('Ready to download! - Total queried size: ' + convertBytes(total));
else $('#totalProgress small').html('Ready to download!');
}
function saveSubtitlesModal() {
const modal = $('#subsModal');
const identifier = $(modal).find('.identifier').val();
const card = getCard(identifier);
const subs = $('#subsLang').select2('data').map((option => option.id));
const autoGen = $('#autoGenSubsLang').select2('data').map(option => option.id);
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, subs: subs, autoGen: autoGen, enabled: $(modal).find('#enableSubs').prop('checked'), unified: $(card).hasClass("unified")});
}
async function showSubtitleModal(identifier, card) {
const modal = $('#subsModal');
const availableLangs = await window.main.invoke("getSubtitles", {identifier: identifier, unified: $(card).hasClass("unified")});
const selectedLangs = await window.main.invoke("getSelectedSubtitles", {identifier: identifier});
if($(modal).find('.identifier').length) {
$(modal).find('.identifier').val(identifier);
} else {
$(modal).append(``);
}
$(modal).find('#subsLang').empty();
if(availableLangs[0].length === 0) {
$(modal).find('#subsLang').closest("div").css("display", "none");
} else {
$(modal).find('#subsLang').closest("div").css("display", "initial");
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('#autoGenSubsLang, #subsLang').unbind('select2:select').on('select2:select', () => {
$(modal).find('#enableSubs').prop("checked", true);
});
if(availableLangs[0].length === 0 && availableLangs[1].length === 0) {
$(modal).find('.description').text("No subtitles available.")
$(modal).find('#enableSubs').prop("checked", false).prop("disabled", true);
} else {
if($(card).hasClass("unified")) {
$(modal).find('.description').html("Select the subtitle languages you want to try to download.")
} else {
$(modal).find('.description').text("Select the subtitle languages you want to download.")
}
$(modal).find('#enableSubs').prop("disabled", false);
}
$(modal).find('#subsLang').val(selectedLangs[0]);
$(modal).find('#autoGenSubsLang').val(selectedLangs[1]);
modal.modal("show");
}
function showInfoModal(info, identifier) {
let modal = $('#infoModal');
let data = info;
if(data == null) {
const card = getCard(identifier);
data = {
title: $(card).find('.card-title').text(),
description: "This video threw an error. Not all info is available.\n\nError:\n" + $(card).find('.metadata').text(),
url: $(card).find('.url').val()
}
}
$(modal).find('img').prop("src", data.thumbnail);
$(modal).find('.modal-title').html(data.title);
$(modal).find('#info-description').html(data.description == null ? "No description was found." : data.description);
$(modal).find('.uploader').html('Uploader: ' + (data.uploader == null ? "Unknown" : data.uploader));
$(modal).find('.extractor').html('Extractor: ' + (data.extractor == null ? "Unknown" : data.extractor));
$(modal).find('.url').html('URL: ' + '' + data.url + '');
$(modal).find('[title="Views"]').html(' ' + (data.view_count == null ? "-" : data.view_count));
$(modal).find('[title="Like / dislikes"]').html(' ' + (data.like_count == null ? "-" : data.like_count) + ' ' + (info.dislike_count == null ? "-" : info.dislike_count));
$(modal).find('[title="Average rating"]').html(' ' + (data.average_rating == null ? "-" : data.average_rating.toString().slice(0,3)));
$(modal).find('[title="Duration"]').html(' ' + (data.duration == null ? "-" : data.duration));
$(modal).find('.identifier').html(identifier);
$(modal).modal("show");
}
function resetProgress(elem, card) {
$(elem).removeClass("progress-bar-striped").removeClass("progress-bar-animated");
$(elem).remove();
$(card).find('.progress').prepend('')
}
function resetTotalProgress() {
$('#totalProgress .progress-bar').remove();
$('#totalProgress').prepend('')
$('#totalProgress small').html('Ready to download!');
window.main.invoke("iconProgress", -1);
}
function updateButtons(videos) {
let downloadableVideos = false;
if(videos.length > 0) $('#clearBtn').prop("disabled", false);
else $('#clearBtn').prop("disabled", true);
for(const video of videos) {
let domVideo = getCard(video.identifier);
if(domVideo == null) continue;
if(video.downloadable) {
$('#downloadBtn').prop("disabled", false);
downloadableVideos = true;
break;
}
if(!downloadableVideos) {
$('#downloadBtn').prop("disabled", true);
}
}
}
function changeSubsToRetry(url, card) {
if(card == null) return;
$(card).find('.subtitle-btn')
.unbind()
.removeClass("subtitle-btn")
.removeClass("disabled")
.addClass("retry-btn")
.html('')
.on('click', function() {
window.main.invoke("videoAction", {action: "stop", identifier: $(card).prop("id")});
if(url == null) {
parseURL($(card).find('.url').val());
} else {
parseURL(url);
}
})
.find('i').removeClass("disabled");
}
function changeDownloadIconToLog(card) {
if(card == null) return;
$(card).find('.download-btn i')
.unbind()
.removeClass("bi-download")
.removeClass("disabled")
.addClass("bi-journal-text")
.on('click', () => {
const id = $(card).prop('id');
$('#logModal').modal("show").find('.identifier').html(id);
$('#logModal .log').html("Loading log...");
openLog(id);
logUpdateTask = setInterval(() => openLog(id), 1000);
});
$(card).find('.download-btn')
.unbind()
.removeClass("disabled");
}
function openLog(identifier) {
const logBox = $('#logModal .log');
window.main.invoke("getLog", identifier).then(log => {
if(log == null) {
$(logBox).val("No log was found for this video.")
} else {
let fullLog = "";
for(const line of log) {
if(line.startsWith("WARNING")) {
const pre = line.slice(0, line.indexOf("W")) + "" + line.slice(line.indexOf("W"));
const suffixed = pre.slice(0, pre.indexOf(":") + 1) + "" + pre.slice(pre.indexOf(":") + 1);
fullLog += "" + suffixed + "
";
} else if(line.startsWith("ERROR")) {
const pre = line.slice(0, line.indexOf("E")) + "" + line.slice(line.indexOf("E"));
const suffixed = pre.slice(0, pre.indexOf(":") + 1) + "" + pre.slice(pre.indexOf(":") + 1);
fullLog += "" + suffixed + "
";
} else if(line.startsWith("[")) {
const pre = line.slice(0, line.indexOf("[")) + "" + line.slice(line.indexOf("["));
const suffixed = pre.slice(0, pre.indexOf("]") + 1) + "" + pre.slice(pre.indexOf("]") + 1);
fullLog += "" + suffixed + "
";
} else {
fullLog += "" + line + "
";
}
}
$(logBox).html(fullLog);
}
})
}
function setError(code, description, unexpected, identifier, url) {
let card = getCard(identifier);
$(card).append(``);
$(card).find('.progress-bar').removeClass("progress-bar-striped").removeClass("progress-bar-animated").css("width", "100%").css('background-color', 'var(--error-color)');
$(card).find('.buttons').children().each(function() {
if($(this).hasClass("remove-btn") || $(this).hasClass("info-btn") || $(this).find("i").hasClass("bi-journal-text")) {
$(this).removeClass("disabled").find('i').removeClass("disabled");
} else if($(this).hasClass("subtitle-btn")) {
changeSubsToRetry(url, card);
} else {
$(this).addClass("disabled").find('i').addClass("disabled");
}
});
$(card).find('.info-btn').on('click', () => {
window.main.invoke("videoAction", {action: "info", identifier: identifier});
});
$(card).find('.report').prop("disabled", false);
$(card).css("box-shadow", "none").css("border", "solid 1px var(--error-color)");
$(card).find('.progress small').html("Error! " + code + ".");
$(card).find('.progress').addClass("d-flex");
sizeCache = sizeCache.filter(item => item[0] !== identifier)
if(unexpected) {
$(card).find('.options, .info, .open').addClass("d-none").removeClass("d-flex");
$(card).find('.error').addClass('d-flex').removeClass("d-none");
$(card).find('.report').unbind().on('click', () => {
window.main.invoke("errorReport", {identifier: identifier, type: $(card).find('.custom-select.download-type').val(), quality: $(card).find('.custom-select.download-quality').val()}).then((id) => {
$(card).find('.progress small').html("Error reported! Report ID: " + id);
$(card).find('.report').prop("disabled", true);
});
});
$(card).find('#fullError').unbind().on('click', () => {
window.main.invoke("messageBox", {title: "Full error message", message: description});
})
} else {
$(card).find('.options, .open').addClass("d-none").removeClass("d-flex");
$(card).find('.info').addClass('d-flex').removeClass("d-none");
$(card).find('.metadata.info').removeClass("d-none").html(description);
}
}
function convertBytes(bytes) {
const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
let l = 0, n = parseInt(bytes, 10) || 0;
while(n >= 1024 && ++l){
n = n/1024;
}
return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l]);
}
function getCard(identifier) {
let card;
$('.video-cards').children().each(function() {
if($(this).prop('id') === identifier) {
card = this;
return false;
}
})
return card;
}