mirror of
https://github.com/alexta69/metube.git
synced 2026-09-21 13:35:01 +00:00
feat: first-class SponsorBlock toggle
A "Remove sponsor segments" switch next to "Split by chapters" queues the download with the same postprocessor pair the CLI's --sponsorblock-remove sponsor builds (SponsorBlock + ModifyChapters). The flag persists as a cookie like the other form options, survives in the queue records, and is carried into retries. The pair is registered above the chapter-splitting block: yt-dlp runs same-stage postprocessors in list order, so ModifyChapters has to rewrite the chapter list before FFmpegSplitChapters cuts the file up, matching what the CLI builds for --sponsorblock-remove sponsor --split-chapters. With both toggles on the other way around the chapter files keep the sponsor segments and the removal desyncs the remaining chapter timings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -725,6 +725,7 @@ def parse_download_options(post: dict) -> dict:
|
|||||||
playlist_item_limit = post.get('playlist_item_limit')
|
playlist_item_limit = post.get('playlist_item_limit')
|
||||||
auto_start = post.get('auto_start')
|
auto_start = post.get('auto_start')
|
||||||
split_by_chapters = post.get('split_by_chapters')
|
split_by_chapters = post.get('split_by_chapters')
|
||||||
|
sponsorblock = bool(post.get('sponsorblock'))
|
||||||
chapter_template = post.get('chapter_template')
|
chapter_template = post.get('chapter_template')
|
||||||
subtitle_language = post.get('subtitle_language')
|
subtitle_language = post.get('subtitle_language')
|
||||||
subtitle_mode = post.get('subtitle_mode')
|
subtitle_mode = post.get('subtitle_mode')
|
||||||
@@ -845,6 +846,7 @@ def parse_download_options(post: dict) -> dict:
|
|||||||
'playlist_item_limit': playlist_item_limit,
|
'playlist_item_limit': playlist_item_limit,
|
||||||
'auto_start': auto_start,
|
'auto_start': auto_start,
|
||||||
'split_by_chapters': split_by_chapters,
|
'split_by_chapters': split_by_chapters,
|
||||||
|
'sponsorblock': sponsorblock,
|
||||||
'chapter_template': chapter_template,
|
'chapter_template': chapter_template,
|
||||||
'subtitle_language': subtitle_language,
|
'subtitle_language': subtitle_language,
|
||||||
'subtitle_mode': subtitle_mode,
|
'subtitle_mode': subtitle_mode,
|
||||||
@@ -890,6 +892,7 @@ async def add(request):
|
|||||||
o['ytdl_options_overrides'],
|
o['ytdl_options_overrides'],
|
||||||
o['clip_start'],
|
o['clip_start'],
|
||||||
o['clip_end'],
|
o['clip_end'],
|
||||||
|
sponsorblock=o['sponsorblock'],
|
||||||
)
|
)
|
||||||
return web.Response(text=serializer.encode(status))
|
return web.Response(text=serializer.encode(status))
|
||||||
|
|
||||||
|
|||||||
@@ -476,6 +476,32 @@ async def test_retry_keeps_overrides_while_still_allowed(dq_env):
|
|||||||
assert dq.queue.get(url).info.ytdl_options_overrides == {"writesubtitles": True}
|
assert dq.queue.get(url).info.ytdl_options_overrides == {"writesubtitles": True}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_retry_carries_the_sponsorblock_flag(dq_env):
|
||||||
|
notifier = AsyncMock()
|
||||||
|
dq = DownloadQueue(dq_env, notifier)
|
||||||
|
url = "https://example.com/watch?v=1"
|
||||||
|
dq.done.put(
|
||||||
|
Download(None, None, None, None, "best", "any", {}, _failed_playlist_item(url, sponsorblock=True))
|
||||||
|
)
|
||||||
|
|
||||||
|
def fake_extract(self, extracted_url, *_args, **_kwargs):
|
||||||
|
return {
|
||||||
|
"_type": "video",
|
||||||
|
"id": "vid1",
|
||||||
|
"title": "Test Video",
|
||||||
|
"url": extracted_url,
|
||||||
|
"webpage_url": extracted_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch.object(DownloadQueue, "_DownloadQueue__extract_info", fake_extract), \
|
||||||
|
patch.object(DownloadQueue, "_DownloadQueue__start_download", new=AsyncMock()):
|
||||||
|
result = await dq.retry(url)
|
||||||
|
|
||||||
|
assert result["status"] == "ok"
|
||||||
|
assert dq.queue.get(url).info.sponsorblock is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_add_entry_duplicate_while_pending_is_skipped_not_clobbered(dq_env):
|
async def test_add_entry_duplicate_while_pending_is_skipped_not_clobbered(dq_env):
|
||||||
notifier = AsyncMock()
|
notifier = AsyncMock()
|
||||||
|
|||||||
@@ -678,6 +678,69 @@ class DownloadResultTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _capture_ytdl_params(download: Download) -> dict:
|
||||||
|
"""Run ``_download`` far enough to capture the params it builds."""
|
||||||
|
fake_ydl = MagicMock()
|
||||||
|
fake_ydl.download.return_value = 0
|
||||||
|
download.status_queue = types.SimpleNamespace(put=lambda _: None)
|
||||||
|
|
||||||
|
with patch('ytdl.install_socket_guard'), \
|
||||||
|
patch.object(Download, '_make_youtube_dl', return_value=fake_ydl) as make:
|
||||||
|
download._download()
|
||||||
|
|
||||||
|
params, = make.call_args.args
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorBlockPostprocessorTests(unittest.TestCase):
|
||||||
|
def test_no_sponsorblock_postprocessors_when_disabled(self):
|
||||||
|
download = _make_test_download()
|
||||||
|
|
||||||
|
params = _capture_ytdl_params(download)
|
||||||
|
|
||||||
|
keys = [pp['key'] for pp in params.get('postprocessors', [])]
|
||||||
|
self.assertNotIn('SponsorBlock', keys)
|
||||||
|
self.assertNotIn('ModifyChapters', keys)
|
||||||
|
|
||||||
|
def test_sponsorblock_pair_matches_the_cli(self):
|
||||||
|
download = _make_test_download()
|
||||||
|
download.info.sponsorblock = True
|
||||||
|
|
||||||
|
params = _capture_ytdl_params(download)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
params['postprocessors'],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'key': 'SponsorBlock',
|
||||||
|
'categories': ['sponsor'],
|
||||||
|
'when': 'after_filter',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'ModifyChapters',
|
||||||
|
'remove_sponsor_segments': ['sponsor'],
|
||||||
|
'force_keyframes': False,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_segment_removal_runs_before_the_chapter_split(self):
|
||||||
|
# yt-dlp runs same-stage postprocessors in list order, so ModifyChapters
|
||||||
|
# has to rewrite the chapter list before FFmpegSplitChapters cuts the
|
||||||
|
# file up -- the order the CLI builds for
|
||||||
|
# --sponsorblock-remove sponsor --split-chapters.
|
||||||
|
download = _make_test_download()
|
||||||
|
download.info.sponsorblock = True
|
||||||
|
download.info.split_by_chapters = True
|
||||||
|
download.info.chapter_template = '%(section_number)s.%(ext)s'
|
||||||
|
|
||||||
|
params = _capture_ytdl_params(download)
|
||||||
|
|
||||||
|
keys = [pp['key'] for pp in params['postprocessors']]
|
||||||
|
self.assertEqual(keys, ['SponsorBlock', 'ModifyChapters', 'FFmpegSplitChapters'])
|
||||||
|
self.assertEqual(params['outtmpl']['chapter'], '%(section_number)s.%(ext)s')
|
||||||
|
|
||||||
|
|
||||||
class ProgressThrottleTests(unittest.TestCase):
|
class ProgressThrottleTests(unittest.TestCase):
|
||||||
def test_downloading_ticks_are_throttled(self):
|
def test_downloading_ticks_are_throttled(self):
|
||||||
dl = _make_test_download()
|
dl = _make_test_download()
|
||||||
|
|||||||
+34
-1
@@ -453,6 +453,7 @@ class DownloadInfo:
|
|||||||
clip_end=None,
|
clip_end=None,
|
||||||
live_status=None,
|
live_status=None,
|
||||||
live_release_timestamp=None,
|
live_release_timestamp=None,
|
||||||
|
sponsorblock=False,
|
||||||
):
|
):
|
||||||
self.id = id if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{id}'
|
self.id = id if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{id}'
|
||||||
self.title = title if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{title}'
|
self.title = title if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{title}'
|
||||||
@@ -472,6 +473,7 @@ class DownloadInfo:
|
|||||||
self.entry = _sanitize_entry_for_pickle(entry) if entry is not None else None
|
self.entry = _sanitize_entry_for_pickle(entry) if entry is not None else None
|
||||||
self.playlist_item_limit = playlist_item_limit
|
self.playlist_item_limit = playlist_item_limit
|
||||||
self.split_by_chapters = split_by_chapters
|
self.split_by_chapters = split_by_chapters
|
||||||
|
self.sponsorblock = sponsorblock
|
||||||
self.chapter_template = chapter_template
|
self.chapter_template = chapter_template
|
||||||
self.subtitle_language = subtitle_language
|
self.subtitle_language = subtitle_language
|
||||||
self.subtitle_mode = subtitle_mode
|
self.subtitle_mode = subtitle_mode
|
||||||
@@ -541,6 +543,8 @@ class DownloadInfo:
|
|||||||
self.playlist_item_limit = 0
|
self.playlist_item_limit = 0
|
||||||
if not hasattr(self, "split_by_chapters"):
|
if not hasattr(self, "split_by_chapters"):
|
||||||
self.split_by_chapters = False
|
self.split_by_chapters = False
|
||||||
|
if not hasattr(self, "sponsorblock"):
|
||||||
|
self.sponsorblock = False
|
||||||
if not hasattr(self, "chapter_template"):
|
if not hasattr(self, "chapter_template"):
|
||||||
self.chapter_template = ""
|
self.chapter_template = ""
|
||||||
if not hasattr(self, "subtitle_language"):
|
if not hasattr(self, "subtitle_language"):
|
||||||
@@ -585,6 +589,7 @@ _PERSISTED_DOWNLOAD_FIELDS = (
|
|||||||
"custom_name_prefix",
|
"custom_name_prefix",
|
||||||
"playlist_item_limit",
|
"playlist_item_limit",
|
||||||
"split_by_chapters",
|
"split_by_chapters",
|
||||||
|
"sponsorblock",
|
||||||
"chapter_template",
|
"chapter_template",
|
||||||
"subtitle_language",
|
"subtitle_language",
|
||||||
"subtitle_mode",
|
"subtitle_mode",
|
||||||
@@ -822,6 +827,27 @@ class Download:
|
|||||||
# this logger, so a user-supplied one must not replace it.
|
# this logger, so a user-supplied one must not replace it.
|
||||||
ytdl_params['logger'] = ytdl_logger
|
ytdl_params['logger'] = ytdl_logger
|
||||||
|
|
||||||
|
# SponsorBlock: mark sponsor segments and cut them out, the same
|
||||||
|
# postprocessor pair the CLI's --sponsorblock-remove sponsor builds.
|
||||||
|
# This has to stay above the chapter-splitting block: yt-dlp runs
|
||||||
|
# same-stage postprocessors in list order, and ModifyChapters must
|
||||||
|
# rewrite the chapter list before FFmpegSplitChapters cuts the file
|
||||||
|
# up, or the chapter files keep the sponsor segments and the
|
||||||
|
# removal desyncs the remaining chapter timings.
|
||||||
|
if getattr(self.info, 'sponsorblock', False):
|
||||||
|
if 'postprocessors' not in ytdl_params:
|
||||||
|
ytdl_params['postprocessors'] = []
|
||||||
|
ytdl_params['postprocessors'].append({
|
||||||
|
'key': 'SponsorBlock',
|
||||||
|
'categories': ['sponsor'],
|
||||||
|
'when': 'after_filter',
|
||||||
|
})
|
||||||
|
ytdl_params['postprocessors'].append({
|
||||||
|
'key': 'ModifyChapters',
|
||||||
|
'remove_sponsor_segments': ['sponsor'],
|
||||||
|
'force_keyframes': False,
|
||||||
|
})
|
||||||
|
|
||||||
# Add chapter splitting options if enabled
|
# Add chapter splitting options if enabled
|
||||||
if self.info.split_by_chapters:
|
if self.info.split_by_chapters:
|
||||||
ytdl_params['outtmpl']['chapter'] = self.info.chapter_template
|
ytdl_params['outtmpl']['chapter'] = self.info.chapter_template
|
||||||
@@ -1619,6 +1645,7 @@ class DownloadQueue:
|
|||||||
already,
|
already,
|
||||||
_add_gen=None,
|
_add_gen=None,
|
||||||
retry_entry=None,
|
retry_entry=None,
|
||||||
|
sponsorblock=False,
|
||||||
):
|
):
|
||||||
if not entry:
|
if not entry:
|
||||||
return {'status': 'error', 'msg': "Invalid/empty data was given."}
|
return {'status': 'error', 'msg': "Invalid/empty data was given."}
|
||||||
@@ -1662,6 +1689,7 @@ class DownloadQueue:
|
|||||||
already,
|
already,
|
||||||
_add_gen,
|
_add_gen,
|
||||||
retry_entry,
|
retry_entry,
|
||||||
|
sponsorblock=sponsorblock,
|
||||||
)
|
)
|
||||||
elif etype == 'playlist' or etype == 'channel':
|
elif etype == 'playlist' or etype == 'channel':
|
||||||
if etype == 'playlist' and self.__is_channel_extraction(entry):
|
if etype == 'playlist' and self.__is_channel_extraction(entry):
|
||||||
@@ -1729,6 +1757,7 @@ class DownloadQueue:
|
|||||||
clip_end,
|
clip_end,
|
||||||
already,
|
already,
|
||||||
_add_gen,
|
_add_gen,
|
||||||
|
sponsorblock=sponsorblock,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if any(res['status'] == 'error' for res in results):
|
if any(res['status'] == 'error' for res in results):
|
||||||
@@ -1769,6 +1798,7 @@ class DownloadQueue:
|
|||||||
clip_end=clip_end,
|
clip_end=clip_end,
|
||||||
live_status=entry.get('live_status'),
|
live_status=entry.get('live_status'),
|
||||||
live_release_timestamp=entry.get('release_timestamp'),
|
live_release_timestamp=entry.get('release_timestamp'),
|
||||||
|
sponsorblock=sponsorblock,
|
||||||
)
|
)
|
||||||
await self.__add_download(dl, auto_start)
|
await self.__add_download(dl, auto_start)
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
@@ -1849,13 +1879,14 @@ class DownloadQueue:
|
|||||||
already=None,
|
already=None,
|
||||||
_add_gen=None,
|
_add_gen=None,
|
||||||
retry_entry=None,
|
retry_entry=None,
|
||||||
|
sponsorblock=False,
|
||||||
):
|
):
|
||||||
if ytdl_options_presets is None:
|
if ytdl_options_presets is None:
|
||||||
ytdl_options_presets = []
|
ytdl_options_presets = []
|
||||||
log.info(
|
log.info(
|
||||||
f'adding {url}: {download_type=} {codec=} {format=} {quality=} {already=} {folder=} {custom_name_prefix=} '
|
f'adding {url}: {download_type=} {codec=} {format=} {quality=} {already=} {folder=} {custom_name_prefix=} '
|
||||||
f'{playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=} '
|
f'{playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=} '
|
||||||
f'{subtitle_language=} {subtitle_mode=} {ytdl_options_presets=} {clip_start=} {clip_end=}'
|
f'{subtitle_language=} {subtitle_mode=} {ytdl_options_presets=} {clip_start=} {clip_end=} {sponsorblock=}'
|
||||||
)
|
)
|
||||||
if already is None:
|
if already is None:
|
||||||
_add_gen = self._add_generation
|
_add_gen = self._add_generation
|
||||||
@@ -1918,6 +1949,7 @@ class DownloadQueue:
|
|||||||
already,
|
already,
|
||||||
_add_gen,
|
_add_gen,
|
||||||
retry_entry,
|
retry_entry,
|
||||||
|
sponsorblock=sponsorblock,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def retry(self, id):
|
async def retry(self, id):
|
||||||
@@ -1954,6 +1986,7 @@ class DownloadQueue:
|
|||||||
info.clip_start,
|
info.clip_start,
|
||||||
info.clip_end,
|
info.clip_end,
|
||||||
retry_entry=info.entry,
|
retry_entry=info.entry,
|
||||||
|
sponsorblock=info.sponsorblock,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def add_entry(
|
async def add_entry(
|
||||||
|
|||||||
@@ -399,6 +399,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="row g-2 align-items-center">
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="checkbox-sponsorblock"
|
||||||
|
name="sponsorblock" [(ngModel)]="sponsorblock" (change)="sponsorblockChanged()"
|
||||||
|
[disabled]="addInProgress || subscribeInProgress || downloads.loading">
|
||||||
|
<label class="form-check-label" for="checkbox-sponsorblock"
|
||||||
|
ngbPopover="Cut out sponsor segments using SponsorBlock's crowd-sourced markers (YouTube only)."
|
||||||
|
triggers="hover" container="body">Remove sponsor segments</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="checkbox-split-chapters"
|
<input class="form-check-input" type="checkbox" role="switch" id="checkbox-split-chapters"
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
|||||||
autoStart: boolean;
|
autoStart: boolean;
|
||||||
playlistItemLimit!: number;
|
playlistItemLimit!: number;
|
||||||
splitByChapters: boolean;
|
splitByChapters: boolean;
|
||||||
|
sponsorblock: boolean;
|
||||||
chapterTemplate: string;
|
chapterTemplate: string;
|
||||||
clipStart = '';
|
clipStart = '';
|
||||||
clipEnd = '';
|
clipEnd = '';
|
||||||
@@ -259,6 +260,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
|||||||
this.quality = this.cookieService.get('metube_quality') || 'best';
|
this.quality = this.cookieService.get('metube_quality') || 'best';
|
||||||
this.autoStart = this.cookieService.get('metube_auto_start') !== 'false';
|
this.autoStart = this.cookieService.get('metube_auto_start') !== 'false';
|
||||||
this.splitByChapters = this.cookieService.get('metube_split_chapters') === 'true';
|
this.splitByChapters = this.cookieService.get('metube_split_chapters') === 'true';
|
||||||
|
this.sponsorblock = this.cookieService.get('metube_sponsorblock') === 'true';
|
||||||
// Will be set from backend configuration, use empty string as placeholder
|
// Will be set from backend configuration, use empty string as placeholder
|
||||||
this.chapterTemplate = this.cookieService.get('metube_chapter_template') || '';
|
this.chapterTemplate = this.cookieService.get('metube_chapter_template') || '';
|
||||||
this.clipStart = this.cookieService.get('metube_clip_start') || '';
|
this.clipStart = this.cookieService.get('metube_clip_start') || '';
|
||||||
@@ -855,6 +857,10 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
|||||||
this.cookieService.set('metube_auto_start', this.autoStart ? 'true' : 'false', { expires: this.settingsCookieExpiryDays });
|
this.cookieService.set('metube_auto_start', this.autoStart ? 'true' : 'false', { expires: this.settingsCookieExpiryDays });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sponsorblockChanged() {
|
||||||
|
this.cookieService.set('metube_sponsorblock', this.sponsorblock ? 'true' : 'false', { expires: this.settingsCookieExpiryDays });
|
||||||
|
}
|
||||||
|
|
||||||
splitByChaptersChanged() {
|
splitByChaptersChanged() {
|
||||||
this.cookieService.set('metube_split_chapters', this.splitByChapters ? 'true' : 'false', { expires: this.settingsCookieExpiryDays });
|
this.cookieService.set('metube_split_chapters', this.splitByChapters ? 'true' : 'false', { expires: this.settingsCookieExpiryDays });
|
||||||
}
|
}
|
||||||
@@ -1111,6 +1117,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
|||||||
playlistItemLimit: overrides.playlistItemLimit ?? this.playlistItemLimit,
|
playlistItemLimit: overrides.playlistItemLimit ?? this.playlistItemLimit,
|
||||||
autoStart: overrides.autoStart ?? this.autoStart,
|
autoStart: overrides.autoStart ?? this.autoStart,
|
||||||
splitByChapters: overrides.splitByChapters ?? this.splitByChapters,
|
splitByChapters: overrides.splitByChapters ?? this.splitByChapters,
|
||||||
|
sponsorblock: overrides.sponsorblock ?? this.sponsorblock,
|
||||||
chapterTemplate: overrides.chapterTemplate ?? this.chapterTemplate,
|
chapterTemplate: overrides.chapterTemplate ?? this.chapterTemplate,
|
||||||
subtitleLanguage: overrides.subtitleLanguage ?? this.subtitleLanguage,
|
subtitleLanguage: overrides.subtitleLanguage ?? this.subtitleLanguage,
|
||||||
subtitleMode: overrides.subtitleMode ?? this.subtitleMode,
|
subtitleMode: overrides.subtitleMode ?? this.subtitleMode,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface Download {
|
|||||||
custom_name_prefix: string;
|
custom_name_prefix: string;
|
||||||
playlist_item_limit: number;
|
playlist_item_limit: number;
|
||||||
split_by_chapters?: boolean;
|
split_by_chapters?: boolean;
|
||||||
|
sponsorblock?: boolean;
|
||||||
chapter_template?: string;
|
chapter_template?: string;
|
||||||
subtitle_language?: string;
|
subtitle_language?: string;
|
||||||
subtitle_mode?: string;
|
subtitle_mode?: string;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ function basePayload(): AddDownloadPayload {
|
|||||||
playlistItemLimit: 0,
|
playlistItemLimit: 0,
|
||||||
autoStart: true,
|
autoStart: true,
|
||||||
splitByChapters: false,
|
splitByChapters: false,
|
||||||
|
sponsorblock: false,
|
||||||
chapterTemplate: '',
|
chapterTemplate: '',
|
||||||
subtitleLanguage: 'en',
|
subtitleLanguage: 'en',
|
||||||
subtitleMode: 'prefer_manual',
|
subtitleMode: 'prefer_manual',
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export interface AddDownloadPayload {
|
|||||||
playlistItemLimit: number;
|
playlistItemLimit: number;
|
||||||
autoStart: boolean;
|
autoStart: boolean;
|
||||||
splitByChapters: boolean;
|
splitByChapters: boolean;
|
||||||
|
sponsorblock: boolean;
|
||||||
chapterTemplate: string;
|
chapterTemplate: string;
|
||||||
subtitleLanguage: string;
|
subtitleLanguage: string;
|
||||||
subtitleMode: string;
|
subtitleMode: string;
|
||||||
@@ -148,6 +149,7 @@ export class DownloadsService {
|
|||||||
playlist_item_limit: payload.playlistItemLimit,
|
playlist_item_limit: payload.playlistItemLimit,
|
||||||
auto_start: payload.autoStart,
|
auto_start: payload.autoStart,
|
||||||
split_by_chapters: payload.splitByChapters,
|
split_by_chapters: payload.splitByChapters,
|
||||||
|
sponsorblock: payload.sponsorblock,
|
||||||
chapter_template: payload.chapterTemplate,
|
chapter_template: payload.chapterTemplate,
|
||||||
subtitle_language: payload.subtitleLanguage,
|
subtitle_language: payload.subtitleLanguage,
|
||||||
subtitle_mode: payload.subtitleMode,
|
subtitle_mode: payload.subtitleMode,
|
||||||
|
|||||||
Reference in New Issue
Block a user