fixed readme + id3 timestamps

This commit is contained in:
Hellow 2023-01-13 14:37:15 +01:00
parent 8a7335c741
commit 54a4be29ea
7 changed files with 141 additions and 35 deletions

View File

@ -24,6 +24,11 @@ pip install music-kraken
music-kraken music-kraken
``` ```
### Notes for Python 3.9
Unfortunately I use features that newly git introduced in [Python 3.10](https://docs.python.org/3/library/types.html#types.UnionType).
So unfortunately you **CAN'T** run this programm with python 3.9. [#10][i10]
### Notes for WSL ### Notes for WSL
If you choose to run it in WSL, make sure ` ~/.local/bin` is added to your `$PATH` [#2][i2] If you choose to run it in WSL, make sure ` ~/.local/bin` is added to your `$PATH` [#2][i2]
@ -331,5 +336,6 @@ To get the Lyrics, I scrape them, and put those in the USLT ID3 Tags of for exam
For the lyrics source the page [https://genius.com/](https://genius.com/) is easily sufficient. It has most songs. Some songs are not present though, but that is fine, because the lyrics are optional anyways. For the lyrics source the page [https://genius.com/](https://genius.com/) is easily sufficient. It has most songs. Some songs are not present though, but that is fine, because the lyrics are optional anyways.
[i10]: https://github.com/HeIIow2/music-downloader/issues/10
[i2]: https://github.com/HeIIow2/music-downloader/issues/2 [i2]: https://github.com/HeIIow2/music-downloader/issues/2
[mb]: https://musicbrainz.org/ [mb]: https://musicbrainz.org/

View File

@ -16,6 +16,9 @@ from music_kraken.tagging import (
import music_kraken.database.new_database as db import music_kraken.database.new_database as db
import datetime
import pycountry
def div(msg: str = ""): def div(msg: str = ""):
print("-" * 50 + msg + "-" * 50) print("-" * 50 + msg + "-" * 50)
@ -39,7 +42,10 @@ feature_artist = Artist(
) )
album_input = Album( album_input = Album(
title="One Final Action" title="One Final Action",
date=datetime.date(1986, 3, 1),
language=pycountry.languages.get(alpha_2="en"),
label="cum productions"
) )
album_input.artists = [ album_input.artists = [
main_artist, main_artist,
@ -62,7 +68,7 @@ song_input = Song(
], ],
album=album_input, album=album_input,
main_artist_list=[main_artist], main_artist_list=[main_artist],
feature_artist_list=[feature_artist] feature_artist_list=[feature_artist],
) )
other_song = Song( other_song = Song(

View File

@ -3,6 +3,8 @@ import os
import logging import logging
from typing import List, Tuple from typing import List, Tuple
from pkg_resources import resource_string from pkg_resources import resource_string
import datetime
import pycountry
from .objects.parents import Reference from .objects.parents import Reference
from .objects.source import Source from .objects.source import Source
@ -40,12 +42,12 @@ FROM Lyrics
WHERE {where}; WHERE {where};
""" """
ALBUM_QUERY_UNJOINED = """ ALBUM_QUERY_UNJOINED = """
SELECT Album.id AS album_id, title, copyright, album_status, language, year, date, country, barcode, albumsort, is_split SELECT Album.id AS album_id, title, label, album_status, language, date, country, barcode, albumsort, is_split
FROM Album FROM Album
WHERE {where}; WHERE {where};
""" """
ALBUM_QUERY_JOINED = """ ALBUM_QUERY_JOINED = """
SELECT a.id AS album_id, a.title, a.copyright, a.album_status, a.language, a.year, a.date, a.country, a.barcode, a.albumsort, a.is_split SELECT a.id AS album_id, a.title, a.label, a.album_status, a.language, a.date, a.country, a.barcode, a.albumsort, a.is_split
FROM Song FROM Song
INNER JOIN Album a ON Song.album_id=a.id INNER JOIN Album a ON Song.album_id=a.id
WHERE {where}; WHERE {where};
@ -131,16 +133,15 @@ class Database:
def push_album(self, album: Album): def push_album(self, album: Album):
table = "Album" table = "Album"
query = f"INSERT OR REPLACE INTO {table} (id, title, copyright, album_status, language, year, date, country, barcode, albumsort, is_split) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" query = f"INSERT OR REPLACE INTO {table} (id, title, label, album_status, language, date, country, barcode, albumsort, is_split) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
values = ( values = (
album.id, album.id,
album.title, album.title,
album.copyright, album.label,
album.album_status, album.album_status,
album.language, album.iso_639_2_language,
album.year, album.date.strftime("%Y-%m-%d"),
album.date,
album.country, album.country,
album.barcode, album.barcode,
album.albumsort, album.albumsort,
@ -541,11 +542,10 @@ class Database:
album_obj = Album( album_obj = Album(
id_=album_id, id_=album_id,
title=album_result['title'], title=album_result['title'],
copyright_=album_result['copyright'], label=album_result['label'],
album_status=album_result['album_status'], album_status=album_result['album_status'],
language=album_result['language'], language=pycountry.languages.get(alpha_3=album_result['language']),
year=album_result['year'], date=datetime.datetime.strptime(album_result['date'], "%Y-%m-%d").date(),
date=album_result['date'],
country=album_result['country'], country=album_result['country'],
barcode=album_result['barcode'], barcode=album_result['barcode'],
is_split=album_result['is_split'], is_split=album_result['is_split'],

View File

@ -1,6 +1,7 @@
from enum import Enum from enum import Enum
from typing import List, Dict, Tuple from typing import List, Dict, Tuple
from mutagen import id3 from mutagen import id3
import datetime
from .parents import ( from .parents import (
ID3Metadata ID3Metadata
@ -10,6 +11,9 @@ from .parents import (
class Mapping(Enum): class Mapping(Enum):
""" """
These frames belong to the id3 standart These frames belong to the id3 standart
https://web.archive.org/web/20220830091059/https://id3.org/id3v2.4.0-frames
https://id3lib.sourceforge.net/id3/id3v2com-00.html
https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.4.0-frames.html
""" """
# Textframes # Textframes
TITLE = "TIT2" TITLE = "TIT2"
@ -37,7 +41,7 @@ class Mapping(Enum):
LYRICIST = "TEXT" LYRICIST = "TEXT"
WRITER = "TEXT" WRITER = "TEXT"
ARTIST = "TPE1" ARTIST = "TPE1"
LANGUAGE = "TLAN" LANGUAGE = "TLAN" # https://en.wikipedia.org/wiki/ISO_639-2
ITUNESCOMPILATION = "TCMP" ITUNESCOMPILATION = "TCMP"
REMIXED_BY = "TPE4" REMIXED_BY = "TPE4"
RADIO_STATION_OWNER = "TRSO" RADIO_STATION_OWNER = "TRSO"
@ -58,6 +62,7 @@ class Mapping(Enum):
ALBUM = "TALB" ALBUM = "TALB"
ALBUMSORTORDER = "TSOA" ALBUMSORTORDER = "TSOA"
ALBUMARTISTSORTORDER = "TSO2" ALBUMARTISTSORTORDER = "TSO2"
TAGGING_TIME = "TDTG"
SOURCE_WEBPAGE_URL = "WOAS" SOURCE_WEBPAGE_URL = "WOAS"
FILE_WEBPAGE_URL = "WOAF" FILE_WEBPAGE_URL = "WOAF"
@ -93,6 +98,91 @@ class Mapping(Enum):
return cls.get_url_instance(key, value) return cls.get_url_instance(key, value)
class ID3Timestamp(datetime.datetime):
def __init__(
self,
year: int = None,
month: int = None,
day: int = None,
hour: int = None,
minute: int = None,
second: int = None,
microsecond=0,
tzinfo=None,
*,
fold=0
):
self.has_year = year is not None
self.has_month = month is not None
self.has_day = day is not None
self.has_hour = hour is not None
self.has_minute = minute is not None
self.has_second = second is not None
self.has_microsecond = microsecond is not None
if not self.has_year:
year = 1
if not self.has_month:
month = 1
if not self.has_day:
day = 1
super().__init__(
year=year,
month=month,
day=day,
hour=hour,
minute=minute,
second=second,
microsecond=microsecond,
tzinfo=tzinfo,
fold=fold
)
def get_timestamp(self) -> str:
"""
https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.4.0-structure.html
The timestamp fields are based on a subset of ISO 8601. When being as precise as possible the format of a
time string is
- yyyy-MM-ddTHH:mm:ss
- (year[%Y], -, month[%m], -, day[%d], T, hour (out of 24)[%H], :, minutes[%M], :, seconds[%S])
- %Y-%m-%dT%H:%M:%S
but the precision may be reduced by removing as many time indicators as wanted. Hence valid timestamps are
- yyyy
- yyyy-MM
- yyyy-MM-dd
- yyyy-MM-ddTHH
- yyyy-MM-ddTHH:mm
- yyyy-MM-ddTHH:mm:ss
All time stamps are UTC. For durations, use the slash character as described in 8601,
and for multiple non-contiguous dates, use multiple strings, if allowed by the frame definition.
:return timestamp: as timestamp in the format of the id3 time as above described
"""
if self.has_year and self.has_month and self.has_day and self.has_hour and self.has_minute and self.has_second:
return self.strftime("%Y-%m-%dT%H:%M:%S")
if self.has_year and self.has_month and self.has_day and self.has_hour and self.has_minute:
return self.strftime("%Y-%m-%dT%H:%M")
if self.has_year and self.has_month and self.has_day and self.has_hour:
return self.strftime("%Y-%m-%dT%H")
if self.has_year and self.has_month and self.has_day:
return self.strftime("%Y-%m-%d")
if self.has_year and self.has_month:
return self.strftime("%Y-%m")
if self.has_year:
return self.strftime("%Y")
return ""
def __str__(self) -> str:
return self.timestamp
def __repr__(self) -> str:
return self.timestamp
timestamp: str = property(fget=get_timestamp)
class Metadata: class Metadata:
""" """
Shall only be read or edited via the Song object. Shall only be read or edited via the Song object.

View File

@ -1,6 +1,7 @@
import os import os
from typing import List, Tuple, Dict from typing import List, Tuple, Dict
from mutagen.easyid3 import EasyID3 import datetime
import pycountry
from .metadata import ( from .metadata import (
Mapping as ID3_MAPPING, Mapping as ID3_MAPPING,
@ -278,11 +279,10 @@ class Album(DatabaseObject, ID3Metadata):
self, self,
id_: str = None, id_: str = None,
title: str = None, title: str = None,
copyright_: str = None, label: str = None,
album_status: str = None, album_status: str = None,
language: str = None, language: pycountry.Languages = None,
year: str = None, date: datetime.date = None,
date: str = None,
country: str = None, country: str = None,
barcode: str = None, barcode: str = None,
is_split: bool = False, is_split: bool = False,
@ -291,20 +291,10 @@ class Album(DatabaseObject, ID3Metadata):
) -> None: ) -> None:
DatabaseObject.__init__(self, id_=id_, dynamic=dynamic) DatabaseObject.__init__(self, id_=id_, dynamic=dynamic)
self.title: str = title self.title: str = title
self.copyright: str = copyright_
self.album_status: str = album_status self.album_status: str = album_status
""" self.label = label
TODO self.language: pycountry.Languages = language
MAKE SURE THIS IS IN THE CORRECT FORMAT self.date: datetime.date = date
"""
self.language: str = language
"""
TODO
only store the date in a python date object and derive the
year from it
"""
self.year: str = year
self.date: str = date
self.country: str = country self.country: str = country
""" """
TODO TODO
@ -344,10 +334,25 @@ class Album(DatabaseObject, ID3Metadata):
return { return {
ID3_MAPPING.ALBUM: [self.title], ID3_MAPPING.ALBUM: [self.title],
ID3_MAPPING.COPYRIGHT: [self.copyright], ID3_MAPPING.COPYRIGHT: [self.copyright],
ID3_MAPPING.LANGUAGE: [self.language], ID3_MAPPING.LANGUAGE: [self.iso_639_2_language],
ID3_MAPPING.ALBUM_ARTIST: [a.name for a in self.artists] ID3_MAPPING.ALBUM_ARTIST: [a.name for a in self.artists]
} }
def get_copyright(self) -> str:
if self.date.year == 1 or self.label is None:
return None
return f"{self.date.year} {self.label}"
def get_iso_639_2_lang(self) -> str:
if self.language is None:
return None
return self.language.alpha_3
copyright = property(fget=get_copyright)
iso_639_2_language = property(fget=get_iso_639_2_lang)
""" """

View File

@ -26,10 +26,9 @@ CREATE TABLE Album
( (
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGINT AUTO_INCREMENT PRIMARY KEY,
title TEXT, title TEXT,
copyright TEXT, label TEXT,
album_status TEXT, album_status TEXT,
language TEXT, language TEXT,
year TEXT,
date TEXT, date TEXT,
country TEXT, country TEXT,
barcode TEXT, barcode TEXT,

Binary file not shown.