Add possibility to rename downloaded file #495
This commit is contained in:
parent
3bef839e18
commit
2f2fb57343
5 changed files with 71 additions and 22 deletions
|
|
@ -220,12 +220,15 @@ async def add(request):
|
||||||
format = post.get('format')
|
format = post.get('format')
|
||||||
folder = post.get('folder')
|
folder = post.get('folder')
|
||||||
custom_name_prefix = post.get('custom_name_prefix')
|
custom_name_prefix = post.get('custom_name_prefix')
|
||||||
|
custom_name = post.get('custom_name')
|
||||||
playlist_strict_mode = post.get('playlist_strict_mode')
|
playlist_strict_mode = post.get('playlist_strict_mode')
|
||||||
playlist_item_limit = post.get('playlist_item_limit')
|
playlist_item_limit = post.get('playlist_item_limit')
|
||||||
auto_start = post.get('auto_start')
|
auto_start = post.get('auto_start')
|
||||||
|
|
||||||
if custom_name_prefix is None:
|
if custom_name_prefix is None:
|
||||||
custom_name_prefix = ''
|
custom_name_prefix = ''
|
||||||
|
if custom_name is None:
|
||||||
|
custom_name = ''
|
||||||
if auto_start is None:
|
if auto_start is None:
|
||||||
auto_start = True
|
auto_start = True
|
||||||
if playlist_strict_mode is None:
|
if playlist_strict_mode is None:
|
||||||
|
|
@ -235,7 +238,7 @@ async def add(request):
|
||||||
|
|
||||||
playlist_item_limit = int(playlist_item_limit)
|
playlist_item_limit = int(playlist_item_limit)
|
||||||
|
|
||||||
status = await dqueue.add(url, quality, format, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start)
|
status = await dqueue.add(url, quality, format, folder, custom_name_prefix, custom_name, playlist_strict_mode, playlist_item_limit, auto_start)
|
||||||
return web.Response(text=serializer.encode(status))
|
return web.Response(text=serializer.encode(status))
|
||||||
|
|
||||||
@routes.post(config.URL_PREFIX + 'delete')
|
@routes.post(config.URL_PREFIX + 'delete')
|
||||||
|
|
|
||||||
54
app/ytdl.py
54
app/ytdl.py
|
|
@ -31,14 +31,24 @@ class DownloadQueueNotifier:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
class DownloadInfo:
|
class DownloadInfo:
|
||||||
def __init__(self, id, title, url, quality, format, folder, custom_name_prefix, error, entry, playlist_item_limit):
|
def __init__(self, id, title, url, quality, format, folder, custom_name_prefix, custom_name, error, entry, playlist_item_limit):
|
||||||
self.id = id if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{id}'
|
# Handle custom name logic - if custom_name is provided, use it; otherwise use prefix logic
|
||||||
self.title = title if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{title}'
|
if custom_name and len(custom_name.strip()) > 0:
|
||||||
|
self.id = custom_name
|
||||||
|
self.title = custom_name
|
||||||
|
elif len(custom_name_prefix) > 0:
|
||||||
|
self.id = f'{custom_name_prefix}.{id}'
|
||||||
|
self.title = f'{custom_name_prefix}.{title}'
|
||||||
|
else:
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
|
||||||
self.url = url
|
self.url = url
|
||||||
self.quality = quality
|
self.quality = quality
|
||||||
self.format = format
|
self.format = format
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
self.custom_name_prefix = custom_name_prefix
|
self.custom_name_prefix = custom_name_prefix
|
||||||
|
self.custom_name = custom_name
|
||||||
self.msg = self.percent = self.speed = self.eta = None
|
self.msg = self.percent = self.speed = self.eta = None
|
||||||
self.status = "pending"
|
self.status = "pending"
|
||||||
self.size = None
|
self.size = None
|
||||||
|
|
@ -333,12 +343,31 @@ class DownloadQueue:
|
||||||
dldirectory, error_message = self.__calc_download_path(dl.quality, dl.format, dl.folder)
|
dldirectory, error_message = self.__calc_download_path(dl.quality, dl.format, dl.folder)
|
||||||
if error_message is not None:
|
if error_message is not None:
|
||||||
return error_message
|
return error_message
|
||||||
output = self.config.OUTPUT_TEMPLATE if len(dl.custom_name_prefix) == 0 else f'{dl.custom_name_prefix}.{self.config.OUTPUT_TEMPLATE}'
|
# Handle custom naming in output template
|
||||||
|
if hasattr(dl, 'custom_name') and dl.custom_name and len(dl.custom_name.strip()) > 0:
|
||||||
|
# Create a custom output template using the custom name
|
||||||
|
# Replace %(title)s with the custom name in the template
|
||||||
|
output = self.config.OUTPUT_TEMPLATE.replace('%(title)s', dl.custom_name)
|
||||||
|
elif len(dl.custom_name_prefix) > 0:
|
||||||
|
output = f'{dl.custom_name_prefix}.{self.config.OUTPUT_TEMPLATE}'
|
||||||
|
else:
|
||||||
|
output = self.config.OUTPUT_TEMPLATE
|
||||||
output_chapter = self.config.OUTPUT_TEMPLATE_CHAPTER
|
output_chapter = self.config.OUTPUT_TEMPLATE_CHAPTER
|
||||||
entry = getattr(dl, 'entry', None)
|
entry = getattr(dl, 'entry', None)
|
||||||
if entry is not None and 'playlist' in entry and entry['playlist'] is not None:
|
if entry is not None and 'playlist' in entry and entry['playlist'] is not None:
|
||||||
if len(self.config.OUTPUT_TEMPLATE_PLAYLIST):
|
if len(self.config.OUTPUT_TEMPLATE_PLAYLIST):
|
||||||
output = self.config.OUTPUT_TEMPLATE_PLAYLIST
|
# Start with the playlist template
|
||||||
|
playlist_output = self.config.OUTPUT_TEMPLATE_PLAYLIST
|
||||||
|
|
||||||
|
# Apply custom naming logic to playlist template
|
||||||
|
if hasattr(dl, 'custom_name') and dl.custom_name and len(dl.custom_name.strip()) > 0:
|
||||||
|
# Replace title with custom name
|
||||||
|
output = playlist_output.replace('%(title)s', dl.custom_name)
|
||||||
|
elif len(dl.custom_name_prefix) > 0:
|
||||||
|
# Add prefix to the playlist template
|
||||||
|
output = f'{dl.custom_name_prefix}.{playlist_output}'
|
||||||
|
else:
|
||||||
|
output = playlist_output
|
||||||
for property, value in entry.items():
|
for property, value in entry.items():
|
||||||
if property.startswith("playlist"):
|
if property.startswith("playlist"):
|
||||||
output = output.replace(f"%({property})s", str(value))
|
output = output.replace(f"%({property})s", str(value))
|
||||||
|
|
@ -347,6 +376,7 @@ class DownloadQueue:
|
||||||
if playlist_item_limit > 0:
|
if playlist_item_limit > 0:
|
||||||
log.info(f'playlist limit is set. Processing only first {playlist_item_limit} entries')
|
log.info(f'playlist limit is set. Processing only first {playlist_item_limit} entries')
|
||||||
ytdl_options['playlistend'] = playlist_item_limit
|
ytdl_options['playlistend'] = playlist_item_limit
|
||||||
|
|
||||||
download = Download(dldirectory, self.config.TEMP_DIR, output, output_chapter, dl.quality, dl.format, ytdl_options, dl)
|
download = Download(dldirectory, self.config.TEMP_DIR, output, output_chapter, dl.quality, dl.format, ytdl_options, dl)
|
||||||
if auto_start is True:
|
if auto_start is True:
|
||||||
self.queue.put(download)
|
self.queue.put(download)
|
||||||
|
|
@ -355,7 +385,7 @@ class DownloadQueue:
|
||||||
self.pending.put(download)
|
self.pending.put(download)
|
||||||
await self.notifier.added(dl)
|
await self.notifier.added(dl)
|
||||||
|
|
||||||
async def __add_entry(self, entry, quality, format, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start, already):
|
async def __add_entry(self, entry, quality, format, folder, custom_name_prefix, custom_name, playlist_strict_mode, playlist_item_limit, auto_start, already):
|
||||||
if not entry:
|
if not entry:
|
||||||
return {'status': 'error', 'msg': "Invalid/empty data was given."}
|
return {'status': 'error', 'msg': "Invalid/empty data was given."}
|
||||||
|
|
||||||
|
|
@ -371,7 +401,7 @@ class DownloadQueue:
|
||||||
|
|
||||||
if etype.startswith('url'):
|
if etype.startswith('url'):
|
||||||
log.debug('Processing as an url')
|
log.debug('Processing as an url')
|
||||||
return await self.add(entry['url'], quality, format, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start, already)
|
return await self.add(entry['url'], quality, format, folder, custom_name_prefix, custom_name, playlist_strict_mode, playlist_item_limit, auto_start, already)
|
||||||
elif etype == 'playlist':
|
elif etype == 'playlist':
|
||||||
log.debug('Processing as a playlist')
|
log.debug('Processing as a playlist')
|
||||||
entries = entry['entries']
|
entries = entry['entries']
|
||||||
|
|
@ -388,7 +418,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, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start, already))
|
results.append(await self.__add_entry(etr, quality, format, folder, custom_name_prefix, custom_name, playlist_strict_mode, playlist_item_limit, auto_start, 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'}
|
||||||
|
|
@ -396,13 +426,13 @@ class DownloadQueue:
|
||||||
log.debug('Processing as a video')
|
log.debug('Processing as a video')
|
||||||
key = entry.get('webpage_url') or entry['url']
|
key = entry.get('webpage_url') or entry['url']
|
||||||
if not self.queue.exists(key):
|
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)
|
dl = DownloadInfo(entry['id'], entry.get('title') or entry['id'], key, quality, format, folder, custom_name_prefix, custom_name, error, entry, playlist_item_limit)
|
||||||
await self.__add_download(dl, auto_start)
|
await self.__add_download(dl, auto_start)
|
||||||
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, already=None):
|
async def add(self, url, quality, format, folder, custom_name_prefix, custom_name, playlist_strict_mode, playlist_item_limit, auto_start=True, already=None):
|
||||||
log.info(f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} {playlist_strict_mode=} {playlist_item_limit=} {auto_start=}')
|
log.info(f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} {custom_name=} {playlist_strict_mode=} {playlist_item_limit=} {auto_start=}')
|
||||||
already = set() if already is None else already
|
already = set() if already is None else already
|
||||||
if url in already:
|
if url in already:
|
||||||
log.info('recursion detected, skipping')
|
log.info('recursion detected, skipping')
|
||||||
|
|
@ -413,7 +443,7 @@ class DownloadQueue:
|
||||||
entry = await asyncio.get_running_loop().run_in_executor(None, self.__extract_info, url, playlist_strict_mode)
|
entry = await asyncio.get_running_loop().run_in_executor(None, self.__extract_info, url, playlist_strict_mode)
|
||||||
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, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start, already)
|
return await self.__add_entry(entry, quality, format, folder, custom_name_prefix, custom_name, playlist_strict_mode, playlist_item_limit, auto_start, already)
|
||||||
|
|
||||||
async def start_pending(self, ids):
|
async def start_pending(self, ids):
|
||||||
for id in ids:
|
for id in ids:
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,18 @@
|
||||||
ngbTooltip="Add a prefix to downloaded filenames">
|
ngbTooltip="Add a prefix to downloaded filenames">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Custom Name</span>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Default"
|
||||||
|
name="customName"
|
||||||
|
[(ngModel)]="customName"
|
||||||
|
[disabled]="addInProgress || downloads.loading"
|
||||||
|
ngbTooltip="Rename downloaded file (extension will be added automatically)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text">Items Limit</span>
|
<span class="input-group-text">Items Limit</span>
|
||||||
|
|
@ -193,7 +205,7 @@
|
||||||
ngbTooltip="Maximum number of items to download from a playlist (0 = no limit)">
|
ngbTooltip="Maximum number of items to download from a playlist (0 = no limit)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-6">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input"
|
<input class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export class AppComponent implements AfterViewInit {
|
||||||
format: string;
|
format: string;
|
||||||
folder: string;
|
folder: string;
|
||||||
customNamePrefix: string;
|
customNamePrefix: string;
|
||||||
|
customName: string;
|
||||||
autoStart: boolean;
|
autoStart: boolean;
|
||||||
playlistStrictMode: boolean;
|
playlistStrictMode: boolean;
|
||||||
playlistItemLimit: number;
|
playlistItemLimit: number;
|
||||||
|
|
@ -252,19 +253,20 @@ export class AppComponent implements AfterViewInit {
|
||||||
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) {
|
addDownload(url?: string, quality?: string, format?: string, folder?: string, customNamePrefix?: string, customName?: string, playlistStrictMode?: boolean, playlistItemLimit?: number, autoStart?: boolean) {
|
||||||
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
|
folder = folder ?? this.folder
|
||||||
customNamePrefix = customNamePrefix ?? this.customNamePrefix
|
customNamePrefix = customNamePrefix ?? this.customNamePrefix
|
||||||
|
customName = customName ?? this.customName
|
||||||
playlistStrictMode = playlistStrictMode ?? this.playlistStrictMode
|
playlistStrictMode = playlistStrictMode ?? this.playlistStrictMode
|
||||||
playlistItemLimit = playlistItemLimit ?? this.playlistItemLimit
|
playlistItemLimit = playlistItemLimit ?? this.playlistItemLimit
|
||||||
autoStart = autoStart ?? this.autoStart
|
autoStart = autoStart ?? this.autoStart
|
||||||
|
|
||||||
console.debug('Downloading: url='+url+' quality='+quality+' format='+format+' folder='+folder+' customNamePrefix='+customNamePrefix+' playlistStrictMode='+playlistStrictMode+' playlistItemLimit='+playlistItemLimit+' autoStart='+autoStart);
|
console.debug('Downloading: url='+url+' quality='+quality+' format='+format+' folder='+folder+' customNamePrefix='+customNamePrefix+' customName='+customName+' playlistStrictMode='+playlistStrictMode+' playlistItemLimit='+playlistItemLimit+' autoStart='+autoStart);
|
||||||
this.addInProgress = true;
|
this.addInProgress = true;
|
||||||
this.downloads.add(url, quality, format, folder, customNamePrefix, playlistStrictMode, playlistItemLimit, autoStart).subscribe((status: Status) => {
|
this.downloads.add(url, quality, format, folder, customNamePrefix, customName, playlistStrictMode, playlistItemLimit, autoStart).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 {
|
||||||
|
|
@ -279,7 +281,7 @@ export class AppComponent implements AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
retryDownload(key: string, download: Download) {
|
retryDownload(key: string, download: Download) {
|
||||||
this.addDownload(download.url, download.quality, download.format, download.folder, download.custom_name_prefix, download.playlist_strict_mode, download.playlist_item_limit, true);
|
this.addDownload(download.url, download.quality, download.format, download.folder, download.custom_name_prefix, download.custom_name, download.playlist_strict_mode, download.playlist_item_limit, true);
|
||||||
this.downloads.delById('done', [key]).subscribe();
|
this.downloads.delById('done', [key]).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -397,7 +399,7 @@ export class AppComponent implements AfterViewInit {
|
||||||
const url = urls[index];
|
const url = urls[index];
|
||||||
this.batchImportStatus = `Importing URL ${index + 1} of ${urls.length}: ${url}`;
|
this.batchImportStatus = `Importing URL ${index + 1} of ${urls.length}: ${url}`;
|
||||||
// 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.customName,
|
||||||
this.playlistStrictMode, this.playlistItemLimit, this.autoStart)
|
this.playlistStrictMode, this.playlistItemLimit, this.autoStart)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (status: Status) => {
|
next: (status: Status) => {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export interface Download {
|
||||||
format: string;
|
format: string;
|
||||||
folder: string;
|
folder: string;
|
||||||
custom_name_prefix: string;
|
custom_name_prefix: string;
|
||||||
|
custom_name: string;
|
||||||
playlist_strict_mode: boolean;
|
playlist_strict_mode: boolean;
|
||||||
playlist_item_limit: number;
|
playlist_item_limit: number;
|
||||||
status: string;
|
status: string;
|
||||||
|
|
@ -110,8 +111,8 @@ export class DownloadsService {
|
||||||
return of({status: 'error', msg: msg})
|
return of({status: 'error', msg: msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(url: string, quality: string, format: string, folder: string, customNamePrefix: string, playlistStrictMode: boolean, playlistItemLimit: number, autoStart: boolean) {
|
public add(url: string, quality: string, format: string, folder: string, customNamePrefix: string, customName: string, playlistStrictMode: boolean, playlistItemLimit: number, autoStart: boolean) {
|
||||||
return this.http.post<Status>('add', {url: url, quality: quality, format: format, folder: folder, custom_name_prefix: customNamePrefix, playlist_strict_mode: playlistStrictMode, playlist_item_limit: playlistItemLimit, auto_start: autoStart}).pipe(
|
return this.http.post<Status>('add', {url: url, quality: quality, format: format, folder: folder, custom_name_prefix: customNamePrefix, custom_name: customName, playlist_strict_mode: playlistStrictMode, playlist_item_limit: playlistItemLimit, auto_start: autoStart}).pipe(
|
||||||
catchError(this.handleHTTPError)
|
catchError(this.handleHTTPError)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -141,12 +142,13 @@ export class DownloadsService {
|
||||||
const defaultFormat = 'mp4';
|
const defaultFormat = 'mp4';
|
||||||
const defaultFolder = '';
|
const defaultFolder = '';
|
||||||
const defaultCustomNamePrefix = '';
|
const defaultCustomNamePrefix = '';
|
||||||
|
const defaultCustomName = '';
|
||||||
const defaultPlaylistStrictMode = false;
|
const defaultPlaylistStrictMode = false;
|
||||||
const defaultPlaylistItemLimit = 0;
|
const defaultPlaylistItemLimit = 0;
|
||||||
const defaultAutoStart = true;
|
const defaultAutoStart = true;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.add(url, defaultQuality, defaultFormat, defaultFolder, defaultCustomNamePrefix, defaultPlaylistStrictMode, defaultPlaylistItemLimit, defaultAutoStart)
|
this.add(url, defaultQuality, defaultFormat, defaultFolder, defaultCustomNamePrefix, defaultCustomName, defaultPlaylistStrictMode, defaultPlaylistItemLimit, defaultAutoStart)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => resolve(response),
|
response => resolve(response),
|
||||||
error => reject(error)
|
error => reject(error)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue