mirror of
https://github.com/csd4ni3l/music-player.git
synced 2026-01-01 12:13:42 +01:00
Use yt-dlp binary instead of module.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -180,3 +180,4 @@ test*.py
|
|||||||
logs/
|
logs/
|
||||||
logs
|
logs
|
||||||
settings.json
|
settings.json
|
||||||
|
bin/
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
11
uv.lock
generated
@@ -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" },
|
|
||||||
]
|
|
||||||
|
|||||||
Reference in New Issue
Block a user