lyrics support

This commit is contained in:
logykk 2022-02-16 21:56:09 +13:00
parent 00d10b15d3
commit b449126495
4 changed files with 41 additions and 14 deletions

View file

@ -64,9 +64,10 @@ def client(args) -> None:
search_text = '' search_text = ''
while len(search_text) == 0: while len(search_text) == 0:
search_text = input('Enter search or URL: ') search_text = input('Enter search or URL: ')
search(search_text)
if not download_from_urls([args.search]): else:
search(args.search) if not download_from_urls([args.search]):
search(args.search)
def download_from_urls(urls: list[str]) -> bool: def download_from_urls(urls: list[str]) -> bool:
""" Downloads from a list of urls """ """ Downloads from a list of urls """

View file

@ -33,6 +33,7 @@ PRINT_PROGRESS_INFO = 'PRINT_PROGRESS_INFO'
PRINT_WARNINGS = 'PRINT_WARNINGS' PRINT_WARNINGS = 'PRINT_WARNINGS'
RETRY_ATTEMPTS = 'RETRY_ATTEMPTS' RETRY_ATTEMPTS = 'RETRY_ATTEMPTS'
CONFIG_VERSION = 'CONFIG_VERSION' CONFIG_VERSION = 'CONFIG_VERSION'
DOWNLOAD_LYRICS = 'DOWNLOAD_LYRICS'
CONFIG_VALUES = { CONFIG_VALUES = {
CREDENTIALS_LOCATION: { 'default': '', 'type': str, 'arg': '--credentials-location' }, CREDENTIALS_LOCATION: { 'default': '', 'type': str, 'arg': '--credentials-location' },
@ -41,6 +42,7 @@ CONFIG_VALUES = {
ROOT_PATH: { 'default': '', 'type': str, 'arg': '--root-path' }, ROOT_PATH: { 'default': '', 'type': str, 'arg': '--root-path' },
ROOT_PODCAST_PATH: { 'default': '', 'type': str, 'arg': '--root-podcast-path' }, ROOT_PODCAST_PATH: { 'default': '', 'type': str, 'arg': '--root-podcast-path' },
SPLIT_ALBUM_DISCS: { 'default': 'False', 'type': bool, 'arg': '--split-album-discs' }, SPLIT_ALBUM_DISCS: { 'default': 'False', 'type': bool, 'arg': '--split-album-discs' },
DOWNLOAD_LYRICS: { 'default': 'True', 'type': bool, 'arg': '--download-lyrics' },
MD_ALLGENRES: { 'default': 'False', 'type': bool, 'arg': '--md-allgenres' }, MD_ALLGENRES: { 'default': 'False', 'type': bool, 'arg': '--md-allgenres' },
MD_GENREDELIMITER: { 'default': ',', 'type': str, 'arg': '--md-genredelimiter' }, MD_GENREDELIMITER: { 'default': ',', 'type': str, 'arg': '--md-genredelimiter' },
DOWNLOAD_FORMAT: { 'default': 'ogg', 'type': str, 'arg': '--download-format' }, DOWNLOAD_FORMAT: { 'default': 'ogg', 'type': str, 'arg': '--download-format' },
@ -187,6 +189,10 @@ class Config:
def get_download_format(cls) -> str: def get_download_format(cls) -> str:
return cls.get(DOWNLOAD_FORMAT) return cls.get(DOWNLOAD_FORMAT)
@classmethod
def get_download_lyrics(cls) -> bool:
return cls.get(DOWNLOAD_LYRICS)
@classmethod @classmethod
def get_bulk_wait_time(cls) -> int: def get_bulk_wait_time(cls) -> int:
return cls.get(BULK_WAIT_TIME) return cls.get(BULK_WAIT_TIME)

View file

@ -1,10 +1,10 @@
from pathlib import Path, PurePath from pathlib import Path, PurePath
import math
import re import re
import time import time
import uuid import uuid
from typing import Any, Tuple, List from typing import Any, Tuple, List
from librespot.audio.decoders import AudioQuality
from librespot.metadata import TrackId from librespot.metadata import TrackId
import ffmpy import ffmpy
@ -64,7 +64,6 @@ def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, An
def get_song_genres(rawartists: List[str], track_name: str) -> List[str]: def get_song_genres(rawartists: List[str], track_name: str) -> List[str]:
try: try:
genres = [] genres = []
for data in rawartists: for data in rawartists:
@ -86,6 +85,26 @@ def get_song_genres(rawartists: List[str], track_name: str) -> List[str]:
raise ValueError(f'Failed to parse GENRES response: {str(e)}\n{raw}') raise ValueError(f'Failed to parse GENRES response: {str(e)}\n{raw}')
def get_song_lyrics(song_id: str, file_save: str) -> None:
raw, lyrics = Zotify.invoke_url(f'https://spclient.wg.spotify.com/color-lyrics/v2/track/{song_id}')
formatted_lyrics = lyrics['lyrics']['lines']
if(lyrics['lyrics']['syncType'] == "UNSYNCED"):
with open(file_save, 'w') as file:
for line in formatted_lyrics:
file.writelines(line['words'] + '\n')
elif(lyrics['lyrics']['syncType'] == "LINE_SYNCED"):
with open(file_save, 'w') as file:
for line in formatted_lyrics:
timestamp = int(line['startTimeMs'])
ts_minutes = str(math.floor(timestamp / 60000)).zfill(2)
ts_seconds = str(math.floor((timestamp % 60000) / 1000)).zfill(2)
ts_millis = str(math.floor(timestamp % 1000))[:2].zfill(2)
file.writelines(f'[{ts_minutes}:{ts_seconds}.{ts_millis}]' + line['words'] + '\n')
else:
raise ValueError(f'Filed to fetch lyrics: {song_id}')
def get_song_duration(song_id: str) -> float: def get_song_duration(song_id: str) -> float:
""" Retrieves duration of song in second as is on spotify """ """ Retrieves duration of song in second as is on spotify """
@ -96,14 +115,9 @@ def get_song_duration(song_id: str) -> float:
# convert to seconds # convert to seconds
duration = float(ms_duration)/1000 duration = float(ms_duration)/1000
# debug
# print(duration)
# print(type(duration))
return duration return duration
# noinspection PyBroadException
def download_track(mode: str, track_id: str, extra_keys=None, disable_progressbar=False) -> None: def download_track(mode: str, track_id: str, extra_keys=None, disable_progressbar=False) -> None:
""" Downloads raw song audio from Spotify """ """ Downloads raw song audio from Spotify """
@ -182,8 +196,8 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
else: else:
if track_id != scraped_song_id: if track_id != scraped_song_id:
track_id = scraped_song_id track_id = scraped_song_id
track_id = TrackId.from_base62(track_id) track = TrackId.from_base62(track_id)
stream = Zotify.get_content_stream(track_id, Zotify.DOWNLOAD_QUALITY) stream = Zotify.get_content_stream(track, Zotify.DOWNLOAD_QUALITY)
create_download_directory(filedir) create_download_directory(filedir)
total_size = stream.input_stream.size total_size = stream.input_stream.size
@ -213,6 +227,8 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
genres = get_song_genres(raw_artists, name) genres = get_song_genres(raw_artists, name)
if(Zotify.CONFIG.get_download_lyrics()):
get_song_lyrics(track_id, PurePath(filedir / str(song_name + '.lrc')))
convert_audio_format(filename_temp) convert_audio_format(filename_temp)
set_audio_tags(filename_temp, artists, genres, name, album_name, release_year, disc_number, track_number) set_audio_tags(filename_temp, artists, genres, name, album_name, release_year, disc_number, track_number)
set_music_thumbnail(filename_temp, image_url) set_music_thumbnail(filename_temp, image_url)

View file

@ -56,14 +56,18 @@ class Zotify:
def get_auth_header(cls): def get_auth_header(cls):
return { return {
'Authorization': f'Bearer {cls.__get_auth_token()}', 'Authorization': f'Bearer {cls.__get_auth_token()}',
'Accept-Language': f'{cls.CONFIG.get_language()}' 'Accept-Language': f'{cls.CONFIG.get_language()}',
'Accept': 'application/json',
'app-platform': 'WebPlayer'
} }
@classmethod @classmethod
def get_auth_header_and_params(cls, limit, offset): def get_auth_header_and_params(cls, limit, offset):
return { return {
'Authorization': f'Bearer {cls.__get_auth_token()}', 'Authorization': f'Bearer {cls.__get_auth_token()}',
'Accept-Language': f'{cls.CONFIG.get_language()}' 'Accept-Language': f'{cls.CONFIG.get_language()}',
'Accept': 'application/json',
'app-platform': 'WebPlayer'
}, {LIMIT: limit, OFFSET: offset} }, {LIMIT: limit, OFFSET: offset}
@classmethod @classmethod