feat: Undo bogus formatting changes
This commit is contained in:
@@ -261,7 +261,7 @@ async def add(request):
|
|||||||
if split_by_chapters is None:
|
if split_by_chapters is None:
|
||||||
split_by_chapters = False
|
split_by_chapters = False
|
||||||
if chapter_template is None:
|
if chapter_template is None:
|
||||||
chapter_template = '%(section_number)02d - %(section_title)s.%(ext)s'
|
chapter_template = '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s'
|
||||||
|
|
||||||
playlist_item_limit = int(playlist_item_limit)
|
playlist_item_limit = int(playlist_item_limit)
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ class Download:
|
|||||||
)})
|
)})
|
||||||
|
|
||||||
def put_status_postprocessor(d):
|
def put_status_postprocessor(d):
|
||||||
log.debug(f"Postprocessor hook called: postprocessor={d.get('postprocessor')}, status={d.get('status')}")
|
|
||||||
|
|
||||||
if d['postprocessor'] == 'MoveFiles' and d['status'] == 'finished':
|
if d['postprocessor'] == 'MoveFiles' and d['status'] == 'finished':
|
||||||
if '__finaldir' in d['info_dict']:
|
if '__finaldir' in d['info_dict']:
|
||||||
filename = os.path.join(d['info_dict']['__finaldir'], os.path.basename(d['info_dict']['filepath']))
|
filename = os.path.join(d['info_dict']['__finaldir'], os.path.basename(d['info_dict']['filepath']))
|
||||||
@@ -217,7 +215,7 @@ class Download:
|
|||||||
self.info.chapter_files = []
|
self.info.chapter_files = []
|
||||||
rel_path = os.path.relpath(chapter_file, self.download_dir)
|
rel_path = os.path.relpath(chapter_file, self.download_dir)
|
||||||
file_size = os.path.getsize(chapter_file) if os.path.exists(chapter_file) else None
|
file_size = os.path.getsize(chapter_file) if os.path.exists(chapter_file) else None
|
||||||
# Upsert: update if exists, otherwise append. Postprocessor hook called multiple times with chapters.
|
#Postprocessor hook called multiple times with chapters. Only insert if not already present.
|
||||||
existing = next((cf for cf in self.info.chapter_files if cf['filename'] == rel_path), None)
|
existing = next((cf for cf in self.info.chapter_files if cf['filename'] == rel_path), None)
|
||||||
if not existing:
|
if not existing:
|
||||||
self.info.chapter_files.append({'filename': rel_path, 'size': file_size})
|
self.info.chapter_files.append({'filename': rel_path, 'size': file_size})
|
||||||
@@ -464,7 +462,7 @@ class DownloadQueue:
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'}
|
return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'}
|
||||||
|
|
||||||
async def add(self, url, quality, format, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start=True, split_by_chapters=False, chapter_template='%(section_number)02d - %(section_title)s.%(ext)s', already=None):
|
async def add(self, url, quality, format, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start=True, split_by_chapters=False, chapter_template='%(title)s - %(section_number)02d - %(section_title)s.%(ext)s', already=None):
|
||||||
log.info(f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} {playlist_strict_mode=} {playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=}')
|
log.info(f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} {playlist_strict_mode=} {playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=}')
|
||||||
already = set() if already is None else already
|
already = set() if already is None else already
|
||||||
if url in already:
|
if url in already:
|
||||||
|
|||||||
@@ -6,34 +6,34 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="download-metrics">
|
<div class="download-metrics">
|
||||||
@if (activeDownloads > 0) {
|
@if (activeDownloads > 0) {
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<fa-icon [icon]="faDownload" class="text-primary" />
|
<fa-icon [icon]="faDownload" class="text-primary" />
|
||||||
<span>{{activeDownloads}} downloading</span>
|
<span>{{activeDownloads}} downloading</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (queuedDownloads > 0) {
|
@if (queuedDownloads > 0) {
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<fa-icon [icon]="faClock" class="text-warning" />
|
<fa-icon [icon]="faClock" class="text-warning" />
|
||||||
<span>{{queuedDownloads}} queued</span>
|
<span>{{queuedDownloads}} queued</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (completedDownloads > 0) {
|
@if (completedDownloads > 0) {
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<fa-icon [icon]="faCheck" class="text-success" />
|
<fa-icon [icon]="faCheck" class="text-success" />
|
||||||
<span>{{completedDownloads}} completed</span>
|
<span>{{completedDownloads}} completed</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (failedDownloads > 0) {
|
@if (failedDownloads > 0) {
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<fa-icon [icon]="faTimesCircle" class="text-danger" />
|
<fa-icon [icon]="faTimesCircle" class="text-danger" />
|
||||||
<span>{{failedDownloads}} failed</span>
|
<span>{{failedDownloads}} failed</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if ((totalSpeed | speed) !== '') {
|
@if ((totalSpeed | speed) !== '') {
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<fa-icon [icon]="faTachometerAlt" class="text-info" />
|
<fa-icon [icon]="faTachometerAlt" class="text-info" />
|
||||||
<span>{{totalSpeed | speed }}</span>
|
<span>{{totalSpeed | speed }}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<!--
|
||||||
@@ -62,20 +62,20 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end position-absolute" aria-labelledby="theme-select">
|
<ul class="dropdown-menu dropdown-menu-end position-absolute" aria-labelledby="theme-select">
|
||||||
@for (theme of themes; track theme) {
|
@for (theme of themes; track theme) {
|
||||||
<li>
|
<li>
|
||||||
<button type="button" class="dropdown-item d-flex align-items-center"
|
<button type="button" class="dropdown-item d-flex align-items-center"
|
||||||
[class.active]="activeTheme === theme"
|
[class.active]="activeTheme === theme"
|
||||||
(click)="themeChanged(theme)">
|
(click)="themeChanged(theme)">
|
||||||
<span class="me-2 opacity-50">
|
<span class="me-2 opacity-50">
|
||||||
<fa-icon [icon]="theme.icon" />
|
<fa-icon [icon]="theme.icon" />
|
||||||
</span>
|
</span>
|
||||||
{{ theme.displayName }}
|
{{ theme.displayName }}
|
||||||
<span class="ms-auto"
|
<span class="ms-auto"
|
||||||
[class.d-none]="activeTheme !== theme">
|
[class.d-none]="activeTheme !== theme">
|
||||||
<fa-icon [icon]="faCheck" />
|
<fa-icon [icon]="faCheck" />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
(click)="addDownload()"
|
(click)="addDownload()"
|
||||||
[disabled]="addInProgress || downloads.loading">
|
[disabled]="addInProgress || downloads.loading">
|
||||||
@if (addInProgress) {
|
@if (addInProgress) {
|
||||||
<span class="spinner-border spinner-border-sm" role="status" id="add-spinner"></span>
|
<span class="spinner-border spinner-border-sm" role="status" id="add-spinner"></span>
|
||||||
}
|
}
|
||||||
{{ addInProgress ? "Adding..." : "Download" }}
|
{{ addInProgress ? "Adding..." : "Download" }}
|
||||||
</button>
|
</button>
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
(change)="qualityChanged()"
|
(change)="qualityChanged()"
|
||||||
[disabled]="addInProgress || downloads.loading">
|
[disabled]="addInProgress || downloads.loading">
|
||||||
@for (q of qualities; track q) {
|
@for (q of qualities; track q) {
|
||||||
<option [ngValue]="q.id">{{ q.text }}</option>
|
<option [ngValue]="q.id">{{ q.text }}</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
(change)="formatChanged()"
|
(change)="formatChanged()"
|
||||||
[disabled]="addInProgress || downloads.loading">
|
[disabled]="addInProgress || downloads.loading">
|
||||||
@for (f of formats; track f) {
|
@for (f of formats; track f) {
|
||||||
<option [ngValue]="f.id">{{ f.text }}</option>
|
<option [ngValue]="f.id">{{ f.text }}</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,9 +188,9 @@
|
|||||||
[searchable]="true"
|
[searchable]="true"
|
||||||
[closeOnSelect]="true"
|
[closeOnSelect]="true"
|
||||||
ngbTooltip="Choose where to save downloads. Type to create a new folder." />
|
ngbTooltip="Choose where to save downloads. Type to create a new folder." />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
<!-- Batch Import Modal -->
|
<!-- Batch Import Modal -->
|
||||||
<div class="modal fade" tabindex="-1" role="dialog"
|
<div class="modal fade" tabindex="-1" role="dialog"
|
||||||
[class.show]="batchImportModalOpen"
|
[class.show]="batchImportModalOpen"
|
||||||
[style.display]="batchImportModalOpen ? 'block' : 'none'">
|
[style.display]="batchImportModalOpen ? 'block' : 'none'">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@@ -307,18 +307,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<textarea [(ngModel)]="batchImportText" class="form-control" rows="6"
|
<textarea [(ngModel)]="batchImportText" class="form-control" rows="6"
|
||||||
placeholder="Paste one video URL per line"></textarea>
|
placeholder="Paste one video URL per line"></textarea>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
@if (batchImportStatus) {
|
@if (batchImportStatus) {
|
||||||
<small>{{ batchImportStatus }}</small>
|
<small>{{ batchImportStatus }}</small>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@if (importInProgress) {
|
@if (importInProgress) {
|
||||||
<button type="button" class="btn btn-danger me-auto" (click)="cancelBatchImport()">
|
<button type="button" class="btn btn-danger me-auto" (click)="cancelBatchImport()">
|
||||||
Cancel Import
|
Cancel Import
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-secondary" (click)="closeBatchImportModal()">Close</button>
|
<button type="button" class="btn btn-secondary" (click)="closeBatchImportModal()">Close</button>
|
||||||
<button type="button" class="btn btn-primary" (click)="startBatchImport()" [disabled]="importInProgress">
|
<button type="button" class="btn btn-primary" (click)="startBatchImport()" [disabled]="importInProgress">
|
||||||
@@ -331,9 +331,9 @@
|
|||||||
|
|
||||||
|
|
||||||
@if (downloads.loading) {
|
@if (downloads.loading) {
|
||||||
<div class="alert alert-info" role="alert">
|
<div class="alert alert-info" role="alert">
|
||||||
Connecting to server...
|
Connecting to server...
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="metube-section-header">Downloading</div>
|
<div class="metube-section-header">Downloading</div>
|
||||||
<div class="px-2 py-3 border-bottom">
|
<div class="px-2 py-3 border-bottom">
|
||||||
@@ -355,29 +355,29 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (download of downloads.queue | keyvalue: asIsOrder; track download.value.id) {
|
@for (download of downloads.queue | keyvalue: asIsOrder; track download.value.id) {
|
||||||
<tr [class.disabled]='download.value.deleting'>
|
<tr [class.disabled]='download.value.deleting'>
|
||||||
<td>
|
<td>
|
||||||
<app-slave-checkbox [id]="download.key" [master]="queueMasterCheckboxRef" [checkable]="download.value" />
|
<app-slave-checkbox [id]="download.key" [master]="queueMasterCheckboxRef" [checkable]="download.value" />
|
||||||
</td>
|
</td>
|
||||||
<td title="{{ download.value.filename }}">
|
<td title="{{ download.value.filename }}">
|
||||||
<div class="d-flex flex-column flex-sm-row align-items-center row-gap-2 column-gap-3">
|
<div class="d-flex flex-column flex-sm-row align-items-center row-gap-2 column-gap-3">
|
||||||
<div>{{ download.value.title }} </div>
|
<div>{{ download.value.title }} </div>
|
||||||
<ngb-progressbar height="1.5rem" [showValue]="download.value.status !== 'preparing'" [striped]="download.value.status === 'preparing'" [animated]="download.value.status === 'preparing'" type="success"
|
<ngb-progressbar height="1.5rem" [showValue]="download.value.status !== 'preparing'" [striped]="download.value.status === 'preparing'" [animated]="download.value.status === 'preparing'" type="success"
|
||||||
[value]="download.value.status === 'preparing' ? 100 : download.value.percent" class="download-progressbar" />
|
[value]="download.value.status === 'preparing' ? 100 : download.value.percent" class="download-progressbar" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ download.value.speed | speed }}</td>
|
<td>{{ download.value.speed | speed }}</td>
|
||||||
<td>{{ download.value.eta | eta }}</td>
|
<td>{{ download.value.eta | eta }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
@if (download.value.status === 'pending') {
|
@if (download.value.status === 'pending') {
|
||||||
<button type="button" class="btn btn-link" (click)="downloadItemByKey(download.key)"><fa-icon [icon]="faDownload" /></button>
|
<button type="button" class="btn btn-link" (click)="downloadItemByKey(download.key)"><fa-icon [icon]="faDownload" /></button>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-link" (click)="delDownload('queue', download.key)"><fa-icon [icon]="faTrashAlt" /></button>
|
<button type="button" class="btn btn-link" (click)="delDownload('queue', download.key)"><fa-icon [icon]="faTrashAlt" /></button>
|
||||||
<a href="{{download.value.url}}" target="_blank" class="btn btn-link"><fa-icon [icon]="faExternalLinkAlt" /></a>
|
<a href="{{download.value.url}}" target="_blank" class="btn btn-link"><fa-icon [icon]="faExternalLinkAlt" /></a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -405,55 +405,55 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (download of downloads.done | keyvalue: asIsOrder; track download.value.id) {
|
@for (download of downloads.done | keyvalue: asIsOrder; track download.value.id) {
|
||||||
<tr [class.disabled]='download.value.deleting'>
|
<tr [class.disabled]='download.value.deleting'>
|
||||||
<td>
|
<td>
|
||||||
<app-slave-checkbox [id]="download.key" [master]="doneMasterCheckboxRef" [checkable]="download.value" />
|
<app-slave-checkbox [id]="download.key" [master]="doneMasterCheckboxRef" [checkable]="download.value" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div style="display: inline-block; width: 1.5rem;">
|
<div style="display: inline-block; width: 1.5rem;">
|
||||||
@if (download.value.status === 'finished') {
|
@if (download.value.status === 'finished') {
|
||||||
<fa-icon [icon]="faCheckCircle" class="text-success" />
|
<fa-icon [icon]="faCheckCircle" class="text-success" />
|
||||||
}
|
}
|
||||||
@if (download.value.status === 'error') {
|
@if (download.value.status === 'error') {
|
||||||
<fa-icon [icon]="faTimesCircle" class="text-danger" />
|
<fa-icon [icon]="faTimesCircle" class="text-danger" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<span ngbTooltip="{{buildResultItemTooltip(download.value)}}">@if (!!download.value.filename) {
|
<span ngbTooltip="{{buildResultItemTooltip(download.value)}}">@if (!!download.value.filename) {
|
||||||
<a href="{{buildDownloadLink(download.value)}}" target="_blank">{{ download.value.title }}</a>
|
<a href="{{buildDownloadLink(download.value)}}" target="_blank">{{ download.value.title }}</a>
|
||||||
} @else {
|
} @else {
|
||||||
{{download.value.title}}
|
{{download.value.title}}
|
||||||
@if (download.value.msg) {
|
@if (download.value.msg) {
|
||||||
<span><br>{{download.value.msg}}</span>
|
<span><br>{{download.value.msg}}</span>
|
||||||
}
|
}
|
||||||
@if (download.value.error) {
|
@if (download.value.error) {
|
||||||
<span><br>Error: {{download.value.error}}</span>
|
<span><br>Error: {{download.value.error}}</span>
|
||||||
}
|
}
|
||||||
}</span>
|
}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (download.value.size) {
|
@if (download.value.size) {
|
||||||
<span>{{ download.value.size | fileSize }}</span>
|
<span>{{ download.value.size | fileSize }}</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
@if (download.value.status === 'error') {
|
@if (download.value.status === 'error') {
|
||||||
<button type="button" class="btn btn-link" (click)="retryDownload(download.key, download.value)"><fa-icon [icon]="faRedoAlt" /></button>
|
<button type="button" class="btn btn-link" (click)="retryDownload(download.key, download.value)"><fa-icon [icon]="faRedoAlt" /></button>
|
||||||
}
|
}
|
||||||
@if (download.value.filename) {
|
@if (download.value.filename) {
|
||||||
<a href="{{buildDownloadLink(download.value)}}" download class="btn btn-link"><fa-icon [icon]="faDownload" /></a>
|
<a href="{{buildDownloadLink(download.value)}}" download class="btn btn-link"><fa-icon [icon]="faDownload" /></a>
|
||||||
}
|
}
|
||||||
<a href="{{download.value.url}}" target="_blank" class="btn btn-link"><fa-icon [icon]="faExternalLinkAlt" /></a>
|
<a href="{{download.value.url}}" target="_blank" class="btn btn-link"><fa-icon [icon]="faExternalLinkAlt" /></a>
|
||||||
<button type="button" class="btn btn-link" (click)="delDownload('done', download.key)"><fa-icon [icon]="faTrashAlt" /></button>
|
<button type="button" class="btn btn-link" (click)="delDownload('done', download.key)"><fa-icon [icon]="faTrashAlt" /></button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@if (download.value.chapter_files && download.value.chapter_files.length > 0) {
|
@if (download.value.chapter_files && download.value.chapter_files.length > 0) {
|
||||||
@for (chapterFile of download.value.chapter_files; track chapterFile.filename) {
|
@for (chapterFile of download.value.chapter_files; track chapterFile.filename) {
|
||||||
<tr [class.disabled]='download.value.deleting'>
|
<tr [class.disabled]='download.value.deleting'>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
<div style="padding-left: 2rem;">
|
<div style="padding-left: 2rem;">
|
||||||
<fa-icon [icon]="faCheckCircle" class="text-success me-2" />
|
<fa-icon [icon]="faCheckCircle" class="text-success me-2" />
|
||||||
<a href="{{buildChapterDownloadLink(download.value, chapterFile.filename)}}" target="_blank">{{
|
<a href="{{buildChapterDownloadLink(download.value, chapterFile.filename)}}" target="_blank">{{
|
||||||
getChapterFileName(chapterFile.filename) }}</a>
|
getChapterFileName(chapterFile.filename) }}</a>
|
||||||
@@ -472,6 +472,8 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -480,31 +482,31 @@
|
|||||||
<footer class="footer navbar-dark bg-dark py-3 mt-5">
|
<footer class="footer navbar-dark bg-dark py-3 mt-5">
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
@if (ytDlpVersion && metubeVersion) {
|
@if (ytDlpVersion && metubeVersion) {
|
||||||
<div class="footer-content">
|
<div class="footer-content">
|
||||||
<div class="version-item">
|
<div class="version-item">
|
||||||
<span class="version-label">yt-dlp</span>
|
<span class="version-label">yt-dlp</span>
|
||||||
<span class="version-value">{{ytDlpVersion}}</span>
|
<span class="version-value">{{ytDlpVersion}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="version-separator"></div>
|
||||||
|
<div class="version-item">
|
||||||
|
<span class="version-label">MeTube</span>
|
||||||
|
<span class="version-value">{{metubeVersion}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="version-separator"></div>
|
||||||
|
@if (ytDlpOptionsUpdateTime) {
|
||||||
|
<div class="version-item">
|
||||||
|
<span class="version-label">yt-dlp-options</span>
|
||||||
|
<span class="version-value">{{ytDlpOptionsUpdateTime}}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (ytDlpOptionsUpdateTime) {
|
||||||
|
<div class="version-separator"></div>
|
||||||
|
}
|
||||||
|
<a href="https://github.com/alexta69/metube" target="_blank" class="github-link">
|
||||||
|
<fa-icon [icon]="faGithub" />
|
||||||
|
<span>GitHub</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="version-separator"></div>
|
|
||||||
<div class="version-item">
|
|
||||||
<span class="version-label">MeTube</span>
|
|
||||||
<span class="version-value">{{metubeVersion}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="version-separator"></div>
|
|
||||||
@if (ytDlpOptionsUpdateTime) {
|
|
||||||
<div class="version-item">
|
|
||||||
<span class="version-label">yt-dlp-options</span>
|
|
||||||
<span class="version-value">{{ytDlpOptionsUpdateTime}}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (ytDlpOptionsUpdateTime) {
|
|
||||||
<div class="version-separator"></div>
|
|
||||||
}
|
|
||||||
<a href="https://github.com/alexta69/metube" target="_blank" class="github-link">
|
|
||||||
<fa-icon [icon]="faGithub" />
|
|
||||||
<span>GitHub</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -5,30 +5,30 @@ import { Observable, map, distinctUntilChanged } from 'rxjs';
|
|||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { NgSelectModule } from '@ng-select/ng-select';
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faGithub } from '@fortawesome/free-brands-svg-icons';
|
import { faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||||
import { CookieService } from 'ngx-cookie-service';
|
import { CookieService } from 'ngx-cookie-service';
|
||||||
import { DownloadsService } from './services/downloads.service';
|
import { DownloadsService } from './services/downloads.service';
|
||||||
import { Themes } from './theme';
|
import { Themes } from './theme';
|
||||||
import { Download, Status, Theme, Quality, Format, Formats, State } from './interfaces';
|
import { Download, Status, Theme , Quality, Format, Formats, State } from './interfaces';
|
||||||
import { EtaPipe, SpeedPipe, FileSizePipe } from './pipes';
|
import { EtaPipe, SpeedPipe, FileSizePipe } from './pipes';
|
||||||
import { MasterCheckboxComponent, SlaveCheckboxComponent } from './components/';
|
import { MasterCheckboxComponent , SlaveCheckboxComponent} from './components/';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [
|
imports: [
|
||||||
FormsModule,
|
FormsModule,
|
||||||
KeyValuePipe,
|
KeyValuePipe,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
NgbModule,
|
NgbModule,
|
||||||
NgSelectModule,
|
NgSelectModule,
|
||||||
EtaPipe,
|
EtaPipe,
|
||||||
SpeedPipe,
|
SpeedPipe,
|
||||||
FileSizePipe,
|
FileSizePipe,
|
||||||
MasterCheckboxComponent,
|
MasterCheckboxComponent,
|
||||||
SlaveCheckboxComponent,
|
SlaveCheckboxComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.sass',
|
styleUrl: './app.sass',
|
||||||
@@ -54,7 +54,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
themes: Theme[] = Themes;
|
themes: Theme[] = Themes;
|
||||||
activeTheme: Theme | undefined;
|
activeTheme: Theme | undefined;
|
||||||
customDirs$!: Observable<string[]>;
|
customDirs$!: Observable<string[]>;
|
||||||
showBatchPanel = false;
|
showBatchPanel = false;
|
||||||
batchImportModalOpen = false;
|
batchImportModalOpen = false;
|
||||||
batchImportText = '';
|
batchImportText = '';
|
||||||
batchImportStatus = '';
|
batchImportStatus = '';
|
||||||
@@ -106,7 +106,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
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.chapterTemplate = this.cookieService.get('metube_chapter_template') || '%(section_number)02d - %(section_title)s.%(ext)s';
|
this.chapterTemplate = this.cookieService.get('metube_chapter_template') || '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s';
|
||||||
|
|
||||||
this.activeTheme = this.getPreferredTheme(this.cookieService);
|
this.activeTheme = this.getPreferredTheme(this.cookieService);
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||||
if (this.activeTheme && this.activeTheme.id === 'auto') {
|
if (this.activeTheme && this.activeTheme.id === 'auto') {
|
||||||
this.setTheme(this.activeTheme);
|
this.setTheme(this.activeTheme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -158,9 +158,9 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
|
|
||||||
// workaround to allow fetching of Map values in the order they were inserted
|
// workaround to allow fetching of Map values in the order they were inserted
|
||||||
// https://github.com/angular/angular/issues/31420
|
// https://github.com/angular/angular/issues/31420
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
asIsOrder() {
|
asIsOrder() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -183,12 +183,12 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAudioType() {
|
isAudioType() {
|
||||||
return this.quality == 'audio' || this.format == 'mp3' || this.format == 'm4a' || this.format == 'opus' || this.format == 'wav' || this.format == 'flac';
|
return this.quality == 'audio' || this.format == 'mp3' || this.format == 'm4a' || this.format == 'opus' || this.format == 'wav' || this.format == 'flac';
|
||||||
}
|
}
|
||||||
|
|
||||||
getMatchingCustomDir(): Observable<string[]> {
|
getMatchingCustomDir() : Observable<string[]> {
|
||||||
return this.downloads.customDirsChanged.asObservable().pipe(
|
return this.downloads.customDirsChanged.asObservable().pipe(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
map((output: any) => {
|
map((output: any) => {
|
||||||
// Keep logic consistent with app/ytdl.py
|
// Keep logic consistent with app/ytdl.py
|
||||||
if (this.isAudioType()) {
|
if (this.isAudioType()) {
|
||||||
@@ -205,20 +205,20 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
|
|
||||||
getYtdlOptionsUpdateTime() {
|
getYtdlOptionsUpdateTime() {
|
||||||
this.downloads.ytdlOptionsChanged.subscribe({
|
this.downloads.ytdlOptionsChanged.subscribe({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
next: (data: any) => {
|
next: (data:any) => {
|
||||||
if (data['success']) {
|
if (data['success']){
|
||||||
const date = new Date(data['update_time'] * 1000);
|
const date = new Date(data['update_time'] * 1000);
|
||||||
this.ytDlpOptionsUpdateTime = date.toLocaleString();
|
this.ytDlpOptionsUpdateTime=date.toLocaleString();
|
||||||
} else {
|
}else{
|
||||||
alert("Error reload yt-dlp options: " + data['msg']);
|
alert("Error reload yt-dlp options: "+data['msg']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getConfiguration() {
|
getConfiguration() {
|
||||||
this.downloads.configurationChanged.subscribe({
|
this.downloads.configurationChanged.subscribe({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
next: (config: any) => {
|
next: (config: any) => {
|
||||||
this.playlistStrictMode = config['DEFAULT_OPTION_PLAYLIST_STRICT_MODE'];
|
this.playlistStrictMode = config['DEFAULT_OPTION_PLAYLIST_STRICT_MODE'];
|
||||||
const playlistItemLimit = config['DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT'];
|
const playlistItemLimit = config['DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT'];
|
||||||
@@ -269,6 +269,10 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
chapterTemplateChanged() {
|
chapterTemplateChanged() {
|
||||||
|
// Restore default if template is cleared
|
||||||
|
if (!this.chapterTemplate || this.chapterTemplate.trim() === '') {
|
||||||
|
this.chapterTemplate = '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s';
|
||||||
|
}
|
||||||
this.cookieService.set('metube_chapter_template', this.chapterTemplate, { expires: 3650 });
|
this.cookieService.set('metube_chapter_template', this.chapterTemplate, { expires: 3650 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,8 +293,8 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
this.qualities = format.qualities
|
this.qualities = format.qualities
|
||||||
const exists = this.qualities.find(el => el.id === this.quality)
|
const exists = this.qualities.find(el => el.id === this.quality)
|
||||||
this.quality = exists ? this.quality : 'best'
|
this.quality = exists ? this.quality : 'best'
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addDownload(url?: string, quality?: string, format?: string, folder?: string, customNamePrefix?: string, playlistStrictMode?: boolean, playlistItemLimit?: number, autoStart?: boolean, splitByChapters?: boolean, chapterTemplate?: string) {
|
addDownload(url?: string, quality?: string, format?: string, folder?: string, customNamePrefix?: string, playlistStrictMode?: boolean, playlistItemLimit?: number, autoStart?: boolean, splitByChapters?: boolean, chapterTemplate?: string) {
|
||||||
url = url ?? this.addUrl
|
url = url ?? this.addUrl
|
||||||
@@ -304,6 +308,12 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
splitByChapters = splitByChapters ?? this.splitByChapters
|
splitByChapters = splitByChapters ?? this.splitByChapters
|
||||||
chapterTemplate = chapterTemplate ?? this.chapterTemplate
|
chapterTemplate = chapterTemplate ?? this.chapterTemplate
|
||||||
|
|
||||||
|
// Validate chapter template if chapter splitting is enabled
|
||||||
|
if (splitByChapters && !chapterTemplate.includes('%(section_number)')) {
|
||||||
|
alert('Chapter template must include %(section_number)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.debug('Downloading: url=' + url + ' quality=' + quality + ' format=' + format + ' folder=' + folder + ' customNamePrefix=' + customNamePrefix + ' playlistStrictMode=' + playlistStrictMode + ' playlistItemLimit=' + playlistItemLimit + ' autoStart=' + autoStart + ' splitByChapters=' + splitByChapters + ' chapterTemplate=' + chapterTemplate);
|
console.debug('Downloading: url=' + url + ' quality=' + quality + ' format=' + format + ' folder=' + folder + ' customNamePrefix=' + customNamePrefix + ' playlistStrictMode=' + playlistStrictMode + ' playlistItemLimit=' + playlistItemLimit + ' autoStart=' + autoStart + ' splitByChapters=' + splitByChapters + ' chapterTemplate=' + chapterTemplate);
|
||||||
this.addInProgress = true;
|
this.addInProgress = true;
|
||||||
this.downloads.add(url, quality, format, folder, customNamePrefix, playlistStrictMode, playlistItemLimit, autoStart, splitByChapters, chapterTemplate).subscribe((status: Status) => {
|
this.downloads.add(url, quality, format, folder, customNamePrefix, playlistStrictMode, playlistItemLimit, autoStart, splitByChapters, chapterTemplate).subscribe((status: Status) => {
|
||||||
@@ -329,7 +339,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
this.downloads.delById(where, [id]).subscribe();
|
this.downloads.delById(where, [id]).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
startSelectedDownloads(where: State) {
|
startSelectedDownloads(where: State){
|
||||||
this.downloads.startByFilter(where, dl => !!dl.checked).subscribe();
|
this.downloads.startByFilter(where, dl => !!dl.checked).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +423,7 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
|
|
||||||
isNumber(event: KeyboardEvent) {
|
isNumber(event: KeyboardEvent) {
|
||||||
const charCode = +event.code || event.keyCode;
|
const charCode = +event.code || event.keyCode;
|
||||||
if (charCode > 31 && (charCode < 48 || charCode > 57)) {
|
if (charCode > 31 && (charCode < 48 || charCode > 57)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,11 +589,11 @@ export class App implements AfterViewInit, OnInit {
|
|||||||
this.queuedDownloads = Array.from(this.downloads.queue.values()).filter(d => d.status === 'pending').length;
|
this.queuedDownloads = Array.from(this.downloads.queue.values()).filter(d => d.status === 'pending').length;
|
||||||
this.completedDownloads = Array.from(this.downloads.done.values()).filter(d => d.status === 'finished').length;
|
this.completedDownloads = Array.from(this.downloads.done.values()).filter(d => d.status === 'finished').length;
|
||||||
this.failedDownloads = Array.from(this.downloads.done.values()).filter(d => d.status === 'error').length;
|
this.failedDownloads = Array.from(this.downloads.done.values()).filter(d => d.status === 'error').length;
|
||||||
|
|
||||||
// Calculate total speed from downloading items
|
// Calculate total speed from downloading items
|
||||||
const downloadingItems = Array.from(this.downloads.queue.values())
|
const downloadingItems = Array.from(this.downloads.queue.values())
|
||||||
.filter(d => d.status === 'downloading');
|
.filter(d => d.status === 'downloading');
|
||||||
|
|
||||||
this.totalSpeed = downloadingItems.reduce((total, item) => total + (item.speed || 0), 0);
|
this.totalSpeed = downloadingItems.reduce((total, item) => total + (item.speed || 0), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,79 +27,79 @@ export class DownloadsService {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.socket.fromEvent('all')
|
this.socket.fromEvent('all')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
const data: [[[string, Download]], [[string, Download]]] = JSON.parse(strdata);
|
const data: [[[string, Download]], [[string, Download]]] = JSON.parse(strdata);
|
||||||
this.queue.clear();
|
this.queue.clear();
|
||||||
data[0].forEach(entry => this.queue.set(...entry));
|
data[0].forEach(entry => this.queue.set(...entry));
|
||||||
this.done.clear();
|
this.done.clear();
|
||||||
data[1].forEach(entry => this.done.set(...entry));
|
data[1].forEach(entry => this.done.set(...entry));
|
||||||
this.queueChanged.next(null);
|
this.queueChanged.next(null);
|
||||||
this.doneChanged.next(null);
|
this.doneChanged.next(null);
|
||||||
});
|
});
|
||||||
this.socket.fromEvent('added')
|
this.socket.fromEvent('added')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
const data: Download = JSON.parse(strdata);
|
const data: Download = JSON.parse(strdata);
|
||||||
this.queue.set(data.url, data);
|
this.queue.set(data.url, data);
|
||||||
this.queueChanged.next(null);
|
this.queueChanged.next(null);
|
||||||
});
|
});
|
||||||
this.socket.fromEvent('updated')
|
this.socket.fromEvent('updated')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
const data: Download = JSON.parse(strdata);
|
const data: Download = JSON.parse(strdata);
|
||||||
const dl: Download | undefined = this.queue.get(data.url);
|
const dl: Download | undefined = this.queue.get(data.url);
|
||||||
data.checked = !!dl?.checked;
|
data.checked = !!dl?.checked;
|
||||||
data.deleting = !!dl?.deleting;
|
data.deleting = !!dl?.deleting;
|
||||||
this.queue.set(data.url, data);
|
this.queue.set(data.url, data);
|
||||||
this.updated.next(null);
|
this.updated.next(null);
|
||||||
});
|
});
|
||||||
this.socket.fromEvent('completed')
|
this.socket.fromEvent('completed')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
const data: Download = JSON.parse(strdata);
|
const data: Download = JSON.parse(strdata);
|
||||||
this.queue.delete(data.url);
|
this.queue.delete(data.url);
|
||||||
this.done.set(data.url, data);
|
this.done.set(data.url, data);
|
||||||
this.queueChanged.next(null);
|
this.queueChanged.next(null);
|
||||||
this.doneChanged.next(null);
|
this.doneChanged.next(null);
|
||||||
});
|
});
|
||||||
this.socket.fromEvent('canceled')
|
this.socket.fromEvent('canceled')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
const data: string = JSON.parse(strdata);
|
const data: string = JSON.parse(strdata);
|
||||||
this.queue.delete(data);
|
this.queue.delete(data);
|
||||||
this.queueChanged.next(null);
|
this.queueChanged.next(null);
|
||||||
});
|
});
|
||||||
this.socket.fromEvent('cleared')
|
this.socket.fromEvent('cleared')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
const data: string = JSON.parse(strdata);
|
const data: string = JSON.parse(strdata);
|
||||||
this.done.delete(data);
|
this.done.delete(data);
|
||||||
this.doneChanged.next(null);
|
this.doneChanged.next(null);
|
||||||
});
|
});
|
||||||
this.socket.fromEvent('configuration')
|
this.socket.fromEvent('configuration')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
const data = JSON.parse(strdata);
|
const data = JSON.parse(strdata);
|
||||||
console.debug("got configuration:", data);
|
console.debug("got configuration:", data);
|
||||||
this.configuration = data;
|
this.configuration = data;
|
||||||
this.configurationChanged.next(data);
|
this.configurationChanged.next(data);
|
||||||
});
|
});
|
||||||
this.socket.fromEvent('custom_dirs')
|
this.socket.fromEvent('custom_dirs')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
const data = JSON.parse(strdata);
|
const data = JSON.parse(strdata);
|
||||||
console.debug("got custom_dirs:", data);
|
console.debug("got custom_dirs:", data);
|
||||||
this.customDirs = data;
|
this.customDirs = data;
|
||||||
this.customDirsChanged.next(data);
|
this.customDirsChanged.next(data);
|
||||||
});
|
});
|
||||||
this.socket.fromEvent('ytdl_options_changed')
|
this.socket.fromEvent('ytdl_options_changed')
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((strdata: string) => {
|
.subscribe((strdata: string) => {
|
||||||
const data = JSON.parse(strdata);
|
const data = JSON.parse(strdata);
|
||||||
this.ytdlOptionsChanged.next(data);
|
this.ytdlOptionsChanged.next(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHTTPError(error: HttpErrorResponse) {
|
handleHTTPError(error: HttpErrorResponse) {
|
||||||
@@ -123,7 +123,7 @@ export class DownloadsService {
|
|||||||
if (obj) {
|
if (obj) {
|
||||||
obj.deleting = true
|
obj.deleting = true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return this.http.post('delete', {where: where, ids: ids});
|
return this.http.post('delete', {where: where, ids: ids});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,13 +145,13 @@ export class DownloadsService {
|
|||||||
}> {
|
}> {
|
||||||
const defaultQuality = 'best';
|
const defaultQuality = 'best';
|
||||||
const defaultFormat = 'mp4';
|
const defaultFormat = 'mp4';
|
||||||
const defaultFolder = '';
|
const defaultFolder = '';
|
||||||
const defaultCustomNamePrefix = '';
|
const defaultCustomNamePrefix = '';
|
||||||
const defaultPlaylistStrictMode = false;
|
const defaultPlaylistStrictMode = false;
|
||||||
const defaultPlaylistItemLimit = 0;
|
const defaultPlaylistItemLimit = 0;
|
||||||
const defaultAutoStart = true;
|
const defaultAutoStart = true;
|
||||||
const defaultSplitByChapters = false;
|
const defaultSplitByChapters = false;
|
||||||
const defaultChapterTemplate = '%(section_number)02d - %(section_title)s.%(ext)s';
|
const defaultChapterTemplate = '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s';
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.add(url, defaultQuality, defaultFormat, defaultFolder, defaultCustomNamePrefix, defaultPlaylistStrictMode, defaultPlaylistItemLimit, defaultAutoStart, defaultSplitByChapters, defaultChapterTemplate)
|
this.add(url, defaultQuality, defaultFormat, defaultFolder, defaultCustomNamePrefix, defaultPlaylistStrictMode, defaultPlaylistItemLimit, defaultAutoStart, defaultSplitByChapters, defaultChapterTemplate)
|
||||||
@@ -164,6 +164,6 @@ export class DownloadsService {
|
|||||||
public exportQueueUrls(): string[] {
|
public exportQueueUrls(): string[] {
|
||||||
return Array.from(this.queue.values()).map(download => download.url);
|
return Array.from(this.queue.values()).map(download => download.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user