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/ 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). 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. Huge Thanks to Python for being the programming language used in this application.
https://www.python.org/ 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 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.preload import button_texture, button_hovered_texture
from utils.utils import UIFocusTextureButton 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.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.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_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=dropdown_style, dropdown_style=dropdown_style, active_style=dropdown_style)) 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="Protest Strike", font_size=32)) 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="Protest Strike", font_size=32, width=self.window.width / 2, height=self.window.height / 10)) 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 = 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() 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 arcade.gui.experimental.focus import UIFocusGroup
from utils.utils import UIFocusTextureButton, ensure_yt_dlp 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 from utils.preload import button_texture, button_hovered_texture
class Downloader(arcade.gui.UIView): 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.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_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="Protest Strike", width=self.window.width / 2, height=self.window.height / 15, 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.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_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=dropdown_style, dropdown_style=dropdown_style, active_style=dropdown_style)) 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)) 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.preload import *
from utils.constants import button_style, slider_style, audio_extensions, discord_presence_id 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 thefuzz import process, fuzz
from pydub import AudioSegment 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_options = self.settings_dict.get("tab_options", ["~/Music", "~/Downloads"])
self.tab_content = {} self.tab_content = {}
self.playlist_content = {} self.playlist_content = {}
self.thumbnails = {}
self.tab_buttons = {} self.tab_buttons = {}
self.music_buttons = {} self.music_buttons = {}
@@ -76,21 +78,26 @@ class Main(arcade.gui.UIView):
def on_show_view(self): def on_show_view(self):
super().on_show_view() 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.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)) self.ui_box = self.anchor.add(arcade.gui.UIBoxLayout(size_hint=(1, 1), space_between=10))
# Tabs # 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: 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.current_playlist = list(self.playlist_content.keys())[0] if self.playlist_content else None
self.load_tabs()
# Scrollable Sounds # 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_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_area.scroll_speed = -50
self.scroll_box.add(self.scroll_area) self.scroll_box.add(self.scroll_area)
@@ -98,8 +105,8 @@ class Main(arcade.gui.UIView):
self.scrollbar.size_hint = (0.02, 1) self.scrollbar.size_hint = (0.02, 1)
self.scroll_box.add(self.scrollbar) self.scroll_box.add(self.scrollbar)
self.music_box = arcade.gui.UIBoxLayout(space_between=2) self.music_grid = arcade.gui.UIGridLayout(horizontal_spacing=30, vertical_spacing=30, row_count=100, column_count=5)
self.scroll_area.add(self.music_box) self.scroll_area.add(self.music_grid)
# Utility # Utility
@@ -125,8 +132,8 @@ class Main(arcade.gui.UIView):
# Controls # Controls
self.control_box = self.ui_box.add(arcade.gui.UIBoxLayout(size_hint=(0.95, 0.1), space_between=10, vertical=False)) 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.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="Protest Strike", 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 = 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 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.progressbar.max_value = self.current_length
self.volume = int(self.current_music_player.volume * 100) 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 = 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 self.volume_slider.on_change = self.on_volume_slider_change
@@ -200,6 +207,8 @@ class Main(arcade.gui.UIView):
self.highest_score_file = "" self.highest_score_file = ""
self.search_term = "" self.search_term = ""
self.load_tabs()
self.reload() self.reload()
def skip_sound(self): def skip_sound(self):
@@ -237,7 +246,7 @@ class Main(arcade.gui.UIView):
self.update_buttons() self.update_buttons()
def show_content(self, tab): def show_content(self, tab):
self.music_box.clear() self.music_grid.clear()
self.music_buttons.clear() self.music_buttons.clear()
if self.current_mode == "files": if self.current_mode == "files":
@@ -245,16 +254,21 @@ class Main(arcade.gui.UIView):
if not self.search_term == "": 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) 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]}" 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] 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] = 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].on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(f"{tab}/{music_filename}") self.music_buttons[music_filename].button.on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(f"{tab}/{music_filename}")
else: else:
self.music_grid.row_count = ceil(len(self.tab_content[tab]) / 5)
self.music_grid._update_size_hints()
self.highest_score_file = "" self.highest_score_file = ""
for music_filename in self.tab_content[tab]: for n, music_filename in enumerate(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)) row = n // 5
self.music_buttons[music_filename].on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(f"{tab}/{music_filename}") 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": elif self.current_mode == "playlist":
self.current_playlist = tab self.current_playlist = tab
@@ -263,45 +277,57 @@ class Main(arcade.gui.UIView):
if not self.search_term == "": 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) 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] self.highest_score_file = matches[0][0]
for match in matches: for n, match in enumerate(matches):
music_filename = match[0] 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] = 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].on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(music_filename) self.music_buttons[music_filename].button.on_click = lambda event, tab=tab, music_filename=music_filename: self.queue.append(music_filename)
else: else:
self.highest_score_file = "" self.music_grid.row_count = ceil((len(self.playlist_content[tab]) + 1) / 5)
for music_filename in self.playlist_content[tab]: self.music_grid._update_size_hints()
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["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.highest_score_file = ""
self.music_buttons["add_music"].on_click = lambda event: self.add_music()
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() self.update_buttons()
def load_content(self): def load_content(self):
self.tab_content.clear()
self.playlist_content.clear()
for tab in self.tab_options: for tab in self.tab_options:
self.tab_content[os.path.expanduser(tab)] = [] self.tab_content[os.path.expanduser(tab)] = []
for filename in os.listdir(os.path.expanduser(tab)): for filename in os.listdir(os.path.expanduser(tab)):
if filename.split(".")[-1] in audio_extensions: 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) self.tab_content[os.path.expanduser(tab)].append(filename)
for playlist, content in self.settings_dict.get("playlists", {}).items(): for content in self.settings_dict.get("playlists", {}).values():
self.playlist_content[playlist] = content 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): 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": if self.current_mode == "files":
for tab in self.tab_options: 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) 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": elif self.current_mode == "playlist":
for playlist in self.playlist_content: 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) self.tab_buttons[playlist].on_click = lambda event, playlist=playlist: self.show_content(playlist)
def on_progress_change(self, event): def on_progress_change(self, event):
@@ -388,7 +414,7 @@ class Main(arcade.gui.UIView):
elif symbol == arcade.key.BACKSPACE: elif symbol == arcade.key.BACKSPACE:
self.search_term = self.search_term[:-1] self.search_term = self.search_term[:-1]
if self.current_mode == "files": 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": elif self.current_mode == "playlist":
self.show_content(self.current_playlist) self.show_content(self.current_playlist)
elif symbol == arcade.key.ENTER and self.highest_score_file: elif symbol == arcade.key.ENTER and self.highest_score_file:
@@ -396,14 +422,14 @@ class Main(arcade.gui.UIView):
self.highest_score_file = "" self.highest_score_file = ""
self.search_term = "" self.search_term = ""
if self.current_mode == "files": 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": elif self.current_mode == "playlist":
self.show_content(self.current_playlist) self.show_content(self.current_playlist)
elif symbol == arcade.key.ESCAPE: elif symbol == arcade.key.ESCAPE:
self.highest_score_file = "" self.highest_score_file = ""
self.search_term = "" self.search_term = ""
if self.current_mode == "files": 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": elif self.current_mode == "playlist":
self.show_content(self.current_playlist) self.show_content(self.current_playlist)
@@ -414,7 +440,7 @@ class Main(arcade.gui.UIView):
self.search_term += text self.search_term += text
if self.current_mode == "files": 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": elif self.current_mode == "playlist":
self.show_content(self.current_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)) 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): def reload(self):
self.ui.clear() self.load_content()
self.on_show_view()
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() self.update_buttons()
def update_presence(self, _): def update_presence(self, _):

