From e28458a74f28fa9eeed374cc851497deaf90b88d Mon Sep 17 00:00:00 2001 From: James Woglom Date: Mon, 29 Aug 2022 18:25:29 -0400 Subject: Backend: support "folder" POST param and add config options --- app/main.py | 7 +++++-- app/ytdl.py | 33 +++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/main.py b/app/main.py index 9fbdf19..ecc5040 100644 --- a/app/main.py +++ b/app/main.py @@ -16,11 +16,13 @@ class Config: _DEFAULTS = { 'DOWNLOAD_DIR': '.', 'AUDIO_DOWNLOAD_DIR': '%%DOWNLOAD_DIR', + 'CUSTOM_DIR': 'true', + 'AUTO_CREATE_CUSTOM_DIR': 'false', 'STATE_DIR': '.', 'URL_PREFIX': '', 'OUTPUT_TEMPLATE': '%(title)s.%(ext)s', 'OUTPUT_TEMPLATE_CHAPTER': '%(title)s - %(section_number)s %(section_title)s.%(ext)s', - 'YTDL_OPTIONS': '{}', + 'YTDL_OPTIONS': '{}' } def __init__(self): @@ -80,7 +82,8 @@ async def add(request): if not url or not quality: raise web.HTTPBadRequest() format = post.get('format') - status = await dqueue.add(url, quality, format) + folder = post.get('folder') + status = await dqueue.add(url, quality, format, folder) return web.Response(text=serializer.encode(status)) @routes.post(config.URL_PREFIX + 'delete') diff --git a/app/ytdl.py b/app/ytdl.py index 21b82da..29fa4bd 100644 --- a/app/ytdl.py +++ b/app/ytdl.py @@ -27,10 +27,11 @@ class DownloadQueueNotifier: raise NotImplementedError class DownloadInfo: - def __init__(self, id, title, url, quality, format): + def __init__(self, id, title, url, quality, format, folder): self.id, self.title, self.url = id, title, url self.quality = quality self.format = format + self.folder = folder self.status = self.msg = self.percent = self.speed = self.eta = None self.filename = None self.timestamp = time.time_ns() @@ -192,7 +193,7 @@ class DownloadQueue: async def __import_queue(self): for k, v in self.queue.saved_items(): - await self.add(v.url, v.quality, v.format) + await self.add(v.url, v.quality, v.format, folder=v.folder) async def initialize(self): self.event = asyncio.Event() @@ -207,7 +208,7 @@ class DownloadQueue: **self.config.YTDL_OPTIONS, }).extract_info(url, download=False) - async def __add_entry(self, entry, quality, format, already): + async def __add_entry(self, entry, quality, format, already, folder=None): etype = entry.get('_type') or 'video' if etype == 'playlist': entries = entry['entries'] @@ -220,14 +221,26 @@ class DownloadQueue: for property in ("id", "title", "uploader", "uploader_id"): if property in entry: etr[f"playlist_{property}"] = entry[property] - results.append(await self.__add_entry(etr, quality, format, already)) + results.append(await self.__add_entry(etr, quality, format, already, folder=folder)) 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': 'ok'} elif etype == 'video' or etype.startswith('url') and 'id' in entry and 'title' in entry: if not self.queue.exists(entry['id']): - dl = DownloadInfo(entry['id'], entry['title'], entry.get('webpage_url') or entry['url'], quality, format) - dldirectory = self.config.DOWNLOAD_DIR if (quality != 'audio' and format != 'mp3') else self.config.AUDIO_DOWNLOAD_DIR + dl = DownloadInfo(entry['id'], entry['title'], entry.get('webpage_url') or entry['url'], quality, format, folder) + base_directory = self.config.DOWNLOAD_DIR if (quality != 'audio' and format != 'mp3') else self.config.AUDIO_DOWNLOAD_DIR + if folder: + if self.config.CUSTOM_DIR != 'true': + return {'status': 'error', 'msg': f'A folder for the download was specified but CUSTOM_DIR is not true in the configuration.'} + dldirectory = os.path.realpath(os.path.join(base_directory, folder)) + if not dldirectory.startswith(base_directory): + return {'status': 'error', 'msg': f'Folder "{folder}" must resolve inside the base download directory "{base_directory}"'} + if not os.path.isdir(dldirectory): + if self.config.AUTO_CREATE_CUSTOM_DIR != 'true': + return {'status': 'error', 'msg': f'Folder "{folder}" for download does not exist, and AUTO_CREATE_CUSTOM_DIR is not true in the configuration.'} + os.makedirs(dldirectory, exist_ok=True) + else: + dldirectory = base_directory output = self.config.OUTPUT_TEMPLATE output_chapter = self.config.OUTPUT_TEMPLATE_CHAPTER for property, value in entry.items(): @@ -238,11 +251,11 @@ class DownloadQueue: await self.notifier.added(dl) return {'status': 'ok'} elif etype.startswith('url'): - return await self.add(entry['url'], quality, format, already) + return await self.add(entry['url'], quality, format, already, folder=folder) return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'} - async def add(self, url, quality, format, already=None): - log.info(f'adding {url}') + async def add(self, url, quality, format, already=None, folder=None): + log.info(f'adding {url}: {quality=} {format=} {already=} {folder=}') already = set() if already is None else already if url in already: log.info('recursion detected, skipping') @@ -253,7 +266,7 @@ class DownloadQueue: entry = await asyncio.get_running_loop().run_in_executor(None, self.__extract_info, url) except yt_dlp.utils.YoutubeDLError as exc: return {'status': 'error', 'msg': str(exc)} - return await self.__add_entry(entry, quality, format, already) + return await self.__add_entry(entry, quality, format, already, folder=folder) async def cancel(self, ids): for id in ids: -- cgit