From 8d510daeae64206899fcbfdaa3c160b6617930dd Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Sat, 24 May 2025 11:58:27 +0200 Subject: [PATCH] Use yt-dlp binary instead of module. --- .gitignore | 1 + menus/downloader.py | 71 ++++++++++++++++++++++++++++++--------------- pyproject.toml | 1 - utils/utils.py | 46 +++++++++++++++++++---------- uv.lock | 11 ------- 5 files changed, 80 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 81c71f9..581252b 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,4 @@ test*.py logs/ logs settings.json +bin/ diff --git a/menus/downloader.py b/menus/downloader.py index e4d0459..181fc81 100644 --- a/menus/downloader.py +++ b/menus/downloader.py @@ -1,12 +1,11 @@ -from yt_dlp import YoutubeDL from mutagen.easyid3 import EasyID3 from pydub import AudioSegment -import arcade, arcade.gui, os, json, yt_dlp, threading +import arcade, arcade.gui, os, json, threading, subprocess from arcade.gui.experimental.focus import UIFocusGroup -from utils.utils import UIFocusTextureButton, BufferLogger +from utils.utils import UIFocusTextureButton, ensure_yt_dlp from utils.constants import button_style, dropdown_style, yt_dlp_parameters from utils.preload import button_texture, button_hovered_texture @@ -29,9 +28,7 @@ class Downloader(arcade.gui.UIView): self.settings_dict = json.load(file) self.tab_options = self.settings_dict.get("tab_options", ["~/Music", "~/Downloads"]) - - self.yt_dl = YoutubeDL(params=yt_dlp_parameters) - self.yt_dl.params["logger"] = self.yt_dl_logger = BufferLogger() + self.yt_dl_buffer = "" def on_show_view(self): super().on_show_view() @@ -58,15 +55,46 @@ class Downloader(arcade.gui.UIView): self.anchor.detect_focusable_widgets() def on_update(self, delta_time: float) -> bool | None: - self.status_label.text = self.yt_dl_logger.buffer + self.status_label.text = self.yt_dl_buffer - if "WARNING" in self.yt_dl_logger.buffer: + if "WARNING" in self.yt_dl_buffer: self.status_label.update_font(font_color=arcade.color.YELLOW) - elif "ERROR" in self.yt_dl_logger.buffer: + elif "ERROR" in self.yt_dl_buffer: self.status_label.update_font(font_color=arcade.color.RED) else: self.status_label.update_font(font_color=arcade.color.LIGHT_GREEN) + def run_yt_dlp(self, url): + yt_dlp_path = ensure_yt_dlp() + + command = [ + yt_dlp_path, f"{url}", + "--write-info-json", + "-x", "--audio-format", "mp3", + "-o", "downloaded_music.mp3", + "--no-playlist", + "--embed-thumbnail", + "--embed-metadata" + ] + + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + + for line in process.stdout: + self.yt_dl_buffer = line + + process.wait() + + if process.returncode != 0: + return None + + try: + with open("downloaded_music.mp3.info.json", "r") as file: + info = json.load(file) + return info + except (json.JSONDecodeError, OSError): + self.yt_dl_buffer += "\nERROR: Failed to parse yt-dlp JSON output.\n" + return None + def download(self): if not self.tab_selector.value: return @@ -78,17 +106,14 @@ class Downloader(arcade.gui.UIView): path = os.path.expanduser(self.tab_selector.value) - try: - info = self.yt_dl.extract_info(url, download=True) - except yt_dlp.DownloadError as e: - message = "".join(e.msg.strip().split("] ")[1:]) if e.msg else "Unknown yt-dlp error." - self.yt_dl_logger.buffer = f"ERROR: {message}" - return + info = self.run_yt_dlp(url) + + os.remove("downloaded_music.mp3.info.json") + os.remove("downloaded_music.info.json") if info: - entry = info['entries'][0] if 'entries' in info else info - title = entry.get('title', 'Unknown') - uploader = entry.get('uploader', 'Unknown') + title = info.get('title', 'Unknown') + uploader = info.get('uploader', 'Unknown') if " - " in title: artist, track_title = title.split(" - ", 1) @@ -103,7 +128,7 @@ class Downloader(arcade.gui.UIView): audio["title"] = track_title audio.save() except Exception as meta_err: - self.yt_dl_logger.buffer = f"ERROR: Tried to override metadata based on title, but failed: {meta_err}" + self.yt_dl_buffer = f"ERROR: Tried to override metadata based on title, but failed: {meta_err}" return if self.settings_dict.get("normalize_audio", True): @@ -117,20 +142,20 @@ class Downloader(arcade.gui.UIView): audio.export("downloaded_music.mp3", format="mp3") except Exception as e: - self.yt_dl_logger.buffer = f"ERROR: Could not normalize volume due to an error: {e}" + self.yt_dl_buffer = f"ERROR: Could not normalize volume due to an error: {e}" return try: output_filename = os.path.join(path, f"{title}.mp3") os.replace("downloaded_music.mp3", output_filename) except Exception as e: - self.yt_dl_logger.buffer = f"ERROR: Could not move file due to an error: {e}" + self.yt_dl_buffer = f"ERROR: Could not move file due to an error: {e}" return else: - self.yt_dl_logger.buffer = f"ERROR: Info unavailable. This maybe due to being unable to download it due to DRM or other issues" + self.yt_dl_buffer = f"ERROR: Info unavailable. This maybe due to being unable to download it due to DRM or other issues" return - self.yt_dl_logger.buffer = f"Successfully downloaded {title} to {path}" + self.yt_dl_buffer = f"Successfully downloaded {title} to {path}" def main_exit(self): from menus.main import Main diff --git a/pyproject.toml b/pyproject.toml index e80080d..6ea4653 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,5 +10,4 @@ dependencies = [ "pydub>=0.25.1", "pypresence>=4.3.0", "thefuzz>=0.22.1", - "yt-dlp>=2025.4.30", ] diff --git a/utils/utils.py b/utils/utils.py index 28512da..8248502 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -1,5 +1,4 @@ -import logging, arcade, arcade.gui, sys, traceback, os, re - +import logging, arcade, arcade.gui, sys, traceback, os, re, platform, urllib.request, stat from mutagen.easyid3 import EasyID3 from utils.constants import menu_background_color @@ -98,24 +97,41 @@ class UIFocusTextureButton(arcade.gui.UITextureButton): else: self.resize(width=self.width / 1.1, height=self.height / 1.1) -class BufferLogger: - def __init__(self): - self.buffer = "No errors." +def get_yt_dlp_binary_path(): + binary = "yt-dlp" + system = platform.system() - def debug(self, msg): - self._log(msg) + if system == "Windows": + binary += ".exe" + elif system == "Darwin": + binary += "_macos" + elif system == "Linux": + binary += "_linux" - def info(self, msg): - self._log(msg) + return os.path.join("bin", binary) - def warning(self, msg): - self._log(f"WARNING: {msg}") +def ensure_yt_dlp(): + path = get_yt_dlp_binary_path() - def error(self, msg): - self._log(f"ERROR: {msg}") + if not os.path.exists("bin"): + os.makedirs("bin") - def _log(self, msg): - self.buffer = msg + if not os.path.exists(path): + system = platform.system() + + if system == "Windows": + url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe" + elif system == "Darwin": + url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos" + elif system == "Linux": + url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux" + else: + raise RuntimeError("Unsupported OS") + + urllib.request.urlretrieve(url, path) + os.chmod(path, 0o755) + + return path def truncate_end(text: str, max_length: int) -> str: if len(text) <= max_length: diff --git a/uv.lock b/uv.lock index c2bd518..d1ef07d 100644 --- a/uv.lock +++ b/uv.lock @@ -81,7 +81,6 @@ dependencies = [ { name = "pydub" }, { name = "pypresence" }, { name = "thefuzz" }, - { name = "yt-dlp" }, ] [package.metadata] @@ -91,7 +90,6 @@ requires-dist = [ { name = "pydub", specifier = ">=0.25.1" }, { name = "pypresence", specifier = ">=4.3.0" }, { name = "thefuzz", specifier = ">=0.22.1" }, - { name = "yt-dlp", specifier = ">=2025.4.30" }, ] [[package]] @@ -315,12 +313,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e3549295 wheels = [ { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload_time = "2025-04-10T14:19:03.967Z" }, ] - -[[package]] -name = "yt-dlp" -version = "2025.4.30" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/ca/1d1a33dec2107463f59bc4b448fcf43718d86a36b6150e8a0cfd1a96a893/yt_dlp-2025.4.30.tar.gz", hash = "sha256:d01367d0c3ae94e35cb1e2eccb7a7c70e181c4ca448f4ee2374f26489d263603", size = 2981094, upload_time = "2025-04-30T23:31:17.679Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/18/bbccb18661a853c3db947152439229b9bf686fe0ab2cc7848ab3a24f2ad2/yt_dlp-2025.4.30-py3-none-any.whl", hash = "sha256:53cd82bf13f12a1fe9c564b0004e001156b153c9247fa3cef14d1400ab359150", size = 3239476, upload_time = "2025-04-30T23:31:14.997Z" }, -]