metadata
This commit is contained in:
parent
4abb17ca8e
commit
bc69cac27c
@ -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 performance’s 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 performance’s 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)
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user