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
settings.json
bin/

View File

@@ -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

View File

@@ -10,5 +10,4 @@ dependencies = [
"pydub>=0.25.1",
"pypresence>=4.3.0",
"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 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:

11
uv.lock generated
View File

@@ -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" },
]