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:
csd4ni3l
2025-05-25 19:09:59 +02:00
parent 31bcf82534
commit caf567b003
14 changed files with 335 additions and 135 deletions

View File

@@ -2,6 +2,8 @@ Some icons used in this project are from Font Awesome Free by Fonticons, Inc.
Licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0): https://creativecommons.org/licenses/by/4.0/
Icons were modified (repainted to white and exported to PNG).
The Roboto Black font used in this project is licensed under the Open Font License. Read assets/fonts/OFL.txt for more information.
Huge Thanks to Python for being the programming language used in this application.
https://www.python.org/

93
assets/fonts/OFL.txt Normal file
View File

@@ -0,0 +1,93 @@
Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,6 +1,6 @@
import arcade, arcade.gui, os, json
from utils.constants import button_style, dropdown_style
from utils.constants import button_style
from utils.preload import button_texture, button_hovered_texture
from utils.utils import UIFocusTextureButton
@@ -32,10 +32,10 @@ class AddMusic(arcade.gui.UIView):
self.anchor = self.add_widget(UIFocusGroup(size_hint=(1, 1)))
self.box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=10), anchor_x="center", anchor_y="center")
self.playlist_label = self.box.add(arcade.gui.UILabel(text="Playlist", font_name="Protest Strike", font_size=32))
self.playlist_option = self.box.add(arcade.gui.UIDropdown(default=list(self.playlists.keys())[0], options=list(self.playlists.keys()), width=self.window.width / 2, height=self.window.height / 15, primary_style=dropdown_style, dropdown_style=dropdown_style, active_style=dropdown_style))
self.music_label = self.box.add(arcade.gui.UILabel(text="Music File Path", font_name="Protest Strike", font_size=32))
self.add_music_input = self.box.add(arcade.gui.UIInputText(font_name="Protest Strike", font_size=32, width=self.window.width / 2, height=self.window.height / 10))
self.playlist_label = self.box.add(arcade.gui.UILabel(text="Playlist", font_name="Roboto", font_size=32))
self.playlist_option = self.box.add(arcade.gui.UIDropdown(default=list(self.playlists.keys())[0], options=list(self.playlists.keys()), width=self.window.width / 2, height=self.window.height / 15, primary_style=button_style, dropdown_style=button_style, active_style=button_style))
self.music_label = self.box.add(arcade.gui.UILabel(text="Music File Path", font_name="Roboto", font_size=32))
self.add_music_input = self.box.add(arcade.gui.UIInputText(font_name="Roboto", font_size=32, width=self.window.width / 2, height=self.window.height / 10))
self.add_music_button = self.box.add(UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Add Music', style=button_style, width=self.window.width / 2, height=self.window.height / 10))
self.add_music_button.on_click = lambda event: self.add_music()

View File

@@ -6,7 +6,7 @@ import arcade, arcade.gui, os, json, threading, subprocess
from arcade.gui.experimental.focus import UIFocusGroup
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
from utils.preload import button_texture, button_hovered_texture
class Downloader(arcade.gui.UIView):
@@ -37,12 +37,12 @@ class Downloader(arcade.gui.UIView):
self.download_box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=10), anchor_x="center", anchor_y="center")
self.url_name_label = self.download_box.add(arcade.gui.UILabel(text="URL or Name:", font_name="Protest Strike", font_size=36))
self.url_name_input = self.download_box.add(arcade.gui.UIInputText(font_name="Protest Strike", width=self.window.width / 2, height=self.window.height / 15, font_size=36))
self.url_name_label = self.download_box.add(arcade.gui.UILabel(text="URL or Name:", font_name="Roboto", font_size=36))
self.url_name_input = self.download_box.add(arcade.gui.UIInputText(font_name="Roboto", width=self.window.width / 2, height=self.window.height / 15, font_size=36))
self.url_name_input.activate()
self.tab_label = self.download_box.add(arcade.gui.UILabel(text="Path:", font_name="Protest Strike", font_size=36))
self.tab_selector = self.download_box.add(arcade.gui.UIDropdown(default=self.tab_options[0], options=self.tab_options, width=self.window.width / 2, height=self.window.height / 15, primary_style=dropdown_style, dropdown_style=dropdown_style, active_style=dropdown_style))
self.tab_label = self.download_box.add(arcade.gui.UILabel(text="Path:", font_name="Roboto", font_size=36))
self.tab_selector = self.download_box.add(arcade.gui.UIDropdown(default=self.tab_options[0], options=self.tab_options, width=self.window.width / 2, height=self.window.height / 15, primary_style=button_style, dropdown_style=button_style, active_style=button_style))
self.status_label = self.download_box.add(arcade.gui.UILabel(text="No errors.", font_size=16, text_color=arcade.color.LIGHT_GREEN))

