Use yt-dlp binary instead of module.

This commit is contained in:
csd4ni3l
2025-05-24 11:58:27 +02:00
parent d3d36e5250
commit 8d510daeae
5 changed files with 80 additions and 50 deletions

1
.gitignore vendored
View File

@@ -180,3 +180,4 @@ test*.py
logs/ logs/
logs logs
settings.json settings.json
bin/

View File

@@ -1,12 +1,11 @@
from yt_dlp import YoutubeDL
from mutagen.easyid3 import EasyID3 from mutagen.easyid3 import EasyID3
from pydub import AudioSegment 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 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.constants import button_style, dropdown_style, yt_dlp_parameters
from utils.preload import button_texture, button_hovered_texture 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.settings_dict = json.load(file)
self.tab_options = self.settings_dict.get("tab_options", ["~/Music", "~/Downloads"]) self.tab_options = self.settings_dict.get("tab_options", ["~/Music", "~/Downloads"])
self.yt_dl_buffer = ""
self.yt_dl = YoutubeDL(params=yt_dlp_parameters)
self.yt_dl.params["logger"] = self.yt_dl_logger = BufferLogger()
def on_show_view(self): def on_show_view(self):
super().on_show_view() super().on_show_view()
@@ -58,15 +55,46 @@ class Downloader(arcade.gui.UIView):
self.anchor.detect_focusable_widgets() self.anchor.detect_focusable_widgets()
def on_update(self, delta_time: float) -> bool | None: 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) 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) self.status_label.update_font(font_color=arcade.color.RED)
else: else:
self.status_label.update_font(font_color=arcade.color.LIGHT_GREEN) 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): def download(self):
if not self.tab_selector.value: if not self.tab_selector.value:
return return
@@ -78,17 +106,14 @@ class Downloader(arcade.gui.UIView):
path = os.path.expanduser(self.tab_selector.value) path = os.path.expanduser(self.tab_selector.value)
try: info = self.run_yt_dlp(url)
info = self.yt_dl.extract_info(url, download=True)
except yt_dlp.DownloadError as e: os.remove("downloaded_music.mp3.info.json")
message = "".join(e.msg.strip().split("] ")[1:]) if e.msg else "Unknown yt-dlp error." os.remove("downloaded_music.info.json")
self.yt_dl_logger.buffer = f"ERROR: {message}"
return
if info: if info:
entry = info['entries'][0] if 'entries' in info else info title = info.get('title', 'Unknown')
title = entry.get('title', 'Unknown') uploader = info.get('uploader', 'Unknown')
uploader = entry.get('uploader', 'Unknown')
if " - " in title: if " - " in title:
artist, track_title = title.split(" - ", 1) artist, track_title = title.split(" - ", 1)
@@ -103,7 +128,7 @@ class Downloader(arcade.gui.UIView):
audio["title"] = track_title audio["title"] = track_title
audio.save() audio.save()
except Exception as meta_err: 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 return
if self.settings_dict.get("normalize_audio", True): if self.settings_dict.get("normalize_audio", True):
@@ -117,20 +142,20 @@ class Downloader(arcade.gui.UIView):
audio.export("downloaded_music.mp3", format="mp3") audio.export("downloaded_music.mp3", format="mp3")
except Exception as e: 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 return
try: try:
output_filename = os.path.join(path, f"{title}.mp3") output_filename = os.path.join(path, f"{title}.mp3")
os.replace("downloaded_music.mp3", output_filename) os.replace("downloaded_music.mp3", output_filename)
except Exception as e: 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 return
else: 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 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): def main_exit(self):
from menus.main import Main from menus.main import Main

View File

@@ -10,5 +10,4 @@ dependencies = [
"pydub>=0.25.1", "pydub>=0.25.1",
"pypresence>=4.3.0", "pypresence>=4.3.0",
"thefuzz>=0.22.1", "thefuzz>=0.22.1",
"yt-dlp>=2025.4.30",
] ]

View File

@@ -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 mutagen.easyid3 import EasyID3
from utils.constants import menu_background_color from utils.constants import menu_background_color
@@ -98,24 +97,41 @@ class UIFocusTextureButton(arcade.gui.UITextureButton):
else: else:
self.resize(width=self.width / 1.1, height=self.height / 1.1) self.resize(width=self.width / 1.1, height=self.height / 1.1)
class BufferLogger: def get_yt_dlp_binary_path():
def __init__(self): binary = "yt-dlp"
self.buffer = "No errors." system = platform.system()
def debug(self, msg): if system == "Windows":
self._log(msg) binary += ".exe"
elif system == "Darwin":
binary += "_macos"
elif system == "Linux":
binary += "_linux"
def info(self, msg): return os.path.join("bin", binary)
self._log(msg)
def warning(self, msg): def ensure_yt_dlp():
self._log(f"WARNING: {msg}") path = get_yt_dlp_binary_path()
def error(self, msg): if not os.path.exists("bin"):
self._log(f"ERROR: {msg}") os.makedirs("bin")
def _log(self, msg): if not os.path.exists(path):
self.buffer = msg 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: def truncate_end(text: str, max_length: int) -> str:
if len(text) <= max_length: if len(text) <= max_length:

11
uv.lock generated
View File

@@ -81,7 +81,6 @@ dependencies = [
{ name = "pydub" }, { name = "pydub" },
{ name = "pypresence" }, { name = "pypresence" },
{ name = "thefuzz" }, { name = "thefuzz" },
{ name = "yt-dlp" },
] ]
[package.metadata] [package.metadata]
@@ -91,7 +90,6 @@ requires-dist = [
{ name = "pydub", specifier = ">=0.25.1" }, { name = "pydub", specifier = ">=0.25.1" },
{ name = "pypresence", specifier = ">=4.3.0" }, { name = "pypresence", specifier = ">=4.3.0" },
{ name = "thefuzz", specifier = ">=0.22.1" }, { name = "thefuzz", specifier = ">=0.22.1" },
{ name = "yt-dlp", specifier = ">=2025.4.30" },
] ]
[[package]] [[package]]
@@ -315,12 +313,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e3549295
wheels = [ 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" }, { 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" },
]