This commit is contained in:
Hellow 2023-01-12 23:01:19 +01:00
parent 25eea44d08
commit f7ee24b3a9
7 changed files with 184 additions and 155 deletions

View File

@ -1,10 +1,10 @@
from . import (
song,
id3_mapping,
metadata,
source
)
ID3_MAPPING = id3_mapping.Mapping
ID3_MAPPING = metadata.Mapping
Song = song.Song
Artist = song.Artist

View File

@ -1,68 +0,0 @@
from enum import Enum
class Mapping(Enum):
"""
These frames belong to the id3 standart
"""
# Textframes
TITLE = "TIT2"
ISRC = "TSRC"
LENGTH = "TLEN"
DATE = "TYER"
TRACKNUMBER = "TRCK"
TOTALTRACKS = "TRCK" # Stored in the same frame with TRACKNUMBER, separated by '/': e.g. '4/9'.
TITLESORTORDER = "TSOT"
ENCODING_SETTINGS = "TSSE"
SUBTITLE = "TIT3"
SET_SUBTITLE = "TSST"
RELEASE_DATE = "TDRL"
RECORDING_DATES = "TXXX"
PUBLISHER_URL = "WPUB"
PUBLISHER = "TPUB"
RATING = "POPM"
PAYMEMT_URL = "WPAY"
DISCNUMBER = "TPOS"
MOVEMENT_COUNT = "MVIN"
TOTALDISCS = "TPOS"
ORIGINAL_RELEASE_DATE = "TDOR"
ORIGINAL_ARTIST = "TOPE"
ORIGINAL_ALBUM = "TOAL"
MEDIA_TYPE = "TMED"
LYRICIST = "TEXT"
WRITER = "TEXT"
ARTIST = "TPE1"
LANGUAGE = "TLAN"
ITUNESCOMPILATION = "TCMP"
REMIXED_BY = "TPE4"
RADIO_STATION_OWNER = "TRSO"
RADIO_STATION = "TRSN"
INITIAL_KEY = "TKEY"
OWNER = "TOWN"
ENCODED_BY = "TENC"
COPYRIGHT = "TCOP"
GENRE = "TCON"
GROUPING = "TIT1"
CONDUCTOR = "TPE3"
COMPOSERSORTORDER = "TSOC"
COMPOSER = "TCOM"
BPM = "TBPM"
ALBUM_ARTIST = "TPE2"
BAND = "TPE2"
ARTISTSORTORDER = "TSOP"
ALBUM = "TALB"
ALBUMSORTORDER = "TSOA"
ALBUMARTISTSORTORDER = "TSO2"
SOURCE_WEBPAGE_URL = "WOAS"
FILE_WEBPAGE_URL = "WOAF"
INTERNET_RADIO_WEBPAGE_URL = "WORS"
ARTIST_WEBPAGE_URL = "WOAR"
COPYRIGHT_URL = "WCOP"
COMMERCIAL_INFORMATION_URL = "WCOM"
MOVEMENT_INDEX = "MVIN"
MOVEMENT_NAME = "MVNM"
UNSYNCED_LYRICS = "USLT"
COMMENT = "COMM"

View File

