music-kraken-core/music_kraken/audio/metadata.py
Lars Noack 130f5edcfe
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
draft: rewrite of interface
2024-06-05 12:02:12 +02:00

139 lines
4.7 KiB
Python

import logging
from pathlib import Path
from typing import List
import mutagen
from mutagen.id3 import APIC, ID3, USLT, Frame
from PIL import Image
from ..connection import Connection
from ..objects import Metadata, Song, Target
from ..objects.metadata import Mapping
from ..utils.config import logging_settings, main_settings
LOGGER = logging_settings["tagging_logger"]
artwork_connection: Connection = Connection()
class AudioMetadata:
def __init__(self, file_location: str = None) -> None:
self._file_location = None
self.frames: ID3 = ID3()
if file_location is not None:
self.file_location = file_location
def add_metadata(self, metadata: Metadata):
for value in metadata:
"""
https://www.programcreek.com/python/example/84797/mutagen.id3.ID3
"""
if value is None:
continue
self.frames.add(value)
def add_song_metadata(self, song: Song):
self.add_metadata(song.metadata)
def save(self, file_location: Path = None):
LOGGER.debug(f"saving following frames: {self.frames.pprint()}")
if file_location is not None:
self.file_location = file_location
if self.file_location is None:
raise Exception("no file target provided to save the data to")
self.frames.save(self.file_location, v2_version=4)
def set_file_location(self, file_location: Path):
# try loading the data from the given file. if it doesn't succeed the frame remains empty
try:
self.frames.load(file_location, v2_version=4)
LOGGER.debug(f"loaded following from \"{file_location}\"\n{self.frames.pprint()}")
except mutagen.MutagenError:
LOGGER.warning(f"couldn't find any metadata at: \"{self.file_location}\"")
self._file_location = file_location
file_location = property(fget=lambda self: self._file_location, fset=set_file_location)
def write_metadata_to_target(metadata: Metadata, target: Target, song: Song):
if not target.exists:
LOGGER.warning(f"file {target.file_path} not found")
return
id3_object = AudioMetadata(file_location=target.file_path)
LOGGER.info(str(metadata))
if song.artwork.best_variant is not None:
best_variant = song.artwork.best_variant
r = artwork_connection.get(
url=best_variant["url"],
name=song.artwork.get_variant_name(best_variant),
)
temp_target: Target = Target.temp()
with temp_target.open("wb") as f:
f.write(r.content)
converted_target: Target = Target.temp(name=f"{song.title.replace('/', '_')}")
with Image.open(temp_target.file_path) as img:
# crop the image if it isn't square in the middle with minimum data loss
width, height = img.size
if width != height:
if width > height:
img = img.crop((width // 2 - height // 2, 0, width // 2 + height // 2, height))
else:
img = img.crop((0, height // 2 - width // 2, width, height // 2 + width // 2))
# resize the image to the preferred resolution
img.thumbnail((main_settings["preferred_artwork_resolution"], main_settings["preferred_artwork_resolution"]))
# https://stackoverflow.com/a/59476938/16804841
if img.mode != 'RGB':
img = img.convert('RGB')
img.save(converted_target.file_path, "JPEG")
# https://stackoverflow.com/questions/70228440/mutagen-how-can-i-correctly-embed-album-art-into-mp3-file-so-that-i-can-see-t
id3_object.frames.delall("APIC")
id3_object.frames.add(
APIC(
encoding=0,
mime="image/jpeg",
type=mutagen.id3.PictureType.COVER_FRONT,
desc=u"Cover",
data=converted_target.read_bytes(),
)
)
id3_object.frames.delall("USLT")
uslt_val = metadata.get_id3_value(Mapping.UNSYNCED_LYRICS)
id3_object.frames.add(
USLT(encoding=3, lang=u'eng', desc=u'desc', text=uslt_val)
)
id3_object.add_metadata(metadata)
id3_object.save()
def write_metadata(song: Song, ignore_file_not_found: bool = True):
target: Target
for target in song.target:
if not target.exists:
if ignore_file_not_found:
continue
else:
raise ValueError(f"{song.target.file} not found")
write_metadata_to_target(metadata=song.metadata, target=target, song=song)
def write_many_metadata(song_list: List[Song]):
for song in song_list:
write_metadata(song=song, ignore_file_not_found=True)