This commit is contained in:
Lars Noack 2023-01-27 12:56:59 +01:00
parent 4abb17ca8e
commit bc69cac27c
3 changed files with 135 additions and 125 deletions

View File

@ -247,100 +247,123 @@ class ID3Timestamp:
timestamp: str = property(fget=get_timestamp)
class Metadata:
class MetadataAttribute:
"""
Shall only be read or edited via the Song object.
call it like a dict to read/write values
This class shall be added to any object, which can return data for tagging
"""
def __init__(self, id3_dict: dict = None) -> 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] = {}
if id3_dict is not None:
self.add_metadata_dict(id3_dict)
class Metadata:
# its a null byte for the later concatenation of text frames
self.null_byte = "\x00"
NULL_BYTE: str = "\x00"
# this is pretty self-explanatory
# the key is an enum from Mapping
# the value is a list with each value
# the mutagen object for each frame will be generated dynamically
id3_dict: Dict[any, list] = dict()
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)}")
def __init__(self, id3_dict: Dict[any, list] = None) -> None:
if id3_dict is not None:
self.add_metadata_dict(id3_dict)
if override_existing:
new_val = []
for elem in value:
if elem is not None:
new_val.append(elem)
if len(new_val) > 0:
self.id3_attributes[key] = new_val
else:
if key not in self.id3_attributes:
self.id3_attributes[key] = value
def __setitem__(self, frame, value_list: list, override_existing: bool = True):
if len(value_list) == 0:
return
self.id3_attributes[key].extend(value)
if type(value_list) != list:
raise ValueError(f"can only set attribute to list, not {type(value_list)}")
def __getitem__(self, key):
if key not in self.id3_attributes:
return None
return self.id3_attributes[key]
new_val = [i for i in value_list if i is not None]
def add_metadata_dict(self, metadata_dict: dict, override_existing: bool = True):
for field_enum, value in metadata_dict.items():
self.__setitem__(field_enum.value, value, override_existing=override_existing)
if len(new_val) == 0:
return
def add_many_metadata_dict(self, id3_metadata_list: List[dict], override_existing: bool = False):
for metadata_dict in id3_metadata_list:
self.add_metadata_dict(metadata_dict, override_existing=override_existing)
if override_existing:
self.id3_dict[frame] = new_val
else:
if frame not in self.id3_dict:
self.id3_dict[frame] = new_val
return
def delete_item(self, key: str):
if key in self.id3_attributes:
return self.id3_attributes.pop(key)
self.id3_attributes[frame].extend(new_val)
def get_id3_value(self, key: str):
if key not in self.id3_attributes:
return None
def __getitem__(self, key):
if key not in self.id3_dict:
return None
return self.id3_dict[key]
list_data = self.id3_attributes[key]
# convert for example the time objects to timestamps
for i, element in enumerate(list_data):
# for performances sake I don't do other checks if it is already the right type
if type(element) == str:
continue
def delete_field(self, key: str):
if key in self.id3_attributes:
return self.id3_attributes.pop(key)
if type(element) == ID3Timestamp:
list_data[i] = element.timestamp
continue
def add_metadata_dict(self, metadata_dict: dict, override_existing: bool = True):
for field_enum, value in metadata_dict.items():
self.__setitem__(field_enum.value, value, override_existing=override_existing)
def merge(self, other, override_existing: bool = False):
"""
adds the values of another metadata obj to this one
"""
self.add_metadata_dict(other.id3_dict, override_existing=override_existing)
def merge_many(self, many_other):
"""
adds the values of many other metadata objects to this one
"""
for other in many_other:
self.merge(other)
def get_id3_value(self, field):
if field not in self.id3_attributes:
return None
list_data = self.id3_dict[field]
# convert for example the time objects to timestamps
for i, element in enumerate(list_data):
# for performances sake I don't do other checks if it is already the right type
if type(element) == str:
continue
if type(element) == ID3Timestamp:
list_data[i] = element.timestamp
continue
"""
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 field.value[0].upper() == "T" and field.value.upper() != "TXXX":
return self.null_byte.join(list_data)
return list_data[0]
def get_mutagen_object(self, field):
return Mapping.get_mutagen_instance(field, self.get_id3_value(field))
def __str__(self) -> str:
rows = []
for key, value in self.id3_attributes.items():
rows.append(f"{key} - {str(value)}")
return "\n".join(rows)
def __iter__(self):
"""
returns a generator, you can iterate through,
to directly tagg a file with id3 container.
"""
# set the tagging timestamp to the current time
self.__setitem__(Mapping.TAGGING_TIME.value, [ID3Timestamp.now()])
for field in self.id3_attributes:
yield self.get_mutagen_object(field)
def get_metadata(self) -> Metadata:
"""
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
this is intendet to be overwritten by the child class
"""
if key[0].upper() == "T" and key.upper() != "TXXX":
return self.null_byte.join(list_data)
return self.Metadata()
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):
# set the tagging timestamp to the current time
self.__setitem__(Mapping.TAGGING_TIME.value, [ID3Timestamp.now()])
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)
metadata = property(fget=get_metadata)

View File

@ -21,7 +21,8 @@ from .parents import (
from .source import (
Source,
SourceTypes,
SourcePages
SourcePages,
SourceAttribute
)
"""
@ -79,15 +80,24 @@ class Target(DatabaseObject, SongAttribute):
exists_on_disc = property(fget=get_exists_on_disc)
class Lyrics(DatabaseObject, SongAttribute):
def __init__(self, text: str, language: str, id_: str = None) -> None:
class Lyrics(DatabaseObject, SongAttribute, SourceAttribute):
def __init__(
self,
text: str,
language: str,
id_: str = None,
source_list: List[Source] = None
) -> None:
DatabaseObject.__init__(self, id_=id_)
SongAttribute.__init__(self)
self.text = text
self.language = language
if source_list is not None:
self.source_list = source_list
class Song(DatabaseObject, ID3Metadata):
class Song(DatabaseObject, ID3Metadata, SourceAttribute):
def __init__(
self,
id_: str = None,
@ -98,7 +108,7 @@ class Song(DatabaseObject, ID3Metadata):
length: int = None,
tracksort: int = None,
genre: str = None,
sources: List[Source] = None,
source_list: List[Source] = None,
target: Target = None,
lyrics: List[Lyrics] = None,
album=None,
@ -122,8 +132,8 @@ class Song(DatabaseObject, ID3Metadata):
self.tracksort: int | None = tracksort
self.genre: str = genre
self._sources: List[Source] = []
self.sources = sources
if source_list:
self.source_list = source_list
self.album = album
@ -187,7 +197,7 @@ class Song(DatabaseObject, ID3Metadata):
def get_metadata(self) -> Metadata:
metadata = Metadata(self.get_id3_dict())
metadata.add_many_metadata_dict([source.get_id3_dict() for source in self.sources])
metadata.add_many_metadata_dict([source.get_id3_dict() for source in self.source_list])
if self.album is not None:
metadata.add_metadata_dict(self.album.get_id3_dict())
metadata.add_many_metadata_dict([artist.get_id3_dict() for artist in self.main_artist_list])
@ -195,15 +205,6 @@ class Song(DatabaseObject, ID3Metadata):
return metadata
def set_sources(self, source_list: List[Source]):
if source_list is None:
return
self._sources = source_list
for source in self._sources:
source.type_enum = SourceTypes.SONG
sources: List[Source] = property(fget=lambda self: self._sources, fset=set_sources)
metadata = property(fget=get_metadata)
@ -212,7 +213,7 @@ All objects dependent on Album
"""
class Album(DatabaseObject, ID3Metadata):
class Album(DatabaseObject, ID3Metadata, SourceAttribute):
"""
-------DB-FIELDS-------
title TEXT,
@ -240,7 +241,7 @@ class Album(DatabaseObject, ID3Metadata):
is_split: bool = False,
albumsort: int = None,
dynamic: bool = False,
sources: List[Source] = None,
source_list: List[Source] = None,
artists: list = None
) -> None:
DatabaseObject.__init__(self, id_=id_, dynamic=dynamic)
@ -261,8 +262,8 @@ class Album(DatabaseObject, ID3Metadata):
self.tracklist: List[Song] = []
self._sources = []
self.sources = sources
if source_list is not None:
self.source_list = source_list
self.artists = []
if artists is not None:
@ -312,16 +313,6 @@ class Album(DatabaseObject, ID3Metadata):
return self.language.alpha_3
def set_sources(self, source_list: List[Source]):
if source_list is None:
return
self._sources = source_list
for source in self._sources:
source.add_song(self)
source.type_enum = SourceTypes.ALBUM
sources: List[Source] = property(fget=lambda self: self._sources, fset=set_sources)
copyright = property(fget=get_copyright)
iso_639_2_language = property(fget=get_iso_639_2_lang)
@ -332,7 +323,7 @@ All objects dependent on Artist
"""
class Artist(DatabaseObject, ID3Metadata):
class Artist(DatabaseObject, ID3Metadata, SourceAttribute):
"""
main_songs
feature_song
@ -344,7 +335,7 @@ class Artist(DatabaseObject, ID3Metadata):
self,
id_: str = None,
name: str = None,
sources: List[Source] = None,
source_list: List[Source] = None,
main_songs: List[Song] = None,
feature_songs: List[Song] = None,
main_albums: List[Album] = None,
@ -368,9 +359,8 @@ class Artist(DatabaseObject, ID3Metadata):
self.main_albums = main_albums
self._sources = []
self.sources = sources
if source_list is not None:
self.source_list = source_list
def __str__(self):
return self.name or ""
@ -427,16 +417,7 @@ class Artist(DatabaseObject, ID3Metadata):
return id3_dict
def set_sources(self, source_list: List[Source]):
if source_list is None:
return
self._sources = source_list
for source in self._sources:
source.add_song(self)
source.type_enum = SourceTypes.ARTIST
sources: List[Source] = property(fget=lambda self: self._sources, fset=set_sources)
discography: List[Album] = property(fget=get_discography)
features: Album = property(fget=get_features)
songs: Album = property(fget=get_songs)

View File

@ -86,7 +86,7 @@ class SourceAttribute:
"""
adds a new Source to the sources
"""
pass
self._source_dict[source.page_enum].append(source)
def get_sources_from_page(self, page_enum) -> List[Source]:
"""
@ -99,7 +99,13 @@ class SourceAttribute:
"""
gets all sources
"""
return []
return [item for _, item in self._source_dict.items()]
def set_source_list(self, source_list: List[Source]):
self._source_dict = {page_enum: list() for page_enum in SourcePages}
for source in source_list:
self.add_source(source)
def get_source_dict(self) -> Dict[any: List[Source]]:
"""
@ -109,5 +115,5 @@ class SourceAttribute:
"""
return self._source_dict
source_list: List[Source] = property(fget=get_source_list)
source_list: List[Source] = property(fget=get_source_list, fset=set_source_list)
source_dict: Dict[any: List[Source]] = property(fget=get_source_dict)