diff --git a/src/music_kraken/database/objects/__init__.py b/src/music_kraken/database/objects/__init__.py index 50f2e5c..0f77c78 100644 --- a/src/music_kraken/database/objects/__init__.py +++ b/src/music_kraken/database/objects/__init__.py @@ -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 diff --git a/src/music_kraken/database/objects/id3_mapping.py b/src/music_kraken/database/objects/id3_mapping.py deleted file mode 100644 index d5da52e..0000000 --- a/src/music_kraken/database/objects/id3_mapping.py +++ /dev/null @@ -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" - \ No newline at end of file diff --git a/src/music_kraken/database/objects/metadata.py b/src/music_kraken/database/objects/metadata.py new file mode 100644 index 0000000..5e45970 --- /dev/null +++ b/src/music_kraken/database/objects/metadata.py @@ -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) diff --git a/src/music_kraken/database/objects/song.py b/src/music_kraken/database/objects/song.py index 49e3b69..b4030b5 100644 --- a/src/music_kraken/database/objects/song.py +++ b/src/music_kraken/database/objects/song.py @@ -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 @@ -285,7 +208,7 @@ class Song(DatabaseObject): self._sources = source_list for source in self._sources: source.add_song(self) - + # self.metadata[ID3_MAPPING.FILE_WEBPAGE_URL.value] = [s.url for s in self._sources] self.metadata.add_many_id3_metadata_obj(self._sources) diff --git a/src/music_kraken/database/objects/source.py b/src/music_kraken/database/objects/source.py index 3d84a3c..b7cf573 100644 --- a/src/music_kraken/database/objects/source.py +++ b/src/music_kraken/database/objects/source.py @@ -1,6 +1,6 @@ from enum import Enum -from .id3_mapping import Mapping +from .metadata import Mapping from .parents import ( DatabaseObject, SongAttribute, diff --git a/src/music_kraken/tagging/id3.py b/src/music_kraken/tagging/id3.py index e55319c..a037897 100644 --- a/src/music_kraken/tagging/id3.py +++ b/src/music_kraken/tagging/id3.py @@ -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 diff --git a/src/test.db b/src/test.db index 6445b10..4e3ac46 100644 Binary files a/src/test.db and b/src/test.db differ