mirror of
https://github.com/csd4ni3l/music-player.git
synced 2025-11-05 05:58:17 +01:00
205 lines
6.1 KiB
Python
205 lines
6.1 KiB
Python
import io, tempfile, re, os, logging, arcade, time
|
|
|
|
from mutagen.easyid3 import EasyID3
|
|
from mutagen.id3 import ID3, TXXX, SYLT, ID3NoHeaderError
|
|
|
|
from pydub import AudioSegment
|
|
from PIL import Image
|
|
|
|
from utils.lyrics_metadata import parse_synchronized_lyrics
|
|
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):
|
|
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]
|
|
|
|
try:
|
|
try:
|
|
easyid3 = EasyID3(file_path)
|
|
if "artist" in easyid3:
|
|
artist = easyid3["artist"][0]
|
|
if "title" in easyid3:
|
|
title = easyid3["title"][0]
|
|
if "date" in easyid3:
|
|
upload_year = int(re.match(r"\d{4}", easyid3["date"][0]).group())
|
|
|
|
id3 = ID3(file_path)
|
|
for frame in id3.getall("WXXX"):
|
|
desc = frame.desc.lower()
|
|
if desc == "uploader":
|
|
uploader_url = frame.url
|
|
elif desc == "source":
|
|
source_url = frame.url
|
|
for frame in id3.getall("TXXX"):
|
|
desc = frame.desc.lower()
|
|
if desc == "last_played":
|
|
last_played = float(frame.text[0])
|
|
elif desc == "play_count":
|
|
play_count = int(frame.text[0])
|
|
|
|
if hasattr(easyid3, "info"):
|
|
sound_length = round(easyid3.info.length, 2)
|
|
bitrate = int((easyid3.info.bitrate or 0) / 1000)
|
|
sample_rate = int(easyid3.info.sample_rate / 1000)
|
|
|
|
apic = id3.getall("APIC")
|
|
thumb_image_data = apic[0].data if apic else None
|
|
|
|
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 ID3NoHeaderError:
|
|
pass
|
|
|
|
except Exception as e:
|
|
logging.debug(f"[Metadata/Thumbnail Error] {file_path}: {e}")
|
|
|
|
if artist == "Unknown" or not title:
|
|
m = re.match(r"^(.*?)\s+-\s+(.*?)$", name_only) # check for artist - title titles in the title
|
|
if m:
|
|
artist = m.group(1)
|
|
title = m.group(2)
|
|
|
|
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)
|
|
|
|
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"
|
|
|
|
def add_metadata_to_file(file_path, musicbrainz_artist_ids, artist, title, synchronized_lyrics, isrc, acoustid_id=None):
|
|
easyid3 = EasyID3(file_path)
|
|
easyid3["musicbrainz_artistid"] = musicbrainz_artist_ids
|
|
easyid3["artist"] = artist
|
|
easyid3["title"] = title
|
|
easyid3["isrc"] = isrc
|
|
|
|
if acoustid_id:
|
|
easyid3["acoustid_id"] = acoustid_id
|
|
|
|
try:
|
|
easyid3.save()
|
|
except:
|
|
pass
|
|
|
|
id3 = ID3(file_path)
|
|
id3.delall("SYLT")
|
|
|
|
if synchronized_lyrics:
|
|
lyrics_dict = parse_synchronized_lyrics(synchronized_lyrics)[1]
|
|
synchronized_lyrics_tuples = [(text, int(lyrics_time * 1000)) for lyrics_time, text in lyrics_dict.items()] # * 1000 because format 2 means milliseconds
|
|
|
|
id3.add(SYLT(encoding=3, lang="eng", format=2, type=1, desc="From lrclib", text=synchronized_lyrics_tuples))
|
|
|
|
try:
|
|
id3.save()
|
|
except:
|
|
pass |