View File

@@ -3,8 +3,9 @@ import arcade, arcade.gui, pyglet
from utils.preload import *
from utils.constants import button_style, slider_style, audio_extensions, discord_presence_id
from utils.utils import FakePyPresence, UIFocusTextureButton, extract_metadata, truncate_end
from utils.utils import FakePyPresence, UIFocusTextureButton, Card, extract_metadata, get_audio_thumbnail_texture, truncate_end
from math import ceil
from thefuzz import process, fuzz
from pydub import AudioSegment
@@ -56,6 +57,7 @@ class Main(arcade.gui.UIView):
self.tab_options = self.settings_dict.get("tab_options", ["~/Music", "~/Downloads"])
self.tab_content = {}
self.playlist_content = {}
self.thumbnails = {}
self.tab_buttons = {}
self.music_buttons = {}
@@ -76,21 +78,26 @@ class Main(arcade.gui.UIView):
def on_show_view(self):
super().on_show_view()
self.load_content()
self.create_ui()
def create_ui(self):
self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1)))
self.ui_box = self.anchor.add(arcade.gui.UIBoxLayout(size_hint=(1, 1), space_between=10))
# Tabs
self.tab_box = self.ui_box.add(arcade.gui.UIBoxLayout(size_hint=(0.95, 0.1), space_between=10, vertical=False))
self.load_tabs()
self.load_content()
if self.current_mode == "playlist" and not self.current_playlist:
self.current_playlist = list(self.playlist_content.keys())[0] if self.playlist_content else None
self.load_tabs()
# Scrollable Sounds
self.scroll_box = self.ui_box.add(arcade.gui.UIBoxLayout(size_hint=(0.95, 0.8), space_between=15, vertical=False))
self.scroll_area = UIScrollArea(size_hint=(0.9, 1)) # center on screen
self.scroll_area = UIScrollArea(size_hint=(0.95, 1)) # center on screen
self.scroll_area.scroll_speed = -50
self.scroll_box.add(self.scroll_area)
@@ -98,8 +105,8 @@ class Main(arcade.gui.UIView):
self.scrollbar.size_hint = (0.02, 1)
self.scroll_box.add(self.scrollbar)
self.music_box = arcade.gui.UIBoxLayout(space_between=2)
self.scroll_area.add(self.music_box)
self.music_grid = arcade.gui.UIGridLayout(horizontal_spacing=30, vertical_spacing=30, row_count=100, column_count=5)
self.scroll_area.add(self.music_grid)
# Utility
@@ -125,8 +132,8 @@ class Main(arcade.gui.UIView):
# Controls
self.control_box = self.ui_box.add(arcade.gui.UIBoxLayout(size_hint=(0.95, 0.1), space_between=10, vertical=False))
self.current_music_label = self.control_box.add(arcade.gui.UILabel(text=truncate_end(self.current_music_name, int(self.window.width / 30)) if self.current_music_name else "No songs playing", font_name="Protest Strike", font_size=16))
self.time_label = self.control_box.add(arcade.gui.UILabel(text="00:00", font_name="Protest Strike", font_size=16))
self.current_music_label = self.control_box.add(arcade.gui.UILabel(text=truncate_end(self.current_music_name, int(self.window.width / 30)) if self.current_music_name else "No songs playing", font_name="Roboto", font_size=16))
self.time_label = self.control_box.add(arcade.gui.UILabel(text="00:00", font_name="Roboto", font_size=16))
self.progressbar = self.control_box.add(arcade.gui.UISlider(style=slider_style, width=self.window.width / 3, height=35))
self.progressbar.on_change = self.on_progress_change
@@ -147,7 +154,7 @@ class Main(arcade.gui.UIView):
self.progressbar.max_value = self.current_length
self.volume = int(self.current_music_player.volume * 100)
self.volume_label = self.control_box.add(arcade.gui.UILabel(text=f"{self.volume}%", font_name="Protest Strike", font_size=16))
self.volume_label = self.control_box.add(arcade.gui.UILabel(text=f"{self.volume}%", font_name="Roboto", font_size=16))
self.volume_slider = self.control_box.add(arcade.gui.UISlider(style=slider_style, width=self.window.width / 10, height=35, value=self.volume, max_value=100))
self.volume_slider.on_change = self.on_volume_slider_change
@@ -200,6 +207,8 @@ class Main(arcade.gui.UIView):
self.highest_score_file = ""
self.search_term = ""
self.load_tabs()
self.reload()
def skip_sound(self):
@@ -237,7 +246,7 @@ class Main(arcade.gui.UIView):
self.update_buttons()
def show_content(self, tab):
self.music_box.clear()
self.music_grid.clear()
self.music_buttons.clear()
if self.current_mode == "files":
@@ -245,16 +254,21 @@ class Main(arcade.gui.UIView):
if not self.search_term == "":
matches = process.extract(self.search_term, self.tab_content[self.current_tab], limit=5, processor=lambda text: text.lower(), scorer=fuzz.partial_token_sort_ratio)
self.highest_score_file = f"{self.current_tab}/{matches[0][0]}"
for match in matches:
for n, match in enumerate(matches):
music_filename = match[0]
self.music_buttons[music_filename] = self.music_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=music_filename, style=button_style, width=self.window.width * 0.85, height=self.window.height / 30))
self.music_buttons[music_filename].on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(f"{tab}/{music_filename}")
self.music_buttons[music_filename] = self.music_grid.add(Card(card_texture=self.thumbnails[f"{tab}/{music_filename}"], font_name="Roboto", font_size=13, text=music_filename, width=self.window.width / 6, height=self.window.height / 5), row=0, column=n)
self.music_buttons[music_filename].button.on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(f"{tab}/{music_filename}")
else:
self.music_grid.row_count = ceil(len(self.tab_content[tab]) / 5)
self.music_grid._update_size_hints()
self.highest_score_file = ""
for music_filename in self.tab_content[tab]:
self.music_buttons[music_filename] = self.music_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=music_filename, style=button_style, width=self.window.width * 0.85, height=self.window.height / 30))
self.music_buttons[music_filename].on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(f"{tab}/{music_filename}")
for n, music_filename in enumerate(self.tab_content[tab]):
row = n // 5
col = n % 5
self.music_buttons[music_filename] = self.music_grid.add(Card(card_texture=self.thumbnails[f"{tab}/{music_filename}"], font_name="Roboto", font_size=13, text=music_filename, width=self.window.width / 6, height=self.window.height / 5), row=row, column=col)
self.music_buttons[music_filename].button.on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(f"{tab}/{music_filename}")
elif self.current_mode == "playlist":
self.current_playlist = tab
@@ -263,45 +277,57 @@ class Main(arcade.gui.UIView):
if not self.search_term == "":
matches = process.extract(self.search_term, self.playlist_content[tab], limit=5, processor=lambda text: text.lower(), scorer=fuzz.partial_token_sort_ratio)
self.highest_score_file = matches[0][0]
for match in matches:
for n, match in enumerate(matches):
music_filename = match[0]
self.music_buttons[music_filename] = self.music_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=music_filename, style=button_style, width=self.window.width * 0.85, height=self.window.height / 30))
self.music_buttons[music_filename].on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(music_filename)
self.music_buttons[music_filename] = self.music_grid.add(Card(card_texture=self.thumbnails[music_filename], font_name="Roboto", font_size=13, text=music_filename, width=self.window.width / 6, height=self.window.height / 5), row=0, column=n)
self.music_buttons[music_filename].button.on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(music_filename)
else:
self.highest_score_file = ""
for music_filename in self.playlist_content[tab]:
self.music_buttons[music_filename] = self.music_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=music_filename, style=button_style, width=self.window.width * 0.85, height=self.window.height / 30))
self.music_buttons[music_filename].on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(music_filename)
self.music_grid.row_count = ceil((len(self.playlist_content[tab]) + 1) / 5)
self.music_grid._update_size_hints()
self.music_buttons["add_music"] = self.music_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="Add Music", style=button_style, width=self.window.width * 0.85, height=self.window.height / 30))
self.music_buttons["add_music"].on_click = lambda event: self.add_music()
self.highest_score_file = ""
for n, music_filename in enumerate(self.playlist_content[tab]):
row = n // 5
col = n % 5
self.music_buttons[music_filename] = self.music_grid.add(Card(card_texture=self.thumbnails[music_filename], font_name="Roboto", font_size=13, text=music_filename, width=self.window.width / 6, height=self.window.height / 5), row=row, column=col)
self.music_buttons[music_filename].button.on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(music_filename)
row = (n + 1) // 5
col = (n + 1) % 5
self.music_buttons["add_music"] = self.music_grid.add(Card(card_texture=music_icon, font_name="Roboto", font_size=13, text="Add Music", width=self.window.width / 6, height=self.window.height / 5), row=row, column=col)
self.music_buttons["add_music"].button.on_click = lambda event: self.add_music()
self.update_buttons()
def load_content(self):
self.tab_content.clear()
self.playlist_content.clear()
for tab in self.tab_options:
self.tab_content[os.path.expanduser(tab)] = []
for filename in os.listdir(os.path.expanduser(tab)):
if filename.split(".")[-1] in audio_extensions:
if f"{os.path.expanduser(tab)}/{filename}" not in self.thumbnails:
self.thumbnails[f"{os.path.expanduser(tab)}/{filename}"] = get_audio_thumbnail_texture(f"{os.path.expanduser(tab)}/{filename}", self.window.size)
self.tab_content[os.path.expanduser(tab)].append(filename)
for playlist, content in self.settings_dict.get("playlists", {}).items():
self.playlist_content[playlist] = content
for content in self.settings_dict.get("playlists", {}).values():
for file in content:
if file not in self.thumbnails:
self.thumbnails[file] = get_audio_thumbnail_texture(file, self.window.size)
self.playlist_content = self.settings_dict.get("playlists", {})
def load_tabs(self):
self.tab_box = self.ui_box.add(arcade.gui.UIBoxLayout(size_hint=(0.95, 0.1), space_between=10, vertical=False))
self.tab_box.clear()
if self.current_mode == "files":
for tab in self.tab_options:
self.tab_buttons[os.path.expanduser(tab)] = self.tab_box.add(UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=os.path.basename(os.path.normpath(os.path.expanduser(tab))), style=button_style, width=self.window.width / 10, height=self.window.height / 15))
self.tab_buttons[os.path.expanduser(tab)] = self.tab_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=os.path.basename(os.path.normpath(os.path.expanduser(tab))), style=button_style, width=self.window.width / 10, height=self.window.height / 15))
self.tab_buttons[os.path.expanduser(tab)].on_click = lambda event, tab=os.path.expanduser(tab): self.show_content(tab)
elif self.current_mode == "playlist":
for playlist in self.playlist_content:
self.tab_buttons[playlist] = self.tab_box.add(UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=playlist, style=button_style, width=self.window.width / 10, height=self.window.height / 15))
self.tab_buttons[playlist] = self.tab_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=playlist, style=button_style, width=self.window.width / 10, height=self.window.height / 15))
self.tab_buttons[playlist].on_click = lambda event, playlist=playlist: self.show_content(playlist)
def on_progress_change(self, event):
@@ -388,7 +414,7 @@ class Main(arcade.gui.UIView):
elif symbol == arcade.key.BACKSPACE:
self.search_term = self.search_term[:-1]
if self.current_mode == "files":
self.show_content(self.current_tab)
self.show_content(os.path.expanduser(self.current_tab))
elif self.current_mode == "playlist":
self.show_content(self.current_playlist)
elif symbol == arcade.key.ENTER and self.highest_score_file:
@@ -396,14 +422,14 @@ class Main(arcade.gui.UIView):
self.highest_score_file = ""
self.search_term = ""
if self.current_mode == "files":
self.show_content(self.current_tab)
self.show_content(os.path.expanduser(self.current_tab))
elif self.current_mode == "playlist":
self.show_content(self.current_playlist)
elif symbol == arcade.key.ESCAPE:
self.highest_score_file = ""
self.search_term = ""
if self.current_mode == "files":
self.show_content(self.current_tab)
self.show_content(os.path.expanduser(self.current_tab))
elif self.current_mode == "playlist":
self.show_content(self.current_playlist)
@@ -414,7 +440,7 @@ class Main(arcade.gui.UIView):
self.search_term += text
if self.current_mode == "files":
self.show_content(self.current_tab)
self.show_content(os.path.expanduser(self.current_tab))
elif self.current_mode == "playlist":
self.show_content(self.current_playlist)
@@ -443,8 +469,13 @@ class Main(arcade.gui.UIView):
self.window.show_view(Downloader(self.pypresence_client, self.current_mode, self.current_music_name, self.current_length, self.current_music_player, self.queue, self.loaded_sounds, self.shuffle))
def reload(self):
self.ui.clear()
self.on_show_view()
self.load_content()
if self.current_mode == "files":
self.show_content(self.current_tab)
elif self.current_mode == "playlist":
self.show_content(self.current_playlist)
self.update_buttons()
def update_presence(self, _):

