From 55936258a3bd3b49288417490546c829d3b249b7 Mon Sep 17 00:00:00 2001 From: Jayden Date: Sat, 23 Oct 2021 21:50:59 -0400 Subject: [PATCH 01/11] docs(readme): update from zspotify.py to app.py updates the specified python file to the python file that starts the app --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 793800c7..41fc0e64 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ Python packages: \*\*Git can be installed via apt for Debian-based distros or by downloading the binaries from [git-scm.com](https://git-scm.com/download/win) for Windows. ``` Command line usage: - python zspotify.py Loads search prompt to find then download a specific track, album or playlist - python zspotify.py Downloads the track, album, playlist or podcast episode specified as a command line argument - python zspotify.py Downloads all albums by specified artist + python app.py Loads search prompt to find then download a specific track, album or playlist + python app.py Downloads the track, album, playlist or podcast episode specified as a command line argument + python app.py Downloads all albums by specified artist Extra command line options: -p, --playlist Downloads a saved playlist from your account From 74920471c334dbc50aea9726a48d678bd582bdbe Mon Sep 17 00:00:00 2001 From: Jayden Date: Sat, 23 Oct 2021 21:53:59 -0400 Subject: [PATCH 02/11] docs(readme): fix spacing --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41fc0e64..7061c93e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Python packages: \*\*Git can be installed via apt for Debian-based distros or by downloading the binaries from [git-scm.com](https://git-scm.com/download/win) for Windows. ``` Command line usage: - python app.py Loads search prompt to find then download a specific track, album or playlist + python app.py Loads search prompt to find then download a specific track, album or playlist python app.py Downloads the track, album, playlist or podcast episode specified as a command line argument python app.py Downloads all albums by specified artist From 703d73cab058fbbf46e5f50527914c80228839bc Mon Sep 17 00:00:00 2001 From: Timur Khadimullin Date: Sun, 24 Oct 2021 15:22:10 +1300 Subject: [PATCH 03/11] added basic Dockerfile this config builds a small-ish (141Mb) docker image and allows running the tool via command line: ``` docker build . -t zspotify docker run --rm -it zspotify ``` --- Dockerfile | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ceb88cf2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.9-alpine as base + +RUN apk --update add git ffmpeg + +FROM base as builder +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN apk add gcc libc-dev zlib zlib-dev jpeg-dev \ + && pip install --prefix="/install" -r /requirements.txt + + +FROM base + +COPY --from=builder /install /usr/local +COPY src /app +COPY zs_config.json / +WORKDIR /app +ENTRYPOINT ["/usr/local/bin/python", "app.py"] From 83bf41091cd07d3e6653181ade8abaf4490487e1 Mon Sep 17 00:00:00 2001 From: Logykk <35679186+logykk@users.noreply.github.com> Date: Sun, 24 Oct 2021 15:35:42 +1300 Subject: [PATCH 04/11] Create pylint.yml --- .github/workflows/pylint.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/pylint.yml diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 00000000..0805af74 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,22 @@ +name: Pylint + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint `ls -R|grep .py$|xargs` From c41fc10bc256c3d6eccff38685c27eea2ed1671d Mon Sep 17 00:00:00 2001 From: logykk Date: Sun, 24 Oct 2021 17:20:41 +1300 Subject: [PATCH 05/11] basic support for downloading an entire podcast --- CONTRIBUTING.md | 40 ++++++++++++++++++++++++++++++++++++++++ src/podcast.py | 14 ++++++++++---- 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1ee5eef8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Introduction + +### Thank you for contributing + +Without people like you this project wouldn't be anywhere near as polished and feature-rich as it is now. + +### Guidelines + +Following these guidelines helps show that you respect the the time and effort spent by the developers and your fellow contributors making this project. + +### What we are looking for + +ZSpotify is a community-driven project. There are many different ways to contribute. From providing tutorials and examples to help new users, reporting bugs, requesting new features, writing new code that can be added to the project, or even writing documentation. + +### What we aren't looking for + +Please don't use the issues section to request help installing or setting up the project. It should be reserved for bugs when running the code, and feature requqests. Instead use the support channel in either our Discord or Matrix server. + +# Ground rules + +### Expectations +* Ensure all code is linted with pylint before pushing. +* Ensure all code passes the [testing criteria](#testing-criteria). +* If you're planning on contributing a new feature, join the Discord or Matrix and discuss it with the Dev Team. +* Please don't commit multiple new features at once. +* Follow the [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/) + +# Your first contribution + +Unsure where to start? Have a look for any issues tagged "good first issue". They should be minor bugs that only require a few lines to fix. +Here are a couple of friendly tutorials on making pull requests: http://makeapullrequest.com/ and http://www.firsttimersonly.com/ + +# Code review process + +The dev team looks at Pull Requests around once per day. After feedback has been given we expect responses within one week. After a week we may close the pull request if it isn't showing any activity. +> ZSpotify updates very frequently, often multiple times per day. If a maintainer asks you to "rebase" your PR, they're saying that a lot of code has changed, and that you need to update your branch so it's easier to merge. + +# Community + +Come and chat with us on Discord or Matrix. Devs try to respond to mentions at least once per day. \ No newline at end of file diff --git a/src/podcast.py b/src/podcast.py index 008f060e..2b7b264e 100644 --- a/src/podcast.py +++ b/src/podcast.py @@ -8,6 +8,7 @@ from const import NAME, ERROR, SHOW, ITEMS, ID, ROOT_PODCAST_PATH, CHUNK_SIZE from utils import sanitize_data, create_download_directory, MusicFormat from zspotify import ZSpotify + EPISODE_INFO_URL = 'https://api.spotify.com/v1/episodes' SHOWS_URL = 'https://api.spotify.com/v1/shows' @@ -21,11 +22,16 @@ def get_episode_info(episode_id_str) -> tuple[Optional[str], Optional[str]]: def get_show_episodes(show_id_str) -> list: episodes = [] + offset = 0 + limit = 50 - resp = ZSpotify.invoke_url(f'{SHOWS_URL}/{show_id_str}/episodes') - - for episode in resp[ITEMS]: - episodes.append(episode[ID]) + while True: + resp = ZSpotify.invoke_url_with_params(f'{SHOWS_URL}/{show_id_str}/episodes', limit=limit, offset=offset) + offset += limit + for episode in resp[ITEMS]: + episodes.append(episode[ID]) + if len(resp[ITEMS]) < limit: + break return episodes From 4f91554097e0fbe4d8d9d0bbabb6bebc016ad4eb Mon Sep 17 00:00:00 2001 From: Jonathan Barratt Date: Sun, 24 Oct 2021 11:35:37 +0700 Subject: [PATCH 06/11] Fixes error locating config if not run from src dir --- src/zspotify.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zspotify.py b/src/zspotify.py index 98aaa6fe..acb7fe01 100644 --- a/src/zspotify.py +++ b/src/zspotify.py @@ -55,7 +55,8 @@ class ZSpotify: @classmethod def load_config(cls) -> None: - with open(CONFIG_FILE_PATH, encoding='utf-8') as config_file: + app_dir = os.path.dirname(__file__) + with open(os.path.join(app_dir, CONFIG_FILE_PATH), encoding='utf-8') as config_file: cls.CONFIG = json.load(config_file) @classmethod From 27a2ecfb032a770e76845e87205e37d891054358 Mon Sep 17 00:00:00 2001 From: logykk Date: Sun, 24 Oct 2021 18:24:54 +1300 Subject: [PATCH 07/11] simplified audio formats --- src/const.py | 2 -- src/podcast.py | 4 ++-- src/track.py | 11 ++++++----- src/utils.py | 3 +-- src/zspotify.py | 8 +------- zs_config.json | 1 - 6 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/const.py b/src/const.py index 8671d95e..0c9a8545 100644 --- a/src/const.py +++ b/src/const.py @@ -84,8 +84,6 @@ SKIP_EXISTING_FILES = 'SKIP_EXISTING_FILES' DOWNLOAD_FORMAT = 'DOWNLOAD_FORMAT' -RAW_AUDIO_AS_IS = 'RAW_AUDIO_AS_IS' - FORCE_PREMIUM = 'FORCE_PREMIUM' ANTI_BAN_WAIT_TIME = 'ANTI_BAN_WAIT_TIME' diff --git a/src/podcast.py b/src/podcast.py index 2b7b264e..ccd8a694 100644 --- a/src/podcast.py +++ b/src/podcast.py @@ -52,7 +52,7 @@ def download_episode(episode_id) -> None: create_download_directory(ZSpotify.get_config(ROOT_PODCAST_PATH) + extra_paths) total_size = stream.input_stream.size - with open(ZSpotify.get_config(ROOT_PODCAST_PATH) + extra_paths + filename + MusicFormat.WAV.value, + with open(ZSpotify.get_config(ROOT_PODCAST_PATH) + extra_paths + filename + MusicFormat.OGG.value, 'wb') as file, tqdm( desc=filename, total=total_size, @@ -65,6 +65,6 @@ def download_episode(episode_id) -> None: stream.input_stream.stream().read(ZSpotify.get_config(CHUNK_SIZE)))) # convert_audio_format(ROOT_PODCAST_PATH + - # extra_paths + filename + '.wav') + # extra_paths + filename + '.ogg') # related functions that do stuff with the spotify API diff --git a/src/track.py b/src/track.py index d9109b6d..dc148b0d 100644 --- a/src/track.py +++ b/src/track.py @@ -9,7 +9,7 @@ from tqdm import tqdm from const import TRACKS, ALBUM, NAME, ITEMS, DISC_NUMBER, TRACK_NUMBER, IS_PLAYABLE, ARTISTS, IMAGES, URL, \ RELEASE_DATE, ID, TRACKS_URL, SAVED_TRACKS_URL, SPLIT_ALBUM_DISCS, ROOT_PATH, DOWNLOAD_FORMAT, CHUNK_SIZE, \ - SKIP_EXISTING_FILES, RAW_AUDIO_AS_IS, ANTI_BAN_WAIT_TIME, OVERRIDE_AUTO_WAIT + SKIP_EXISTING_FILES, ANTI_BAN_WAIT_TIME, OVERRIDE_AUTO_WAIT from utils import sanitize_data, set_audio_tags, set_music_thumbnail, create_download_directory, \ MusicFormat from zspotify import ZSpotify @@ -99,23 +99,24 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='', p_bar.update(file.write( stream.input_stream.stream().read(ZSpotify.get_config(CHUNK_SIZE)))) - if not ZSpotify.get_config(RAW_AUDIO_AS_IS): + if ZSpotify.get_config(DOWNLOAD_FORMAT) == 'mp3': convert_audio_format(filename) set_audio_tags(filename, artists, name, album_name, - release_year, disc_number, track_number) + release_year, disc_number, track_number) set_music_thumbnail(filename, image_url) if not ZSpotify.get_config(OVERRIDE_AUTO_WAIT): time.sleep(ZSpotify.get_config(ANTI_BAN_WAIT_TIME)) - except Exception: + except Exception as e: print('### SKIPPING:', song_name, '(GENERAL DOWNLOAD ERROR) ###') + print(e) if os.path.exists(filename): os.remove(filename) def convert_audio_format(filename) -> None: - """ Converts raw audio into playable mp3 or ogg vorbis """ + """ Converts raw audio into playable mp3 """ # print('### CONVERTING TO ' + MUSIC_FORMAT.upper() + ' ###') raw_audio = AudioSegment.from_file(filename, format=MusicFormat.OGG.value, frame_rate=44100, channels=2, sample_width=2) diff --git a/src/utils.py b/src/utils.py index c601e525..3fd91612 100644 --- a/src/utils.py +++ b/src/utils.py @@ -14,8 +14,7 @@ from const import SANITIZE, ARTIST, TRACKTITLE, ALBUM, YEAR, DISCNUMBER, TRACKNU class MusicFormat(str, Enum): MP3 = 'mp3', OGG = 'ogg', - WAV = 'wav' - + def create_download_directory(download_path: str) -> None: os.makedirs(download_path, exist_ok=True) diff --git a/src/zspotify.py b/src/zspotify.py index 98aaa6fe..b692ff6c 100644 --- a/src/zspotify.py +++ b/src/zspotify.py @@ -17,7 +17,7 @@ from librespot.audio.decoders import VorbisOnlyAudioQuality from librespot.core import Session from const import CREDENTIALS_JSON, TYPE, \ - PREMIUM, USER_READ_EMAIL, AUTHORIZATION, OFFSET, LIMIT, CONFIG_FILE_PATH, FORCE_PREMIUM, RAW_AUDIO_AS_IS, \ + PREMIUM, USER_READ_EMAIL, AUTHORIZATION, OFFSET, LIMIT, CONFIG_FILE_PATH, FORCE_PREMIUM, \ PLAYLIST_READ_PRIVATE from utils import MusicFormat @@ -29,7 +29,6 @@ class ZSpotify: def __init__(self): ZSpotify.load_config() - ZSpotify.check_raw() ZSpotify.login() @classmethod @@ -62,11 +61,6 @@ class ZSpotify: def get_config(cls, key) -> Any: return cls.CONFIG.get(key) - @classmethod - def check_raw(cls) -> None: - if cls.get_config(RAW_AUDIO_AS_IS): - cls.DOWNLOAD_FORMAT = MusicFormat.WAV - @classmethod def get_content_stream(cls, content_id, quality): return cls.SESSION.content_feeder().load(content_id, VorbisOnlyAudioQuality(quality), False, None) diff --git a/zs_config.json b/zs_config.json index 7daf8b54..b5dbb919 100644 --- a/zs_config.json +++ b/zs_config.json @@ -3,7 +3,6 @@ "ROOT_PODCAST_PATH": "../ZSpotify Podcasts/", "SKIP_EXISTING_FILES": true, "DOWNLOAD_FORMAT": "mp3", - "RAW_AUDIO_AS_IS": false, "FORCE_PREMIUM": false, "ANTI_BAN_WAIT_TIME": 1, "OVERRIDE_AUTO_WAIT": false, From ad4c73057843f93086604c47628de756585b11bb Mon Sep 17 00:00:00 2001 From: logykk Date: Sun, 24 Oct 2021 18:41:53 +1300 Subject: [PATCH 08/11] Updated readme to v2.2 --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7061c93e..3f7062b3 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,7 @@ Options that can be configured in zs_config.json: SKIP_EXISTING_FILES Set this to false if you want ZSpotify to overwrite files with the same name rather than skipping the song - MUSIC_FORMAT Set this to "ogg" if you would rather that format audio over "mp3" - RAW_AUDIO_AS_IS Set this to true to only stream the audio to a file and do no re-encoding or post processing + MUSIC_FORMAT Can be "mp3" or "ogg", mp3 is required for track metadata however ogg is slightly higer quality as it is not trsnacoded. FORCE_PREMIUM Set this to true if ZSpotify isn't automatically detecting that you are using a premium account @@ -60,9 +59,20 @@ This isn't to say _you_ won't get banned as it is technically against Spotify's If you see this, don't worry! Just try logging back in. If you see the incorrect username or password error, reset your password and you should be able to log back in and continue using Spotify. ### Contributing -Please be sure to lint your code with pylint before issuing a pull-request, thanks! +Please refer to CONTRIBUTING.md ## **Changelog:** +**v2.2 (24 Oct 2021):** +- Added basic support for downloading an entire podcast series. +- Split code into multiple files for easier maintenance. +- Changed initial launch script to app.py +- Simplified audio formats. +- Added prebuild exe for Windows users. +- Added Docker file. +- Added CONTRIBUTING.md. +- Fixed artist names getting cutoff in metadata. +- Removed data sanitization of metadata tags. + **v2.1 (23 Oct 2021):** - Moved configuration from hard-coded values to separate zs_config.json file. - Add subfolders for each disc. From 21b970f0e1f563aeb0289385fb35099c42f75670 Mon Sep 17 00:00:00 2001 From: Logykk <35679186+logykk@users.noreply.github.com> Date: Sun, 24 Oct 2021 20:36:31 +1300 Subject: [PATCH 09/11] Bumped minimum python version to 3.9 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f7062b3..7b8f1b0e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Requirements: Binaries -- Python 3.8 or greater +- Python 3.9 or greater - ffmpeg* - Git** From 05ecb1d3bb024e3dc0a5085dfc6a74276c4f941c Mon Sep 17 00:00:00 2001 From: Jonathan Barratt Date: Sun, 24 Oct 2021 15:35:06 +0700 Subject: [PATCH 10/11] Use absolute paths for download directories --- src/podcast.py | 10 +++++----- src/track.py | 13 +++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/podcast.py b/src/podcast.py index ccd8a694..43dae318 100644 --- a/src/podcast.py +++ b/src/podcast.py @@ -1,3 +1,4 @@ +import os from typing import Optional from librespot.audio.decoders import VorbisOnlyAudioQuality @@ -49,10 +50,11 @@ def download_episode(episode_id) -> None: episode_id = EpisodeId.from_base62(episode_id) stream = ZSpotify.get_content_stream(episode_id, ZSpotify.DOWNLOAD_QUALITY) - create_download_directory(ZSpotify.get_config(ROOT_PODCAST_PATH) + extra_paths) + download_directory = os.path.dirname(__file__) + ZSpotify.get_config(ROOT_PODCAST_PATH) + extra_paths + create_download_directory(download_directory) total_size = stream.input_stream.size - with open(ZSpotify.get_config(ROOT_PODCAST_PATH) + extra_paths + filename + MusicFormat.OGG.value, + with open(download_directory + filename + MusicFormat.OGG.value, 'wb') as file, tqdm( desc=filename, total=total_size, @@ -65,6 +67,4 @@ def download_episode(episode_id) -> None: stream.input_stream.stream().read(ZSpotify.get_config(CHUNK_SIZE)))) # convert_audio_format(ROOT_PODCAST_PATH + - # extra_paths + filename + '.ogg') - - # related functions that do stuff with the spotify API + # extra_paths + filename + '.ogg') \ No newline at end of file diff --git a/src/track.py b/src/track.py index dc148b0d..96dcd6a8 100644 --- a/src/track.py +++ b/src/track.py @@ -53,6 +53,7 @@ def get_song_info(song_id) -> tuple[list[str], str, str, Any, Any, Any, Any, Any # noinspection PyBroadException def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='', disable_progressbar=False) -> None: """ Downloads raw song audio from Spotify """ + download_directory = os.path.join(os.path.dirname(__file__), ZSpotify.get_config(ROOT_PATH), extra_paths) try: (artists, album_name, name, image_url, release_year, disc_number, track_number, scraped_song_id, is_playable) = get_song_info(track_id) @@ -63,11 +64,11 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='', ) else f'{prefix_value} - {song_name}' if ZSpotify.get_config(SPLIT_ALBUM_DISCS): - filename = os.path.join(ZSpotify.get_config(ROOT_PATH), extra_paths, 'Disc ' + str( - disc_number) + '/' + song_name + '.' + ZSpotify.get_config(DOWNLOAD_FORMAT)) + filename = os.path.join(download_directory, f'Disc {disc_number}', + f'{song_name}.{ZSpotify.get_config(DOWNLOAD_FORMAT)}') else: - filename = os.path.join(ZSpotify.get_config(ROOT_PATH), extra_paths, - song_name + '.' + ZSpotify.get_config(DOWNLOAD_FORMAT)) + filename = os.path.join(download_directory, + f'{song_name}.{ZSpotify.get_config(DOWNLOAD_FORMAT)}') except Exception: print('### SKIPPING SONG - FAILED TO QUERY METADATA ###') else: @@ -84,7 +85,7 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='', track_id = scraped_song_id track_id = TrackId.from_base62(track_id) stream = ZSpotify.get_content_stream(track_id, ZSpotify.DOWNLOAD_QUALITY) - create_download_directory(ZSpotify.get_config(ROOT_PATH) + extra_paths) + create_download_directory(download_directory) total_size = stream.input_stream.size with open(filename, 'wb') as file, tqdm( @@ -102,7 +103,7 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='', if ZSpotify.get_config(DOWNLOAD_FORMAT) == 'mp3': convert_audio_format(filename) set_audio_tags(filename, artists, name, album_name, - release_year, disc_number, track_number) + release_year, disc_number, track_number) set_music_thumbnail(filename, image_url) if not ZSpotify.get_config(OVERRIDE_AUTO_WAIT): From a08b54c8b35698025e2390a24306eeeb116a1c8f Mon Sep 17 00:00:00 2001 From: logykk Date: Sun, 24 Oct 2021 22:04:53 +1300 Subject: [PATCH 11/11] revert metadata patch --- src/album.py | 2 +- src/track.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/album.py b/src/album.py index d797d023..12f86eb6 100644 --- a/src/album.py +++ b/src/album.py @@ -28,7 +28,7 @@ def get_album_tracks(album_id): def get_album_name(album_id): """ Returns album name """ resp = ZSpotify.invoke_url(f'{ALBUM_URL}/{album_id}') - return sanitize_data(resp[ARTISTS][0][NAME]), sanitize_data(resp[NAME]) + return resp[ARTISTS][0][NAME], sanitize_data(resp[NAME]) def get_artist_albums(artist_id): diff --git a/src/track.py b/src/track.py index dc148b0d..d2ed1b10 100644 --- a/src/track.py +++ b/src/track.py @@ -37,9 +37,9 @@ def get_song_info(song_id) -> tuple[list[str], str, str, Any, Any, Any, Any, Any artists = [] for data in info[TRACKS][0][ARTISTS]: - artists.append(data[NAME]) - album_name = info[TRACKS][0][ALBUM][NAME] - name = info[TRACKS][0][NAME] + artists.append(sanitize_data(data[NAME])) + album_name = sanitize_data(info[TRACKS][0][ALBUM][NAME]) + name = sanitize_data(info[TRACKS][0][NAME]) image_url = info[TRACKS][0][ALBUM][IMAGES][0][URL] release_year = info[TRACKS][0][ALBUM][RELEASE_DATE].split('-')[0] disc_number = info[TRACKS][0][DISC_NUMBER] @@ -57,7 +57,7 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='', (artists, album_name, name, image_url, release_year, disc_number, track_number, scraped_song_id, is_playable) = get_song_info(track_id) - song_name = sanitize_data(artists[0]) + ' - ' + sanitize_data(name) + song_name = artists[0] + ' - ' + name if prefix: song_name = f'{prefix_value.zfill(2)} - {song_name}' if prefix_value.isdigit( ) else f'{prefix_value} - {song_name}'