Initial commit

This commit is contained in:
csd4ni3l
2025-05-24 10:33:23 +02:00
commit 26b9f17970
34 changed files with 2642 additions and 0 deletions

72
utils/constants.py Normal file
View File

@@ -0,0 +1,72 @@
import arcade.color
from arcade.types import Color
from arcade.gui.widgets.buttons import UITextureButtonStyle, UIFlatButtonStyle
from arcade.gui.widgets.slider import UISliderStyle
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"
]
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
}
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)}
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=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_style = {'normal': slider_default_style, 'hover': slider_hover_style, 'press': slider_hover_style, 'disabled': slider_default_style}
settings = {
"Music": {
"Default Volume": {"type": "slider", "min": 0, "max": 100, "config_key": "default_volume", "default": 100},
"Audio Mode": {"type": "option", "options": ["Stream", "Preload"], "config_key": "audio_mode", "default": "Stream"},
"Normalize Audio": {"type": "bool", "config_key": "normalize_audio", "default": True},
"Normalized dBFS": {"type": "slider", "min": -30, "max": 0, "config_key": "normalized_volume", "default": -8},
},
"Graphics": {
"Window Mode": {"type": "option", "options": ["Windowed", "Fullscreen", "Borderless"], "config_key": "window_mode", "default": "Windowed"},
"Resolution": {"type": "option", "options": ["1366x768", "1440x900", "1600x900", "1920x1080", "2560x1440", "3840x2160"], "config_key": "resolution"},
"Anti-Aliasing": {"type": "option", "options": ["None", "2x MSAA", "4x MSAA", "8x MSAA", "16x MSAA"], "config_key": "anti_aliasing", "default": "4x MSAA"},
"VSync": {"type": "bool", "config_key": "vsync", "default": True},
"FPS Limit": {"type": "slider", "min": 0, "max": 480, "config_key": "fps_limit", "default": 60},
},
"Miscellaneous": {
"Discord RPC": {"type": "bool", "config_key": "discord_rpc", "default": True},
},
"Credits": {}
}
settings_start_category = "Music"

21
utils/preload.py Normal file
View File

@@ -0,0 +1,21 @@
import arcade.gui, arcade
button_texture = arcade.gui.NinePatchTexture(64 // 4, 64 // 4, 64 // 4, 64 // 4, arcade.load_texture("assets/graphics/button.png"))
button_hovered_texture = arcade.gui.NinePatchTexture(64 // 4, 64 // 4, 64 // 4, 64 // 4, arcade.load_texture("assets/graphics/button_hovered.png"))
pause_icon = arcade.load_texture("assets/graphics/pause.png")
resume_icon = arcade.load_texture("assets/graphics/resume.png")
stop_icon = arcade.load_texture("assets/graphics/stop.png")
loop_icon = arcade.load_texture("assets/graphics/loop.png")
no_loop_icon = arcade.load_texture("assets/graphics/no_loop.png")
shuffle_icon = arcade.load_texture("assets/graphics/shuffle.png")
no_shuffle_icon = arcade.load_texture("assets/graphics/no_shuffle.png")
settings_icon = arcade.load_texture("assets/graphics/settings.png")
reload_icon = arcade.load_texture("assets/graphics/reload.png")
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")

168
utils/utils.py Normal file
View File

@@ -0,0 +1,168 @@
import logging, arcade, arcade.gui, sys, traceback, os, re
from mutagen.easyid3 import EasyID3
from utils.constants import menu_background_color
import pyglet
def dump_platform():
import platform
logging.debug(f'Platform: {platform.platform()}')
logging.debug(f'Release: {platform.release()}')
logging.debug(f'Machine: {platform.machine()}')
logging.debug(f'Architecture: {platform.architecture()}')
def dump_gl():
from pyglet.gl import gl_info as info
logging.debug(f'gl_info.get_version(): {info.get_version()}')
logging.debug(f'gl_info.get_vendor(): {info.get_vendor()}')
logging.debug(f'gl_info.get_renderer(): {info.get_renderer()}')
def print_debug_info():
logging.debug('########################## DEBUG INFO ##########################')
logging.debug('')
dump_platform()
dump_gl()
logging.debug('')
logging.debug(f'Number of screens: {len(pyglet.display.get_display().get_screens())}')
logging.debug('')
for n, screen in enumerate(pyglet.display.get_display().get_screens()):
logging.debug(f"Screen #{n+1}:")
logging.debug(f'DPI: {screen.get_dpi()}')
logging.debug(f'Scale: {screen.get_scale()}')
logging.debug(f'Size: {screen.width}, {screen.height}')
logging.debug(f'Position: {screen.x}, {screen.y}')
logging.debug('')
logging.debug('########################## DEBUG INFO ##########################')
logging.debug('')
class ErrorView(arcade.gui.UIView):
def __init__(self, message: str, title: str):
super().__init__()
self.message = message
self.title = title
def exit(self):
logging.fatal('Exited with error code 1.')
sys.exit(1)
def on_show_view(self):
super().on_show_view()
self.window.set_caption('Music Player - Error')
self.window.set_mouse_visible(True)
self.window.set_exclusive_mouse(False)
arcade.set_background_color(menu_background_color)
msgbox = arcade.gui.UIMessageBox(width=self.window.width / 2, height=self.window.height / 2, message_text=self.message, title=self.title)
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):
...
def update(self, *args, **kwargs):
...
def close(self, *args, **kwargs):
...
class UIFocusTextureButton(arcade.gui.UITextureButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
arcade.gui.bind(self, "hovered", self.on_hover)
def on_hover(self):
if self.hovered:
self.resize(width=self.width * 1.1, height=self.height * 1.1)
else:
self.resize(width=self.width / 1.1, height=self.height / 1.1)
class BufferLogger:
def __init__(self):
self.buffer = "No errors."
def debug(self, msg):
self._log(msg)
def info(self, msg):
self._log(msg)
def warning(self, msg):
self._log(f"WARNING: {msg}")
def error(self, msg):
self._log(f"ERROR: {msg}")
def _log(self, msg):
self.buffer = msg
def truncate_end(text: str, max_length: int) -> str:
if len(text) <= max_length:
return text
if max_length <= 3:
return text
return text[:max_length - 3] + '...'
def extract_metadata(filename):
artist = "Unknown"
title = ""
basename = os.path.basename(filename)
name_only = os.path.splitext(basename)[0]
name_only = re.sub(r'\s*\[[a-zA-Z0-9\-_]{5,}\]$', '', name_only)
try:
audio = EasyID3(filename)
artist = str(audio["artist"][0])
title = str(audio["title"][0])
artist_title_match = re.search(r'^.+\s*-\s*.+$', title) # check for Artist - Title titles, so Artist doesnt appear twice
if artist_title_match:
title = title.split("- ")[1]
if artist != "Unknown" and title:
return artist, title
except:
pass
if artist == "Unknown" or not title:
match = re.search(r'^(.*?)\s+-\s+(.*?)$', name_only)
if match:
filename_artist, filename_title = match.groups()
if artist == "Unknown":
artist = filename_artist
if not title:
title = filename_title
return artist, title
if not title:
title = name_only
return artist, title