View File

@@ -2,7 +2,6 @@ import arcade, arcade.gui, os, json
from utils.constants import button_style
from utils.preload import button_texture, button_hovered_texture
from utils.utils import UIFocusTextureButton
from arcade.gui.experimental.focus import UIFocusGroup
@@ -34,17 +33,17 @@ class NewTab(arcade.gui.UIView):
self.box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=10), anchor_x="center", anchor_y="center")
if self.current_mode == "files":
self.new_tab_label = self.box.add(arcade.gui.UILabel(text="New Tab Path:", font_name="Protest Strike", font_size=32))
self.new_tab_input = self.box.add(arcade.gui.UIInputText(font_name="Protest Strike", font_size=32, width=self.window.width / 2, height=self.window.height / 10))
self.new_tab_button = self.box.add(UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Add new tab', style=button_style, width=self.window.width / 2, height=self.window.height / 10))
self.new_tab_label = self.box.add(arcade.gui.UILabel(text="New Tab Path:", font_name="Roboto", font_size=32))
self.new_tab_input = self.box.add(arcade.gui.UIInputText(font_name="Roboto", font_size=32, width=self.window.width / 2, height=self.window.height / 10))
self.new_tab_button = self.box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Add new tab', style=button_style, width=self.window.width / 2, height=self.window.height / 10))
self.new_tab_button.on_click = lambda event: self.add_tab()
elif self.current_mode == "playlist":
self.new_tab_label = self.box.add(arcade.gui.UILabel(text="New Playlist Name:", font_name="Protest Strike", font_size=32))
self.new_tab_input = self.box.add(arcade.gui.UIInputText(font_name="Protest Strike", font_size=32, width=self.window.width / 2, height=self.window.height / 10))
self.new_tab_button = self.box.add(UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Add new Playlist', style=button_style, width=self.window.width / 2, height=self.window.height / 10))
self.new_tab_label = self.box.add(arcade.gui.UILabel(text="New Playlist Name:", font_name="Roboto", font_size=32))
self.new_tab_input = self.box.add(arcade.gui.UIInputText(font_name="Roboto", font_size=32, width=self.window.width / 2, height=self.window.height / 10))
self.new_tab_button = self.box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Add new Playlist', style=button_style, width=self.window.width / 2, height=self.window.height / 10))
self.new_tab_button.on_click = lambda event: self.add_tab()
self.back_button = self.anchor.add(UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50), anchor_x="left", anchor_y="top", align_x=5, align_y=-5)
self.back_button = self.anchor.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50), anchor_x="left", anchor_y="top", align_x=5, align_y=-5)
self.back_button.on_click = lambda event: self.main_exit()
self.anchor.detect_focusable_widgets()