View File

@@ -2,7 +2,6 @@ import arcade, arcade.gui, os, json
from utils.constants import button_style from utils.constants import button_style
from utils.preload import button_texture, button_hovered_texture from utils.preload import button_texture, button_hovered_texture
from utils.utils import UIFocusTextureButton
from arcade.gui.experimental.focus import UIFocusGroup 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") self.box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=10), anchor_x="center", anchor_y="center")
if self.current_mode == "files": 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_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="Protest Strike", font_size=32, width=self.window.width / 2, height=self.window.height / 10)) 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(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_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() self.new_tab_button.on_click = lambda event: self.add_tab()
elif self.current_mode == "playlist": 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_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="Protest Strike", font_size=32, width=self.window.width / 2, height=self.window.height / 10)) 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(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_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.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.back_button.on_click = lambda event: self.main_exit()
self.anchor.detect_focusable_widgets() self.anchor.detect_focusable_widgets()

View File

@@ -1,9 +1,10 @@
from arcade.gui.widgets.buttons import UITextureButton
import copy, pypresence, json import copy, pypresence, json
import arcade, arcade.gui import arcade, arcade.gui
from utils.constants import button_style, dropdown_style, slider_style, settings, discord_presence_id, settings_start_category from utils.constants import button_style, slider_style, settings, discord_presence_id, settings_start_category
from utils.utils import FakePyPresence, UIFocusTextureButton from utils.utils import FakePyPresence
from utils.preload import button_texture, button_hovered_texture from utils.preload import button_texture, button_hovered_texture
from arcade.gui.experimental.focus import UIFocusGroup from arcade.gui.experimental.focus import UIFocusGroup
@@ -55,7 +56,7 @@ class Settings(arcade.gui.UIView):
self.ui.push_handlers(self) 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.back_button.on_click = lambda event: self.main_exit()
self.top_box.add(self.back_button) self.top_box.add(self.back_button)
@@ -67,7 +68,7 @@ class Settings(arcade.gui.UIView):
def display_categories(self): def display_categories(self):
for category in settings: 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": if not category == "Credits":
category_button.on_click = lambda event, category=category: self.display_category(category) 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() self.value_layout.clear()
for setting in settings[category]: 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) self.key_layout.add(label)
setting_dict = settings[category][setting] setting_dict = settings[category][setting]
if setting_dict['type'] == "option": 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") dropdown.on_change = lambda _, setting=setting, dropdown=dropdown: self.update(setting, dropdown.value, "option")
self.value_layout.add(dropdown) self.value_layout.add(dropdown)
elif setting_dict['type'] == "bool": elif setting_dict['type'] == "bool":
button_layout = self.value_layout.add(arcade.gui.UIBoxLayout(space_between=50, vertical=False)) 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 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) 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 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) button_layout.add(off_radiobutton)
if self.settings_dict.get(setting_dict["config_key"], setting_dict["default"]): 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.sliders[setting] = slider
self.value_layout.add(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.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) 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.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.back_button.on_click = lambda event: self.main_exit()
self.top_box.add(self.back_button) self.top_box.add(self.back_button)
@@ -275,7 +276,7 @@ class Settings(arcade.gui.UIView):
else: else:
font_size = 12 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) 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 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.utils import get_closest_resolution, print_debug_info, on_exception, ErrorView
from utils.constants import log_dir, menu_background_color from utils.constants import log_dir, menu_background_color
from menus.main import Main from menus.main import Main

