Link to audio files and those with custom folders properly
This commit is contained in:
parent
52e3307d99
commit
63baa1fc25
|
|
@ -150,6 +150,7 @@ if config.URL_PREFIX != '/':
|
||||||
|
|
||||||
routes.static(config.URL_PREFIX + 'favicon/', 'favicon')
|
routes.static(config.URL_PREFIX + 'favicon/', 'favicon')
|
||||||
routes.static(config.URL_PREFIX + 'download/', config.DOWNLOAD_DIR)
|
routes.static(config.URL_PREFIX + 'download/', config.DOWNLOAD_DIR)
|
||||||
|
routes.static(config.URL_PREFIX + 'audio_download/', config.AUDIO_DOWNLOAD_DIR)
|
||||||
routes.static(config.URL_PREFIX, 'ui/dist/metube')
|
routes.static(config.URL_PREFIX, 'ui/dist/metube')
|
||||||
try:
|
try:
|
||||||
app.add_routes(routes)
|
app.add_routes(routes)
|
||||||
|
|
|
||||||
10
app/ytdl.py
10
app/ytdl.py
|
|
@ -208,7 +208,7 @@ class DownloadQueue:
|
||||||
**self.config.YTDL_OPTIONS,
|
**self.config.YTDL_OPTIONS,
|
||||||
}).extract_info(url, download=False)
|
}).extract_info(url, download=False)
|
||||||
|
|
||||||
async def __add_entry(self, entry, quality, format, already, folder=None):
|
async def __add_entry(self, entry, quality, format, folder, already):
|
||||||
etype = entry.get('_type') or 'video'
|
etype = entry.get('_type') or 'video'
|
||||||
if etype == 'playlist':
|
if etype == 'playlist':
|
||||||
entries = entry['entries']
|
entries = entry['entries']
|
||||||
|
|
@ -221,7 +221,7 @@ class DownloadQueue:
|
||||||
for property in ("id", "title", "uploader", "uploader_id"):
|
for property in ("id", "title", "uploader", "uploader_id"):
|
||||||
if property in entry:
|
if property in entry:
|
||||||
etr[f"playlist_{property}"] = entry[property]
|
etr[f"playlist_{property}"] = entry[property]
|
||||||
results.append(await self.__add_entry(etr, quality, format, already, folder=folder))
|
results.append(await self.__add_entry(etr, quality, format, folder, already))
|
||||||
if any(res['status'] == 'error' for res in results):
|
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': 'error', 'msg': ', '.join(res['msg'] for res in results if res['status'] == 'error' and 'msg' in res)}
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
@ -252,10 +252,10 @@ class DownloadQueue:
|
||||||
await self.notifier.added(dl)
|
await self.notifier.added(dl)
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
elif etype.startswith('url'):
|
elif etype.startswith('url'):
|
||||||
return await self.add(entry['url'], quality, format, already, folder=folder)
|
return await self.add(entry['url'], quality, format, folder, already)
|
||||||
return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'}
|
return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'}
|
||||||
|
|
||||||
async def add(self, url, quality, format, already=None, folder=None):
|
async def add(self, url, quality, format, folder, already=None):
|
||||||
log.info(f'adding {url}: {quality=} {format=} {already=} {folder=}')
|
log.info(f'adding {url}: {quality=} {format=} {already=} {folder=}')
|
||||||
already = set() if already is None else already
|
already = set() if already is None else already
|
||||||
if url in already:
|
if url in already:
|
||||||
|
|
@ -267,7 +267,7 @@ class DownloadQueue:
|
||||||
entry = await asyncio.get_running_loop().run_in_executor(None, self.__extract_info, url)
|
entry = await asyncio.get_running_loop().run_in_executor(None, self.__extract_info, url)
|
||||||
except yt_dlp.utils.YoutubeDLError as exc:
|
except yt_dlp.utils.YoutubeDLError as exc:
|
||||||
return {'status': 'error', 'msg': str(exc)}
|
return {'status': 'error', 'msg': str(exc)}
|
||||||
return await self.__add_entry(entry, quality, format, already, folder=folder)
|
return await self.__add_entry(entry, quality, format, folder, already)
|
||||||
|
|
||||||
async def cancel(self, ids):
|
async def cancel(self, ids):
|
||||||
for id in ids:
|
for id in ids:
|
||||||
|
|
|
||||||
|
|
@ -129,11 +129,11 @@
|
||||||
<fa-icon *ngIf="download.value.status == 'finished'" [icon]="faCheckCircle" style="color: green;"></fa-icon>
|
<fa-icon *ngIf="download.value.status == 'finished'" [icon]="faCheckCircle" style="color: green;"></fa-icon>
|
||||||
<fa-icon *ngIf="download.value.status == 'error'" [icon]="faTimesCircle" style="color: red;"></fa-icon>
|
<fa-icon *ngIf="download.value.status == 'error'" [icon]="faTimesCircle" style="color: red;"></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
<span ngbTooltip="{{download.value.msg}}"><a *ngIf="!!download.value.filename; else noDownloadLink" href="download/{{download.value.filename | encodeURIComponent}}" target="_blank">{{ download.value.title }}</a></span>
|
<span ngbTooltip="{{download.value.msg}}"><a *ngIf="!!download.value.filename; else noDownloadLink" href="{{buildDownloadLink(download.value)}}" target="_blank">{{ download.value.title }}</a></span>
|
||||||
<ng-template #noDownloadLink>{{ download.value.title }}</ng-template>
|
<ng-template #noDownloadLink>{{ download.value.title }}</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button *ngIf="download.value.status == 'error'" type="button" class="btn btn-link" (click)="retryDownload(download.key, download.value.url, download.value.quality)"><fa-icon [icon]="faRedoAlt"></fa-icon></button>
|
<button *ngIf="download.value.status == 'error'" type="button" class="btn btn-link" (click)="retryDownload(download.key, download.value.url, download.value.quality, download.value.folder)"><fa-icon [icon]="faRedoAlt"></fa-icon></button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{download.value.url}}" target="_blank"><fa-icon [icon]="faExternalLinkAlt"></fa-icon></a>
|
<a href="{{download.value.url}}" target="_blank"><fa-icon [icon]="faExternalLinkAlt"></fa-icon></a>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { faRedoAlt, faSun, faMoon, faExternalLinkAlt } from '@fortawesome/free-s
|
||||||
import { CookieService } from 'ngx-cookie-service';
|
import { CookieService } from 'ngx-cookie-service';
|
||||||
import { map, Observable, of } from 'rxjs';
|
import { map, Observable, of } from 'rxjs';
|
||||||
|
|
||||||
import { DownloadsService, Status } from './downloads.service';
|
import { Download, DownloadsService, Status } from './downloads.service';
|
||||||
import { MasterCheckboxComponent } from './master-checkbox.component';
|
import { MasterCheckboxComponent } from './master-checkbox.component';
|
||||||
import { Formats, Format, Quality } from './formats';
|
import { Formats, Format, Quality } from './formats';
|
||||||
|
|
||||||
|
|
@ -89,15 +89,19 @@ export class AppComponent implements AfterViewInit {
|
||||||
return this.downloads.configuration['CREATE_DIRS'] == 'true';
|
return this.downloads.configuration['CREATE_DIRS'] == 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAudioType() {
|
||||||
|
return this.quality == 'audio' || this.format == 'mp3';
|
||||||
|
}
|
||||||
|
|
||||||
getMatchingCustomDir() : Observable<string[]> {
|
getMatchingCustomDir() : Observable<string[]> {
|
||||||
return this.downloads.customDirsChanged.asObservable().pipe(map((output) => {
|
return this.downloads.customDirsChanged.asObservable().pipe(map((output) => {
|
||||||
// Keep logic consistent with app/ytdl.py
|
// Keep logic consistent with app/ytdl.py
|
||||||
if (this.quality != 'audio' && this.format != 'mp3') {
|
if (this.isAudioType()) {
|
||||||
console.debug("Showing default download directories");
|
|
||||||
return output["download_dir"];
|
|
||||||
} else {
|
|
||||||
console.debug("Showing audio-specific download directories");
|
console.debug("Showing audio-specific download directories");
|
||||||
return output["audio_download_dir"];
|
return output["audio_download_dir"];
|
||||||
|
} else {
|
||||||
|
console.debug("Showing default download directories");
|
||||||
|
return output["download_dir"];
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -146,13 +150,14 @@ export class AppComponent implements AfterViewInit {
|
||||||
this.quality = exists ? this.quality : 'best'
|
this.quality = exists ? this.quality : 'best'
|
||||||
}
|
}
|
||||||
|
|
||||||
addDownload(url?: string, quality?: string, format?: string) {
|
addDownload(url?: string, quality?: string, format?: string, folder?: string) {
|
||||||
url = url ?? this.addUrl
|
url = url ?? this.addUrl
|
||||||
quality = quality ?? this.quality
|
quality = quality ?? this.quality
|
||||||
format = format ?? this.format
|
format = format ?? this.format
|
||||||
|
folder = folder ?? this.folder
|
||||||
|
|
||||||
this.addInProgress = true;
|
this.addInProgress = true;
|
||||||
this.downloads.add(url, quality, format).subscribe((status: Status) => {
|
this.downloads.add(url, quality, format, folder).subscribe((status: Status) => {
|
||||||
if (status.status === 'error') {
|
if (status.status === 'error') {
|
||||||
alert(`Error adding URL: ${status.msg}`);
|
alert(`Error adding URL: ${status.msg}`);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -162,8 +167,8 @@ export class AppComponent implements AfterViewInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
retryDownload(key: string, url: string, quality: string, format: string) {
|
retryDownload(key: string, url: string, quality: string, format: string, folder: string) {
|
||||||
this.addDownload(url, quality, format);
|
this.addDownload(url, quality, format, folder);
|
||||||
this.downloads.delById('done', [key]).subscribe();
|
this.downloads.delById('done', [key]).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,4 +187,17 @@ export class AppComponent implements AfterViewInit {
|
||||||
clearFailedDownloads() {
|
clearFailedDownloads() {
|
||||||
this.downloads.delByFilter('done', dl => dl.status === 'error').subscribe();
|
this.downloads.delByFilter('done', dl => dl.status === 'error').subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildDownloadLink(download: Download) {
|
||||||
|
let baseDir = 'download/';
|
||||||
|
if (download.quality == 'audio' || download.filename.endsWith('.mp3')) {
|
||||||
|
baseDir = 'audio_download/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (download.folder) {
|
||||||
|
baseDir += download.folder + '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseDir + encodeURIComponent(download.filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,14 @@ export interface Status {
|
||||||
msg?: string;
|
msg?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Download {
|
export interface Download {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
url: string,
|
url: string,
|
||||||
status: string;
|
status: string;
|
||||||
msg: string;
|
msg: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
|
folder: string;
|
||||||
quality: string;
|
quality: string;
|
||||||
percent: number;
|
percent: number;
|
||||||
speed: number;
|
speed: number;
|
||||||
|
|
@ -96,8 +97,8 @@ export class DownloadsService {
|
||||||
return of({status: 'error', msg: msg})
|
return of({status: 'error', msg: msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(url: string, quality: string, format: string) {
|
public add(url: string, quality: string, format: string, folder: string) {
|
||||||
return this.http.post<Status>('add', {url: url, quality: quality, format: format}).pipe(
|
return this.http.post<Status>('add', {url: url, quality: quality, format: format, folder: folder}).pipe(
|
||||||
catchError(this.handleHTTPError)
|
catchError(this.handleHTTPError)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue