mirror of
https://github.com/csd4ni3l/music-player.git
synced 2026-01-01 12:13:42 +01:00
Add a view metadata button, add more metadata to file after download and extract more from files.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
from mutagen.easyid3 import EasyID3
|
from mutagen.id3 import ID3, TIT2, TPE1, WXXX
|
||||||
|
from mutagen.mp3 import MP3
|
||||||
|
|
||||||
import arcade, arcade.gui, os, json, threading, subprocess
|
import arcade, arcade.gui, os, json, threading, subprocess, traceback
|
||||||
|
|
||||||
from arcade.gui.experimental.focus import UIFocusGroup
|
from arcade.gui.experimental.focus import UIFocusGroup
|
||||||
|
|
||||||
@@ -122,9 +123,18 @@ class Downloader(arcade.gui.UIView):
|
|||||||
title = f"{artist} - {track_title}"
|
title = f"{artist} - {track_title}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
audio = EasyID3("downloaded_music.mp3")
|
audio = MP3("downloaded_music.mp3", ID3=ID3)
|
||||||
audio["artist"] = artist
|
if audio.tags is None:
|
||||||
audio["title"] = track_title
|
audio.add_tags()
|
||||||
|
else:
|
||||||
|
for frame_id in ("TIT2", "TPE1", "WXXX"):
|
||||||
|
audio.tags.delall(frame_id)
|
||||||
|
audio.tags.add(TIT2(encoding=3, text=track_title))
|
||||||
|
audio.tags.add(TPE1(encoding=3, text=artist))
|
||||||
|
if info.get("creator_url"):
|
||||||
|
audio.tags.add(WXXX(desc="Uploader", url=info["uploader_url"]))
|
||||||
|
audio.tags.add(WXXX(desc="Source", url=info["webpage_url"]))
|
||||||
|
|
||||||
audio.save()
|
audio.save()
|
||||||
except Exception as meta_err:
|
except Exception as meta_err:
|
||||||
self.yt_dl_buffer = f"ERROR: Tried to override metadata based on title, but failed: {meta_err}"
|
self.yt_dl_buffer = f"ERROR: Tried to override metadata based on title, but failed: {meta_err}"
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import random, asyncio, pypresence, time, copy, json, os, logging
|
import random, asyncio, pypresence, time, copy, json, os, logging, webbrowser
|
||||||
import arcade, pyglet
|
import arcade, 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, ListItem, extract_metadata, get_audio_thumbnail_texture, truncate_end, adjust_volume
|
from utils.utils import FakePyPresence, UIFocusTextureButton, MusicItem, extract_metadata_and_thumbnail, truncate_end, adjust_volume
|
||||||
|
|
||||||
from math import ceil
|
|
||||||
from thefuzz import process, fuzz
|
from thefuzz import process, fuzz
|
||||||
|
|
||||||
from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar
|
from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar
|
||||||
@@ -57,6 +56,7 @@ class Main(arcade.gui.UIView):
|
|||||||
self.tab_options = self.settings_dict.get("tab_options", [os.path.join("~", "Music"), os.path.join("~", "Downloads")])
|
self.tab_options = self.settings_dict.get("tab_options", [os.path.join("~", "Music"), os.path.join("~", "Downloads")])
|
||||||
self.tab_content = {}
|
self.tab_content = {}
|
||||||
self.playlist_content = {}
|
self.playlist_content = {}
|
||||||
|
self.file_metadata = {}
|
||||||
self.thumbnails = {}
|
self.thumbnails = {}
|
||||||
self.tab_buttons = {}
|
self.tab_buttons = {}
|
||||||
self.music_buttons = {}
|
self.music_buttons = {}
|
||||||
@@ -257,10 +257,22 @@ class Main(arcade.gui.UIView):
|
|||||||
self.shuffle = not self.shuffle
|
self.shuffle = not self.shuffle
|
||||||
self.update_buttons()
|
self.update_buttons()
|
||||||
|
|
||||||
|
def metadata_button_action(self, action, metadata):
|
||||||
|
if action != "Close":
|
||||||
|
webbrowser.open(metadata["uploader_url"] if action == "Uploader" else metadata["source_url"])
|
||||||
|
|
||||||
|
def open_metadata(self, file_path):
|
||||||
|
metadata = self.file_metadata[file_path]
|
||||||
|
|
||||||
|
metadata_text = f"File path: {file_path}\nArtist: {metadata['artist']}\nTitle: {metadata['title']}\nSound length: {int(metadata['sound_length'])}\nBitrate: {metadata['bit_rate']}Kbps"
|
||||||
|
|
||||||
|
msgbox = arcade.gui.UIMessageBox(title=f"{metadata['artist']} - {metadata['title']} Metadata", buttons=("Uploader", "Source", "Close"), message_text=metadata_text, width=self.window.width / 2, height=self.window.height / 2)
|
||||||
|
msgbox.on_action = lambda event, metadata=metadata: self.metadata_button_action(event.action, metadata)
|
||||||
|
self.anchor.add(msgbox, anchor_x="center", anchor_y="center")
|
||||||
|
|
||||||
def show_content(self, tab):
|
def show_content(self, tab):
|
||||||
for music_button in self.music_buttons.values():
|
for music_button in self.music_buttons.values():
|
||||||
music_button.remove(music_button.button)
|
music_button.clear()
|
||||||
music_button.remove(music_button.image)
|
|
||||||
self.music_box.remove(music_button)
|
self.music_box.remove(music_button)
|
||||||
del music_button
|
del music_button
|
||||||
|
|
||||||
@@ -272,10 +284,13 @@ 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 match in matches:
|
||||||
music_filename = match[0]
|
music_filename = match[0]
|
||||||
self.music_buttons[f"{tab}/{music_filename}"] = self.music_box.add(ListItem(texture=self.thumbnails[f"{tab}/{music_filename}"], font_name="Roboto", font_size=13, text=music_filename, width=self.window.width / 1.2, height=self.window.height / 11))
|
metadata = self.file_metadata[f"{tab}/{music_filename}"]
|
||||||
self.music_buttons[f"{tab}/{music_filename}"].button.on_click = lambda event, tab=tab, music_filename=music_filename: self.music_button_click(event, f"{tab}/{music_filename}")
|
self.music_buttons[f"{tab}/{music_filename}"] = self.music_box.add(MusicItem(metadata=metadata, texture=self.thumbnails[f"{tab}/{music_filename}"], width=self.window.width / 1.2, height=self.window.height / 11))
|
||||||
|
self.music_buttons[f"{tab}/{music_filename}"].button.on_click = lambda event, music_path=f"{tab}/{music_filename}": self.music_button_click(event, music_path)
|
||||||
|
self.music_buttons[f"{tab}/{music_filename}"].view_metadata_button.on_click = lambda event, music_path=f"{tab}/{music_filename}": self.open_metadata(music_path)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.highest_score_file = ""
|
self.highest_score_file = ""
|
||||||
@@ -283,10 +298,11 @@ class Main(arcade.gui.UIView):
|
|||||||
self.no_music_label.visible = not self.tab_content[tab]
|
self.no_music_label.visible = not self.tab_content[tab]
|
||||||
|
|
||||||
for music_filename in self.tab_content[tab]:
|
for music_filename in self.tab_content[tab]:
|
||||||
self.music_buttons[f"{tab}/{music_filename}"] = self.music_box.add(ListItem(texture=self.thumbnails[f"{tab}/{music_filename}"], font_name="Roboto", font_size=13, text=music_filename, width=self.window.width / 1.2, height=self.window.height / 11))
|
metadata = self.file_metadata[f"{tab}/{music_filename}"]
|
||||||
self.music_buttons[f"{tab}/{music_filename}"].button.on_click = lambda event, tab=tab, music_filename=music_filename: self.music_button_click(event, f"{tab}/{music_filename}")
|
|
||||||
|
|
||||||
self.music_box._update_size_hints()
|
self.music_buttons[f"{tab}/{music_filename}"] = self.music_box.add(MusicItem(metadata=metadata, texture=self.thumbnails[f"{tab}/{music_filename}"], width=self.window.width / 1.2, height=self.window.height / 11))
|
||||||
|
self.music_buttons[f"{tab}/{music_filename}"].button.on_click = lambda event, music_path=f"{tab}/{music_filename}": self.music_button_click(event, music_path)
|
||||||
|
self.music_buttons[f"{tab}/{music_filename}"].view_metadata_button.on_click = lambda event, music_path=f"{tab}/{music_filename}": self.open_metadata(music_path)
|
||||||
|
|
||||||
elif self.current_mode == "playlist":
|
elif self.current_mode == "playlist":
|
||||||
self.current_playlist = tab
|
self.current_playlist = tab
|
||||||
@@ -296,22 +312,24 @@ class Main(arcade.gui.UIView):
|
|||||||
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 match in matches:
|
||||||
music_filename = match[0]
|
music_path = match[0]
|
||||||
self.music_buttons[music_filename] = self.music_box.add(ListItem(texture=self.thumbnails[music_filename], font_name="Roboto", font_size=13, text=music_filename, width=self.window.width / 1.2, height=self.window.height / 11))
|
metadata = self.file_metadata[music_path]
|
||||||
self.music_buttons[music_filename].button.on_click = lambda event, music_filename=music_filename: self.music_button_click(event, music_filename)
|
self.music_buttons[music_path] = self.music_box.add(MusicItem(metadata=metadata, texture=self.thumbnails[music_path], width=self.window.width / 1.2, height=self.window.height / 11))
|
||||||
|
self.music_buttons[music_path].button.on_click = lambda event, music_path=music_path: self.music_button_click(event, music_path)
|
||||||
|
self.music_buttons[music_path].view_metadata_button.on_click = lambda event, music_path=music_path: self.open_metadata(music_path)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.highest_score_file = ""
|
self.highest_score_file = ""
|
||||||
|
|
||||||
self.no_music_label.visible = not self.playlist_content[tab]
|
self.no_music_label.visible = not self.playlist_content[tab]
|
||||||
|
|
||||||
for music_filename in self.playlist_content[tab]:
|
for music_path in self.playlist_content[tab]:
|
||||||
self.music_buttons[music_filename] = self.music_box.add(ListItem(texture=self.thumbnails[music_filename], font_name="Roboto", font_size=13, text=music_filename, width=self.window.width / 1.2, height=self.window.height / 11))
|
metadata = self.file_metadata[music_path]
|
||||||
self.music_buttons[music_filename].button.on_click = lambda event, music_filename=music_filename: self.music_button_click(event, music_filename)
|
self.music_buttons[music_path] = self.music_box.add(MusicItem(metadata=metadata, texture=self.thumbnails[music_path], width=self.window.width / 1.2, height=self.window.height / 11))
|
||||||
|
self.music_buttons[music_path].button.on_click = lambda event, music_path=music_path: self.music_button_click(event, music_path)
|
||||||
|
self.music_buttons[music_path].view_metadata_button.on_click = lambda event, music_path=music_path: self.open_metadata(music_filename)
|
||||||
|
|
||||||
self.music_box._update_size_hints()
|
self.music_buttons["add_music"] = self.music_box.add(MusicItem(metadata=None, texture=music_icon, width=self.window.width / 1.2, height=self.window.height / 11))
|
||||||
|
|
||||||
self.music_buttons["add_music"] = self.music_box.add(ListItem(texture=plus_icon, font_name="Roboto", font_size=13, text="Add Music", width=self.window.width / 1.2, height=self.window.height / 11))
|
|
||||||
self.music_buttons["add_music"].button.on_click = lambda event: self.add_music()
|
self.music_buttons["add_music"].button.on_click = lambda event: self.add_music()
|
||||||
|
|
||||||
self.anchor.detect_focusable_widgets()
|
self.anchor.detect_focusable_widgets()
|
||||||
@@ -334,6 +352,8 @@ class Main(arcade.gui.UIView):
|
|||||||
def load_content(self):
|
def load_content(self):
|
||||||
self.tab_content.clear()
|
self.tab_content.clear()
|
||||||
self.playlist_content.clear()
|
self.playlist_content.clear()
|
||||||
|
self.file_metadata.clear()
|
||||||
|
self.thumbnails.clear()
|
||||||
|
|
||||||
for tab in self.tab_options:
|
for tab in self.tab_options:
|
||||||
expanded_tab = os.path.expanduser(tab)
|
expanded_tab = os.path.expanduser(tab)
|
||||||
@@ -346,8 +366,10 @@ class Main(arcade.gui.UIView):
|
|||||||
|
|
||||||
for filename in os.listdir(expanded_tab):
|
for filename in os.listdir(expanded_tab):
|
||||||
if filename.split(".")[-1] in audio_extensions:
|
if filename.split(".")[-1] in audio_extensions:
|
||||||
if f"{expanded_tab}/{filename}" not in self.thumbnails:
|
if f"{expanded_tab}/{filename}" not in self.file_metadata:
|
||||||
self.thumbnails[f"{expanded_tab}/{filename}"] = get_audio_thumbnail_texture(f"{expanded_tab}/{filename}", self.window.size)
|
sound_length, bit_rate, uploader_url, source_url, artist, title, thumbnail = extract_metadata_and_thumbnail(f"{expanded_tab}/{filename}", (int(self.window.width / 16), int(self.window.height / 9)))
|
||||||
|
self.file_metadata[f"{expanded_tab}/{filename}"] = {"sound_length": sound_length, "bit_rate": bit_rate, "uploader_url": uploader_url, "source_url": source_url, "artist": artist, "title": title}
|
||||||
|
self.thumbnails[f"{expanded_tab}/{filename}"] = thumbnail
|
||||||
self.tab_content[expanded_tab].append(filename)
|
self.tab_content[expanded_tab].append(filename)
|
||||||
|
|
||||||
for playlist, content in self.settings_dict.get("playlists", {}).items():
|
for playlist, content in self.settings_dict.get("playlists", {}).items():
|
||||||
@@ -356,9 +378,10 @@ class Main(arcade.gui.UIView):
|
|||||||
content.remove(file) # also removes reference from self.settings_dict["playlists"]
|
content.remove(file) # also removes reference from self.settings_dict["playlists"]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if file not in self.thumbnails:
|
if file not in self.file_metadata:
|
||||||
self.thumbnails[file] = get_audio_thumbnail_texture(file, self.window.size)
|
sound_length, bit_rate, uploader_url, source_url, artist, title, thumbnail = extract_metadata_and_thumbnail(file, (int(self.window.width / 16), int(self.window.height / 9)))
|
||||||
|
self.file_metadata[file] = {"sound_length": sound_length, "bit_rate": bit_rate, "uploader_url": uploader_url, "source_url": source_url, "artist": artist, "title": title}
|
||||||
|
self.thumbnails[file] = thumbnail
|
||||||
self.playlist_content[playlist] = content
|
self.playlist_content[playlist] = content
|
||||||
|
|
||||||
def load_tabs(self):
|
def load_tabs(self):
|
||||||
@@ -391,7 +414,7 @@ class Main(arcade.gui.UIView):
|
|||||||
if len(self.queue) > 0:
|
if len(self.queue) > 0:
|
||||||
music_path = self.queue.pop(0)
|
music_path = self.queue.pop(0)
|
||||||
|
|
||||||
artist, title = extract_metadata(music_path)
|
artist, title = self.file_metadata[music_path]["artist"], self.file_metadata[music_path]["title"]
|
||||||
|
|
||||||
music_name = f"{artist} - {title}"
|
music_name = f"{artist} - {title}"
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import arcade.color
|
import arcade.color
|
||||||
from arcade.types import Color
|
from arcade.types import Color
|
||||||
from arcade.gui.widgets.buttons import UITextureButtonStyle, UIFlatButtonStyle
|
from arcade.gui.widgets.buttons import UIFlatButtonStyle
|
||||||
from arcade.gui.widgets.slider import UISliderStyle
|
from arcade.gui.widgets.slider import UISliderStyle
|
||||||
|
|
||||||
menu_background_color = (17, 17, 17)
|
menu_background_color = (17, 17, 17)
|
||||||
log_dir = 'logs'
|
log_dir = 'logs'
|
||||||
discord_presence_id = 1368277020332523530
|
discord_presence_id = 1368277020332523530
|
||||||
|
|
||||||
audio_extensions = ["mp3", "m4a", "mp4", "aac", "flac", "ogg", "opus", "wav"]
|
audio_extensions = ["mp3", "m4a", "aac", "flac", "ogg", "opus", "wav"]
|
||||||
|
|
||||||
DARK_GRAY = Color(45, 45, 45)
|
DARK_GRAY = Color(45, 45, 45)
|
||||||
GRAY = Color(70, 70, 70)
|
GRAY = Color(70, 70, 70)
|
||||||
|
|||||||
135
utils/utils.py
135
utils/utils.py
@@ -1,4 +1,4 @@
|
|||||||
import logging, sys, traceback, os, re, platform, urllib.request, io, base64, tempfile
|
import logging, sys, traceback, os, re, platform, urllib.request, io, base64, tempfile, struct
|
||||||
|
|
||||||
from mutagen.easyid3 import EasyID3
|
from mutagen.easyid3 import EasyID3
|
||||||
from mutagen.id3 import ID3
|
from mutagen.id3 import ID3
|
||||||
@@ -87,28 +87,41 @@ class UIFocusTextureButton(arcade.gui.UITextureButton):
|
|||||||
else:
|
else:
|
||||||
self.resize(width=self.width / 1.1, height=self.height / 1.1)
|
self.resize(width=self.width / 1.1, height=self.height / 1.1)
|
||||||
|
|
||||||
class ListItem(arcade.gui.UIBoxLayout):
|
class MusicItem(arcade.gui.UIBoxLayout):
|
||||||
def __init__(self, width: int, height: int, font_name: str, font_size: int, text: str, texture: arcade.Texture, padding=10):
|
def __init__(self, metadata: dict, width: int, height: int, texture: arcade.Texture, padding=10):
|
||||||
super().__init__(width=width, height=height, space_between=padding, align="top", vertical=False)
|
super().__init__(width=width, height=height, space_between=padding, align="top", vertical=False)
|
||||||
|
|
||||||
|
if metadata:
|
||||||
self.image = self.add(arcade.gui.UIImage(
|
self.image = self.add(arcade.gui.UIImage(
|
||||||
texture=texture,
|
texture=texture,
|
||||||
width=width * 0.1,
|
width=height * 1.5,
|
||||||
height=height
|
height=height,
|
||||||
))
|
))
|
||||||
|
|
||||||
self.button = self.add(arcade.gui.UITextureButton(
|
self.button = self.add(arcade.gui.UITextureButton(
|
||||||
text=text,
|
text=f"{metadata['artist']} - {metadata['title']}" if metadata else "Add Music",
|
||||||
texture=button_texture,
|
texture=button_texture,
|
||||||
texture_hovered=button_hovered_texture,
|
texture_hovered=button_hovered_texture,
|
||||||
texture_pressed=button_texture,
|
texture_pressed=button_texture,
|
||||||
texture_disabled=button_texture,
|
texture_disabled=button_texture,
|
||||||
style=button_style,
|
style=button_style,
|
||||||
width=width * 0.9,
|
width=width * 0.85,
|
||||||
height=height,
|
height=height,
|
||||||
interaction_buttons=[arcade.MOUSE_BUTTON_LEFT, arcade.MOUSE_BUTTON_RIGHT]
|
interaction_buttons=[arcade.MOUSE_BUTTON_LEFT, arcade.MOUSE_BUTTON_RIGHT]
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if metadata:
|
||||||
|
self.view_metadata_button = self.add(arcade.gui.UITextureButton(
|
||||||
|
text="View Metadata",
|
||||||
|
texture=button_texture,
|
||||||
|
texture_hovered=button_hovered_texture,
|
||||||
|
texture_pressed=button_texture,
|
||||||
|
texture_disabled=button_texture,
|
||||||
|
style=button_style,
|
||||||
|
width=width * 0.1,
|
||||||
|
height=height,
|
||||||
|
))
|
||||||
|
|
||||||
def on_exception(*exc_info):
|
def on_exception(*exc_info):
|
||||||
logging.error(f"Unhandled exception:\n{''.join(traceback.format_exception(exc_info[1], limit=None))}")
|
logging.error(f"Unhandled exception:\n{''.join(traceback.format_exception(exc_info[1], limit=None))}")
|
||||||
|
|
||||||
@@ -171,88 +184,84 @@ def truncate_end(text: str, max_length: int) -> str:
|
|||||||
return text
|
return text
|
||||||
return text[:max_length - 3] + '...'
|
return text[:max_length - 3] + '...'
|
||||||
|
|
||||||
def extract_metadata(filename):
|
def extract_metadata_and_thumbnail(filename: str, thumb_resolution: tuple) -> tuple:
|
||||||
artist = "Unknown"
|
artist = "Unknown"
|
||||||
title = ""
|
title = ""
|
||||||
|
source_url = "Unknown"
|
||||||
|
creator_url = "Unknown"
|
||||||
|
thumb_texture = None
|
||||||
|
sound_length = 0
|
||||||
|
bit_rate = 0
|
||||||
|
|
||||||
basename = os.path.basename(filename)
|
basename = os.path.basename(filename)
|
||||||
name_only = os.path.splitext(basename)[0]
|
name_only = re.sub(r'\s*\[[a-zA-Z0-9\-_]{5,}\]$', '', os.path.splitext(basename)[0])
|
||||||
|
ext = os.path.splitext(filename)[1].lower().lstrip('.')
|
||||||
name_only = re.sub(r'\s*\[[a-zA-Z0-9\-_]{5,}\]$', '', name_only)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
thumb_audio = EasyID3(filename)
|
thumb_audio = EasyID3(filename)
|
||||||
|
try:
|
||||||
artist = str(thumb_audio["artist"][0])
|
artist = str(thumb_audio["artist"][0])
|
||||||
title = str(thumb_audio["title"][0])
|
title = str(thumb_audio["title"][0])
|
||||||
|
except KeyError:
|
||||||
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)
|
||||||
|
|
||||||
if artist_title_match:
|
if artist_title_match:
|
||||||
title = title.split("- ")[1]
|
title = title.split("- ")[1]
|
||||||
|
|
||||||
if artist != "Unknown" and title:
|
file_audio = File(filename)
|
||||||
return artist, title
|
if hasattr(file_audio, 'info'):
|
||||||
except:
|
sound_length = round(file_audio.info.length, 2)
|
||||||
pass
|
bit_rate = int((file_audio.info.bitrate or 0) / 1000)
|
||||||
|
|
||||||
|
thumb_image_data = None
|
||||||
|
if ext == 'mp3':
|
||||||
|
for tag in file_audio.values():
|
||||||
|
if tag.FrameID == "APIC":
|
||||||
|
thumb_image_data = tag.data
|
||||||
|
break
|
||||||
|
elif ext in ('m4a', 'aac'):
|
||||||
|
if 'covr' in file_audio:
|
||||||
|
thumb_image_data = file_audio['covr'][0]
|
||||||
|
elif ext == 'flac':
|
||||||
|
if file_audio.pictures:
|
||||||
|
thumb_image_data = file_audio.pictures[0].data
|
||||||
|
elif ext in ('ogg', 'opus'):
|
||||||
|
if "metadata_block_picture" in file_audio:
|
||||||
|
pic_data = base64.b64decode(file_audio["metadata_block_picture"][0])
|
||||||
|
header_len = struct.unpack(">I", pic_data[0:4])[0]
|
||||||
|
thumb_image_data = pic_data[4 + header_len:]
|
||||||
|
|
||||||
|
id3 = ID3(filename)
|
||||||
|
for frame in id3.getall("WXXX"):
|
||||||
|
if frame.desc.lower() == "creator":
|
||||||
|
creator_url = frame.url
|
||||||
|
elif frame.desc.lower() == "source":
|
||||||
|
source_url = frame.url
|
||||||
|
|
||||||
|
if thumb_image_data:
|
||||||
|
pil_image = Image.open(io.BytesIO(thumb_image_data)).convert("RGBA")
|
||||||
|
pil_image = pil_image.resize(thumb_resolution)
|
||||||
|
thumb_texture = arcade.Texture(pil_image)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(f"[Metadata/Thumbnail Error] {filename}: {e}")
|
||||||
|
|
||||||
if artist == "Unknown" or not title:
|
if artist == "Unknown" or not title:
|
||||||
match = re.search(r'^(.*?)\s+-\s+(.*?)$', name_only)
|
match = re.search(r'^(.*?)\s+-\s+(.*?)$', name_only)
|
||||||
if match:
|
if match:
|
||||||
filename_artist, filename_title = match.groups()
|
filename_artist, filename_title = match.groups()
|
||||||
|
|
||||||
if artist == "Unknown":
|
if artist == "Unknown":
|
||||||
artist = filename_artist
|
artist = filename_artist
|
||||||
|
|
||||||
if not title:
|
if not title:
|
||||||
title = filename_title
|
title = filename_title
|
||||||
|
|
||||||
return artist, title
|
|
||||||
|
|
||||||
if not title:
|
if not title:
|
||||||
title = name_only
|
title = name_only
|
||||||
|
|
||||||
return artist, title
|
if thumb_texture is None:
|
||||||
|
|
||||||
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
|
from utils.preload import music_icon
|
||||||
return music_icon
|
thumb_texture = music_icon
|
||||||
|
|
||||||
|
return sound_length, bit_rate, creator_url, source_url, artist, title, thumb_texture
|
||||||
|
|
||||||
def adjust_volume(input_path, volume):
|
def adjust_volume(input_path, volume):
|
||||||
try:
|
try:
|
||||||
|
|||||||
6
uv.lock
generated
6
uv.lock
generated
@@ -307,9 +307,9 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.13.2"
|
version = "4.14.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload_time = "2025-04-10T14:19:05.416Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload_time = "2025-06-02T14:52:11.399Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload_time = "2025-04-10T14:19:03.967Z" },
|
{ url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload_time = "2025-06-02T14:52:10.026Z" },
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user