Allow same URL downloads for different output types
This commit is contained in:
parent
85b86cb47f
commit
b0fa114663
1 changed files with 71 additions and 38 deletions
109
app/ytdl.py
109
app/ytdl.py
|
|
@ -666,14 +666,25 @@ class PersistentQueue:
|
|||
self.dict[k] = Download(None, None, None, None, getattr(v, 'quality', 'best'), getattr(v, 'format', 'any'), {}, v)
|
||||
|
||||
def exists(self, key):
|
||||
return key in self.dict
|
||||
return self._resolve_key(key) is not None
|
||||
|
||||
def get(self, key):
|
||||
return self.dict[key]
|
||||
resolved = self._resolve_key(key)
|
||||
if resolved is None:
|
||||
raise KeyError(key)
|
||||
return self.dict[resolved]
|
||||
|
||||
def items(self):
|
||||
return self.dict.items()
|
||||
|
||||
def _resolve_key(self, key):
|
||||
if key in self.dict:
|
||||
return key
|
||||
for existing_key, download in self.dict.items():
|
||||
if getattr(download.info, "url", None) == key:
|
||||
return existing_key
|
||||
return None
|
||||
|
||||
def saved_items(self):
|
||||
items = [
|
||||
(item["key"], _download_info_from_record(item["info"]))
|
||||
|
|
@ -739,7 +750,7 @@ class PersistentQueue:
|
|||
return items
|
||||
|
||||
def put(self, value):
|
||||
key = value.info.url
|
||||
key = getattr(value.info, "key", value.info.url)
|
||||
old = self.dict.get(key)
|
||||
self.dict[key] = value
|
||||
try:
|
||||
|
|
@ -752,13 +763,14 @@ class PersistentQueue:
|
|||
raise
|
||||
|
||||
def delete(self, key):
|
||||
if key in self.dict:
|
||||
old = self.dict[key]
|
||||
del self.dict[key]
|
||||
resolved = self._resolve_key(key)
|
||||
if resolved is not None:
|
||||
old = self.dict[resolved]
|
||||
del self.dict[resolved]
|
||||
try:
|
||||
self._save_dict()
|
||||
except Exception:
|
||||
self.dict[key] = old
|
||||
self.dict[resolved] = old
|
||||
raise
|
||||
|
||||
def next(self):
|
||||
|
|
@ -781,6 +793,25 @@ class DownloadQueue:
|
|||
self._add_generation = 0
|
||||
self._canceled_urls = set() # URLs canceled during current playlist add
|
||||
|
||||
@staticmethod
|
||||
def _download_key(dl: DownloadInfo) -> str:
|
||||
return "|".join([
|
||||
dl.url,
|
||||
dl.download_type,
|
||||
dl.codec,
|
||||
dl.format,
|
||||
dl.quality,
|
||||
dl.folder or "",
|
||||
dl.custom_name_prefix or "",
|
||||
str(dl.playlist_item_limit or 0),
|
||||
"1" if dl.split_by_chapters else "0",
|
||||
dl.chapter_template or "",
|
||||
dl.subtitle_language or "",
|
||||
dl.subtitle_mode or "",
|
||||
str(dl.clip_start if dl.clip_start is not None else ""),
|
||||
str(dl.clip_end if dl.clip_end is not None else ""),
|
||||
])
|
||||
|
||||
def cancel_add(self):
|
||||
self._add_generation += 1
|
||||
log.info('Playlist add operation canceled by user')
|
||||
|
|
@ -818,10 +849,11 @@ class DownloadQueue:
|
|||
pass
|
||||
download.info.status = 'error'
|
||||
download.close()
|
||||
if self.queue.exists(download.info.url):
|
||||
self.queue.delete(download.info.url)
|
||||
key = getattr(download.info, 'key', download.info.url)
|
||||
if self.queue.exists(key):
|
||||
self.queue.delete(key)
|
||||
if download.canceled:
|
||||
asyncio.create_task(self.notifier.canceled(download.info.url))
|
||||
asyncio.create_task(self.notifier.canceled(key))
|
||||
else:
|
||||
self.done.put(download)
|
||||
asyncio.create_task(self.notifier.completed(download.info))
|
||||
|
|
@ -831,14 +863,14 @@ class DownloadQueue:
|
|||
log.error(f'CLEAR_COMPLETED_AFTER is set to an invalid value "{self.config.CLEAR_COMPLETED_AFTER}", expected an integer number of seconds')
|
||||
clear_after = 0
|
||||
if clear_after > 0:
|
||||
task = asyncio.create_task(self.__auto_clear_after_delay(download.info.url, clear_after))
|
||||
task = asyncio.create_task(self.__auto_clear_after_delay(key, clear_after))
|
||||
task.add_done_callback(lambda t: log.error(f'Auto-clear task failed: {t.exception()}') if not t.cancelled() and t.exception() else None)
|
||||
|
||||
async def __auto_clear_after_delay(self, url, delay_seconds):
|
||||
async def __auto_clear_after_delay(self, key, delay_seconds):
|
||||
await asyncio.sleep(delay_seconds)
|
||||
if self.done.exists(url):
|
||||
log.debug(f'Auto-clearing completed download: {url}')
|
||||
await self.clear([url])
|
||||
if self.done.exists(key):
|
||||
log.debug(f'Auto-clearing completed download: {key}')
|
||||
await self.clear([key])
|
||||
|
||||
def _build_ytdl_options(self, ytdl_options_presets=None, ytdl_options_overrides=None):
|
||||
"""Merge global options, presets (in order), and per-download overrides."""
|
||||
|
|
@ -1037,29 +1069,30 @@ class DownloadQueue:
|
|||
if key in self._canceled_urls:
|
||||
log.info(f'Skipping canceled URL: {entry.get("title") or key}')
|
||||
return {'status': 'ok'}
|
||||
if not self.queue.exists(key):
|
||||
dl = DownloadInfo(
|
||||
id=entry['id'],
|
||||
title=entry.get('title') or entry['id'],
|
||||
url=key,
|
||||
quality=quality,
|
||||
download_type=download_type,
|
||||
codec=codec,
|
||||
format=format,
|
||||
folder=folder,
|
||||
custom_name_prefix=custom_name_prefix,
|
||||
error=error,
|
||||
entry=entry,
|
||||
playlist_item_limit=playlist_item_limit,
|
||||
split_by_chapters=split_by_chapters,
|
||||
chapter_template=chapter_template,
|
||||
subtitle_language=subtitle_language,
|
||||
subtitle_mode=subtitle_mode,
|
||||
ytdl_options_presets=ytdl_options_presets,
|
||||
ytdl_options_overrides=ytdl_options_overrides,
|
||||
clip_start=clip_start,
|
||||
clip_end=clip_end,
|
||||
)
|
||||
dl = DownloadInfo(
|
||||
id=entry['id'],
|
||||
title=entry.get('title') or entry['id'],
|
||||
url=key,
|
||||
quality=quality,
|
||||
download_type=download_type,
|
||||
codec=codec,
|
||||
format=format,
|
||||
folder=folder,
|
||||
custom_name_prefix=custom_name_prefix,
|
||||
error=error,
|
||||
entry=entry,
|
||||
playlist_item_limit=playlist_item_limit,
|
||||
split_by_chapters=split_by_chapters,
|
||||
chapter_template=chapter_template,
|
||||
subtitle_language=subtitle_language,
|
||||
subtitle_mode=subtitle_mode,
|
||||
ytdl_options_presets=ytdl_options_presets,
|
||||
ytdl_options_overrides=ytdl_options_overrides,
|
||||
clip_start=clip_start,
|
||||
clip_end=clip_end,
|
||||
)
|
||||
dl.key = self._download_key(dl)
|
||||
if not self.queue.exists(dl.key):
|
||||
await self.__add_download(dl, auto_start)
|
||||
return {'status': 'ok'}
|
||||
return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'}
|
||||
|
|
|
|||
Loading…
Reference in a new issue