feat: add subtitle language selector modal

Add language selector to master
This commit is contained in:
Jelle Glebbeek
2021-05-25 14:39:26 +02:00
committed by GitHub
13 changed files with 575 additions and 38 deletions

10
main.js
View File

@@ -104,11 +104,15 @@ function startCriticalHandlers(env) {
ipcMain.handle('setDoneAction', (event, args) => {
env.doneAction = args.action;
})
});
ipcMain.handle('getSubtitles', (event, args) => {
return queryManager.getAvailableSubtitles(args.identifier);
})
return queryManager.getAvailableSubtitles(args.identifier, args.unified);
});
ipcMain.handle('getSelectedSubtitles', (event, args) => {
return queryManager.getSelectedSubtitles(args.identifier);
});
ipcMain.handle('videoAction', async (event, args) => {
switch (args.action) {

View File

@@ -345,7 +345,7 @@ class QueryManager {
}
//Fallback
if(args.type === "folder") {
shell.showItemInFolder(video.downloadedPath);
shell.openPath(video.downloadedPath);
} else if(args.type === "item") {
shell.openPath(path.join(video.downloadedPath, video.getFilename()) + ".mp4");
} else {
@@ -354,8 +354,31 @@ class QueryManager {
});
}
getAvailableSubtitles(identifier) {
getUnifiedAvailableSubtitles(videos) {
let totalSubs = [];
let totalAutoGen = [];
for(const video of videos) {
if(video.subtitles != null && video.subtitles.length !== 0) {
totalSubs = totalSubs.concat(Object.keys(video.subtitles).map(sub => {
return {iso: sub, name: Utils.getNameFromISO(sub)};
}))
}
if(video.autoCaptions != null && video.autoCaptions.length !== 0) {
totalAutoGen = totalAutoGen.concat(Object.keys(video.autoCaptions).map(sub => {
return {iso: sub, name: Utils.getNameFromISO(sub)};
}))
}
}
const totalSubsDedupe = Utils.dedupeSubtitles(totalSubs);
const totalAutoGenDedupe = Utils.dedupeSubtitles(totalAutoGen);
return [totalSubsDedupe.sort(Utils.sortSubtitles), totalAutoGenDedupe.sort(Utils.sortSubtitles)];
}
getAvailableSubtitles(identifier, unified) {
const video = this.getVideo(identifier);
if(unified) {
return this.getUnifiedAvailableSubtitles(video.videos);
}
let subs = [];
let autoGen = [];
if(video.subtitles != null && video.subtitles.length !== 0) {
@@ -363,12 +386,17 @@ class QueryManager {
return {iso: sub, name: Utils.getNameFromISO(sub)};
})
}
if(video.autoCaptions != null && video.autoCaptions.length !== 0 && this.environment.settings.autoGenSubs) {
if(video.autoCaptions != null && video.autoCaptions.length !== 0) {
autoGen = Object.keys(video.autoCaptions).map(sub => {
return {iso: sub, name: Utils.getNameFromISO(sub)};
})
}
return [subs.sort(), autoGen.sort()];
return [subs.sort(Utils.sortSubtitles), autoGen.sort(Utils.sortSubtitles)];
}
getSelectedSubtitles(identifier) {
const video = this.getVideo(identifier);
return video.selectedSubs;
}
showInfo(identifier) {
@@ -424,16 +452,28 @@ class QueryManager {
this.window.webContents.send("updateGlobalButtons", videos);
}
setUnifiedSubtitle(videos, args) {
for(const video of videos) {
video.downloadSubs = args.enabled;
video.selectedSubs = [args.subs, args.autoGen];
video.subLanguages = [...new Set([...args.subs, ...args.autoGen])];
}
}
setSubtitle(args) {
const video = this.getVideo(args.identifier);
if(args.unified) {
this.setUnifiedSubtitle(video.videos, args);
}
video.downloadSubs = args.enabled;
video.subLanguages = args.langs;
video.selectedSubs = [args.subs, args.autoGen];
video.subLanguages = [...new Set([...args.subs, ...args.autoGen])];
}
setGlobalSubtitle(value) {
this.environment.mainDownloadSubs = value;
for(const video of this.managedVideos) {
video.downloadSubs = value;
this.environment.mainDownloadSubs = value;
}
}

View File

@@ -90,6 +90,21 @@ class Utils {
}
}
static sortSubtitles(a, b) {
if (a.name < b.name){
return -1;
}
if (a.name > b.name){
return 1;
}
return 0;
}
static dedupeSubtitles(subs) {
const keys = ['name'];
return subs.filter((s => o => (k => !s.has(k) && s.add(k))(keys.map(k => o[k]).join('|')))(new Set()));
}
static detectInfoType(infoQueryResult) {
if(infoQueryResult == null) return infoQueryResult;
if(Object.keys(infoQueryResult).length === 0) return infoQueryResult;

View File

@@ -58,7 +58,7 @@ class DownloadQuery extends Query {
'--output-na-placeholder', ""
];
}
if (this.video.downloadSubs) {
if (this.video.downloadSubs && this.video.subLanguages.length > 0) {
this.progressBar.setInitial("Downloading subtitles");
args.push("--write-sub");
args.push("--write-auto-sub");
@@ -67,7 +67,6 @@ class DownloadQuery extends Query {
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

@@ -2,14 +2,13 @@ const os = require("os");
const fs = require("fs").promises;
class Settings {
constructor(paths, env, outputFormat, spoofUserAgent, validateCertificate, taskList, autoGenSubs, nameFormat, nameFormatMode, sizeMode, splitMode, maxConcurrent, updateBinary, updateApplication, cookiePath, statSend, downloadMetadata, downloadThumbnail, keepUnmerged, calculateTotalSize, theme) {
constructor(paths, env, outputFormat, spoofUserAgent, validateCertificate, taskList, nameFormat, nameFormatMode, sizeMode, splitMode, maxConcurrent, updateBinary, updateApplication, cookiePath, statSend, downloadMetadata, downloadThumbnail, keepUnmerged, calculateTotalSize, theme) {
this.paths = paths;
this.env = env
this.outputFormat = outputFormat == null ? "none" : outputFormat;
this.spoofUserAgent = spoofUserAgent == null ? true : spoofUserAgent;
this.validateCertificate = validateCertificate == null ? false : validateCertificate;
this.taskList = taskList == null ? true : taskList;
this.autoGenSubs = autoGenSubs == null ? false : autoGenSubs;
this.nameFormat = nameFormat == null ? "%(title).200s-(%(height)sp%(fps).0d).%(ext)s" : nameFormat;
this.nameFormatMode = nameFormatMode == null ? "%(title).200s-(%(height)sp%(fps).0d).%(ext)s" : nameFormatMode;
this.downloadMetadata = downloadMetadata == null ? true : downloadMetadata;
@@ -30,7 +29,7 @@ class Settings {
try {
let result = await fs.readFile(paths.settings, "utf8");
let data = JSON.parse(result);
return new Settings(paths, env, data.outputFormat, data.spoofUserAgent, data.validateCertificate, data.taskList, data.autoGenSubs, data.nameFormat, data.nameFormatMode, data.sizeMode, data.splitMode, data.maxConcurrent, data.updateBinary, data.updateApplication, data.cookiePath, data.statSend, data.downloadMetadata, data.downloadThumbnail, data.keepUnmerged, data.calculateTotalSize, data.theme);
return new Settings(paths, env, data.outputFormat, data.spoofUserAgent, data.validateCertificate, data.taskList, data.nameFormat, data.nameFormatMode, data.sizeMode, data.splitMode, data.maxConcurrent, data.updateBinary, data.updateApplication, data.cookiePath, data.statSend, data.downloadMetadata, data.downloadThumbnail, data.keepUnmerged, data.calculateTotalSize, data.theme);
} catch(err) {
console.log(err);
let settings = new Settings(paths, env);
@@ -45,7 +44,6 @@ class Settings {
this.spoofUserAgent = settings.spoofUserAgent;
this.validateCertificate = settings.validateCertificate;
this.taskList = settings.taskList;
this.autoGenSubs = settings.autoGenSubs;
this.nameFormat = settings.nameFormat;
this.nameFormatMode = settings.nameFormatMode;
this.downloadMetadata = settings.downloadMetadata;
@@ -73,7 +71,6 @@ class Settings {
spoofUserAgent: this.spoofUserAgent,
validateCertificate: this.validateCertificate,
taskList: this.taskList,
autoGenSubs: this.autoGenSubs,
nameFormat: this.nameFormat,
nameFormatMode: this.nameFormatMode,
sizeMode: this.sizeMode,

View File

@@ -11,6 +11,7 @@ class Video {
this.videoOnlySizeCache = [];
this.downloadSubs = environment.mainDownloadSubs;
this.subLanguages = [];
this.selectedSubs = [];
this.downloadingAudio = false;
this.webpage_url = this.url;
this.hasMetadata = false;

View File

@@ -21,7 +21,8 @@ contextBridge.exposeInMainWorld(
"restoreTaskList",
"getDoneActions",
"setDoneAction",
"getSubtitles"
"getSubtitles",
"getSelectedSubtitles"
];
if (validChannels.includes(channel)) {
return await ipcRenderer.invoke(channel, data);

View File

@@ -187,7 +187,7 @@
</div>
<div class="modal-body">
<div class="container-fluid">
<p>Select the subtitle languages you want to download.</p>
<p class="description">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>
@@ -304,10 +304,6 @@
<input class="check-input" type="checkbox" value="" id="downloadThumbnail">
<label class="check-label" for="downloadThumbnail">Save thumbnail as file</label>
</div>
<div class="mb-1">
<input class="check-input" type="checkbox" value="" id="autoGenSubs">
<label class="check-label" for="autoGenSubs">Download auto-generated subtitles</label>
</div>
<div class="mb-1">
<input class="check-input" type="checkbox" value="" id="keepUnmerged">
<label class="check-label" for="keepUnmerged">Keep unmerged files</label>

View File

@@ -154,7 +154,6 @@ async function init() {
spoofUserAgent: $('#spoofUserAgent').prop('checked'),
validateCertificate: $('#validateCertificate').prop('checked'),
taskList: $('#taskList').prop('checked'),
autoGenSubs: $('#autoGenSubs').prop('checked'),
nameFormatMode: $('#nameFormat').val(),
nameFormat: $('#nameFormatCustom').val(),
downloadMetadata: $('#downloadMetadata').prop('checked'),
@@ -191,7 +190,6 @@ async function init() {
$('#spoofUserAgent').prop('checked', settings.spoofUserAgent);
$('#validateCertificate').prop('checked', settings.validateCertificate);
$('#taskList').prop('checked', settings.taskList);
$('#autoGenSubs').prop('checked', settings.autoGenSubs);
$('#nameFormatCustom').val(settings.nameFormat);
$('#nameFormat').val(settings.nameFormatMode);
$('#outputFormat').val(settings.outputFormat);
@@ -439,6 +437,7 @@ function addVideo(args) {
.addClass('progress-bar-striped')
.addClass('progress-bar-animated')
.width("100%");
console.log(args.subtitles);
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");
@@ -801,26 +800,31 @@ 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);
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, langs: langs, enabled: $(modal).find('#enableSubs').prop('checked')});
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});
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(`<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('#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) {
@@ -832,7 +836,22 @@ async function showSubtitleModal(identifier, card) {
$(modal).find('#autoGenSubsLang').append(option);
}
}
$(modal).find('#enableSubs').prop('checked', !$(card).find('.subtitle-btn i').hasClass("bi-card-text-strike"));
$(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");
}
@@ -896,7 +915,9 @@ function updateButtons(videos) {
function changeSubsToRetry(url, card) {
if(card == null) return;
$(card).find('.subtitle-btn').removeClass("subtitle-btn")
$(card).find('.subtitle-btn')
.unbind()
.removeClass("subtitle-btn")
.removeClass("disabled")
.addClass("retry-btn")
.html('<i title="Retry" class="bi bi-arrow-counterclockwise"></i>')

View File

@@ -2,8 +2,8 @@ const fs = require('fs').promises;
const os = require("os");
const Settings = require('../modules/persistence/Settings');
const env = {version: "2.0.0-test1"};
const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", true, false, true, false, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark");
const defaultSettings = "{\"outputFormat\":\"none\",\"spoofUserAgent\":true,\"validateCertificate\":false,\"taskList\":true,\"autoGenSubs\":false,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}"
const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", true, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark");
const defaultSettings = "{\"outputFormat\":\"none\",\"spoofUserAgent\":true,\"validateCertificate\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}"
describe('Load settings from file', () => {
beforeEach(() => {

View File

@@ -27,6 +27,27 @@ describe('getRandomID', () => {
})
});
describe('dedupeSubtitles', () => {
it('dedupes by name', () => {
const testList = [ {name: "dutch", iso: "nl"}, {name: "dutch", iso: "nl"}, {name: "english", iso: "en"} ];
const testListDeduped = [ {name: "dutch", iso: "nl"}, {name: "english", iso: "en"} ];
expect(Utils.dedupeSubtitles(testList)).toEqual(testListDeduped);
})
});
describe('sortSubtitles', () => {
const testList = [{name: "finland", iso: "fi"}, {name: "english", iso: "en"}, {name: "afrikaans", iso: "af"}, {name: "afrikaans", iso: "af"}, {name: "belgium", iso: "be"}, {name: "dutch", iso: "nl"} ];
const testListSorted = [ {name: "afrikaans", iso: "af"}, {name: "afrikaans", iso: "af"}, {name: "belgium", iso: "be"}, {name: "dutch", iso: "nl"}, {name: "english", iso: "en"}, {name: "finland", iso: "fi"} ];
expect(testList.sort(Utils.sortSubtitles)).toEqual(testListSorted);
});
describe('getIsoFromName', () => {
const isoNames = require('./iso-test.json');
for(const isoName of isoNames) {
expect(Utils.getNameFromISO(isoName.iso)).toBe(isoName.name);
}
})
describe('convertBytes', () => {
it('returns a defined value', () => {

442
tests/iso-test.json Normal file
View File

@@ -0,0 +1,442 @@
[
{
"iso": "tg",
"name": "Tajik"
},
{
"iso": "nl",
"name": "Dutch"
},
{
"iso": "es",
"name": "Spanish"
},
{
"iso": "az",
"name": "Azerbaijani"
},
{
"iso": "zh-Hant",
"name": "Chinese (Traditional)"
},
{
"iso": "de",
"name": "German"
},
{
"iso": "bg",
"name": "Bulgarian"
},
{
"iso": "gu",
"name": "Gujarati"
},
{
"iso": "yo",
"name": "Yoruba"
},
{
"iso": "sw",
"name": "Swahili"
},
{
"iso": "cy",
"name": "Welsh"
},
{
"iso": "ht",
"name": "Haitian"
},
{
"iso": "sq",
"name": "Albanian"
},
{
"iso": "hu",
"name": "Hungarian"
},
{
"iso": "mn",
"name": "Mongolian"
},
{
"iso": "bs",
"name": "Bosnian"
},
{
"iso": "zh-Hans",
"name": "Chinese (Simplified)"
},
{
"iso": "lo",
"name": "Lao"
},
{
"iso": "st",
"name": "Sotho"
},
{
"iso": "kn",
"name": "Kannada"
},
{
"iso": "la",
"name": "Latin"
},
{
"iso": "hi",
"name": "Hindi"
},
{
"iso": "pl",
"name": "Polish"
},
{
"iso": "ug",
"name": "Uighur"
},
{
"iso": "jv",
"name": "Javanese"
},
{
"iso": "ga",
"name": "Irish"
},
{
"iso": "fi",
"name": "Finnish"
},
{
"iso": "ne",
"name": "Nepali"
},
{
"iso": "tr",
"name": "Turkish"
},
{
"iso": "id",
"name": "Indonesian"
},
{
"iso": "en",
"name": "English"
},
{
"iso": "pa",
"name": "Panjabi"
},
{
"iso": "ca",
"name": "Catalan"
},
{
"iso": "it",
"name": "Italian"
},
{
"iso": "lv",
"name": "Latvian"
},
{
"iso": "mr",
"name": "Marathi"
},
{
"iso": "ka",
"name": "Georgian"
},
{
"iso": "ceb",
"name": "Cebuano"
},
{
"iso": "eu",
"name": "Basque"
},
{
"iso": "te",
"name": "Telugu"
},
{
"iso": "ta",
"name": "Tamil"
},
{
"iso": "ig",
"name": "Igbo"
},
{
"iso": "mi",
"name": "Maori"
},
{
"iso": "fil",
"name": "Filipino"
},
{
"iso": "or",
"name": "Oriya"
},
{
"iso": "hy",
"name": "Armenian"
},
{
"iso": "iw",
"name": "Hebrew"
},
{
"iso": "el",
"name": "Greek"
},
{
"iso": "eo",
"name": "Esperanto"
},
{
"iso": "sd",
"name": "Sindhi"
},
{
"iso": "zu",
"name": "Zulu"
},
{
"iso": "af",
"name": "Afrikaans"
},
{
"iso": "mk",
"name": "Macedonian"
},
{
"iso": "ro",
"name": "Romanian"
},
{
"iso": "ku",
"name": "Kurdish"
},
{
"iso": "fr",
"name": "French"
},
{
"iso": "mg",
"name": "Malagasy"
},
{
"iso": "ja",
"name": "Japanese"
},
{
"iso": "vi",
"name": "Vietnamese"
},
{
"iso": "hmn",
"name": "Hmong"
},
{
"iso": "fy",
"name": "Western Frisian"
},
{
"iso": "no",
"name": "Norwegian"
},
{
"iso": "sm",
"name": "Samoan"
},
{
"iso": "pt",
"name": "Portuguese"
},
{
"iso": "co",
"name": "Corsican"
},
{
"iso": "ha",
"name": "Hausa"
},
{
"iso": "ru",
"name": "Russian"
},
{
"iso": "ar",
"name": "Arabic"
},
{
"iso": "lt",
"name": "Lithuanian"
},
{
"iso": "haw",
"name": "Hawaiian"
},
{
"iso": "gd",
"name": "Gaelic"
},
{
"iso": "be",
"name": "Belarusian"
},
{
"iso": "sr",
"name": "Serbian"
},
{
"iso": "si",
"name": "Sinhala"
},
{
"iso": "km",
"name": "Central Khmer"
},
{
"iso": "gl",
"name": "Galician"
},
{
"iso": "xh",
"name": "Xhosa"
},
{
"iso": "ny",
"name": "Chichewa"
},
{
"iso": "mt",
"name": "Maltese"
},
{
"iso": "ky",
"name": "Kirghiz"
},
{
"iso": "sn",
"name": "Shona"
},
{
"iso": "ps",
"name": "Pushto"
},
{
"iso": "rw",
"name": "Kinyarwanda"
},
{
"iso": "cs",
"name": "Czech"
},
{
"iso": "am",
"name": "Amharic"
},
{
"iso": "bn",
"name": "Bengali"
},
{
"iso": "tk",
"name": "Turkmen"
},
{
"iso": "lb",
"name": "Luxembourgish"
},
{
"iso": "yi",
"name": "Yiddish"
},
{
"iso": "so",
"name": "Somali"
},
{
"iso": "da",
"name": "Danish"
},
{
"iso": "uk",
"name": "Ukrainian"
},
{
"iso": "tt",
"name": "Tatar"
},
{
"iso": "hr",
"name": "Croatian"
},
{
"iso": "my",
"name": "Burmese"
},
{
"iso": "sl",
"name": "Slovenian"
},
{
"iso": "uz",
"name": "Uzbek"
},
{
"iso": "ur",
"name": "Urdu"
},
{
"iso": "ml",
"name": "Malayalam"
},
{
"iso": "sk",
"name": "Slovak"
},
{
"iso": "kk",
"name": "Kazakh"
},
{
"iso": "et",
"name": "Estonian"
},
{
"iso": "ms",
"name": "Malay"
},
{
"iso": "sv",
"name": "Swedish"
},
{
"iso": "fa",
"name": "Persian"
},
{
"iso": "su",
"name": "Sundanese"
},
{
"iso": "is",
"name": "Icelandic"
},
{
"iso": "th",
"name": "Thai"
},
{
"iso": "ko",
"name": "Korean"
},
{
"iso": "invalid",
"name": "invalid"
}
]

View File

@@ -1 +1 @@
{"outputFormat":"none","spoofUserAgent":true,"validateCertificate": false,"taskList":true,"autoGenSubs":false,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"}
{"outputFormat":"none","spoofUserAgent":true,"validateCertificate": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"}