Merge pull request #750 from xerdream/feat/multiple-video-formats
Add "Format" column to frontend interface to display downloaded audio/video formats
This commit is contained in:
commit
f83dd6a951
3 changed files with 69 additions and 23 deletions
70
app/ytdl.py
70
app/ytdl.py
|
|
@ -33,6 +33,7 @@ class DownloadQueueNotifier:
|
|||
class DownloadInfo:
|
||||
def __init__(self, id, title, url, quality, format, folder, custom_name_prefix, error):
|
||||
self.id = id if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{id}'
|
||||
self.id = f'{self.id}.{format}'
|
||||
self.title = title if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{title}'
|
||||
self.url = url
|
||||
self.quality = quality
|
||||
|
|
@ -51,8 +52,8 @@ class Download:
|
|||
def __init__(self, download_dir, temp_dir, output_template, output_template_chapter, quality, format, ytdl_opts, info):
|
||||
self.download_dir = download_dir
|
||||
self.temp_dir = temp_dir
|
||||
self.output_template = output_template
|
||||
self.output_template_chapter = output_template_chapter
|
||||
self.output_template = self._add_format_identifier(format, output_template)
|
||||
self.output_template_chapter = self._add_format_identifier(format, output_template_chapter)
|
||||
self.format = get_format(format, quality)
|
||||
self.ytdl_opts = get_opts(format, quality, ytdl_opts)
|
||||
if "impersonate" in self.ytdl_opts:
|
||||
|
|
@ -139,6 +140,8 @@ class Download:
|
|||
if self.status_queue is not None:
|
||||
self.status_queue.put(None)
|
||||
|
||||
self._delete_format_identifier()
|
||||
|
||||
def running(self):
|
||||
try:
|
||||
return self.proc is not None and self.proc.is_alive()
|
||||
|
|
@ -174,6 +177,49 @@ class Download:
|
|||
self.info.eta = status.get('eta')
|
||||
log.info(f"Updating status for {self.info.title}: {status}")
|
||||
await self.notifier.updated(self.info)
|
||||
|
||||
def _add_format_identifier(self, identifier, template):
|
||||
# Preventing the post-processing of YT-DLP from deleting the intermediate file which was download before.
|
||||
return f'{identifier}_{template}'
|
||||
|
||||
def _delete_format_identifier(self):
|
||||
# Delete the identifier in the file name after the post-processing is complete.
|
||||
if self.canceled or self.info.status != 'finished' or not hasattr(self.info,'filename'):
|
||||
return
|
||||
|
||||
try:
|
||||
filename = re.sub(r'^\w+_', '', self.info.filename)
|
||||
filepath_idt = os.path.join(self.download_dir, self.info.filename)
|
||||
filepath = os.path.join(self.download_dir, filename)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
os.rename(filepath_idt, filepath)
|
||||
log.info(f"Renamed file '{filepath_idt}' to '{filepath}'")
|
||||
except PermissionError as e:
|
||||
log.warning(f"Error deleting old file '{filepath}': {e} ")
|
||||
return
|
||||
except Exception as e:
|
||||
log.warning(f"Error renaming file '{filepath_idt}': {e} ")
|
||||
return
|
||||
|
||||
self.info.filename = filename
|
||||
|
||||
def delete_tmpfile(self):
|
||||
if not self.tmpfilename or not self.download_dir:
|
||||
return
|
||||
if not os.path.isdir(self.download_dir):
|
||||
return
|
||||
|
||||
tmpfilename = os.path.basename(self.tmpfilename)
|
||||
def is_tmpfile(filename):
|
||||
return filename.startswith(tmpfilename)
|
||||
|
||||
try:
|
||||
tmpfiles = filter(is_tmpfile, os.listdir(self.download_dir))
|
||||
for tmpfile in tmpfiles:
|
||||
os.remove(os.path.join(self.download_dir, tmpfile))
|
||||
except Exception as e:
|
||||
log.warning(f"Error deleting temporary files: {e}")
|
||||
|
||||
class PersistentQueue:
|
||||
def __init__(self, path):
|
||||
|
|
@ -203,7 +249,7 @@ class PersistentQueue:
|
|||
return sorted(shelf.items(), key=lambda item: item[1].timestamp)
|
||||
|
||||
def put(self, value):
|
||||
key = value.info.url
|
||||
key = value.info.id
|
||||
self.dict[key] = value
|
||||
with shelve.open(self.path, 'w') as shelf:
|
||||
shelf[key] = value.info
|
||||
|
|
@ -278,17 +324,13 @@ class DownloadQueue:
|
|||
|
||||
def _post_download_cleanup(self, download):
|
||||
if download.info.status != 'finished':
|
||||
if download.tmpfilename and os.path.isfile(download.tmpfilename):
|
||||
try:
|
||||
os.remove(download.tmpfilename)
|
||||
except:
|
||||
pass
|
||||
download.delete_tmpfile()
|
||||
download.info.status = 'error'
|
||||
download.close()
|
||||
if self.queue.exists(download.info.url):
|
||||
self.queue.delete(download.info.url)
|
||||
if self.queue.exists(download.info.id):
|
||||
self.queue.delete(download.info.id)
|
||||
if download.canceled:
|
||||
asyncio.create_task(self.notifier.canceled(download.info.url))
|
||||
asyncio.create_task(self.notifier.canceled(download.info.id))
|
||||
else:
|
||||
self.done.put(download)
|
||||
asyncio.create_task(self.notifier.completed(download.info))
|
||||
|
|
@ -361,9 +403,9 @@ class DownloadQueue:
|
|||
return {'status': 'ok'}
|
||||
elif etype == 'video' or (etype.startswith('url') and 'id' in entry and 'title' in entry):
|
||||
log.debug('Processing as a video')
|
||||
key = entry.get('webpage_url') or entry['url']
|
||||
if not self.queue.exists(key):
|
||||
dl = DownloadInfo(entry['id'], entry.get('title') or entry['id'], key, quality, format, folder, custom_name_prefix, error)
|
||||
url = entry.get('webpage_url') or entry['url']
|
||||
dl = DownloadInfo(entry['id'], entry.get('title') or entry['id'], url, quality, format, folder, custom_name_prefix, error)
|
||||
if not self.queue.exists(dl.id):
|
||||
dldirectory, error_message = self.__calc_download_path(quality, format, folder)
|
||||
if error_message is not None:
|
||||
return error_message
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@
|
|||
<th scope="col" style="width: 1rem;">
|
||||
<app-master-checkbox #queueMasterCheckbox [id]="'queue'" [list]="downloads.queue" (changed)="queueSelectionChanged($event)"></app-master-checkbox>
|
||||
</th>
|
||||
<th scope="col">Video</th>
|
||||
<th scope="col">Media</th>
|
||||
<th scope="col" style="width: 8rem;">Speed</th>
|
||||
<th scope="col" style="width: 7rem;">ETA</th>
|
||||
<th scope="col" style="width: 6rem;"></th>
|
||||
|
|
@ -336,7 +336,8 @@
|
|||
<th scope="col" style="width: 1rem;">
|
||||
<app-master-checkbox #doneMasterCheckbox [id]="'done'" [list]="downloads.done" (changed)="doneSelectionChanged($event)"></app-master-checkbox>
|
||||
</th>
|
||||
<th scope="col">Video</th>
|
||||
<th scope="col">Media</th>
|
||||
<th scope="col">Format</th>
|
||||
<th scope="col">File Size</th>
|
||||
<th scope="col" style="width: 8rem;"></th>
|
||||
</tr>
|
||||
|
|
@ -358,6 +359,9 @@
|
|||
<span *ngIf="download.value.error"><br>Error: {{download.value.error}}</span>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="download.value.format">{{ download.value.format }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="download.value.size">{{ download.value.size | fileSize }}</span>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -59,21 +59,21 @@ export class DownloadsService {
|
|||
});
|
||||
socket.fromEvent('added').subscribe((strdata: string) => {
|
||||
let data: Download = JSON.parse(strdata);
|
||||
this.queue.set(data.url, data);
|
||||
this.queue.set(data.id, data);
|
||||
this.queueChanged.next(null);
|
||||
});
|
||||
socket.fromEvent('updated').subscribe((strdata: string) => {
|
||||
let data: Download = JSON.parse(strdata);
|
||||
let dl: Download = this.queue.get(data.url);
|
||||
let dl: Download = this.queue.get(data.id);
|
||||
data.checked = dl.checked;
|
||||
data.deleting = dl.deleting;
|
||||
this.queue.set(data.url, data);
|
||||
this.queue.set(data.id, data);
|
||||
this.updated.next(null);
|
||||
});
|
||||
socket.fromEvent('completed').subscribe((strdata: string) => {
|
||||
let data: Download = JSON.parse(strdata);
|
||||
this.queue.delete(data.url);
|
||||
this.done.set(data.url, data);
|
||||
this.queue.delete(data.id);
|
||||
this.done.set(data.id, data);
|
||||
this.queueChanged.next(null);
|
||||
this.doneChanged.next(null);
|
||||
});
|
||||
|
|
@ -127,13 +127,13 @@ export class DownloadsService {
|
|||
|
||||
public startByFilter(where: string, filter: (dl: Download) => boolean) {
|
||||
let ids: string[] = [];
|
||||
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.url) });
|
||||
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.id) });
|
||||
return this.startById(ids);
|
||||
}
|
||||
|
||||
public delByFilter(where: string, filter: (dl: Download) => boolean) {
|
||||
let ids: string[] = [];
|
||||
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.url) });
|
||||
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.id) });
|
||||
return this.delById(where, ids);
|
||||
}
|
||||
public addDownloadByUrl(url: string): Promise<any> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue