mirror of
https://github.com/alexta69/metube.git
synced 2026-03-18 22:43:51 +00:00
Add video codec selector and codec/quality columns in done list
Allow users to prefer a specific video codec (H.264, H.265, AV1, VP9) when adding downloads. The selector filters available formats via yt-dlp format strings, falling back to best available if the preferred codec is not found. The completed downloads table now shows Quality and Codec columns.
This commit is contained in:
@@ -3,6 +3,13 @@ import copy
|
|||||||
AUDIO_FORMATS = ("m4a", "mp3", "opus", "wav", "flac")
|
AUDIO_FORMATS = ("m4a", "mp3", "opus", "wav", "flac")
|
||||||
CAPTION_MODES = ("auto_only", "manual_only", "prefer_manual", "prefer_auto")
|
CAPTION_MODES = ("auto_only", "manual_only", "prefer_manual", "prefer_auto")
|
||||||
|
|
||||||
|
CODEC_FILTER_MAP = {
|
||||||
|
'h264': "[vcodec~='^(h264|avc)']",
|
||||||
|
'h265': "[vcodec~='^(h265|hevc)']",
|
||||||
|
'av1': "[vcodec~='^av0?1']",
|
||||||
|
'vp9': "[vcodec~='^vp0?9']",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _normalize_caption_mode(mode: str) -> str:
|
def _normalize_caption_mode(mode: str) -> str:
|
||||||
mode = (mode or "").strip()
|
mode = (mode or "").strip()
|
||||||
@@ -14,13 +21,14 @@ def _normalize_subtitle_language(language: str) -> str:
|
|||||||
return language or "en"
|
return language or "en"
|
||||||
|
|
||||||
|
|
||||||
def get_format(format: str, quality: str) -> str:
|
def get_format(format: str, quality: str, video_codec: str = "auto") -> str:
|
||||||
"""
|
"""
|
||||||
Returns format for download
|
Returns format for download
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
format (str): format selected
|
format (str): format selected
|
||||||
quality (str): quality selected
|
quality (str): quality selected
|
||||||
|
video_codec (str): video codec filter (auto, h264, h265, av1, vp9)
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
Exception: unknown quality, unknown format
|
Exception: unknown quality, unknown format
|
||||||
@@ -52,6 +60,7 @@ def get_format(format: str, quality: str) -> str:
|
|||||||
vfmt, afmt = ("[ext=mp4]", "[ext=m4a]") if format == "mp4" else ("", "")
|
vfmt, afmt = ("[ext=mp4]", "[ext=m4a]") if format == "mp4" else ("", "")
|
||||||
vres = f"[height<={quality}]" if quality not in ("best", "best_ios", "worst") else ""
|
vres = f"[height<={quality}]" if quality not in ("best", "best_ios", "worst") else ""
|
||||||
vcombo = vres + vfmt
|
vcombo = vres + vfmt
|
||||||
|
codec_filter = CODEC_FILTER_MAP.get(video_codec, "")
|
||||||
|
|
||||||
if quality == "best_ios":
|
if quality == "best_ios":
|
||||||
# iOS has strict requirements for video files, requiring h264 or h265
|
# iOS has strict requirements for video files, requiring h264 or h265
|
||||||
@@ -61,6 +70,10 @@ def get_format(format: str, quality: str) -> str:
|
|||||||
# convert if needed), and falls back to getting the best available MP4
|
# convert if needed), and falls back to getting the best available MP4
|
||||||
# file.
|
# file.
|
||||||
return f"bestvideo[vcodec~='^((he|a)vc|h26[45])']{vres}+bestaudio[acodec=aac]/bestvideo[vcodec~='^((he|a)vc|h26[45])']{vres}+bestaudio{afmt}/bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
|
return f"bestvideo[vcodec~='^((he|a)vc|h26[45])']{vres}+bestaudio[acodec=aac]/bestvideo[vcodec~='^((he|a)vc|h26[45])']{vres}+bestaudio{afmt}/bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
|
||||||
|
|
||||||
|
if codec_filter:
|
||||||
|
# Try codec-filtered first, fall back to unfiltered
|
||||||
|
return f"bestvideo{codec_filter}{vcombo}+bestaudio{afmt}/bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
|
||||||
return f"bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
|
return f"bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
|
||||||
|
|
||||||
raise Exception(f"Unkown format {format}")
|
raise Exception(f"Unkown format {format}")
|
||||||
@@ -73,6 +86,7 @@ def get_opts(
|
|||||||
subtitle_format: str = "srt",
|
subtitle_format: str = "srt",
|
||||||
subtitle_language: str = "en",
|
subtitle_language: str = "en",
|
||||||
subtitle_mode: str = "prefer_manual",
|
subtitle_mode: str = "prefer_manual",
|
||||||
|
video_codec: str = "auto",
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Returns extra download options
|
Returns extra download options
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ async def add(request):
|
|||||||
subtitle_format = post.get('subtitle_format')
|
subtitle_format = post.get('subtitle_format')
|
||||||
subtitle_language = post.get('subtitle_language')
|
subtitle_language = post.get('subtitle_language')
|
||||||
subtitle_mode = post.get('subtitle_mode')
|
subtitle_mode = post.get('subtitle_mode')
|
||||||
|
video_codec = post.get('video_codec')
|
||||||
|
|
||||||
if custom_name_prefix is None:
|
if custom_name_prefix is None:
|
||||||
custom_name_prefix = ''
|
custom_name_prefix = ''
|
||||||
@@ -319,6 +320,12 @@ async def add(request):
|
|||||||
if subtitle_mode not in VALID_SUBTITLE_MODES:
|
if subtitle_mode not in VALID_SUBTITLE_MODES:
|
||||||
raise web.HTTPBadRequest(reason=f'subtitle_mode must be one of {sorted(VALID_SUBTITLE_MODES)}')
|
raise web.HTTPBadRequest(reason=f'subtitle_mode must be one of {sorted(VALID_SUBTITLE_MODES)}')
|
||||||
|
|
||||||
|
if video_codec is None:
|
||||||
|
video_codec = 'auto'
|
||||||
|
video_codec = str(video_codec).strip().lower()
|
||||||
|
if video_codec not in ('auto', 'h264', 'h265', 'av1', 'vp9'):
|
||||||
|
raise web.HTTPBadRequest(reason="video_codec must be one of ['auto', 'h264', 'h265', 'av1', 'vp9']")
|
||||||
|
|
||||||
playlist_item_limit = int(playlist_item_limit)
|
playlist_item_limit = int(playlist_item_limit)
|
||||||
|
|
||||||
status = await dqueue.add(
|
status = await dqueue.add(
|
||||||
@@ -334,6 +341,7 @@ async def add(request):
|
|||||||
subtitle_format,
|
subtitle_format,
|
||||||
subtitle_language,
|
subtitle_language,
|
||||||
subtitle_mode,
|
subtitle_mode,
|
||||||
|
video_codec=video_codec,
|
||||||
)
|
)
|
||||||
return web.Response(text=serializer.encode(status))
|
return web.Response(text=serializer.encode(status))
|
||||||
|
|
||||||
|
|||||||
14
app/ytdl.py
14
app/ytdl.py
@@ -162,6 +162,7 @@ class DownloadInfo:
|
|||||||
subtitle_format="srt",
|
subtitle_format="srt",
|
||||||
subtitle_language="en",
|
subtitle_language="en",
|
||||||
subtitle_mode="prefer_manual",
|
subtitle_mode="prefer_manual",
|
||||||
|
video_codec="auto",
|
||||||
):
|
):
|
||||||
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}'
|
||||||
@@ -184,6 +185,7 @@ class DownloadInfo:
|
|||||||
self.subtitle_language = subtitle_language
|
self.subtitle_language = subtitle_language
|
||||||
self.subtitle_mode = subtitle_mode
|
self.subtitle_mode = subtitle_mode
|
||||||
self.subtitle_files = []
|
self.subtitle_files = []
|
||||||
|
self.video_codec = video_codec
|
||||||
|
|
||||||
class Download:
|
class Download:
|
||||||
manager = None
|
manager = None
|
||||||
@@ -194,7 +196,7 @@ class Download:
|
|||||||
self.output_template = output_template
|
self.output_template = output_template
|
||||||
self.output_template_chapter = output_template_chapter
|
self.output_template_chapter = output_template_chapter
|
||||||
self.info = info
|
self.info = info
|
||||||
self.format = get_format(format, quality)
|
self.format = get_format(format, quality, video_codec=getattr(info, 'video_codec', 'auto'))
|
||||||
self.ytdl_opts = get_opts(
|
self.ytdl_opts = get_opts(
|
||||||
format,
|
format,
|
||||||
quality,
|
quality,
|
||||||
@@ -202,6 +204,7 @@ class Download:
|
|||||||
subtitle_format=getattr(info, 'subtitle_format', 'srt'),
|
subtitle_format=getattr(info, 'subtitle_format', 'srt'),
|
||||||
subtitle_language=getattr(info, 'subtitle_language', 'en'),
|
subtitle_language=getattr(info, 'subtitle_language', 'en'),
|
||||||
subtitle_mode=getattr(info, 'subtitle_mode', 'prefer_manual'),
|
subtitle_mode=getattr(info, 'subtitle_mode', 'prefer_manual'),
|
||||||
|
video_codec=getattr(info, 'video_codec', 'auto'),
|
||||||
)
|
)
|
||||||
if "impersonate" in self.ytdl_opts:
|
if "impersonate" in self.ytdl_opts:
|
||||||
self.ytdl_opts["impersonate"] = yt_dlp.networking.impersonate.ImpersonateTarget.from_str(self.ytdl_opts["impersonate"])
|
self.ytdl_opts["impersonate"] = yt_dlp.networking.impersonate.ImpersonateTarget.from_str(self.ytdl_opts["impersonate"])
|
||||||
@@ -687,6 +690,7 @@ class DownloadQueue:
|
|||||||
subtitle_mode,
|
subtitle_mode,
|
||||||
already,
|
already,
|
||||||
_add_gen=None,
|
_add_gen=None,
|
||||||
|
video_codec="auto",
|
||||||
):
|
):
|
||||||
if not entry:
|
if not entry:
|
||||||
return {'status': 'error', 'msg': "Invalid/empty data was given."}
|
return {'status': 'error', 'msg': "Invalid/empty data was given."}
|
||||||
@@ -718,6 +722,7 @@ class DownloadQueue:
|
|||||||
subtitle_mode,
|
subtitle_mode,
|
||||||
already,
|
already,
|
||||||
_add_gen,
|
_add_gen,
|
||||||
|
video_codec,
|
||||||
)
|
)
|
||||||
elif etype == 'playlist' or etype == 'channel':
|
elif etype == 'playlist' or etype == 'channel':
|
||||||
log.debug(f'Processing as a {etype}')
|
log.debug(f'Processing as a {etype}')
|
||||||
@@ -757,6 +762,7 @@ class DownloadQueue:
|
|||||||
subtitle_mode,
|
subtitle_mode,
|
||||||
already,
|
already,
|
||||||
_add_gen,
|
_add_gen,
|
||||||
|
video_codec,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if any(res['status'] == 'error' for res in results):
|
if any(res['status'] == 'error' for res in results):
|
||||||
@@ -785,6 +791,7 @@ class DownloadQueue:
|
|||||||
subtitle_format,
|
subtitle_format,
|
||||||
subtitle_language,
|
subtitle_language,
|
||||||
subtitle_mode,
|
subtitle_mode,
|
||||||
|
video_codec,
|
||||||
)
|
)
|
||||||
await self.__add_download(dl, auto_start)
|
await self.__add_download(dl, auto_start)
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
@@ -806,11 +813,13 @@ class DownloadQueue:
|
|||||||
subtitle_mode="prefer_manual",
|
subtitle_mode="prefer_manual",
|
||||||
already=None,
|
already=None,
|
||||||
_add_gen=None,
|
_add_gen=None,
|
||||||
|
video_codec="auto",
|
||||||
):
|
):
|
||||||
log.info(
|
log.info(
|
||||||
f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} '
|
f'adding {url}: {quality=} {format=} {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_format=} {subtitle_language=} {subtitle_mode=}'
|
f'{subtitle_format=} {subtitle_language=} {subtitle_mode=} '
|
||||||
|
f'{video_codec=}'
|
||||||
)
|
)
|
||||||
if already is None:
|
if already is None:
|
||||||
_add_gen = self._add_generation
|
_add_gen = self._add_generation
|
||||||
@@ -840,6 +849,7 @@ class DownloadQueue:
|
|||||||
subtitle_mode,
|
subtitle_mode,
|
||||||
already,
|
already,
|
||||||
_add_gen,
|
_add_gen,
|
||||||
|
video_codec,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def start_pending(self, ids):
|
async def start_pending(self, ids):
|
||||||
|
|||||||
@@ -77,7 +77,8 @@
|
|||||||
"buildTarget": "metube:build:production"
|
"buildTarget": "metube:build:production"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"buildTarget": "metube:build:development"
|
"buildTarget": "metube:build:development",
|
||||||
|
"proxyConfig": "proxy.conf.json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
|
|||||||
@@ -279,6 +279,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@if (isVideoType()) {
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Video Codec</span>
|
||||||
|
<select class="form-select"
|
||||||
|
name="videoCodec"
|
||||||
|
[(ngModel)]="videoCodec"
|
||||||
|
(change)="videoCodecChanged()"
|
||||||
|
[disabled]="addInProgress || downloads.loading"
|
||||||
|
ngbTooltip="Prefer a specific video codec. Falls back to best available if not found.">
|
||||||
|
@for (vc of videoCodecs; track vc.id) {
|
||||||
|
<option [ngValue]="vc.id">{{ vc.text }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</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="col-auto">
|
||||||
@@ -489,6 +506,8 @@
|
|||||||
<app-master-checkbox #doneMasterCheckboxRef [id]="'done'" [list]="downloads.done" (changed)="doneSelectionChanged($event)" />
|
<app-master-checkbox #doneMasterCheckboxRef [id]="'done'" [list]="downloads.done" (changed)="doneSelectionChanged($event)" />
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">Video</th>
|
<th scope="col">Video</th>
|
||||||
|
<th scope="col">Quality</th>
|
||||||
|
<th scope="col">Codec</th>
|
||||||
<th scope="col">File Size</th>
|
<th scope="col">File Size</th>
|
||||||
<th scope="col">Downloaded</th>
|
<th scope="col">Downloaded</th>
|
||||||
<th scope="col" style="width: 8rem;"></th>
|
<th scope="col" style="width: 8rem;"></th>
|
||||||
@@ -552,6 +571,12 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="text-nowrap">
|
||||||
|
{{ formatQualityLabel(entry[1]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-nowrap">
|
||||||
|
{{ formatCodecLabel(entry[1]) }}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (entry[1].size) {
|
@if (entry[1].size) {
|
||||||
<span>{{ entry[1].size | fileSize }}</span>
|
<span>{{ entry[1].size | fileSize }}</span>
|
||||||
@@ -586,6 +611,8 @@
|
|||||||
getChapterFileName(chapterFile.filename) }}</a>
|
getChapterFileName(chapterFile.filename) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
@if (chapterFile.size) {
|
@if (chapterFile.size) {
|
||||||
<span>{{ chapterFile.size | fileSize }}</span>
|
<span>{{ chapterFile.size | fileSize }}</span>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
subtitleFormat: string;
|
subtitleFormat: string;
|
||||||
subtitleLanguage: string;
|
subtitleLanguage: string;
|
||||||
subtitleMode: string;
|
subtitleMode: string;
|
||||||
|
videoCodec: string;
|
||||||
addInProgress = false;
|
addInProgress = false;
|
||||||
cancelRequested = false;
|
cancelRequested = false;
|
||||||
hasCookies = false;
|
hasCookies = false;
|
||||||
@@ -169,6 +170,13 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
{ id: 'manual_only', text: 'Manual Only' },
|
{ id: 'manual_only', text: 'Manual Only' },
|
||||||
{ id: 'auto_only', text: 'Auto Only' },
|
{ id: 'auto_only', text: 'Auto Only' },
|
||||||
];
|
];
|
||||||
|
videoCodecs = [
|
||||||
|
{ id: 'auto', text: 'Auto' },
|
||||||
|
{ id: 'h264', text: 'H.264' },
|
||||||
|
{ id: 'h265', text: 'H.265 (HEVC)' },
|
||||||
|
{ id: 'av1', text: 'AV1' },
|
||||||
|
{ id: 'vp9', text: 'VP9' },
|
||||||
|
];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.format = this.cookieService.get('metube_format') || 'any';
|
this.format = this.cookieService.get('metube_format') || 'any';
|
||||||
@@ -182,6 +190,11 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
this.subtitleFormat = this.cookieService.get('metube_subtitle_format') || 'srt';
|
this.subtitleFormat = this.cookieService.get('metube_subtitle_format') || 'srt';
|
||||||
this.subtitleLanguage = this.cookieService.get('metube_subtitle_language') || 'en';
|
this.subtitleLanguage = this.cookieService.get('metube_subtitle_language') || 'en';
|
||||||
this.subtitleMode = this.cookieService.get('metube_subtitle_mode') || 'prefer_manual';
|
this.subtitleMode = this.cookieService.get('metube_subtitle_mode') || 'prefer_manual';
|
||||||
|
this.videoCodec = this.cookieService.get('metube_video_codec') || 'auto';
|
||||||
|
const allowedVideoCodecs = new Set(this.videoCodecs.map(c => c.id));
|
||||||
|
if (!allowedVideoCodecs.has(this.videoCodec)) {
|
||||||
|
this.videoCodec = 'auto';
|
||||||
|
}
|
||||||
const allowedSubtitleFormats = new Set(this.subtitleFormats.map(fmt => fmt.id));
|
const allowedSubtitleFormats = new Set(this.subtitleFormats.map(fmt => fmt.id));
|
||||||
const allowedSubtitleModes = new Set(this.subtitleModes.map(mode => mode.id));
|
const allowedSubtitleModes = new Set(this.subtitleModes.map(mode => mode.id));
|
||||||
if (!allowedSubtitleFormats.has(this.subtitleFormat)) {
|
if (!allowedSubtitleFormats.has(this.subtitleFormat)) {
|
||||||
@@ -379,6 +392,28 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
this.cookieService.set('metube_subtitle_mode', this.subtitleMode, { expires: 3650 });
|
this.cookieService.set('metube_subtitle_mode', this.subtitleMode, { expires: 3650 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoCodecChanged() {
|
||||||
|
this.cookieService.set('metube_video_codec', this.videoCodec, { expires: 3650 });
|
||||||
|
}
|
||||||
|
|
||||||
|
isVideoType() {
|
||||||
|
return (this.format === 'any' || this.format === 'mp4') && this.quality !== 'audio';
|
||||||
|
}
|
||||||
|
|
||||||
|
formatQualityLabel(download: Download): string {
|
||||||
|
const q = download.quality;
|
||||||
|
if (!q) return '';
|
||||||
|
if (/^\d+$/.test(q)) return `${q}p`;
|
||||||
|
if (q === 'best_ios') return 'Best (iOS)';
|
||||||
|
return q.charAt(0).toUpperCase() + q.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCodecLabel(download: Download): string {
|
||||||
|
const codec = download.video_codec;
|
||||||
|
if (!codec || codec === 'auto') return 'Auto';
|
||||||
|
return this.videoCodecs.find(c => c.id === codec)?.text ?? codec;
|
||||||
|
}
|
||||||
|
|
||||||
queueSelectionChanged(checked: number) {
|
queueSelectionChanged(checked: number) {
|
||||||
this.queueDelSelected().nativeElement.disabled = checked == 0;
|
this.queueDelSelected().nativeElement.disabled = checked == 0;
|
||||||
this.queueDownloadSelected().nativeElement.disabled = checked == 0;
|
this.queueDownloadSelected().nativeElement.disabled = checked == 0;
|
||||||
@@ -412,6 +447,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
subtitleFormat?: string,
|
subtitleFormat?: string,
|
||||||
subtitleLanguage?: string,
|
subtitleLanguage?: string,
|
||||||
subtitleMode?: string,
|
subtitleMode?: string,
|
||||||
|
videoCodec?: string,
|
||||||
) {
|
) {
|
||||||
url = url ?? this.addUrl
|
url = url ?? this.addUrl
|
||||||
quality = quality ?? this.quality
|
quality = quality ?? this.quality
|
||||||
@@ -425,6 +461,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
subtitleFormat = subtitleFormat ?? this.subtitleFormat
|
subtitleFormat = subtitleFormat ?? this.subtitleFormat
|
||||||
subtitleLanguage = subtitleLanguage ?? this.subtitleLanguage
|
subtitleLanguage = subtitleLanguage ?? this.subtitleLanguage
|
||||||
subtitleMode = subtitleMode ?? this.subtitleMode
|
subtitleMode = subtitleMode ?? this.subtitleMode
|
||||||
|
videoCodec = videoCodec ?? this.videoCodec
|
||||||
|
|
||||||
// Validate chapter template if chapter splitting is enabled
|
// Validate chapter template if chapter splitting is enabled
|
||||||
if (splitByChapters && !chapterTemplate.includes('%(section_number)')) {
|
if (splitByChapters && !chapterTemplate.includes('%(section_number)')) {
|
||||||
@@ -432,10 +469,10 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
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 + ' videoCodec=' + videoCodec);
|
||||||
this.addInProgress = true;
|
this.addInProgress = true;
|
||||||
this.cancelRequested = false;
|
this.cancelRequested = false;
|
||||||
this.downloads.add(url, quality, format, folder, customNamePrefix, playlistItemLimit, autoStart, splitByChapters, chapterTemplate, subtitleFormat, subtitleLanguage, subtitleMode).subscribe((status: Status) => {
|
this.downloads.add(url, quality, format, folder, customNamePrefix, playlistItemLimit, autoStart, splitByChapters, chapterTemplate, subtitleFormat, subtitleLanguage, subtitleMode, videoCodec).subscribe((status: Status) => {
|
||||||
if (status.status === 'error' && !this.cancelRequested) {
|
if (status.status === 'error' && !this.cancelRequested) {
|
||||||
alert(`Error adding URL: ${status.msg}`);
|
alert(`Error adding URL: ${status.msg}`);
|
||||||
} else if (status.status !== 'error') {
|
} else if (status.status !== 'error') {
|
||||||
@@ -473,6 +510,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
download.subtitle_format,
|
download.subtitle_format,
|
||||||
download.subtitle_language,
|
download.subtitle_language,
|
||||||
download.subtitle_mode,
|
download.subtitle_mode,
|
||||||
|
download.video_codec,
|
||||||
);
|
);
|
||||||
this.downloads.delById('done', [key]).subscribe();
|
this.downloads.delById('done', [key]).subscribe();
|
||||||
}
|
}
|
||||||
@@ -620,7 +658,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
// Now pass the selected quality, format, folder, etc. to the add() method
|
// 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.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)
|
this.subtitleFormat, this.subtitleLanguage, this.subtitleMode, this.videoCodec)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (status: Status) => {
|
next: (status: Status) => {
|
||||||
if (status.status === 'error') {
|
if (status.status === 'error') {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface Download {
|
|||||||
subtitle_format?: string;
|
subtitle_format?: string;
|
||||||
subtitle_language?: string;
|
subtitle_language?: string;
|
||||||
subtitle_mode?: string;
|
subtitle_mode?: string;
|
||||||
|
video_codec?: string;
|
||||||
status: string;
|
status: string;
|
||||||
msg: string;
|
msg: string;
|
||||||
percent: number;
|
percent: number;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export class DownloadsService {
|
|||||||
subtitleFormat: string,
|
subtitleFormat: string,
|
||||||
subtitleLanguage: string,
|
subtitleLanguage: string,
|
||||||
subtitleMode: string,
|
subtitleMode: string,
|
||||||
|
videoCodec: string,
|
||||||
) {
|
) {
|
||||||
return this.http.post<Status>('add', {
|
return this.http.post<Status>('add', {
|
||||||
url: url,
|
url: url,
|
||||||
@@ -133,7 +134,8 @@ export class DownloadsService {
|
|||||||
chapter_template: chapterTemplate,
|
chapter_template: chapterTemplate,
|
||||||
subtitle_format: subtitleFormat,
|
subtitle_format: subtitleFormat,
|
||||||
subtitle_language: subtitleLanguage,
|
subtitle_language: subtitleLanguage,
|
||||||
subtitle_mode: subtitleMode
|
subtitle_mode: subtitleMode,
|
||||||
|
video_codec: videoCodec,
|
||||||
}).pipe(
|
}).pipe(
|
||||||
catchError(this.handleHTTPError)
|
catchError(this.handleHTTPError)
|
||||||
);
|
);
|
||||||
@@ -183,6 +185,7 @@ export class DownloadsService {
|
|||||||
const defaultSubtitleFormat = 'srt';
|
const defaultSubtitleFormat = 'srt';
|
||||||
const defaultSubtitleLanguage = 'en';
|
const defaultSubtitleLanguage = 'en';
|
||||||
const defaultSubtitleMode = 'prefer_manual';
|
const defaultSubtitleMode = 'prefer_manual';
|
||||||
|
const defaultVideoCodec = 'auto';
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.add(
|
this.add(
|
||||||
@@ -198,6 +201,7 @@ export class DownloadsService {
|
|||||||
defaultSubtitleFormat,
|
defaultSubtitleFormat,
|
||||||
defaultSubtitleLanguage,
|
defaultSubtitleLanguage,
|
||||||
defaultSubtitleMode,
|
defaultSubtitleMode,
|
||||||
|
defaultVideoCodec,
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (response) => resolve(response),
|
next: (response) => resolve(response),
|
||||||
|
|||||||
Reference in New Issue
Block a user