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)
|
self.dict[k] = Download(None, None, None, None, getattr(v, 'quality', 'best'), getattr(v, 'format', 'any'), {}, v)
|
||||||
|
|
||||||
def exists(self, key):
|
def exists(self, key):
|
||||||
return key in self.dict
|
return self._resolve_key(key) is not None
|
||||||
|
|
||||||
def get(self, key):
|
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):
|
def items(self):
|
||||||
return self.dict.items()
|
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):
|
def saved_items(self):
|
||||||
items = [
|
items = [
|
||||||
(item["key"], _download_info_from_record(item["info"]))
|
(item["key"], _download_info_from_record(item["info"]))
|
||||||
|
|
@ -739,7 +750,7 @@ class PersistentQueue:
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def put(self, value):
|
def put(self, value):
|
||||||
key = value.info.url
|
key = getattr(value.info, "key", value.info.url)
|
||||||
old = self.dict.get(key)
|
old = self.dict.get(key)
|
||||||
self.dict[key] = value
|
self.dict[key] = value
|
||||||
try:
|
try:
|
||||||
|
|
@ -752,13 +763,14 @@ class PersistentQueue:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def delete(self, key):
|
def delete(self, key):
|
||||||
if key in self.dict:
|
resolved = self._resolve_key(key)
|
||||||
old = self.dict[key]
|
if resolved is not None:
|
||||||
del self.dict[key]
|
old = self.dict[resolved]
|
||||||
|
del self.dict[resolved]
|
||||||
try:
|
try:
|
||||||
self._save_dict()
|
self._save_dict()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.dict[key] = old
|
self.dict[resolved] = old
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
|
|
@ -781,6 +793,25 @@ class DownloadQueue:
|
||||||
self._add_generation = 0
|
self._add_generation = 0
|
||||||
self._canceled_urls = set() # URLs canceled during current playlist add
|
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):
|
def cancel_add(self):
|
||||||
self._add_generation += 1
|
self._add_generation += 1
|
||||||
log.info('Playlist add operation canceled by user')
|
log.info('Playlist add operation canceled by user')
|
||||||
|
|
@ -818,10 +849,11 @@ class DownloadQueue:
|
||||||
pass
|
pass
|
||||||
download.info.status = 'error'
|
download.info.status = 'error'
|
||||||
download.close()
|
download.close()
|
||||||
if self.queue.exists(download.info.url):
|
key = getattr(download.info, 'key', download.info.url)
|
||||||
self.queue.delete(download.info.url)
|
if self.queue.exists(key):
|
||||||
|
self.queue.delete(key)
|
||||||
if download.canceled:
|
if download.canceled:
|
||||||
asyncio.create_task(self.notifier.canceled(download.info.url))
|
asyncio.create_task(self.notifier.canceled(key))
|
||||||
else:
|
else:
|
||||||
self.done.put(download)
|
self.done.put(download)
|
||||||
asyncio.create_task(self.notifier.completed(download.info))
|
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')
|
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
|
clear_after = 0
|
||||||
if 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)
|
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)
|
await asyncio.sleep(delay_seconds)
|
||||||
if self.done.exists(url):
|
if self.done.exists(key):
|
||||||
log.debug(f'Auto-clearing completed download: {url}')
|
log.debug(f'Auto-clearing completed download: {key}')
|
||||||
await self.clear([url])
|
await self.clear([key])
|
||||||
|
|
||||||
def _build_ytdl_options(self, ytdl_options_presets=None, ytdl_options_overrides=None):
|
def _build_ytdl_options(self, ytdl_options_presets=None, ytdl_options_overrides=None):
|
||||||
"""Merge global options, presets (in order), and per-download overrides."""
|
"""Merge global options, presets (in order), and per-download overrides."""
|
||||||
|
|
@ -1037,29 +1069,30 @@ class DownloadQueue:
|
||||||
if key in self._canceled_urls:
|
if key in self._canceled_urls:
|
||||||
log.info(f'Skipping canceled URL: {entry.get("title") or key}')
|
log.info(f'Skipping canceled URL: {entry.get("title") or key}')
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
if not self.queue.exists(key):
|
dl = DownloadInfo(
|
||||||
dl = DownloadInfo(
|
id=entry['id'],
|
||||||
id=entry['id'],
|
title=entry.get('title') or entry['id'],
|
||||||
title=entry.get('title') or entry['id'],
|
url=key,
|
||||||
url=key,
|
quality=quality,
|
||||||
quality=quality,
|
download_type=download_type,
|
||||||
download_type=download_type,
|
codec=codec,
|
||||||
codec=codec,
|
format=format,
|
||||||
format=format,
|
folder=folder,
|
||||||
folder=folder,
|
custom_name_prefix=custom_name_prefix,
|
||||||
custom_name_prefix=custom_name_prefix,
|
error=error,
|
||||||
error=error,
|
entry=entry,
|
||||||
entry=entry,
|
playlist_item_limit=playlist_item_limit,
|
||||||
playlist_item_limit=playlist_item_limit,
|
split_by_chapters=split_by_chapters,
|
||||||
split_by_chapters=split_by_chapters,
|
chapter_template=chapter_template,
|
||||||
chapter_template=chapter_template,
|
subtitle_language=subtitle_language,
|
||||||
subtitle_language=subtitle_language,
|
subtitle_mode=subtitle_mode,
|
||||||
subtitle_mode=subtitle_mode,
|
ytdl_options_presets=ytdl_options_presets,
|
||||||
ytdl_options_presets=ytdl_options_presets,
|
ytdl_options_overrides=ytdl_options_overrides,
|
||||||
ytdl_options_overrides=ytdl_options_overrides,
|
clip_start=clip_start,
|
||||||
clip_start=clip_start,
|
clip_end=clip_end,
|
||||||
clip_end=clip_end,
|
)
|
||||||
)
|
dl.key = self._download_key(dl)
|
||||||
|
if not self.queue.exists(dl.key):
|
||||||
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}"'}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue