Skip to content

Commit f83dd6a

Browse files
authored
Merge pull request #750 from xerdream/feat/multiple-video-formats
Add "Format" column to frontend interface to display downloaded audio/video formats
2 parents fae3663 + 93f973d commit f83dd6a

File tree

3 files changed

+69
-23
lines changed

3 files changed

+69
-23
lines changed

app/ytdl.py

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ async def cleared(self, id):
3333
class DownloadInfo:
3434
def __init__(self, id, title, url, quality, format, folder, custom_name_prefix, error):
3535
self.id = id if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{id}'
36+
self.id = f'{self.id}.{format}'
3637
self.title = title if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{title}'
3738
self.url = url
3839
self.quality = quality
@@ -51,8 +52,8 @@ class Download:
5152
def __init__(self, download_dir, temp_dir, output_template, output_template_chapter, quality, format, ytdl_opts, info):
5253
self.download_dir = download_dir
5354
self.temp_dir = temp_dir
54-
self.output_template = output_template
55-
self.output_template_chapter = output_template_chapter
55+
self.output_template = self._add_format_identifier(format, output_template)
56+
self.output_template_chapter = self._add_format_identifier(format, output_template_chapter)
5657
self.format = get_format(format, quality)
5758
self.ytdl_opts = get_opts(format, quality, ytdl_opts)
5859
if "impersonate" in self.ytdl_opts:
@@ -139,6 +140,8 @@ def close(self):
139140
if self.status_queue is not None:
140141
self.status_queue.put(None)
141142

143+
self._delete_format_identifier()
144+
142145
def running(self):
143146
try:
144147
return self.proc is not None and self.proc.is_alive()
@@ -174,6 +177,49 @@ async def update_status(self):
174177
self.info.eta = status.get('eta')
175178
log.info(f"Updating status for {self.info.title}: {status}")
176179
await self.notifier.updated(self.info)
180+
181+
def _add_format_identifier(self, identifier, template):
182+
# Preventing the post-processing of YT-DLP from deleting the intermediate file which was download before.
183+
return f'{identifier}_{template}'
184+
185+
def _delete_format_identifier(self):
186+
# Delete the identifier in the file name after the post-processing is complete.
187+
if self.canceled or self.info.status != 'finished' or not hasattr(self.info,'filename'):
188+
return
189+
190+
try:
191+
filename = re.sub(r'^\w+_', '', self.info.filename)
192+
filepath_idt = os.path.join(self.download_dir, self.info.filename)
193+
filepath = os.path.join(self.download_dir, filename)
194+
if os.path.exists(filepath):
195+
os.remove(filepath)
196+
os.rename(filepath_idt, filepath)
197+
log.info(f"Renamed file '{filepath_idt}' to '{filepath}'")
198+
except PermissionError as e:
199+
log.warning(f"Error deleting old file '{filepath}': {e} ")
200+
return
201+
except Exception as e:
202+
log.warning(f"Error renaming file '{filepath_idt}': {e} ")
203+
return
204+
205+
self.info.filename = filename
206+
207+
def delete_tmpfile(self):
208+
if not self.tmpfilename or not self.download_dir:
209+
return
210+
if not os.path.isdir(self.download_dir):
211+
return
212+
213+
tmpfilename = os.path.basename(self.tmpfilename)
214+
def is_tmpfile(filename):
215+
return filename.startswith(tmpfilename)
216+
217+
try:
218+
tmpfiles = filter(is_tmpfile, os.listdir(self.download_dir))
219+
for tmpfile in tmpfiles:
220+
os.remove(os.path.join(self.download_dir, tmpfile))
221+
except Exception as e:
222+
log.warning(f"Error deleting temporary files: {e}")
177223

178224
class PersistentQueue:
179225
def __init__(self, path):
@@ -203,7 +249,7 @@ def saved_items(self):
203249
return sorted(shelf.items(), key=lambda item: item[1].timestamp)
204250

205251
def put(self, value):
206-
key = value.info.url
252+
key = value.info.id
207253
self.dict[key] = value
208254
with shelve.open(self.path, 'w') as shelf:
209255
shelf[key] = value.info
@@ -278,17 +324,13 @@ async def _run_download(self, download):
278324

