Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add progress bar using rich progress #237

Merged
merged 4 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 21 additions & 12 deletions GETTING_STARTED.md
@@ -1,6 +1,11 @@
#### Getting start
# Getting started

Pre-requisite: You need Python 3.6+
### Pre-requisites

You need Python 3.6+. To download songs as MP3, you will need ffmpeg.

- Linux users can get them by installing libav-tools by using apt-get `sudo apt-get install -y libav-tools`) or a package manager which comes with your distro
- Windows users can download FFMPEG pre-built binaries from [here](http://ffmpeg.zeranoe.com/builds/). Extract the file using [7-zip](http://7-zip.org/) to a foldrer and [add the folder to your PATH environment variable](http://www.wikihow.com/Install-FFmpeg-on-Windows)

1. Install using pip

Expand All @@ -10,20 +15,26 @@ Pre-requisite: You need Python 3.6+

3. Make a note of Client ID and Client Secret. These values need to be then set `SPOTIPY_CLIENT_ID`, `SPOTIPY_CLIENT_SECRET` environment variables respectively.

You can set environment variables in Linux like so:
You can set environment variables as mentioned below:

Linux:

export SPOTIPY_CLIENT_ID=your-spotify-client-id
export SPOTIPY_CLIENT_SECRET=your-spotify-client-secret

Windows users, check for [this question](http://superuser.com/a/284351/4377) for details on how you can set environment variables. If you don't wish to use my URL for the redirect, you are free to use any valid URL. Just ensurethe redirect URL set as the environment variable matches with what you have entered in the developer console & in the environment variable above.

4. Create your YouTube API key & fetch the keys from [Google Developer Console](https://console.developers.google.com/apis/api/youtube/overview). Set the key as `YOUTUBE_DEV_KEY` environment variable as mentioned above. Note that as of **version 5 you do not have to set this** - it will fallback to scraping the YouTube page.
Windows Powershell:
$env:SPOTIPY_CLIENT_ID='your-spotify-client-id'
$env:SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'

Windows CMD:
set SPOTIPY_CLIENT_ID='your-spotify-client-id'
set SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'

export YOUTUBE_DEV_KEY=youtube-dev-key
See [this question](http://superuser.com/a/284351/4377) for more info,

5. Run the script using `spotify_dl`. spotify_dl accepts different parameters, for more details run `spotify_dl -h`.
4. Run the script using `spotify_dl`. spotify_dl accepts different parameters, for more details run `spotify_dl -h`.

For most users `spotify_dl -l spotify_playlist_link -o download_directory -s yes` should do where
For most users `spotify_dl -l spotify_playlist_link -o download_directory` should do where

- `spotify_playlist_link` is a link to Spotify's playlist. You can get it from the 3-dot menu.

Expand All @@ -33,9 +44,7 @@ Pre-requisite: You need Python 3.6+

- `download_directory` is the location where the songs must be downloaded to. If you give a `.` then it will download to the current directory.

6. To retrieve download songs as MP3, you will need to install ffmpeg. If you prefer to skip MP3 conversion, pass `-m` or `--skip_mp3` as a parameter when running the script
- Linux users can get them by installing libav-tools by using apt-get (`sudo apt-get install -y libav-tools`) or a package manager which comes with your distro
- Windows users can download FFMPEG pre-built binaries from [here](http://ffmpeg.zeranoe.com/builds/). Extract the file using [7-zip](http://7-zip.org/) to a foldrer and [add the folder to your PATH environment variable](http://www.wikihow.com/Install-FFmpeg-on-Windows)
6. If you prefer to skip MP3 conversion, pass `-m` or `--skip_mp3` as a parameter when running the script

### Use Docker

Expand Down
5 changes: 3 additions & 2 deletions requirements.txt
@@ -1,4 +1,5 @@
sentry_sdk==0.19.4
yt-dlp>=2021.10.10
sentry_sdk==1.5.3
yt-dlp>=2022.01.21
spotipy==2.16.1
mutagen==1.45.1
rich==11.0.0
5 changes: 3 additions & 2 deletions spotify_dl/constants.py
@@ -1,10 +1,11 @@
import os
from pathlib import Path

__all__ = ['VERSION']

VERSION = '7.6.0'
VERSION = '8.0.0'

if os.getenv("XDG_CACHE_HOME") is not None:
SAVE_PATH = os.getenv("XDG_CACHE_HOME") + "/spotifydl"
else:
SAVE_PATH = os.getenv("HOME") + "/.cache/spotifydl"
SAVE_PATH = str(Path.home()) + "/.cache/spotifydl"
12 changes: 9 additions & 3 deletions spotify_dl/scaffold.py
@@ -1,12 +1,16 @@
import logging
from os import getenv
import sentry_sdk
from rich.logging import RichHandler
from rich.console import Console

__all__ = ['log', 'check_for_tokens']
__all__ = ['log', 'check_for_tokens', 'console']

logging.basicConfig(level=logging.INFO,
format='%(message)s')

format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(show_level=False, show_time=False)])
console = Console()
log = logging.getLogger('sdl')
sentry_sdk.init("https://fc66a23d79634b9bba1690ea13e289f0@o321064.ingest.sentry.io/2383261")

Expand All @@ -29,9 +33,11 @@ def check_for_tokens():
Linux:
export SPOTIPY_CLIENT_ID='your-spotify-client-id'
export SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'

Windows Powershell:
$env:SPOTIPY_CLIENT_ID='your-spotify-client-id'
$env:SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'

Windows CMD:
set SPOTIPY_CLIENT_ID='your-spotify-client-id'
set SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'
Expand Down
151 changes: 74 additions & 77 deletions spotify_dl/spotify.py
Expand Up @@ -2,8 +2,7 @@

from spotify_dl.scaffold import log
from spotify_dl.utils import sanitize


from rich.progress import Progress
def fetch_tracks(sp, item_type, url):
"""
Fetches tracks from the provided URL.
Expand All @@ -16,87 +15,85 @@ def fetch_tracks(sp, item_type, url):
offset = 0

if item_type == 'playlist':
while True:
items = sp.playlist_items(playlist_id=url,

fields='items.track.name,items.track.artists(name, uri),'
'items.track.album(name, release_date, total_tracks, images),'

'items.track.track_number,total, next,offset,'
'items.track.id',
additional_types=['track'], offset=offset)
total_songs = items.get('total')
for item in items['items']:
track_info = item.get('track')
# If the user has a podcast in their playlist, there will be no track
# Without this conditional, the program will fail later on when the metadata is fetched
if track_info is None:
with Progress() as progress:
songs_task = progress.add_task(description="Fetching songs from playlist..")
while True:
items = sp.playlist_items(playlist_id=url,
fields='items.track.name,items.track.artists(name, uri),'
'items.track.album(name, release_date, total_tracks, images),'
'items.track.track_number,total, next,offset,'
'items.track.id',
additional_types=['track'], offset=offset)
total_songs = items.get('total')
track_info_task = progress.add_task(description="Fetching track info", total=len(items['items']))
for item in items['items']:
track_info = item.get('track')
# If the user has a podcast in their playlist, there will be no track
# Without this conditional, the program will fail later on when the metadata is fetched
if track_info is None:
offset += 1
continue
track_album_info = track_info.get('album')
track_num = track_info.get('track_number')
spotify_id = track_info.get('id')
track_name = track_info.get('name')
track_artist = ", ".join([artist['name'] for artist in track_info.get('artists')])
if track_album_info:
track_album = track_album_info.get('name')
track_year = track_album_info.get('release_date')[:4] if track_album_info.get('release_date') else ''
album_total = track_album_info.get('total_tracks')
if len(item['track']['album']['images']) > 0:
cover = item['track']['album']['images'][0]['url']
else:
cover = None

artists = track_info.get('artists')
main_artist_id = artists[0].get('uri', None) if len(artists) > 0 else None
genres = sp.artist(artist_id=main_artist_id).get('genres', []) if main_artist_id else []
if len(genres) > 0:
genre = genres[0]
else:
genre = ""
songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year,
"num_tracks": album_total, "num": track_num, "playlist_num": offset + 1,
"cover": cover, "genre": genre, "spotify_id": spotify_id})
offset += 1
continue
track_album_info = track_info.get('album')

track_num = track_info.get('track_number')
spotify_id = track_info.get('id')
track_name = track_info.get('name')
track_artist = ", ".join([artist['name'] for artist in track_info.get('artists')])

if track_album_info:
track_album = track_album_info.get('name')
track_year = track_album_info.get('release_date')[:4] if track_album_info.get('release_date') else ''
album_total = track_album_info.get('total_tracks')

if len(item['track']['album']['images']) > 0:
cover = item['track']['album']['images'][0]['url']
progress.update(task_id=track_info_task, description=f"Fetching track info for \n{track_name}",advance=1)
progress.update(task_id=songs_task, description=f"Fetched {offset} of {total_songs} songs from the playlist", advance=100, total=total_songs)
if total_songs == offset:
break

elif item_type == 'album':
with Progress() as progress:
album_songs_task = progress.add_task(description="Fetching songs from the album..")
while True:
album_info = sp.album(album_id=url)
items = sp.album_tracks(album_id=url)
total_songs = items.get('total')
track_album = album_info.get('name')
track_year = album_info.get('release_date')[:4] if album_info.get('release_date') else ''
album_total = album_info.get('total_tracks')
if len(album_info['images']) > 0:
cover = album_info['images'][0]['url']
else:
cover = None

artists = track_info.get('artists')
main_artist_id = artists[0].get('uri', None) if len(artists) > 0 else None
genres = sp.artist(artist_id=main_artist_id).get('genres', []) if main_artist_id else []
if len(genres) > 0:
genre = genres[0]
if len(sp.artist(artist_id=album_info['artists'][0]['uri'])['genres']) > 0:
genre = sp.artist(artist_id=album_info['artists'][0]['uri'])['genres'][0]
else:
genre = ""
songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year,
"num_tracks": album_total, "num": track_num, "playlist_num": offset + 1,
"cover": cover, "genre": genre, "spotify_id": spotify_id})
offset += 1

log.info(f"Fetched {offset}/{total_songs} songs in the playlist")
if total_songs == offset:
log.info('All pages fetched, time to leave. Added %s songs in total', offset)
break

elif item_type == 'album':
while True:
album_info = sp.album(album_id=url)
items = sp.album_tracks(album_id=url)
total_songs = items.get('total')
track_album = album_info.get('name')
track_year = album_info.get('release_date')[:4] if album_info.get('release_date') else ''
album_total = album_info.get('total_tracks')
if len(album_info['images']) > 0:
cover = album_info['images'][0]['url']
else:
cover = None
if len(sp.artist(artist_id=album_info['artists'][0]['uri'])['genres']) > 0:
genre = sp.artist(artist_id=album_info['artists'][0]['uri'])['genres'][0]
else:
genre = ""
for item in items['items']:
track_name = item.get('name')
track_artist = ", ".join([artist['name'] for artist in item['artists']])
track_num = item['track_number']
spotify_id = item.get('id')
songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year,
"num_tracks": album_total, "num": track_num, "playlist_num": offset + 1,
"cover": cover, "genre": genre, "spotify_id": spotify_id})
offset += 1
for item in items['items']:
track_name = item.get('name')
track_artist = ", ".join([artist['name'] for artist in item['artists']])
track_num = item['track_number']
spotify_id = item.get('id')
songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year,
"num_tracks": album_total, "num": track_num, "playlist_num": offset + 1,
"cover": cover, "genre": genre, "spotify_id": spotify_id})
offset += 1

log.info(f"Fetched {offset}/{total_songs} songs in the album")
if total_songs == offset:
log.info('All pages fetched, time to leave. Added %s songs in total', offset)
break
progress.update(task_id=album_songs_task, description=f"Fetched {offset} of {album_total} songs from the album {track_album}", advance=offset, total=album_total)
if total_songs == offset:
break

elif item_type == 'track':
items = sp.track(track_id=url)
Expand Down
11 changes: 5 additions & 6 deletions spotify_dl/spotify_dl.py
Expand Up @@ -5,12 +5,11 @@
import sys
from logging import DEBUG
from pathlib import Path, PurePath

import spotipy
from spotipy.oauth2 import SpotifyClientCredentials

from spotify_dl.constants import VERSION
from spotify_dl.scaffold import log, check_for_tokens
from spotify_dl.scaffold import log, check_for_tokens, console
from spotify_dl.spotify import fetch_tracks, parse_spotify_url, validate_spotify_url, get_item_name
from spotify_dl.youtube import download_songs, default_filename, playlist_num_filename

Expand Down Expand Up @@ -42,7 +41,7 @@ def spotify_dl():
args = parser.parse_args()

if args.version:
print("spotify_dl v{}".format(VERSION))
console.print(f"spotify_dl [bold green]v{VERSION}[/bold green]")
sys.exit(0)

if os.path.isfile(os.path.expanduser('~/.spotify_dl_settings')):
Expand All @@ -60,10 +59,10 @@ def spotify_dl():

if not hasattr(args, 'url'):
raise(Exception("No playlist url provided"))
if not hasattr(args, 'o'):
if not hasattr(args, 'output'):
raise(Exception("No output folder configured"))

log.info('Starting spotify_dl')
console.log(f"Starting spotify_dl [bold green]v{VERSION}[/bold green]")
log.debug('Setting debug mode on spotify_dl')

if not check_for_tokens():
Expand All @@ -83,7 +82,7 @@ def spotify_dl():
directory_name = get_item_name(sp, item_type, item_id)
save_path = Path(PurePath.joinpath(Path(args.output), Path(directory_name)))
save_path.mkdir(parents=True, exist_ok=True)
log.info("Saving songs to: {}".format(directory_name))
console.print(f"Saving songs to [bold green]{directory_name}[/bold green] directory")

songs = fetch_tracks(sp, item_type, args.url)
if args.download is True:
Expand Down
1 change: 1 addition & 0 deletions spotify_dl/youtube.py
Expand Up @@ -47,6 +47,7 @@ def download_songs(songs, download_directory, format_string, skip_mp3,
'outtmpl': outtmpl,
'default_search': 'ytsearch',
'noplaylist': True,
'no_color': False,
'postprocessor_args': ['-metadata', 'title=' + song.get('name'),
'-metadata', 'artist=' + song.get('artist'),
'-metadata', 'album=' + song.get('album')]
Expand Down