View File

@@ -1,9 +1,10 @@
from arcade.gui.widgets.buttons import UITextureButton
import copy, pypresence, json
import arcade, arcade.gui
from utils.constants import button_style, dropdown_style, slider_style, settings, discord_presence_id, settings_start_category
from utils.utils import FakePyPresence, UIFocusTextureButton
from utils.constants import button_style, slider_style, settings, discord_presence_id, settings_start_category
from utils.utils import FakePyPresence
from utils.preload import button_texture, button_hovered_texture
from arcade.gui.experimental.focus import UIFocusGroup
@@ -55,7 +56,7 @@ class Settings(arcade.gui.UIView):
self.ui.push_handlers(self)
self.back_button = UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50)
self.back_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50)
self.back_button.on_click = lambda event: self.main_exit()
self.top_box.add(self.back_button)
@@ -67,7 +68,7 @@ class Settings(arcade.gui.UIView):
def display_categories(self):
for category in settings:
category_button = UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=category, style=button_style, width=self.window.width / 10, height=50)
category_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=category, style=button_style, width=self.window.width / 10, height=50)
if not category == "Credits":
category_button.on_click = lambda event, category=category: self.display_category(category)
@@ -93,27 +94,27 @@ class Settings(arcade.gui.UIView):
self.value_layout.clear()
for setting in settings[category]:
label = arcade.gui.UILabel(text=setting, font_name="Protest Strike", font_size=28, text_color=arcade.color.WHITE )
label = arcade.gui.UILabel(text=setting, font_name="Roboto", font_size=28, text_color=arcade.color.WHITE )
self.key_layout.add(label)
setting_dict = settings[category][setting]
if setting_dict['type'] == "option":
dropdown = arcade.gui.UIDropdown(options=setting_dict['options'], width=200, height=50, default=self.settings_dict.get(setting_dict["config_key"], setting_dict["options"][0]), active_style=dropdown_style, dropdown_style=dropdown_style, primary_style=dropdown_style)
dropdown = arcade.gui.UIDropdown(options=setting_dict['options'], width=200, height=50, default=self.settings_dict.get(setting_dict["config_key"], setting_dict["options"][0]), active_style=button_style, dropdown_style=button_style, primary_style=button_style)
dropdown.on_change = lambda _, setting=setting, dropdown=dropdown: self.update(setting, dropdown.value, "option")
self.value_layout.add(dropdown)
elif setting_dict['type'] == "bool":
button_layout = self.value_layout.add(arcade.gui.UIBoxLayout(space_between=50, vertical=False))
on_radiobutton = UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="ON", style=button_style, width=150, height=50)
on_radiobutton = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="ON", style=button_style, width=150, height=50)
self.on_radiobuttons[setting] = on_radiobutton
on_radiobutton.on_click = lambda _, setting=setting: self.update(setting, True, "bool")
on_radiobutton.on_click = lambda event, setting=setting: self.update(setting, True, "bool")
button_layout.add(on_radiobutton)
off_radiobutton = UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="OFF", style=button_style, width=150, height=50)
off_radiobutton = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="OFF", style=button_style, width=150, height=50)
self.off_radiobuttons[setting] = off_radiobutton
off_radiobutton.on_click = lambda _, setting=setting: self.update(setting, False, "bool")
off_radiobutton.on_click = lambda event, setting=setting: self.update(setting, False, "bool")
button_layout.add(off_radiobutton)
if self.settings_dict.get(setting_dict["config_key"], setting_dict["default"]):
@@ -142,7 +143,7 @@ class Settings(arcade.gui.UIView):
self.sliders[setting] = slider
self.value_layout.add(slider)
self.apply_button = UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Apply', style=button_style, width=200, height=100)
self.apply_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Apply', style=button_style, width=200, height=100)
self.apply_button.on_click = lambda event: self.apply_settings()
self.anchor.add(self.apply_button, anchor_x="right", anchor_y="bottom", align_x=-10, align_y=10)
@@ -205,7 +206,7 @@ class Settings(arcade.gui.UIView):
self.create_layouts()
self.back_button = UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50)
self.back_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50)
self.back_button.on_click = lambda event: self.main_exit()
self.top_box.add(self.back_button)
@@ -275,7 +276,7 @@ class Settings(arcade.gui.UIView):
else:
font_size = 12
self.credits_label = arcade.gui.UILabel(text=text, text_color=arcade.color.WHITE, font_name="Protest Strike", font_size=font_size, align="center", multiline=True)
self.credits_label = arcade.gui.UILabel(text=text, text_color=arcade.color.WHITE, font_name="Roboto", font_size=font_size, align="center", multiline=True)
self.key_layout.add(self.credits_label)

2
run.py
View File

@@ -9,6 +9,8 @@ pyglet.options.debug_gl = False
import logging, datetime, json, sys, arcade
arcade.ArcadeContext.atlas_size = (16384, 16384)
from utils.utils import get_closest_resolution, print_debug_info, on_exception, ErrorView
from utils.constants import log_dir, menu_background_color
from menus.main import Main

View File

@@ -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": {

View File

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

View File

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