@ -0,0 +1,172 @@
from enum import Enum
from typing import List
from mutagen import id3
from .parents import (
ID3Metadata
)
class Mapping(Enum):
"""
These frames belong to the id3 standart
"""
# Textframes
TITLE = "TIT2"
ISRC = "TSRC"
LENGTH = "TLEN"
DATE = "TYER"
TRACKNUMBER = "TRCK"
TOTALTRACKS = "TRCK" # Stored in the same frame with TRACKNUMBER, separated by '/': e.g. '4/9'.
TITLESORTORDER = "TSOT"
ENCODING_SETTINGS = "TSSE"
SUBTITLE = "TIT3"
SET_SUBTITLE = "TSST"
RELEASE_DATE = "TDRL"
RECORDING_DATES = "TXXX"
PUBLISHER_URL = "WPUB"
PUBLISHER = "TPUB"
RATING = "POPM"
PAYMEMT_URL = "WPAY"
DISCNUMBER = "TPOS"
MOVEMENT_COUNT = "MVIN"
TOTALDISCS = "TPOS"
ORIGINAL_RELEASE_DATE = "TDOR"
ORIGINAL_ARTIST = "TOPE"
ORIGINAL_ALBUM = "TOAL"
MEDIA_TYPE = "TMED"
LYRICIST = "TEXT"
WRITER = "TEXT"
ARTIST = "TPE1"
LANGUAGE = "TLAN"
ITUNESCOMPILATION = "TCMP"
REMIXED_BY = "TPE4"
RADIO_STATION_OWNER = "TRSO"
RADIO_STATION = "TRSN"
INITIAL_KEY = "TKEY"
OWNER = "TOWN"
ENCODED_BY = "TENC"
COPYRIGHT = "TCOP"
GENRE = "TCON"
GROUPING = "TIT1"
CONDUCTOR = "TPE3"
COMPOSERSORTORDER = "TSOC"
COMPOSER = "TCOM"
BPM = "TBPM"
ALBUM_ARTIST = "TPE2"
BAND = "TPE2"
ARTISTSORTORDER = "TSOP"
ALBUM = "TALB"
ALBUMSORTORDER = "TSOA"
ALBUMARTISTSORTORDER = "TSO2"
SOURCE_WEBPAGE_URL = "WOAS"
FILE_WEBPAGE_URL = "WOAF"
INTERNET_RADIO_WEBPAGE_URL = "WORS"
ARTIST_WEBPAGE_URL = "WOAR"
COPYRIGHT_URL = "WCOP"
COMMERCIAL_INFORMATION_URL = "WCOM"
MOVEMENT_INDEX = "MVIN"
MOVEMENT_NAME = "MVNM"
UNSYNCED_LYRICS = "USLT"
COMMENT = "COMM"
@classmethod
def get_text_instance(cls, key: str, value: str):
return id3.Frames[key](encoding=3, text=value)
@classmethod
def get_url_instance(cls, key: str, url: str):
return id3.Frames[key](encoding=3, url=url)
@classmethod
def get_mutagen_instance(cls, attribute, value):
key = attribute.value
if key[0] == 'T':
# a text fiel
return cls.get_text_instance(key, value)
if key[0] == "W":
# an url field
return cls.get_url_instance(key, value)
class Metadata:
"""
Shall only be read or edited via the Song object.
call it like a dict to read/write values
"""
def __init__(self, data: dict = {}) -> None:
# this is pretty self-explanatory
# the key is a 4 letter key from the id3 standards like TITL
self.id3_attributes: Dict[str, list] = {}
# its a null byte for the later concatenation of text frames
self.null_byte = "\x00"
def get_all_metadata(self):
return list(self.id3_attributes.items())
def __setitem__(self, key: str, value: list, override_existing: bool = True):
if len(value) == 0:
return
if type(value) != list:
raise ValueError(f"can only set attribute to list, not {type(value)}")
if override_existing:
self.id3_attributes[key] = value
else:
if key not in self.id3_attributes:
self.id3_attributes[key] = value
return
self.id3_attributes[key].extend(value)
def __getitem__(self, key):
if key not in self.id3_attributes:
return None
return self.id3_attributes[key]
def add_id3_metadata_obj(self, id3_metadata: ID3Metadata, override_existing: bool = True):
metadata_dict = id3_metadata.get_id3_dict()
for field_enum, value in metadata_dict.items():
self.__setitem__(field_enum.value, value, override_existing=override_existing)
def add_many_id3_metadata_obj(self, id3_metadata_list: List[ID3Metadata], override_existing: bool = False):
for id3_metadata in id3_metadata_list:
self.add_id3_metadata_obj(id3_metadata, override_existing=override_existing)
def delete_item(self, key: str):
if key in self.id3_attributes:
return self.id3_attributes.pop(key)
def get_id3_value(self, key: str):
if key not in self.id3_attributes:
return None
list_data = self.id3_attributes[key]
"""
Version 2.4 of the specification prescribes that all text fields (the fields that start with a T, except for TXXX) can contain multiple values separated by a null character.
Thus if above conditions are met, I concatenate the list,
else I take the first element
"""
if key[0].upper() == "T" and key.upper() != "TXXX":
return self.null_byte.join(list_data)
return list_data[0]
def get_mutagen_object(self, key: str):
return Mapping.get_mutagen_instance(Mapping(key), self.get_id3_value(key))
def __iter__(self):
for key in self.id3_attributes:
yield key, self.get_mutagen_object(key)
def __str__(self) -> str:
rows = []
for key, value in self.id3_attributes.items():
rows.append(f"{key} - {str(value)}")
return "\n".join(rows)

