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) timestamp: str = property(fget=get_timestamp)
class Metadata: class MetadataAttribute:
""" """
Shall only be read or edited via the Song object. This class shall be added to any object, which can return data for tagging
call it like a dict to read/write values
""" """
def __init__(self, id3_dict: dict = None) -> None: class Metadata:
# 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)
# its a null byte for the later concatenation of text frames # 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): def __init__(self, id3_dict: Dict[any, list] = None) -> None:
if len(value) == 0: if id3_dict is not None:
return self.add_metadata_dict(id3_dict)
if type(value) != list:
raise ValueError(f"can only set attribute to list, not {type(value)}")
if override_existing: def __setitem__(self, frame, value_list: list, override_existing: bool = True):
new_val = [] if len(value_list) == 0:
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
return 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)}")
new_val = [i for i in value_list if i is not None]
if len(new_val) == 0:
return
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 __getitem__(self, key): self.id3_attributes[frame].extend(new_val)
if key not in self.id3_attributes:
return None
return self.id3_attributes[key]
def add_metadata_dict(self, metadata_dict: dict, override_existing: bool = True): def __getitem__(self, key):
for field_enum, value in metadata_dict.items(): if key not in self.id3_dict:
self.__setitem__(field_enum.value, value, override_existing=override_existing) return None
return self.id3_dict[key]
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)
def delete_item(self, key: str): def delete_field(self, key: str):
if key in self.id3_attributes: if key in self.id3_attributes:
return self.id3_attributes.pop(key) return self.id3_attributes.pop(key)
def get_id3_value(self, key: str): def add_metadata_dict(self, metadata_dict: dict, override_existing: bool = True):
if key not in self.id3_attributes: for field_enum, value in metadata_dict.items():
return None self.__setitem__(field_enum.value, value, override_existing=override_existing)
list_data = self.id3_attributes[key] 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)
# convert for example the time objects to timestamps def get_id3_value(self, field):
for i, element in enumerate(list_data): if field not in self.id3_attributes:
# for performances sake I don't do other checks if it is already the right type return None
if type(element) == str:
continue
if type(element) == ID3Timestamp: list_data = self.id3_dict[field]
list_data[i] = element.timestamp
continue
# 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. this is intendet to be overwritten by the child class
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.Metadata()
return self.null_byte.join(list_data)
return list_data[0] metadata = property(fget=get_metadata)
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)

View File

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

View File

@ -86,7 +86,7 @@ class SourceAttribute:
""" """
adds a new Source to the sources 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]: def get_sources_from_page(self, page_enum) -> List[Source]:
""" """
@ -99,7 +99,13 @@ class SourceAttribute:
""" """
gets all sources 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]]: def get_source_dict(self) -> Dict[any: List[Source]]:
""" """
@ -109,5 +115,5 @@ class SourceAttribute:
""" """
return self._source_dict 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) source_dict: Dict[any: List[Source]] = property(fget=get_source_dict)