mirror of
https://github.com/csd4ni3l/music-player.git
synced 2026-01-01 04:03:42 +01:00
Move from list to card grid view, only support popular file extensions,
Use Roboto Black as a font, dont recreate ui on refresh, update styles
This commit is contained in:
@@ -7,47 +7,51 @@ menu_background_color = (17, 17, 17)
|
||||
log_dir = 'logs'
|
||||
discord_presence_id = 1368277020332523530
|
||||
|
||||
audio_extensions = [
|
||||
"3g2", "3gp", "aac", "ac3", "aiff", "alac", "amr", "ape", "au", "caf",
|
||||
"dts", "flac", "gsm", "m4a", "mka", "mlp", "mmf", "mp2", "mp3",
|
||||
"oga", "ogg", "opus", "ra", "rm", "sln", "tta", "vorbis", "voc", "vox",
|
||||
"wav", "webm", "wma", "wv"
|
||||
]
|
||||
audio_extensions = ["mp3", "m4a", "mp4", "aac", "flac", "ogg", "opus", "wav"]
|
||||
|
||||
yt_dlp_parameters = {
|
||||
"final_ext": "mp3",
|
||||
"format": "bestaudio/best",
|
||||
"outtmpl": {"pl_thumbnail": "", "default": "downloaded_music.mp3"},
|
||||
"postprocessors": [
|
||||
{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"nopostoverwrites": False,
|
||||
"preferredcodec": "mp3",
|
||||
"preferredquality": "5"
|
||||
},
|
||||
{
|
||||
"add_chapters": True,
|
||||
"add_infojson": "if_exists",
|
||||
"add_metadata": True,
|
||||
"key": "FFmpegMetadata"
|
||||
},
|
||||
{ "already_have_thumbnail": False, "key": "EmbedThumbnail" }
|
||||
],
|
||||
"writethumbnail": True
|
||||
DARK_GRAY = Color(45, 45, 45)
|
||||
GRAY = Color(70, 70, 70)
|
||||
LIGHT_GRAY = Color(150, 150, 150)
|
||||
PRIMARY = Color(0, 189, 126)
|
||||
PRIMARY_DARK = Color(0, 145, 96)
|
||||
DISABLED = Color(90, 90, 90)
|
||||
FONT_COLOR = arcade.color.BLACK
|
||||
FONT = "Roboto"
|
||||
FONT_SIZE = 14
|
||||
BIG_FONT_SIZE = 22
|
||||
|
||||
button_style = {
|
||||
"normal": UIFlatButtonStyle(font_name=FONT, font_size=FONT_SIZE, font_color=FONT_COLOR, bg=GRAY),
|
||||
"hover": UIFlatButtonStyle(font_name=FONT, font_size=FONT_SIZE, font_color=FONT_COLOR, bg=PRIMARY),
|
||||
"press": UIFlatButtonStyle(font_name=FONT, font_size=FONT_SIZE, font_color=FONT_COLOR, bg=PRIMARY_DARK),
|
||||
"disabled": UIFlatButtonStyle(font_name=FONT, font_size=FONT_SIZE, font_color=LIGHT_GRAY, bg=DISABLED),
|
||||
}
|
||||
|
||||
button_style = {'normal': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK), 'hover': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK),
|
||||
'press': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK), 'disabled': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK)}
|
||||
big_button_style = {'normal': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, font_size=26), 'hover': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, font_size=26),
|
||||
'press': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, font_size=26), 'disabled': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, font_size=26)}
|
||||
big_button_style = {
|
||||
"normal": UIFlatButtonStyle(font_name=FONT, font_size=BIG_FONT_SIZE, font_color=FONT_COLOR, bg=GRAY),
|
||||
"hover": UIFlatButtonStyle(font_name=FONT, font_size=BIG_FONT_SIZE, font_color=FONT_COLOR, bg=PRIMARY),
|
||||
"press": UIFlatButtonStyle(font_name=FONT, font_size=BIG_FONT_SIZE, font_color=FONT_COLOR, bg=PRIMARY_DARK),
|
||||
"disabled": UIFlatButtonStyle(font_name=FONT, font_size=BIG_FONT_SIZE, font_color=LIGHT_GRAY, bg=DISABLED),
|
||||
}
|
||||
|
||||
dropdown_style = {'normal': UIFlatButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, bg=Color(128, 128, 128)), 'hover': UIFlatButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, bg=Color(49, 154, 54)),
|
||||
'press': UIFlatButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, bg=Color(128, 128, 128)), 'disabled': UIFlatButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, bg=Color(128, 128, 128))}
|
||||
slider_default_style = UISliderStyle(
|
||||
bg=GRAY,
|
||||
unfilled_track=DARK_GRAY,
|
||||
filled_track=PRIMARY
|
||||
)
|
||||
|
||||
slider_default_style = UISliderStyle(bg=Color(128, 128, 128), unfilled_track=Color(128, 128, 128), filled_track=Color(49, 154, 54))
|
||||
slider_hover_style = UISliderStyle(bg=Color(49, 154, 54), unfilled_track=Color(128, 128, 128), filled_track=Color(49, 154, 54))
|
||||
slider_hover_style = UISliderStyle(
|
||||
bg=PRIMARY,
|
||||
unfilled_track=DARK_GRAY,
|
||||
filled_track=PRIMARY_DARK
|
||||
)
|
||||
|
||||
slider_style = {'normal': slider_default_style, 'hover': slider_hover_style, 'press': slider_hover_style, 'disabled': slider_default_style}
|
||||
slider_style = {
|
||||
"normal": slider_default_style,
|
||||
"hover": slider_hover_style,
|
||||
"press": slider_hover_style,
|
||||
"disabled": slider_default_style,
|
||||
}
|
||||
|
||||
settings = {
|
||||
"Music": {
|
||||
|
||||
@@ -19,3 +19,5 @@ download_icon = arcade.load_texture("assets/graphics/download.png")
|
||||
plus_icon = arcade.load_texture("assets/graphics/plus.png")
|
||||
playlist_icon = arcade.load_texture("assets/graphics/playlist.png")
|
||||
files_icon = arcade.load_texture("assets/graphics/files.png")
|
||||
|
||||
music_icon = arcade.load_texture("assets/graphics/music.png")
|
||||
|
||||
124
utils/utils.py
124
utils/utils.py
@@ -1,8 +1,9 @@
|
||||
import logging, arcade, arcade.gui, sys, traceback, os, re, platform, urllib.request, zipfile, subprocess
|
||||
import logging, sys, traceback, os, re, platform, urllib.request, zipfile, subprocess, textwrap, io, base64
|
||||
from mutagen.easyid3 import EasyID3
|
||||
|
||||
from mutagen import File
|
||||
from PIL import Image
|
||||
from utils.constants import menu_background_color
|
||||
import pyglet
|
||||
import pyglet, arcade, arcade.gui
|
||||
|
||||
def dump_platform():
|
||||
import platform
|
||||
@@ -58,25 +59,6 @@ class ErrorView(arcade.gui.UIView):
|
||||
msgbox.on_action = lambda event: self.exit()
|
||||
self.add_widget(msgbox)
|
||||
|
||||
def on_exception(*exc_info):
|
||||
logging.error(f"Unhandled exception:\n{''.join(traceback.format_exception(exc_info[1], limit=None))}")
|
||||
|
||||
def get_closest_resolution():
|
||||
allowed_resolutions = [(1366, 768), (1440, 900), (1600,900), (1920,1080), (2560,1440), (3840,2160)]
|
||||
screen_width, screen_height = arcade.get_screens()[0].width, arcade.get_screens()[0].height
|
||||
if (screen_width, screen_height) in allowed_resolutions:
|
||||
if not allowed_resolutions.index((screen_width, screen_height)) == 0:
|
||||
closest_resolution = allowed_resolutions[allowed_resolutions.index((screen_width, screen_height))-1]
|
||||
else:
|
||||
closest_resolution = (screen_width, screen_height)
|
||||
else:
|
||||
target_width, target_height = screen_width // 2, screen_height // 2
|
||||
|
||||
closest_resolution = min(
|
||||
allowed_resolutions,
|
||||
key=lambda res: abs(res[0] - target_width) + abs(res[1] - target_height)
|
||||
)
|
||||
return closest_resolution
|
||||
|
||||
class FakePyPresence():
|
||||
def __init__(self):
|
||||
@@ -97,16 +79,60 @@ class UIFocusTextureButton(arcade.gui.UITextureButton):
|
||||
else:
|
||||
self.resize(width=self.width / 1.1, height=self.height / 1.1)
|
||||
|
||||
class Card(arcade.gui.UIBoxLayout):
|
||||
def __init__(self, width: int, height: int, font_name: str, font_size: int, text: str, card_texture: arcade.Texture, padding=10):
|
||||
super().__init__(width=width, height=height, space_between=padding, align="bottom")
|
||||
|
||||
self.button = self.add(arcade.gui.UITextureButton(
|
||||
texture=card_texture,
|
||||
texture_hovered=card_texture,
|
||||
texture_pressed=card_texture,
|
||||
texture_disabled=card_texture,
|
||||
width=width / 2,
|
||||
height=height * 0.5,
|
||||
))
|
||||
|
||||
wrapped_lines = textwrap.wrap(text, width=int(width / (font_size * 0.6)))
|
||||
wrapped_text = "\n".join(wrapped_lines)
|
||||
|
||||
self.label = self.add(arcade.gui.UILabel(
|
||||
text=wrapped_text,
|
||||
font_name=font_name,
|
||||
font_size=font_size,
|
||||
width=width,
|
||||
height=height * 0.5,
|
||||
multiline=True
|
||||
))
|
||||
|
||||
def on_exception(*exc_info):
|
||||
logging.error(f"Unhandled exception:\n{''.join(traceback.format_exception(exc_info[1], limit=None))}")
|
||||
|
||||
def get_closest_resolution():
|
||||
allowed_resolutions = [(1366, 768), (1440, 900), (1600,900), (1920,1080), (2560,1440), (3840,2160)]
|
||||
screen_width, screen_height = arcade.get_screens()[0].width, arcade.get_screens()[0].height
|
||||
if (screen_width, screen_height) in allowed_resolutions:
|
||||
if not allowed_resolutions.index((screen_width, screen_height)) == 0:
|
||||
closest_resolution = allowed_resolutions[allowed_resolutions.index((screen_width, screen_height))-1]
|
||||
else:
|
||||
closest_resolution = (screen_width, screen_height)
|
||||
else:
|
||||
target_width, target_height = screen_width // 2, screen_height // 2
|
||||
|
||||
closest_resolution = min(
|
||||
allowed_resolutions,
|
||||
key=lambda res: abs(res[0] - target_width) + abs(res[1] - target_height)
|
||||
)
|
||||
return closest_resolution
|
||||
|
||||
def get_yt_dlp_binary_path():
|
||||
binary = "yt-dlp"
|
||||
system = platform.system()
|
||||
|
||||
if system == "Windows":
|
||||
binary += ".exe"
|
||||
binary = "yt-dlp.exe"
|
||||
elif system == "Darwin":
|
||||
binary += "_macos"
|
||||
binary = "yt-dlp_macos"
|
||||
elif system == "Linux":
|
||||
binary += "_linux"
|
||||
binary = "yt-dlp_linux"
|
||||
|
||||
return os.path.join("bin", binary)
|
||||
|
||||
@@ -150,10 +176,10 @@ def extract_metadata(filename):
|
||||
name_only = re.sub(r'\s*\[[a-zA-Z0-9\-_]{5,}\]$', '', name_only)
|
||||
|
||||
try:
|
||||
audio = EasyID3(filename)
|
||||
thumb_audio = EasyID3(filename)
|
||||
|
||||
artist = str(audio["artist"][0])
|
||||
title = str(audio["title"][0])
|
||||
artist = str(thumb_audio["artist"][0])
|
||||
title = str(thumb_audio["title"][0])
|
||||
|
||||
artist_title_match = re.search(r'^.+\s*-\s*.+$', title) # check for Artist - Title titles, so Artist doesnt appear twice
|
||||
|
||||
@@ -182,3 +208,43 @@ def extract_metadata(filename):
|
||||
title = name_only
|
||||
|
||||
return artist, title
|
||||
|
||||
def get_audio_thumbnail_texture(audio_path: str, window_resolution: tuple) -> arcade.Texture:
|
||||
ext = os.path.splitext(audio_path)[1].lower().lstrip('.')
|
||||
thumb_audio = File(audio_path)
|
||||
|
||||
thumb_image_data = None
|
||||
|
||||
try:
|
||||
if ext == 'mp3':
|
||||
for tag in thumb_audio.values():
|
||||
if tag.FrameID == "APIC":
|
||||
thumb_image_data = tag.data
|
||||
break
|
||||
|
||||
elif ext in ('m4a', 'mp4', 'aac'):
|
||||
if 'covr' in thumb_audio:
|
||||
thumb_image_data = thumb_audio['covr'][0]
|
||||
|
||||
elif ext == 'flac':
|
||||
if thumb_audio.pictures:
|
||||
thumb_image_data = thumb_audio.pictures[0].data
|
||||
|
||||
elif ext in ('ogg', 'opus'):
|
||||
if "metadata_block_picture" in thumb_audio:
|
||||
pic_data = base64.b64decode(thumb_audio["metadata_block_picture"][0])
|
||||
import struct
|
||||
header_len = struct.unpack(">I", pic_data[0:4])[0]
|
||||
thumb_image_data = pic_data[4 + header_len:]
|
||||
|
||||
if thumb_image_data:
|
||||
pil_image = Image.open(io.BytesIO(thumb_image_data)).convert("RGBA")
|
||||
pil_image = pil_image.resize((int(window_resolution[0] / 5), int(window_resolution[1] / 8)))
|
||||
thumb_texture = arcade.Texture(pil_image)
|
||||
return thumb_texture
|
||||
|
||||
except Exception as e:
|
||||
logging.debug(f"[Thumbnail Error] {audio_path}: {e}")
|
||||
|
||||
from utils.preload import music_icon
|
||||
return music_icon
|
||||
|
||||
Reference in New Issue
Block a user