mirror of
https://github.com/alexta69/metube.git
synced 2026-03-18 14:33:50 +00:00
add advanced options for captions download format
This commit is contained in:
committed by
Alex Shnitman
parent
973a87ffc6
commit
ce9703cd04
@@ -59,7 +59,7 @@ Certain values can be set via environment variables, using the `-e` parameter on
|
||||
* __OUTPUT_TEMPLATE_CHANNEL__: The template for the filenames of the downloaded videos when downloaded as a channel. Defaults to `%(channel)s/%(title)s.%(ext)s`. When empty, then `OUTPUT_TEMPLATE` is used.
|
||||
* __YTDL_OPTIONS__: Additional options to pass to yt-dlp in JSON format. [See available options here](https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/YoutubeDL.py#L222). They roughly correspond to command-line options, though some do not have exact equivalents here. For example, `--recode-video` has to be specified via `postprocessors`. Also note that dashes are replaced with underscores. You may find [this script](https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/cli_to_api.py) helpful for converting from command-line options to `YTDL_OPTIONS`.
|
||||
* __YTDL_OPTIONS_FILE__: A path to a JSON file that will be loaded and used for populating `YTDL_OPTIONS` above. Please note that if both `YTDL_OPTIONS_FILE` and `YTDL_OPTIONS` are specified, the options in `YTDL_OPTIONS` take precedence. The file will be monitored for changes and reloaded automatically when changes are detected.
|
||||
* UI format __Captions (EN, VTT)__: Downloads subtitles/captions only (no media) by applying yt-dlp options equivalent to `--skip-download --write-subs --write-auto-subs --sub-langs en --sub-format vtt`.
|
||||
* UI format __Captions__: Downloads subtitles/captions only (no media). Subtitle format, language, and source preference are configurable from Advanced Options (defaults: `ass`, `en`, `prefer_manual`).
|
||||
|
||||
### 🌐 Web Server & URLs
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import copy
|
||||
|
||||
AUDIO_FORMATS = ("m4a", "mp3", "opus", "wav", "flac")
|
||||
CAPTION_MODES = ("auto_only", "manual_only", "prefer_manual", "prefer_auto")
|
||||
|
||||
|
||||
def _normalize_caption_mode(mode: str) -> str:
|
||||
mode = (mode or "").strip()
|
||||
return mode if mode in CAPTION_MODES else "prefer_manual"
|
||||
|
||||
|
||||
def _normalize_subtitle_language(language: str) -> str:
|
||||
language = (language or "").strip()
|
||||
return language or "en"
|
||||
|
||||
|
||||
def get_format(format: str, quality: str) -> str:
|
||||
@@ -55,7 +66,14 @@ def get_format(format: str, quality: str) -> str:
|
||||
raise Exception(f"Unkown format {format}")
|
||||
|
||||
|
||||
def get_opts(format: str, quality: str, ytdl_opts: dict) -> dict:
|
||||
def get_opts(
|
||||
format: str,
|
||||
quality: str,
|
||||
ytdl_opts: dict,
|
||||
subtitle_format: str = "ass",
|
||||
subtitle_language: str = "en",
|
||||
subtitle_mode: str = "prefer_manual",
|
||||
) -> dict:
|
||||
"""
|
||||
Returns extra download options
|
||||
Mostly postprocessing options
|
||||
@@ -103,11 +121,28 @@ def get_opts(format: str, quality: str, ytdl_opts: dict) -> dict:
|
||||
)
|
||||
|
||||
if format == "captions":
|
||||
mode = _normalize_caption_mode(subtitle_mode)
|
||||
language = _normalize_subtitle_language(subtitle_language)
|
||||
opts["skip_download"] = True
|
||||
opts["writesubtitles"] = True
|
||||
opts["writeautomaticsub"] = True
|
||||
opts["subtitleslangs"] = ["en"]
|
||||
opts["subtitlesformat"] = "vtt"
|
||||
opts["subtitlesformat"] = subtitle_format or "ass"
|
||||
if mode == "manual_only":
|
||||
opts["writesubtitles"] = True
|
||||
opts["writeautomaticsub"] = False
|
||||
opts["subtitleslangs"] = [language]
|
||||
elif mode == "auto_only":
|
||||
opts["writesubtitles"] = False
|
||||
opts["writeautomaticsub"] = True
|
||||
# `-orig` captures common YouTube auto-sub tags. The plain language
|
||||
# fallback keeps behavior useful across other extractors.
|
||||
opts["subtitleslangs"] = [f"{language}-orig", language]
|
||||
elif mode == "prefer_auto":
|
||||
opts["writesubtitles"] = True
|
||||
opts["writeautomaticsub"] = True
|
||||
opts["subtitleslangs"] = [f"{language}-orig", language]
|
||||
else:
|
||||
opts["writesubtitles"] = True
|
||||
opts["writeautomaticsub"] = True
|
||||
opts["subtitleslangs"] = [language, f"{language}-orig"]
|
||||
|
||||
opts["postprocessors"] = postprocessors + (
|
||||
opts["postprocessors"] if "postprocessors" in opts else []
|
||||
|
||||
27
app/main.py
27
app/main.py
@@ -247,6 +247,9 @@ async def add(request):
|
||||
auto_start = post.get('auto_start')
|
||||
split_by_chapters = post.get('split_by_chapters')
|
||||
chapter_template = post.get('chapter_template')
|
||||
subtitle_format = post.get('subtitle_format')
|
||||
subtitle_language = post.get('subtitle_language')
|
||||
subtitle_mode = post.get('subtitle_mode')
|
||||
|
||||
if custom_name_prefix is None:
|
||||
custom_name_prefix = ''
|
||||
@@ -260,12 +263,34 @@ async def add(request):
|
||||
split_by_chapters = False
|
||||
if chapter_template is None:
|
||||
chapter_template = config.OUTPUT_TEMPLATE_CHAPTER
|
||||
if subtitle_format is None:
|
||||
subtitle_format = 'ass'
|
||||
if subtitle_language is None:
|
||||
subtitle_language = 'en'
|
||||
if subtitle_mode is None:
|
||||
subtitle_mode = 'prefer_manual'
|
||||
if chapter_template and ('..' in chapter_template or chapter_template.startswith('/') or chapter_template.startswith('\\')):
|
||||
raise web.HTTPBadRequest(reason='chapter_template must not contain ".." or start with a path separator')
|
||||
valid_subtitle_modes = {'auto_only', 'manual_only', 'prefer_manual', 'prefer_auto'}
|
||||
if subtitle_mode not in valid_subtitle_modes:
|
||||
raise web.HTTPBadRequest(reason=f'subtitle_mode must be one of {sorted(valid_subtitle_modes)}')
|
||||
|
||||
playlist_item_limit = int(playlist_item_limit)
|
||||
|
||||
status = await dqueue.add(url, quality, format, folder, custom_name_prefix, playlist_item_limit, auto_start, split_by_chapters, chapter_template)
|
||||
status = await dqueue.add(
|
||||
url,
|
||||
quality,
|
||||
format,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
playlist_item_limit,
|
||||
auto_start,
|
||||
split_by_chapters,
|
||||
chapter_template,
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
)
|
||||
return web.Response(text=serializer.encode(status))
|
||||
|
||||
@routes.post(config.URL_PREFIX + 'delete')
|
||||
|
||||
141
app/ytdl.py
141
app/ytdl.py
@@ -86,7 +86,24 @@ class DownloadQueueNotifier:
|
||||
raise NotImplementedError
|
||||
|
||||
class DownloadInfo:
|
||||
def __init__(self, id, title, url, quality, format, folder, custom_name_prefix, error, entry, playlist_item_limit, split_by_chapters, chapter_template):
|
||||
def __init__(
|
||||
self,
|
||||
id,
|
||||
title,
|
||||
url,
|
||||
quality,
|
||||
format,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
error,
|
||||
entry,
|
||||
playlist_item_limit,
|
||||
split_by_chapters,
|
||||
chapter_template,
|
||||
subtitle_format="ass",
|
||||
subtitle_language="en",
|
||||
subtitle_mode="prefer_manual",
|
||||
):
|
||||
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.url = url
|
||||
@@ -104,6 +121,9 @@ class DownloadInfo:
|
||||
self.playlist_item_limit = playlist_item_limit
|
||||
self.split_by_chapters = split_by_chapters
|
||||
self.chapter_template = chapter_template
|
||||
self.subtitle_format = subtitle_format
|
||||
self.subtitle_language = subtitle_language
|
||||
self.subtitle_mode = subtitle_mode
|
||||
|
||||
class Download:
|
||||
manager = None
|
||||
@@ -113,11 +133,18 @@ class Download:
|
||||
self.temp_dir = temp_dir
|
||||
self.output_template = output_template
|
||||
self.output_template_chapter = output_template_chapter
|
||||
self.info = info
|
||||
self.format = get_format(format, quality)
|
||||
self.ytdl_opts = get_opts(format, quality, ytdl_opts)
|
||||
self.ytdl_opts = get_opts(
|
||||
format,
|
||||
quality,
|
||||
ytdl_opts,
|
||||
subtitle_format=getattr(info, 'subtitle_format', 'ass'),
|
||||
subtitle_language=getattr(info, 'subtitle_language', 'en'),
|
||||
subtitle_mode=getattr(info, 'subtitle_mode', 'prefer_manual'),
|
||||
)
|
||||
if "impersonate" in self.ytdl_opts:
|
||||
self.ytdl_opts["impersonate"] = yt_dlp.networking.impersonate.ImpersonateTarget.from_str(self.ytdl_opts["impersonate"])
|
||||
self.info = info
|
||||
self.canceled = False
|
||||
self.tmpfilename = None
|
||||
self.status_queue = None
|
||||
@@ -553,7 +580,22 @@ class DownloadQueue:
|
||||
self.pending.put(download)
|
||||
await self.notifier.added(dl)
|
||||
|
||||
async def __add_entry(self, entry, quality, format, folder, custom_name_prefix, playlist_item_limit, auto_start, split_by_chapters, chapter_template, already):
|
||||
async def __add_entry(
|
||||
self,
|
||||
entry,
|
||||
quality,
|
||||
format,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
playlist_item_limit,
|
||||
auto_start,
|
||||
split_by_chapters,
|
||||
chapter_template,
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
already,
|
||||
):
|
||||
if not entry:
|
||||
return {'status': 'error', 'msg': "Invalid/empty data was given."}
|
||||
|
||||
@@ -569,7 +611,21 @@ class DownloadQueue:
|
||||
|
||||
if etype.startswith('url'):
|
||||
log.debug('Processing as a url')
|
||||
return await self.add(entry['url'], quality, format, folder, custom_name_prefix, playlist_item_limit, auto_start, split_by_chapters, chapter_template, already)
|
||||
return await self.add(
|
||||
entry['url'],
|
||||
quality,
|
||||
format,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
playlist_item_limit,
|
||||
auto_start,
|
||||
split_by_chapters,
|
||||
chapter_template,
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
already,
|
||||
)
|
||||
elif etype == 'playlist' or etype == 'channel':
|
||||
log.debug(f'Processing as a {etype}')
|
||||
entries = entry['entries']
|
||||
@@ -589,7 +645,23 @@ class DownloadQueue:
|
||||
for property in ("id", "title", "uploader", "uploader_id"):
|
||||
if property in entry:
|
||||
etr[f"{etype}_{property}"] = entry[property]
|
||||
results.append(await self.__add_entry(etr, quality, format, folder, custom_name_prefix, playlist_item_limit, auto_start, split_by_chapters, chapter_template, already))
|
||||
results.append(
|
||||
await self.__add_entry(
|
||||
etr,
|
||||
quality,
|
||||
format,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
playlist_item_limit,
|
||||
auto_start,
|
||||
split_by_chapters,
|
||||
chapter_template,
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
already,
|
||||
)
|
||||
)
|
||||
if any(res['status'] == 'error' for res in results):
|
||||
return {'status': 'error', 'msg': ', '.join(res['msg'] for res in results if res['status'] == 'error' and 'msg' in res)}
|
||||
return {'status': 'ok'}
|
||||
@@ -597,13 +669,48 @@ class DownloadQueue:
|
||||
log.debug('Processing as a video')
|
||||
key = entry.get('webpage_url') or entry['url']
|
||||
if not self.queue.exists(key):
|
||||
dl = DownloadInfo(entry['id'], entry.get('title') or entry['id'], key, quality, format, folder, custom_name_prefix, error, entry, playlist_item_limit, split_by_chapters, chapter_template)
|
||||
dl = DownloadInfo(
|
||||
entry['id'],
|
||||
entry.get('title') or entry['id'],
|
||||
key,
|
||||
quality,
|
||||
format,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
error,
|
||||
entry,
|
||||
playlist_item_limit,
|
||||
split_by_chapters,
|
||||
chapter_template,
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
)
|
||||
await self.__add_download(dl, auto_start)
|
||||
return {'status': 'ok'}
|
||||
return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'}
|
||||
|
||||
async def add(self, url, quality, format, folder, custom_name_prefix, playlist_item_limit, auto_start=True, split_by_chapters=False, chapter_template=None, already=None):
|
||||
log.info(f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} {playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=}')
|
||||
async def add(
|
||||
self,
|
||||
url,
|
||||
quality,
|
||||
format,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
playlist_item_limit,
|
||||
auto_start=True,
|
||||
split_by_chapters=False,
|
||||
chapter_template=None,
|
||||
subtitle_format="ass",
|
||||
subtitle_language="en",
|
||||
subtitle_mode="prefer_manual",
|
||||
already=None,
|
||||
):
|
||||
log.info(
|
||||
f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} '
|
||||
f'{playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=} '
|
||||
f'{subtitle_format=} {subtitle_language=} {subtitle_mode=}'
|
||||
)
|
||||
already = set() if already is None else already
|
||||
if url in already:
|
||||
log.info('recursion detected, skipping')
|
||||
@@ -614,7 +721,21 @@ class DownloadQueue:
|
||||
entry = await asyncio.get_running_loop().run_in_executor(None, self.__extract_info, url)
|
||||
except yt_dlp.utils.YoutubeDLError as exc:
|
||||
return {'status': 'error', 'msg': str(exc)}
|
||||
return await self.__add_entry(entry, quality, format, folder, custom_name_prefix, playlist_item_limit, auto_start, split_by_chapters, chapter_template, already)
|
||||
return await self.__add_entry(
|
||||
entry,
|
||||
quality,
|
||||
format,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
playlist_item_limit,
|
||||
auto_start,
|
||||
split_by_chapters,
|
||||
chapter_template,
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
already,
|
||||
)
|
||||
|
||||
async def start_pending(self, ids):
|
||||
for id in ids:
|
||||
|
||||
@@ -218,6 +218,51 @@
|
||||
ngbTooltip="Maximum number of items to download from a playlist or channel (0 = no limit)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Subtitles</span>
|
||||
<select class="form-select"
|
||||
name="subtitleFormat"
|
||||
[(ngModel)]="subtitleFormat"
|
||||
(change)="subtitleFormatChanged()"
|
||||
[disabled]="addInProgress || downloads.loading"
|
||||
ngbTooltip="Subtitle output format for captions mode">
|
||||
@for (fmt of subtitleFormats; track fmt.id) {
|
||||
<option [ngValue]="fmt.id">{{ fmt.text }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Language</span>
|
||||
<select class="form-select"
|
||||
name="subtitleLanguage"
|
||||
[(ngModel)]="subtitleLanguage"
|
||||
(change)="subtitleLanguageChanged()"
|
||||
[disabled]="addInProgress || downloads.loading"
|
||||
ngbTooltip="Preferred subtitle language for captions mode">
|
||||
@for (lang of subtitleLanguages; track lang.id) {
|
||||
<option [ngValue]="lang.id">{{ lang.text }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Subtitle Source</span>
|
||||
<select class="form-select"
|
||||
name="subtitleMode"
|
||||
[(ngModel)]="subtitleMode"
|
||||
(change)="subtitleModeChanged()"
|
||||
[disabled]="addInProgress || downloads.loading"
|
||||
ngbTooltip="Choose manual, auto, or fallback preference for captions mode">
|
||||
@for (mode of subtitleModes; track mode.id) {
|
||||
<option [ngValue]="mode.id">{{ mode.text }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
|
||||
@@ -49,6 +49,9 @@ export class App implements AfterViewInit, OnInit {
|
||||
playlistItemLimit!: number;
|
||||
splitByChapters: boolean;
|
||||
chapterTemplate: string;
|
||||
subtitleFormat: string;
|
||||
subtitleLanguage: string;
|
||||
subtitleMode: string;
|
||||
addInProgress = false;
|
||||
themes: Theme[] = Themes;
|
||||
activeTheme: Theme | undefined;
|
||||
@@ -97,6 +100,31 @@ export class App implements AfterViewInit, OnInit {
|
||||
faGithub = faGithub;
|
||||
faClock = faClock;
|
||||
faTachometerAlt = faTachometerAlt;
|
||||
subtitleFormats = [
|
||||
{ id: 'ass', text: 'ASS' },
|
||||
{ id: 'vtt', text: 'VTT' },
|
||||
{ id: 'srt', text: 'SRT' },
|
||||
{ id: 'ttml', text: 'TTML' },
|
||||
];
|
||||
subtitleLanguages = [
|
||||
{ id: 'en', text: 'English' },
|
||||
{ id: 'es', text: 'Spanish' },
|
||||
{ id: 'fr', text: 'French' },
|
||||
{ id: 'de', text: 'German' },
|
||||
{ id: 'it', text: 'Italian' },
|
||||
{ id: 'pt', text: 'Portuguese' },
|
||||
{ id: 'ru', text: 'Russian' },
|
||||
{ id: 'uk', text: 'Ukrainian' },
|
||||
{ id: 'ja', text: 'Japanese' },
|
||||
{ id: 'ko', text: 'Korean' },
|
||||
{ id: 'zh-Hans', text: 'Chinese (Simplified)' },
|
||||
];
|
||||
subtitleModes = [
|
||||
{ id: 'prefer_manual', text: 'Prefer Manual' },
|
||||
{ id: 'prefer_auto', text: 'Prefer Auto' },
|
||||
{ id: 'manual_only', text: 'Manual Only' },
|
||||
{ id: 'auto_only', text: 'Auto Only' },
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.format = this.cookieService.get('metube_format') || 'any';
|
||||
@@ -107,6 +135,9 @@ export class App implements AfterViewInit, OnInit {
|
||||
this.splitByChapters = this.cookieService.get('metube_split_chapters') === 'true';
|
||||
// Will be set from backend configuration, use empty string as placeholder
|
||||
this.chapterTemplate = this.cookieService.get('metube_chapter_template') || '';
|
||||
this.subtitleFormat = this.cookieService.get('metube_subtitle_format') || 'ass';
|
||||
this.subtitleLanguage = this.cookieService.get('metube_subtitle_language') || 'en';
|
||||
this.subtitleMode = this.cookieService.get('metube_subtitle_mode') || 'prefer_manual';
|
||||
|
||||
this.activeTheme = this.getPreferredTheme(this.cookieService);
|
||||
|
||||
@@ -279,6 +310,18 @@ export class App implements AfterViewInit, OnInit {
|
||||
this.cookieService.set('metube_chapter_template', this.chapterTemplate, { expires: 3650 });
|
||||
}
|
||||
|
||||
subtitleFormatChanged() {
|
||||
this.cookieService.set('metube_subtitle_format', this.subtitleFormat, { expires: 3650 });
|
||||
}
|
||||
|
||||
subtitleLanguageChanged() {
|
||||
this.cookieService.set('metube_subtitle_language', this.subtitleLanguage, { expires: 3650 });
|
||||
}
|
||||
|
||||
subtitleModeChanged() {
|
||||
this.cookieService.set('metube_subtitle_mode', this.subtitleMode, { expires: 3650 });
|
||||
}
|
||||
|
||||
queueSelectionChanged(checked: number) {
|
||||
this.queueDelSelected().nativeElement.disabled = checked == 0;
|
||||
this.queueDownloadSelected().nativeElement.disabled = checked == 0;
|
||||
@@ -299,7 +342,20 @@ export class App implements AfterViewInit, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
addDownload(url?: string, quality?: string, format?: string, folder?: string, customNamePrefix?: string, playlistItemLimit?: number, autoStart?: boolean, splitByChapters?: boolean, chapterTemplate?: string) {
|
||||
addDownload(
|
||||
url?: string,
|
||||
quality?: string,
|
||||
format?: string,
|
||||
folder?: string,
|
||||
customNamePrefix?: string,
|
||||
playlistItemLimit?: number,
|
||||
autoStart?: boolean,
|
||||
splitByChapters?: boolean,
|
||||
chapterTemplate?: string,
|
||||
subtitleFormat?: string,
|
||||
subtitleLanguage?: string,
|
||||
subtitleMode?: string,
|
||||
) {
|
||||
url = url ?? this.addUrl
|
||||
quality = quality ?? this.quality
|
||||
format = format ?? this.format
|
||||
@@ -309,6 +365,9 @@ export class App implements AfterViewInit, OnInit {
|
||||
autoStart = autoStart ?? this.autoStart
|
||||
splitByChapters = splitByChapters ?? this.splitByChapters
|
||||
chapterTemplate = chapterTemplate ?? this.chapterTemplate
|
||||
subtitleFormat = subtitleFormat ?? this.subtitleFormat
|
||||
subtitleLanguage = subtitleLanguage ?? this.subtitleLanguage
|
||||
subtitleMode = subtitleMode ?? this.subtitleMode
|
||||
|
||||
// Validate chapter template if chapter splitting is enabled
|
||||
if (splitByChapters && !chapterTemplate.includes('%(section_number)')) {
|
||||
@@ -316,9 +375,9 @@ export class App implements AfterViewInit, OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('Downloading: url=' + url + ' quality=' + quality + ' format=' + format + ' folder=' + folder + ' customNamePrefix=' + customNamePrefix + ' playlistItemLimit=' + playlistItemLimit + ' autoStart=' + autoStart + ' splitByChapters=' + splitByChapters + ' chapterTemplate=' + chapterTemplate);
|
||||
console.debug('Downloading: url=' + url + ' quality=' + quality + ' format=' + format + ' folder=' + folder + ' customNamePrefix=' + customNamePrefix + ' playlistItemLimit=' + playlistItemLimit + ' autoStart=' + autoStart + ' splitByChapters=' + splitByChapters + ' chapterTemplate=' + chapterTemplate + ' subtitleFormat=' + subtitleFormat + ' subtitleLanguage=' + subtitleLanguage + ' subtitleMode=' + subtitleMode);
|
||||
this.addInProgress = true;
|
||||
this.downloads.add(url, quality, format, folder, customNamePrefix, playlistItemLimit, autoStart, splitByChapters, chapterTemplate).subscribe((status: Status) => {
|
||||
this.downloads.add(url, quality, format, folder, customNamePrefix, playlistItemLimit, autoStart, splitByChapters, chapterTemplate, subtitleFormat, subtitleLanguage, subtitleMode).subscribe((status: Status) => {
|
||||
if (status.status === 'error') {
|
||||
alert(`Error adding URL: ${status.msg}`);
|
||||
} else {
|
||||
@@ -333,7 +392,20 @@ export class App implements AfterViewInit, OnInit {
|
||||
}
|
||||
|
||||
retryDownload(key: string, download: Download) {
|
||||
this.addDownload(download.url, download.quality, download.format, download.folder, download.custom_name_prefix, download.playlist_item_limit, true, download.split_by_chapters, download.chapter_template);
|
||||
this.addDownload(
|
||||
download.url,
|
||||
download.quality,
|
||||
download.format,
|
||||
download.folder,
|
||||
download.custom_name_prefix,
|
||||
download.playlist_item_limit,
|
||||
true,
|
||||
download.split_by_chapters,
|
||||
download.chapter_template,
|
||||
download.subtitle_format,
|
||||
download.subtitle_language,
|
||||
download.subtitle_mode,
|
||||
);
|
||||
this.downloads.delById('done', [key]).subscribe();
|
||||
}
|
||||
|
||||
@@ -479,7 +551,8 @@ export class App implements AfterViewInit, OnInit {
|
||||
this.batchImportStatus = `Importing URL ${index + 1} of ${urls.length}: ${url}`;
|
||||
// Now pass the selected quality, format, folder, etc. to the add() method
|
||||
this.downloads.add(url, this.quality, this.format, this.folder, this.customNamePrefix,
|
||||
this.playlistItemLimit, this.autoStart, this.splitByChapters, this.chapterTemplate)
|
||||
this.playlistItemLimit, this.autoStart, this.splitByChapters, this.chapterTemplate,
|
||||
this.subtitleFormat, this.subtitleLanguage, this.subtitleMode)
|
||||
.subscribe({
|
||||
next: (status: Status) => {
|
||||
if (status.status === 'error') {
|
||||
|
||||
@@ -10,6 +10,9 @@ export interface Download {
|
||||
playlist_item_limit: number;
|
||||
split_by_chapters?: boolean;
|
||||
chapter_template?: string;
|
||||
subtitle_format?: string;
|
||||
subtitle_language?: string;
|
||||
subtitle_mode?: string;
|
||||
status: string;
|
||||
msg: string;
|
||||
percent: number;
|
||||
|
||||
@@ -75,7 +75,7 @@ export const Formats: Format[] = [
|
||||
},
|
||||
{
|
||||
id: 'captions',
|
||||
text: 'Captions (EN, VTT)',
|
||||
text: 'Captions',
|
||||
qualities: [{ id: 'best', text: 'Best' }],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -107,8 +107,34 @@ export class DownloadsService {
|
||||
return of({status: 'error', msg: msg})
|
||||
}
|
||||
|
||||
public add(url: string, quality: string, format: string, folder: string, customNamePrefix: string, playlistItemLimit: number, autoStart: boolean, splitByChapters: boolean, chapterTemplate: string) {
|
||||
return this.http.post<Status>('add', { url: url, quality: quality, format: format, folder: folder, custom_name_prefix: customNamePrefix, playlist_item_limit: playlistItemLimit, auto_start: autoStart, split_by_chapters: splitByChapters, chapter_template: chapterTemplate }).pipe(
|
||||
public add(
|
||||
url: string,
|
||||
quality: string,
|
||||
format: string,
|
||||
folder: string,
|
||||
customNamePrefix: string,
|
||||
playlistItemLimit: number,
|
||||
autoStart: boolean,
|
||||
splitByChapters: boolean,
|
||||
chapterTemplate: string,
|
||||
subtitleFormat: string,
|
||||
subtitleLanguage: string,
|
||||
subtitleMode: string,
|
||||
) {
|
||||
return this.http.post<Status>('add', {
|
||||
url: url,
|
||||
quality: quality,
|
||||
format: format,
|
||||
folder: folder,
|
||||
custom_name_prefix: customNamePrefix,
|
||||
playlist_item_limit: playlistItemLimit,
|
||||
auto_start: autoStart,
|
||||
split_by_chapters: splitByChapters,
|
||||
chapter_template: chapterTemplate,
|
||||
subtitle_format: subtitleFormat,
|
||||
subtitle_language: subtitleLanguage,
|
||||
subtitle_mode: subtitleMode
|
||||
}).pipe(
|
||||
catchError(this.handleHTTPError)
|
||||
);
|
||||
}
|
||||
@@ -154,9 +180,25 @@ export class DownloadsService {
|
||||
const defaultAutoStart = true;
|
||||
const defaultSplitByChapters = false;
|
||||
const defaultChapterTemplate = this.configuration['OUTPUT_TEMPLATE_CHAPTER'];
|
||||
const defaultSubtitleFormat = 'ass';
|
||||
const defaultSubtitleLanguage = 'en';
|
||||
const defaultSubtitleMode = 'prefer_manual';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.add(url, defaultQuality, defaultFormat, defaultFolder, defaultCustomNamePrefix, defaultPlaylistItemLimit, defaultAutoStart, defaultSplitByChapters, defaultChapterTemplate)
|
||||
this.add(
|
||||
url,
|
||||
defaultQuality,
|
||||
defaultFormat,
|
||||
defaultFolder,
|
||||
defaultCustomNamePrefix,
|
||||
defaultPlaylistItemLimit,
|
||||
defaultAutoStart,
|
||||
defaultSplitByChapters,
|
||||
defaultChapterTemplate,
|
||||
defaultSubtitleFormat,
|
||||
defaultSubtitleLanguage,
|
||||
defaultSubtitleMode,
|
||||
)
|
||||
.subscribe({
|
||||
next: (response) => resolve(response),
|
||||
error: (error) => reject(error)
|
||||
|
||||
Reference in New Issue
Block a user