View File

@ -2,7 +2,10 @@ import os
from typing import List, Tuple, Dict
from mutagen.easyid3 import EasyID3
from .id3_mapping import Mapping as ID3_MAPPING
from .metadata import (
Mapping as ID3_MAPPING,
Metadata
)
from ...utils.shared import (
MUSIC_DIR,
DATABASE_LOGGER as logger
@ -20,86 +23,6 @@ All Objects dependent
"""
class Metadata:
"""
Shall only be read or edited via the Song object.
call it like a dict to read/write values
"""
def __init__(self, data: dict = {}) -> None:
# this is pretty self explanatory
# the key is a 4 letter key from the id3 standarts like TITL
self.id3_attributes: Dict[str, list] = {}
# its a null byte for the later concatination of text frames
self.null_byte = "\x00"
def get_all_metadata(self):
return list(self.id3_attributes.items())
def __setitem__(self, key: str, value: list, override_existing: bool = True):
if len(value) == 0:
return
if type(value) != list:
raise ValueError(f"can only set attribute to list, not {type(value)}")
# self.id3_attributes[key] = [value[0], "HHHHSSSS"]
if override_existing:
self.id3_attributes[key] = value
else:
if key not in self.id3_attributes:
self.id3_attributes[key] = value
return
self.id3_attributes[key].extend(value)
def __getitem__(self, key):
if key not in self.id3_attributes:
return None
return self.id3_attributes[key]
def add_id3_metadata_obj(self, id3_metadata: ID3Metadata, override_existing: bool = True):
metadata_dict = id3_metadata.get_id3_dict()
for field_enum, value in metadata_dict.items():
self.__setitem__(field_enum.value, value, override_existing=override_existing)
def add_many_id3_metadata_obj(self, id3_metadata_list: List[ID3Metadata], override_existing: bool = False):
for id3_metadata in id3_metadata_list:
self.add_id3_metadata_obj(id3_metadata, override_existing=override_existing)
def delete_item(self, key: str):
if key in self.id3_attributes:
return self.id3_attributes.pop(key)
def get_id3_value(self, key: str):
if key not in self.id3_attributes:
return None
list_data = self.id3_attributes[key]
"""
Version 2.4 of the specification prescribes that all text fields (the fields that start with a T, except for TXXX) can contain multiple values separated by a null character.
Thus if above conditions are met, I concetonate the list,
else I take the first element
"""
if key[0].upper() == "T" and key.upper() != "TXXX":
return self.null_byte.join(list_data)
return list_data[0]
def __iter__(self):
for key in self.id3_attributes:
yield (key, self.get_id3_value(key))
def __str__(self) -> str:
rows = []
for key, value in self.id3_attributes.items():
rows.append(f"{key} - {str(value)}")
return "\n".join(rows)
class Target(DatabaseObject, SongAttribute):
"""
create somehow like that

View File

@ -1,6 +1,6 @@
from enum import Enum
from .id3_mapping import Mapping
from .metadata import Mapping
from .parents import (
DatabaseObject,
SongAttribute,

View File

@ -22,14 +22,15 @@ class AudioMetadata:
self.file_location = file_location
def add_song_metadata(self, song: Song):
print("adding")
for key, value in song.metadata:
"""
https://www.programcreek.com/python/example/84797/mutagen.id3.ID3
"""
self.frames.add(mutagen.id3.Frames[key](encoding=3, text=value))
self.frames.add(value)
def save(self, file_location: str = None):
print("saving")
print(self.frames.pprint())
if file_location is not None:
self.file_location = file_location
@ -41,6 +42,7 @@ class AudioMetadata:
# 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.info(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

Binary file not shown.