mirror of
https://github.com/csd4ni3l/music-player.git
synced 2026-01-01 04:03:42 +01:00
194 lines
6.0 KiB
Python
194 lines
6.0 KiB
Python
import io, base64, tempfile, struct, re, os, logging, arcade, time
|
|
|
|
from mutagen.easyid3 import EasyID3
|
|
from mutagen.id3 import ID3, TXXX, ID3NoHeaderError
|
|
from mutagen import File
|
|
|
|
from pydub import AudioSegment
|
|
from PIL import Image
|
|
|
|
from utils.utils import convert_seconds_to_date
|
|
|
|
def truncate_end(text: str, max_length: int) -> str:
|
|
if len(text) <= max_length:
|
|
return text
|
|
if max_length <= 3:
|
|
return text
|
|
return text[:max_length - 3] + '...'
|
|
|
|
def extract_metadata_and_thumbnail(file_path: str, thumb_resolution: tuple) -> tuple:
|
|
artist = "Unknown"
|
|
title = ""
|
|
source_url = "Unknown"
|
|
uploader_url = "Unknown"
|
|
thumb_texture = None
|
|
sound_length = 0
|
|
bitrate = 0
|
|
sample_rate = 0
|
|
last_played = 0
|
|
play_count = 0
|
|
upload_year = 0
|
|
|
|
basename = os.path.basename(file_path)
|
|
name_only = os.path.splitext(basename)[0]
|
|
ext = os.path.splitext(file_path)[1].lower().lstrip('.')
|
|
|
|
try:
|
|
thumb_audio = EasyID3(file_path)
|
|
try:
|
|
artist = str(thumb_audio["artist"][0])
|
|
title = str(thumb_audio["title"][0])
|
|
upload_year = int(thumb_audio["date"][0])
|
|
except KeyError:
|
|
artist_title_match = re.search(r'^.+\s*-\s*.+$', title)
|
|
if artist_title_match:
|
|
title = title.split("- ")[1]
|
|
|
|
file_audio = File(file_path)
|
|
if hasattr(file_audio, 'info'):
|
|
sound_length = round(file_audio.info.length, 2)
|
|
bitrate = int((file_audio.info.bitrate or 0) / 1000)
|
|
sample_rate = int(file_audio.info.sample_rate / 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(file_path)
|
|
for frame in id3.getall("WXXX"):
|
|
if frame.desc.lower() == "uploader":
|
|
uploader_url = frame.url
|
|
elif frame.desc.lower() == "source":
|
|
source_url = frame.url
|
|
|
|
for frame in id3.getall("TXXX"):
|
|
if frame.desc.lower() == "last_played":
|
|
last_played = float(frame.text[0])
|
|
elif frame.desc.lower() == "play_count":
|
|
play_count = int(frame.text[0])
|
|
|
|
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] {file_path}: {e}")
|
|
|
|
if artist == "Unknown" or not title:
|
|
match = re.search(r'^(.*?)\s+-\s+(.*?)$', name_only)
|
|
if match:
|
|
file_path_artist, file_path_title = match.groups()
|
|
if artist == "Unknown":
|
|
artist = file_path_artist
|
|
if not title:
|
|
title = file_path_title
|
|
|
|
if not title:
|
|
title = name_only
|
|
|
|
if thumb_texture is None:
|
|
from utils.preload import music_icon
|
|
thumb_texture = music_icon
|
|
|
|
file_size = round(os.path.getsize(file_path) / (1024 ** 2), 2) # MiB
|
|
|
|
return {
|
|
"sound_length": sound_length,
|
|
"bitrate": bitrate,
|
|
"file_size": file_size,
|
|
"last_played": last_played,
|
|
"play_count": play_count,
|
|
"upload_year": upload_year,
|
|
"sample_rate": sample_rate,
|
|
"uploader_url": uploader_url,
|
|
"source_url": source_url,
|
|
"artist": artist,
|
|
"title": title,
|
|
"thumbnail": thumb_texture
|
|
}
|
|
|
|
def adjust_volume(input_path, volume):
|
|
audio = AudioSegment.from_file(input_path)
|
|
change = volume - audio.dBFS
|
|
|
|
if abs(change) < 1.0:
|
|
return
|
|
|
|
try:
|
|
easy_tags = EasyID3(input_path)
|
|
tags = dict(easy_tags)
|
|
tags = {k: v[0] if isinstance(v, list) else v for k, v in tags.items()}
|
|
except Exception as e:
|
|
tags = {}
|
|
|
|
try:
|
|
id3 = ID3(input_path)
|
|
apic_frames = [f for f in id3.values() if f.FrameID == "APIC"]
|
|
cover_path = None
|
|
if apic_frames:
|
|
apic = apic_frames[0]
|
|
ext = ".jpg" if apic.mime == "image/jpeg" else ".png"
|
|
temp_cover = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
|
|
temp_cover.write(apic.data)
|
|
temp_cover.close()
|
|
cover_path = temp_cover.name
|
|
else:
|
|
cover_path = None
|
|
except Exception as e:
|
|
cover_path = None
|
|
|
|
audio = audio.apply_gain(change)
|
|
|
|
export_args = {
|
|
"format": "mp3",
|
|
"tags": tags
|
|
}
|
|
if cover_path:
|
|
export_args["cover"] = cover_path
|
|
|
|
audio.export(input_path, **export_args)
|
|
|
|
def update_last_play_statistics(filepath):
|
|
try:
|
|
audio = ID3(filepath)
|
|
except ID3NoHeaderError:
|
|
audio = ID3()
|
|
|
|
audio.setall("TXXX:last_played", [TXXX(desc="last_played", text=str(time.time()))])
|
|
|
|
play_count_frames = audio.getall("TXXX:play_count")
|
|
if play_count_frames:
|
|
try:
|
|
count = int(play_count_frames[0].text[0])
|
|
except (ValueError, IndexError):
|
|
count = 0
|
|
else:
|
|
count = 0
|
|
|
|
audio.setall("TXXX:play_count", [TXXX(desc="play_count", text=str(count + 1))])
|
|
|
|
audio.save(filepath)
|
|
|
|
def convert_timestamp_to_time_ago(timestamp):
|
|
current_timestamp = time.time()
|
|
elapsed_time = current_timestamp - timestamp
|
|
if not timestamp == 0:
|
|
return convert_seconds_to_date(elapsed_time) + ' ago'
|
|
else:
|
|
return "Never"
|