View File

@@ -7,47 +7,51 @@ menu_background_color = (17, 17, 17)
log_dir = 'logs' log_dir = 'logs'
discord_presence_id = 1368277020332523530 discord_presence_id = 1368277020332523530
audio_extensions = [ audio_extensions = ["mp3", "m4a", "mp4", "aac", "flac", "ogg", "opus", "wav"]
"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 = { DARK_GRAY = Color(45, 45, 45)
"final_ext": "mp3", GRAY = Color(70, 70, 70)
"format": "bestaudio/best", LIGHT_GRAY = Color(150, 150, 150)
"outtmpl": {"pl_thumbnail": "", "default": "downloaded_music.mp3"}, PRIMARY = Color(0, 189, 126)
"postprocessors": [ PRIMARY_DARK = Color(0, 145, 96)
{ DISABLED = Color(90, 90, 90)
"key": "FFmpegExtractAudio", FONT_COLOR = arcade.color.BLACK
"nopostoverwrites": False, FONT = "Roboto"
"preferredcodec": "mp3", FONT_SIZE = 14
"preferredquality": "5" BIG_FONT_SIZE = 22
},
{ button_style = {
"add_chapters": True, "normal": UIFlatButtonStyle(font_name=FONT, font_size=FONT_SIZE, font_color=FONT_COLOR, bg=GRAY),
"add_infojson": "if_exists", "hover": UIFlatButtonStyle(font_name=FONT, font_size=FONT_SIZE, font_color=FONT_COLOR, bg=PRIMARY),
"add_metadata": True, "press": UIFlatButtonStyle(font_name=FONT, font_size=FONT_SIZE, font_color=FONT_COLOR, bg=PRIMARY_DARK),
"key": "FFmpegMetadata" "disabled": UIFlatButtonStyle(font_name=FONT, font_size=FONT_SIZE, font_color=LIGHT_GRAY, bg=DISABLED),
},
{ "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), big_button_style = {
'press': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK), 'disabled': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK)} "normal": UIFlatButtonStyle(font_name=FONT, font_size=BIG_FONT_SIZE, font_color=FONT_COLOR, bg=GRAY),
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), "hover": UIFlatButtonStyle(font_name=FONT, font_size=BIG_FONT_SIZE, font_color=FONT_COLOR, bg=PRIMARY),
'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)} "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)), slider_default_style = UISliderStyle(
'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))} 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(
slider_hover_style = UISliderStyle(bg=Color(49, 154, 54), unfilled_track=Color(128, 128, 128), filled_track=Color(49, 154, 54)) 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 = { settings = {
"Music": { "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") plus_icon = arcade.load_texture("assets/graphics/plus.png")
playlist_icon = arcade.load_texture("assets/graphics/playlist.png") playlist_icon = arcade.load_texture("assets/graphics/playlist.png")
files_icon = arcade.load_texture("assets/graphics/files.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.easyid3 import EasyID3
from mutagen import File
from PIL import Image
from utils.constants import menu_background_color from utils.constants import menu_background_color
import pyglet import pyglet, arcade, arcade.gui
def dump_platform(): def dump_platform():
import platform import platform
@@ -58,25 +59,6 @@ class ErrorView(arcade.gui.UIView):
msgbox.on_action = lambda event: self.exit() msgbox.on_action = lambda event: self.exit()
self.add_widget(msgbox) 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(): class FakePyPresence():
def __init__(self): def __init__(self):
@@ -97,16 +79,60 @@ 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 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(): def get_yt_dlp_binary_path():
binary = "yt-dlp"
system = platform.system() system = platform.system()
if system == "Windows": if system == "Windows":
binary += ".exe" binary = "yt-dlp.exe"
elif system == "Darwin": elif system == "Darwin":
binary += "_macos" binary = "yt-dlp_macos"
elif system == "Linux": elif system == "Linux":
binary += "_linux" binary = "yt-dlp_linux"
return os.path.join("bin", binary) 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) name_only = re.sub(r'\s*\[[a-zA-Z0-9\-_]{5,}\]$', '', name_only)
try: try:
audio = EasyID3(filename) thumb_audio = EasyID3(filename)
artist = str(audio["artist"][0]) artist = str(thumb_audio["artist"][0])
title = str(audio["title"][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 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 title = name_only
return artist, title 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