From b4491264952ee74e8b8fd9560e2c92da83a30ca2 Mon Sep 17 00:00:00 2001 From: logykk Date: Wed, 16 Feb 2022 21:56:09 +1300 Subject: [PATCH] lyrics support --- zotify/app.py | 7 ++++--- zotify/config.py | 6 ++++++ zotify/track.py | 34 +++++++++++++++++++++++++--------- zotify/zotify.py | 8 ++++++-- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/zotify/app.py b/zotify/app.py index b8659c4..8d1144d 100644 --- a/zotify/app.py +++ b/zotify/app.py @@ -64,9 +64,10 @@ def client(args) -> None: search_text = '' while len(search_text) == 0: search_text = input('Enter search or URL: ') - - if not download_from_urls([args.search]): - search(args.search) + search(search_text) + else: + if not download_from_urls([args.search]): + search(args.search) def download_from_urls(urls: list[str]) -> bool: """ Downloads from a list of urls """ diff --git a/zotify/config.py b/zotify/config.py index 8b55597..5585d29 100644 --- a/zotify/config.py +++ b/zotify/config.py @@ -33,6 +33,7 @@ PRINT_PROGRESS_INFO = 'PRINT_PROGRESS_INFO' PRINT_WARNINGS = 'PRINT_WARNINGS' RETRY_ATTEMPTS = 'RETRY_ATTEMPTS' CONFIG_VERSION = 'CONFIG_VERSION' +DOWNLOAD_LYRICS = 'DOWNLOAD_LYRICS' CONFIG_VALUES = { CREDENTIALS_LOCATION: { 'default': '', 'type': str, 'arg': '--credentials-location' }, @@ -41,6 +42,7 @@ CONFIG_VALUES = { ROOT_PATH: { 'default': '', 'type': str, 'arg': '--root-path' }, ROOT_PODCAST_PATH: { 'default': '', 'type': str, 'arg': '--root-podcast-path' }, 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_GENREDELIMITER: { 'default': ',', 'type': str, 'arg': '--md-genredelimiter' }, DOWNLOAD_FORMAT: { 'default': 'ogg', 'type': str, 'arg': '--download-format' }, @@ -187,6 +189,10 @@ class Config: def get_download_format(cls) -> str: return cls.get(DOWNLOAD_FORMAT) + @classmethod + def get_download_lyrics(cls) -> bool: + return cls.get(DOWNLOAD_LYRICS) + @classmethod def get_bulk_wait_time(cls) -> int: return cls.get(BULK_WAIT_TIME) diff --git a/zotify/track.py b/zotify/track.py index 779ebb9..bb56b4f 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -1,10 +1,10 @@ from pathlib import Path, PurePath +import math import re import time import uuid from typing import Any, Tuple, List -from librespot.audio.decoders import AudioQuality from librespot.metadata import TrackId 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]: - try: genres = [] 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}') +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: """ 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 duration = float(ms_duration)/1000 - # debug - # print(duration) - # print(type(duration)) - return duration -# noinspection PyBroadException def download_track(mode: str, track_id: str, extra_keys=None, disable_progressbar=False) -> None: """ Downloads raw song audio from Spotify """ @@ -182,8 +196,8 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba else: if track_id != scraped_song_id: track_id = scraped_song_id - track_id = TrackId.from_base62(track_id) - stream = Zotify.get_content_stream(track_id, Zotify.DOWNLOAD_QUALITY) + track = TrackId.from_base62(track_id) + stream = Zotify.get_content_stream(track, Zotify.DOWNLOAD_QUALITY) create_download_directory(filedir) 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) + if(Zotify.CONFIG.get_download_lyrics()): + get_song_lyrics(track_id, PurePath(filedir / str(song_name + '.lrc'))) convert_audio_format(filename_temp) set_audio_tags(filename_temp, artists, genres, name, album_name, release_year, disc_number, track_number) set_music_thumbnail(filename_temp, image_url) diff --git a/zotify/zotify.py b/zotify/zotify.py index 1712d22..e63ff88 100644 --- a/zotify/zotify.py +++ b/zotify/zotify.py @@ -56,14 +56,18 @@ class Zotify: def get_auth_header(cls): return { '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 def get_auth_header_and_params(cls, limit, offset): return { '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} @classmethod