279325
def _post_download_cleanup(self, download):
280326
if download.info.status != 'finished':
281-
if download.tmpfilename and os.path.isfile(download.tmpfilename):
282-
try:
283-
os.remove(download.tmpfilename)
284-
except:
285-
pass
327+
download.delete_tmpfile()
286328
download.info.status = 'error'
287329
download.close()
288-
if self.queue.exists(download.info.url):
289-
self.queue.delete(download.info.url)
330+
if self.queue.exists(download.info.id):
331+
self.queue.delete(download.info.id)
290332
if download.canceled:
291-
asyncio.create_task(self.notifier.canceled(download.info.url))
333+
asyncio.create_task(self.notifier.canceled(download.info.id))
292334
else:
293335
self.done.put(download)
294336
asyncio.create_task(self.notifier.completed(download.info))
@@ -361,9 +403,9 @@ async def __add_entry(self, entry, quality, format, folder, custom_name_prefix,
361403
return {'status': 'ok'}
362404
elif etype == 'video' or (etype.startswith('url') and 'id' in entry and 'title' in entry):
363405
log.debug('Processing as a video')
364-
key = entry.get('webpage_url') or entry['url']
365-
if not self.queue.exists(key):
366-
dl = DownloadInfo(entry['id'], entry.get('title') or entry['id'], key, quality, format, folder, custom_name_prefix, error)
406+
url = entry.get('webpage_url') or entry['url']
407+
dl = DownloadInfo(entry['id'], entry.get('title') or entry['id'], url, quality, format, folder, custom_name_prefix, error)
408+
if not self.queue.exists(dl.id):
367409
dldirectory, error_message = self.__calc_download_path(quality, format, folder)
368410
if error_message is not None:
369411
return error_message

ui/src/app/app.component.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ <h5 class="modal-title">Batch Import URLs</h5>
290290
<th scope="col" style="width: 1rem;">
291291
<app-master-checkbox #queueMasterCheckbox [id]="'queue'" [list]="downloads.queue" (changed)="queueSelectionChanged($event)"></app-master-checkbox>
292292
</th>
293-
<th scope="col">Video</th>
293+
<th scope="col">Media</th>
294294
<th scope="col" style="width: 8rem;">Speed</th>
295295
<th scope="col" style="width: 7rem;">ETA</th>
296296
<th scope="col" style="width: 6rem;"></th>
@@ -336,7 +336,8 @@ <h5 class="modal-title">Batch Import URLs</h5>
336336
<th scope="col" style="width: 1rem;">
337337
<app-master-checkbox #doneMasterCheckbox [id]="'done'" [list]="downloads.done" (changed)="doneSelectionChanged($event)"></app-master-checkbox>
338338
</th>
339-
<th scope="col">Video</th>
339+
<th scope="col">Media</th>
340+
<th scope="col">Format</th>
340341
<th scope="col">File Size</th>
341342
<th scope="col" style="width: 8rem;"></th>
342343
</tr>
@@ -358,6 +359,9 @@ <h5 class="modal-title">Batch Import URLs</h5>
358359
<span *ngIf="download.value.error"><br>Error: {{download.value.error}}</span>
359360
</ng-template>
360361
</td>
362+
<td>
363+
<span *ngIf="download.value.format">{{ download.value.format }}</span>
364+
</td>
361365
<td>
362366
<span *ngIf="download.value.size">{{ download.value.size | fileSize }}</span>
363367
</td>

ui/src/app/downloads.service.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,21 @@ export class DownloadsService {
5959
});
6060
socket.fromEvent('added').subscribe((strdata: string) => {
6161
let data: Download = JSON.parse(strdata);
62-
this.queue.set(data.url, data);
62+
this.queue.set(data.id, data);
6363
this.queueChanged.next(null);
6464
});
6565
socket.fromEvent('updated').subscribe((strdata: string) => {
6666
let data: Download = JSON.parse(strdata);
67-
let dl: Download = this.queue.get(data.url);
67+
let dl: Download = this.queue.get(data.id);
6868
data.checked = dl.checked;
6969
data.deleting = dl.deleting;
70-
this.queue.set(data.url, data);
70+
this.queue.set(data.id, data);
7171
this.updated.next(null);
7272
});
7373
socket.fromEvent('completed').subscribe((strdata: string) => {
7474
let data: Download = JSON.parse(strdata);
75-
this.queue.delete(data.url);
76-
this.done.set(data.url, data);
75+
this.queue.delete(data.id);
76+
this.done.set(data.id, data);
7777
this.queueChanged.next(null);
7878
this.doneChanged.next(null);
7979
});
@@ -127,13 +127,13 @@ export class DownloadsService {
127127

128128
public startByFilter(where: string, filter: (dl: Download) => boolean) {
129129
let ids: string[] = [];
130-
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.url) });
130+
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.id) });
131131
return this.startById(ids);
132132
}
133133

134134
public delByFilter(where: string, filter: (dl: Download) => boolean) {
135135
let ids: string[] = [];
136-
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.url) });
136+
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.id) });
137137
return this.delById(where, ids);
138138
}
139139
public addDownloadByUrl(url: string): Promise<any> {

0 commit comments

Comments
 (0)