mirror of
https://github.com/jely2002/youtube-dl-gui.git
synced 2021-11-01 22:46:21 +03:00
feat: add support for sponsorblock (#183)
This commit is contained in:
@@ -125,6 +125,15 @@ class DownloadQuery extends Query {
|
||||
if(this.environment.settings.downloadThumbnail) {
|
||||
args.push('--write-thumbnail');
|
||||
}
|
||||
if(this.environment.settings.sponsorblockMark !== "") {
|
||||
args.push("--sponsorblock-mark");
|
||||
args.push(this.environment.settings.sponsorblockMark);
|
||||
}
|
||||
|
||||
if(this.environment.settings.sponsorblockRemove !== "") {
|
||||
args.push("--sponsorblock-remove");
|
||||
args.push(this.environment.settings.sponsorblockRemove);
|
||||
}
|
||||
if(this.environment.settings.keepUnmerged) args.push('--keep-video');
|
||||
let destinationCount = 0;
|
||||
let initialReset = false;
|
||||
|
||||
@@ -267,5 +267,10 @@
|
||||
"code": "Thumbnail embedding not supported",
|
||||
"description": "Only mp3 and m4a/mp4 are supported for thumbnail embedding for now.",
|
||||
"trigger": "ERROR: Only mp3 and m4a/mp4 are supported for thumbnail embedding for now."
|
||||
},
|
||||
{
|
||||
"code": "SponsorBlock API unreachable",
|
||||
"description": "Unable to communicate with SponsorBlock API, please try again.",
|
||||
"trigger": "ERROR: Unable to communicate with SponsorBlock API"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8,8 +8,8 @@ class Settings {
|
||||
proxy, rateLimit, autoFillClipboard, noPlaylist, globalShortcut, userAgent,
|
||||
validateCertificate, enableEncoding, taskList, nameFormat, nameFormatMode,
|
||||
sizeMode, splitMode, maxConcurrent, updateBinary, downloadType, updateApplication, cookiePath,
|
||||
statSend, downloadMetadata, downloadJsonMetadata, downloadThumbnail, keepUnmerged,
|
||||
calculateTotalSize, theme
|
||||
statSend, sponsorblockMark, sponsorblockRemove, sponsorblockApi, downloadMetadata, downloadJsonMetadata,
|
||||
downloadThumbnail, keepUnmerged, calculateTotalSize, theme
|
||||
) {
|
||||
this.paths = paths;
|
||||
this.env = env
|
||||
@@ -27,6 +27,9 @@ class Settings {
|
||||
this.taskList = taskList == null ? true : taskList;
|
||||
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.sponsorblockMark = sponsorblockMark == null ? "" : sponsorblockMark;
|
||||
this.sponsorblockRemove = sponsorblockRemove == null ? "" : sponsorblockRemove;
|
||||
this.sponsorblockApi = sponsorblockApi == null ? "https://sponsor.ajay.app" : sponsorblockApi;
|
||||
this.downloadMetadata = downloadMetadata == null ? true : downloadMetadata;
|
||||
this.downloadJsonMetadata = downloadJsonMetadata == null ? false : downloadJsonMetadata;
|
||||
this.downloadThumbnail = downloadThumbnail == null ? false : downloadThumbnail;
|
||||
@@ -73,6 +76,9 @@ class Settings {
|
||||
data.updateApplication,
|
||||
data.cookiePath,
|
||||
data.statSend,
|
||||
data.sponsorblockMark,
|
||||
data.sponsorblockRemove,
|
||||
data.sponsorblockApi,
|
||||
data.downloadMetadata,
|
||||
data.downloadJsonMetadata,
|
||||
data.downloadThumbnail,
|
||||
@@ -103,6 +109,9 @@ class Settings {
|
||||
this.taskList = settings.taskList;
|
||||
this.nameFormat = settings.nameFormat;
|
||||
this.nameFormatMode = settings.nameFormatMode;
|
||||
this.sponsorblockMark = settings.sponsorblockMark;
|
||||
this.sponsorblockRemove = settings.sponsorblockRemove;
|
||||
this.sponsorblockApi = settings.sponsorblockApi;
|
||||
this.downloadMetadata = settings.downloadMetadata;
|
||||
this.downloadJsonMetadata = settings.downloadJsonMetadata;
|
||||
this.downloadThumbnail = settings.downloadThumbnail;
|
||||
@@ -150,6 +159,9 @@ class Settings {
|
||||
updateApplication: this.updateApplication,
|
||||
cookiePath: this.cookiePath,
|
||||
statSend: this.statSend,
|
||||
sponsorblockMark: this.sponsorblockMark,
|
||||
sponsorblockRemove: this.sponsorblockRemove,
|
||||
sponsorblockApi: this.sponsorblockApi,
|
||||
downloadMetadata: this.downloadMetadata,
|
||||
downloadJsonMetadata: this.downloadJsonMetadata,
|
||||
downloadThumbnail: this.downloadThumbnail,
|
||||
|
||||
@@ -386,6 +386,36 @@
|
||||
<label class="check-label" for="keepUnmerged">Keep unmerged files</label>
|
||||
</div>
|
||||
<hr/>
|
||||
<h3>Sponsorblock</h3>
|
||||
<div class="mb-3">
|
||||
<label for="subsLang" class="form-label d-block">Sections to mark as chapter:</label>
|
||||
<select class="w-75 mb-2" id="sponsorblockMark" multiple>
|
||||
<option value="sponsor">Sponsor</option>
|
||||
<option value="selfpromo">Self-promotion</option>
|
||||
<option value="interaction">Interaction reminder</option>
|
||||
<option value="intro">Intermission / Intro animation</option>
|
||||
<option value="outro">Endcards / Credits</option>
|
||||
<option value="preview">Preview / Recap</option>
|
||||
<option value="music_offtopic">Music off-topic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="autoGenSubsLang" class="form-label d-block">Sections to remove:</label>
|
||||
<select class="w-75 mb-2" id="sponsorblockRemove" multiple>
|
||||
<option value="sponsor">Sponsor</option>
|
||||
<option value="selfpromo">Self-promotion</option>
|
||||
<option value="interaction">Interaction reminder</option>
|
||||
<option value="intro">Intermission / Intro animation</option>
|
||||
<option value="outro">Endcards / Credits</option>
|
||||
<option value="preview">Preview / Recap</option>
|
||||
<option value="music_offtopic">Music off-topic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<input class="check-input" type="checkbox" value="" id="sponsorblockApi">
|
||||
<label class="check-label" for="sponsorblockApi">Sponsorblock API location</label>
|
||||
</div>
|
||||
<hr/>
|
||||
<h3>Advanced</h3>
|
||||
<div class="mb-1">
|
||||
<input class="check-input" type="checkbox" value="" id="taskList">
|
||||
|
||||
@@ -104,6 +104,8 @@ async function init() {
|
||||
//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) {
|
||||
@@ -991,6 +993,9 @@ async function getSettings() {
|
||||
$('#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);
|
||||
@@ -1021,6 +1026,9 @@ function sendSettings() {
|
||||
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'),
|
||||
|
||||
@@ -1,83 +1,121 @@
|
||||
const fs = require('fs').promises;
|
||||
const os = require("os");
|
||||
const os = require('os');
|
||||
const Settings = require('../modules/persistence/Settings');
|
||||
const env = {version: "2.0.0-test1", app: {getPath: jest.fn().mockReturnValue("test/path")}};
|
||||
const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "none", "test/path", "", "", true, false, true, "spoof", false, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, "video", true, "C:\\Users\\user\\cookies.txt", false, true, false, false, false, true, "dark");
|
||||
const defaultSettings = "{\"outputFormat\":\"none\",\"audioOutputFormat\":\"none\",\"downloadPath\":\"test/path\",\"proxy\":\"\",\"rateLimit\":\"\",\"autoFillClipboard\":true,\"noPlaylist\":false,\"globalShortcut\":true,\"userAgent\":\"spoof\",\"validateCertificate\":false,\"enableEncoding\":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,\"downloadType\":\"video\",\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadJsonMetadata\":false,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}"
|
||||
const env = {version: '2.0.0-test1', app: {getPath: jest.fn().mockReturnValue('test/path')}};
|
||||
const defaultSettingsInstance = new Settings({settings: 'tests/test-settings.json'}, env, 'none', 'none', 'test/path', '', '', true, false, true, 'spoof', false, false, true, '%(title).200s-(%(height)sp%(fps).0d).%(ext)s', '%(title).200s-(%(height)sp%(fps).0d).%(ext)s', 'click', '49', 8, true, 'video', true, 'C:\\Users\\user\\cookies.txt', false, '', '', 'https://sponsor.ajay.app', true, false, false, false, true, 'dark');
|
||||
const defaultSettings = {
|
||||
outputFormat: 'none',
|
||||
audioOutputFormat: 'none',
|
||||
downloadPath: 'test/path',
|
||||
proxy: '',
|
||||
rateLimit: '',
|
||||
autoFillClipboard: true,
|
||||
noPlaylist: false,
|
||||
globalShortcut: true,
|
||||
userAgent: 'spoof',
|
||||
validateCertificate: false,
|
||||
enableEncoding: 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,
|
||||
downloadType: 'video',
|
||||
updateApplication: true,
|
||||
statSend: false,
|
||||
sponsorblockMark: '',
|
||||
sponsorblockRemove: '',
|
||||
sponsorblockApi: 'https://sponsor.ajay.app',
|
||||
downloadMetadata: true,
|
||||
downloadJsonMetadata: false,
|
||||
downloadThumbnail: false,
|
||||
keepUnmerged: false,
|
||||
calculateTotalSize: true,
|
||||
theme: 'dark',
|
||||
version: '2.0.0-test1',
|
||||
};
|
||||
|
||||
describe('Load settings from file', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
fs.writeFile = jest.fn().mockResolvedValue("");
|
||||
console.log = jest.fn().mockImplementation(() => {});
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
fs.writeFile = jest.fn().mockResolvedValue('');
|
||||
console.log = jest.fn().mockImplementation(() => {
|
||||
});
|
||||
it('reads the specified file', () => {
|
||||
const readFileSpy = jest.spyOn(fs, 'readFile');
|
||||
return Settings.loadFromFile({settings: "tests/test-settings.json"}, env).then((data) => {
|
||||
expect(readFileSpy).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
it('reads the specified file', () => {
|
||||
const readFileSpy = jest.spyOn(fs, 'readFile');
|
||||
return Settings.loadFromFile({settings: 'tests/test-settings.json'}, env).then((data) => {
|
||||
expect(readFileSpy).toBeCalledTimes(1);
|
||||
});
|
||||
it('returns a settings instance', () => {
|
||||
return Settings.loadFromFile({settings: "tests/test-settings.json"}, env).then((data) => {
|
||||
expect(data).toBeInstanceOf(Settings);
|
||||
});
|
||||
});
|
||||
it('returns a settings instance', () => {
|
||||
return Settings.loadFromFile({settings: 'tests/test-settings.json'}, env).then((data) => {
|
||||
expect(data).toBeInstanceOf(Settings);
|
||||
});
|
||||
it('returns a settings instance with the right values', () => {
|
||||
return Settings.loadFromFile({settings: "tests/test-settings.json"}, env).then((data) => {
|
||||
expect(data).toMatchObject(defaultSettingsInstance);
|
||||
});
|
||||
});
|
||||
it('returns a settings instance with the right values', () => {
|
||||
return Settings.loadFromFile({settings: 'tests/test-settings.json'}, env).then((data) => {
|
||||
expect(data).toMatchObject(defaultSettingsInstance);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Create new settings file on error', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
os.cpus = jest.fn().mockImplementation(() => { return new Array(16) });
|
||||
fs.writeFile = jest.fn().mockResolvedValue("");
|
||||
console.log = jest.fn().mockImplementation(() => {});
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
os.cpus = jest.fn().mockImplementation(() => {
|
||||
return new Array(16);
|
||||
});
|
||||
it('uses the path defined in paths', () => {
|
||||
return Settings.loadFromFile({settings: "tests/non-existent-file.json"}, env).then(() => {
|
||||
expect(fs.writeFile.mock.calls[0]).toContain("tests/non-existent-file.json");
|
||||
});
|
||||
}) ;
|
||||
it('writes the new settings file', () => {
|
||||
return Settings.loadFromFile({settings: "tests/non-existent-file.json"}, env).then(() => {
|
||||
expect(fs.writeFile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
fs.writeFile = jest.fn().mockResolvedValue('');
|
||||
console.log = jest.fn().mockImplementation(() => {
|
||||
});
|
||||
it('writes the given settings', () => {
|
||||
return Settings.loadFromFile({settings: "tests/non-existent-file.json"}, env).then(() => {
|
||||
expect(fs.writeFile.mock.calls[0]).toContainEqual(defaultSettings);
|
||||
});
|
||||
});
|
||||
it('uses the path defined in paths', () => {
|
||||
return Settings.loadFromFile({settings: 'tests/non-existent-file.json'}, env).then(() => {
|
||||
expect(fs.writeFile.mock.calls[0]).toContain('tests/non-existent-file.json');
|
||||
});
|
||||
});
|
||||
it('writes the new settings file', () => {
|
||||
return Settings.loadFromFile({settings: 'tests/non-existent-file.json'}, env).then(() => {
|
||||
expect(fs.writeFile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
it('writes the given settings', () => {
|
||||
return Settings.loadFromFile({settings: 'tests/non-existent-file.json'}, env).then(() => {
|
||||
expect(fs.writeFile.mock.calls[0]).toContainEqual(JSON.stringify(defaultSettings));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update settings to file', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
fs.writeFile = jest.fn().mockResolvedValue("");
|
||||
env.appUpdater = { setUpdateSetting: jest.fn() };
|
||||
env.changeMaxConcurrent = jest.fn();
|
||||
console.log = jest.fn().mockImplementation(() => {});
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
fs.writeFile = jest.fn().mockResolvedValue('');
|
||||
env.appUpdater = {setUpdateSetting: jest.fn()};
|
||||
env.changeMaxConcurrent = jest.fn();
|
||||
console.log = jest.fn().mockImplementation(() => {
|
||||
});
|
||||
it('writes the updated file', () => {
|
||||
return Settings.loadFromFile({settings: "tests/test-settings.json"}, env).then(data => {
|
||||
delete data.cookiePath;
|
||||
data.update(JSON.parse(defaultSettings));
|
||||
expect(fs.writeFile).toBeCalledTimes(1);
|
||||
expect(fs.writeFile.mock.calls[0]).toContainEqual(defaultSettings);
|
||||
});
|
||||
});
|
||||
it('writes the updated file', () => {
|
||||
return Settings.loadFromFile({settings: 'tests/test-settings.json'}, env).then(data => {
|
||||
delete data.cookiePath;
|
||||
data.update(JSON.parse(JSON.stringify(defaultSettings)));
|
||||
expect(fs.writeFile).toBeCalledTimes(1);
|
||||
expect(fs.writeFile.mock.calls[0]).toContainEqual(JSON.stringify(defaultSettings));
|
||||
});
|
||||
it('updates the maxConcurrent value when it changes', () => {
|
||||
const changedDefaultSettings = JSON.parse(defaultSettings);
|
||||
changedDefaultSettings.maxConcurrent = 4;
|
||||
});
|
||||
it('updates the maxConcurrent value when it changes', () => {
|
||||
const changedDefaultSettings = JSON.parse(JSON.stringify(defaultSettings));
|
||||
changedDefaultSettings.maxConcurrent = 4;
|
||||
|
||||
return Settings.loadFromFile({settings: "tests/test-settings.json"}, env).then(data => {
|
||||
data.update(changedDefaultSettings);
|
||||
expect(env.changeMaxConcurrent).toBeCalledTimes(1);
|
||||
});
|
||||
return Settings.loadFromFile({settings: 'tests/test-settings.json'}, env).then(data => {
|
||||
data.update(changedDefaultSettings);
|
||||
expect(env.changeMaxConcurrent).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"outputFormat":"none","audioOutputFormat":"none","downloadPath": "test/path","proxy": "","rateLimit": "","autoFillClipboard":true,"noPlaylist": false,"globalShortcut":true,"userAgent":"spoof","validateCertificate": false,"enableEncoding": 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,"downloadType":"video","updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadJsonMetadata":false,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"}
|
||||
{"outputFormat":"none","audioOutputFormat":"none","downloadPath": "test/path","proxy": "","rateLimit": "","autoFillClipboard":true,"noPlaylist": false,"globalShortcut":true,"userAgent":"spoof","validateCertificate": false,"enableEncoding": 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,"downloadType":"video","updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"sponsorblockMark":"","sponsorblockRemove":"","sponsorblockApi":"https://sponsor.ajay.app","downloadMetadata":true,"downloadJsonMetadata":false,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"}
|
||||
|
||||
Reference in New Issue
Block a user