Skip to content

Commit 8ac3bf2

Browse files
authored
feat: add progress bar using rich progress (#237)
* add progress bar using rich progress update getting started docs update yt-dlp fixes #235 * make deepsource happy * add progress bars for album download * update sentry sdk
1 parent 5e4dd38 commit 8ac3bf2

7 files changed

+116
-102
lines changed

GETTING_STARTED.md

+21-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
#### Getting start
1+
# Getting started
22

3-
Pre-requisite: You need Python 3.6+
3+
### Pre-requisites
4+
5+
You need Python 3.6+. To download songs as MP3, you will need ffmpeg.
6+
7+
- 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
8+
- 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)
49

510
1. Install using pip
611

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

1116
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.
1217

13-
You can set environment variables in Linux like so:
18+
You can set environment variables as mentioned below:
19+
20+
Linux:
1421

1522
export SPOTIPY_CLIENT_ID=your-spotify-client-id
1623
export SPOTIPY_CLIENT_SECRET=your-spotify-client-secret
1724

18-
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.
19-
20-
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.
25+
Windows Powershell:
26+
$env:SPOTIPY_CLIENT_ID='your-spotify-client-id'
27+
$env:SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'
28+
29+
Windows CMD:
30+
set SPOTIPY_CLIENT_ID='your-spotify-client-id'
31+
set SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'
2132

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

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

26-
For most users `spotify_dl -l spotify_playlist_link -o download_directory -s yes` should do where
37+
For most users `spotify_dl -l spotify_playlist_link -o download_directory` should do where
2738

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

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

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

36-
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
37-
- 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
38-
- 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)
47+
6. If you prefer to skip MP3 conversion, pass `-m` or `--skip_mp3` as a parameter when running the script
3948

4049
### Use Docker
4150

requirements.txt

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
sentry_sdk==0.19.4
2-
yt-dlp>=2021.10.10
1+
sentry_sdk==1.5.3
2+
yt-dlp>=2022.01.21
33
spotipy==2.16.1
44
mutagen==1.45.1
5+
rich==11.0.0

spotify_dl/constants.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import os
2+
from pathlib import Path
23

34
__all__ = ['VERSION']
45

5-
VERSION = '7.6.0'
6+
VERSION = '8.0.0'
67

78
if os.getenv("XDG_CACHE_HOME") is not None:
89
SAVE_PATH = os.getenv("XDG_CACHE_HOME") + "/spotifydl"
910
else:
10-
SAVE_PATH = os.getenv("HOME") + "/.cache/spotifydl"
11+
SAVE_PATH = str(Path.home()) + "/.cache/spotifydl"

spotify_dl/scaffold.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import logging
22
from os import getenv
33
import sentry_sdk
4+
from rich.logging import RichHandler
5+
from rich.console import Console
46

5-
__all__ = ['log', 'check_for_tokens']
7+
__all__ = ['log', 'check_for_tokens', 'console']
68

79
logging.basicConfig(level=logging.INFO,
8-
format='%(message)s')
9-
10+
format="%(message)s",
11+
datefmt="[%X]",
12+
handlers=[RichHandler(show_level=False, show_time=False)])
13+
console = Console()
1014
log = logging.getLogger('sdl')
1115
sentry_sdk.init("https://[email protected]/2383261")
1216

@@ -29,9 +33,11 @@ def check_for_tokens():
2933
Linux:
3034
export SPOTIPY_CLIENT_ID='your-spotify-client-id'
3135
export SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'
36+
3237
Windows Powershell:
3338
$env:SPOTIPY_CLIENT_ID='your-spotify-client-id'
3439
$env:SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'
40+
3541
Windows CMD:
3642
set SPOTIPY_CLIENT_ID='your-spotify-client-id'
3743
set SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'

spotify_dl/spotify.py

+74-77
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
from spotify_dl.scaffold import log
44
from spotify_dl.utils import sanitize
5-
6-
5+
from rich.progress import Progress
76
def fetch_tracks(sp, item_type, url):
87
"""
98
Fetches tracks from the provided URL.
@@ -16,87 +15,85 @@ def fetch_tracks(sp, item_type, url):
1615
offset = 0
1716

1817
if item_type == 'playlist':
19-
while True:
20-
items = sp.playlist_items(playlist_id=url,
21-
22-
fields='items.track.name,items.track.artists(name, uri),'
23-
'items.track.album(name, release_date, total_tracks, images),'
24-
25-
'items.track.track_number,total, next,offset,'
26-
'items.track.id',
27-
additional_types=['track'], offset=offset)
28-
total_songs = items.get('total')
29-
for item in items['items']:
30-
track_info = item.get('track')
31-
# If the user has a podcast in their playlist, there will be no track
32-
# Without this conditional, the program will fail later on when the metadata is fetched
33-
if track_info is None:
18+
with Progress() as progress:
19+
songs_task = progress.add_task(description="Fetching songs from playlist..")
20+
while True:
21+
items = sp.playlist_items(playlist_id=url,
22+
fields='items.track.name,items.track.artists(name, uri),'
23+
'items.track.album(name, release_date, total_tracks, images),'
24+
'items.track.track_number,total, next,offset,'
25+
'items.track.id',
26+
additional_types=['track'], offset=offset)
27+
total_songs = items.get('total')
28+
track_info_task = progress.add_task(description="Fetching track info", total=len(items['items']))
29+
for item in items['items']:
30+
track_info = item.get('track')
31+
# If the user has a podcast in their playlist, there will be no track
32+
# Without this conditional, the program will fail later on when the metadata is fetched
33+
if track_info is None:
34+
offset += 1
35+
continue
36+
track_album_info = track_info.get('album')
37+
track_num = track_info.get('track_number')
38+
spotify_id = track_info.get('id')
39+
track_name = track_info.get('name')
40+
track_artist = ", ".join([artist['name'] for artist in track_info.get('artists')])
41+
if track_album_info:
42+
track_album = track_album_info.get('name')
43+
track_year = track_album_info.get('release_date')[:4] if track_album_info.get('release_date') else ''
44+
album_total = track_album_info.get('total_tracks')
45+
if len(item['track']['album']['images']) > 0:
46+
cover = item['track']['album']['images'][0]['url']
47+
else:
48+
cover = None
49+
50+
artists = track_info.get('artists')
51+
main_artist_id = artists[0].get('uri', None) if len(artists) > 0 else None
52+
genres = sp.artist(artist_id=main_artist_id).get('genres', []) if main_artist_id else []
53+
if len(genres) > 0:
54+
genre = genres[0]
55+
else:
56+
genre = ""
57+
songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year,
58+
"num_tracks": album_total, "num": track_num, "playlist_num": offset + 1,
59+
"cover": cover, "genre": genre, "spotify_id": spotify_id})
3460
offset += 1
35-
continue
36-
track_album_info = track_info.get('album')
37-
38-
track_num = track_info.get('track_number')
39-
spotify_id = track_info.get('id')
40-
track_name = track_info.get('name')
41-
track_artist = ", ".join([artist['name'] for artist in track_info.get('artists')])
42-
43-
if track_album_info:
44-
track_album = track_album_info.get('name')
45-
track_year = track_album_info.get('release_date')[:4] if track_album_info.get('release_date') else ''
46-
album_total = track_album_info.get('total_tracks')
47-
48-
if len(item['track']['album']['images']) > 0:
49-
cover = item['track']['album']['images'][0]['url']
61+
progress.update(task_id=track_info_task, description=f"Fetching track info for \n{track_name}",advance=1)
62+
progress.update(task_id=songs_task, description=f"Fetched {offset} of {total_songs} songs from the playlist", advance=100, total=total_songs)
63+
if total_songs == offset:
64+
break
65+
66+
elif item_type == 'album':
67+
with Progress() as progress:
68+
album_songs_task = progress.add_task(description="Fetching songs from the album..")
69+
while True:
70+
album_info = sp.album(album_id=url)
71+
items = sp.album_tracks(album_id=url)
72+
total_songs = items.get('total')
73+
track_album = album_info.get('name')
74+
track_year = album_info.get('release_date')[:4] if album_info.get('release_date') else ''
75+
album_total = album_info.get('total_tracks')
76+
if len(album_info['images']) > 0:
77+
cover = album_info['images'][0]['url']
5078
else:
5179
cover = None
52-
53-
artists = track_info.get('artists')
54-
main_artist_id = artists[0].get('uri', None) if len(artists) > 0 else None
55-
genres = sp.artist(artist_id=main_artist_id).get('genres', []) if main_artist_id else []
56-
if len(genres) > 0:
57-
genre = genres[0]
80+
if len(sp.artist(artist_id=album_info['artists'][0]['uri'])['genres']) > 0:
81+
genre = sp.artist(artist_id=album_info['artists'][0]['uri'])['genres'][0]
5882
else:
5983
genre = ""
60-
songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year,
61-
"num_tracks": album_total, "num": track_num, "playlist_num": offset + 1,
62-
"cover": cover, "genre": genre, "spotify_id": spotify_id})
63-
offset += 1
64-
65-
log.info(f"Fetched {offset}/{total_songs} songs in the playlist")
66-
if total_songs == offset:
67-
log.info('All pages fetched, time to leave. Added %s songs in total', offset)
68-
break
69-
70-
elif item_type == 'album':
71-
while True:
72-
album_info = sp.album(album_id=url)
73-
items = sp.album_tracks(album_id=url)
74-
total_songs = items.get('total')
75-
track_album = album_info.get('name')
76-
track_year = album_info.get('release_date')[:4] if album_info.get('release_date') else ''
77-
album_total = album_info.get('total_tracks')
78-
if len(album_info['images']) > 0:
79-
cover = album_info['images'][0]['url']
80-
else:
81-
cover = None
82-
if len(sp.artist(artist_id=album_info['artists'][0]['uri'])['genres']) > 0:
83-
genre = sp.artist(artist_id=album_info['artists'][0]['uri'])['genres'][0]
84-
else:
85-
genre = ""
86-
for item in items['items']:
87-
track_name = item.get('name')
88-
track_artist = ", ".join([artist['name'] for artist in item['artists']])
89-
track_num = item['track_number']
90-
spotify_id = item.get('id')
91-
songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year,
92-
"num_tracks": album_total, "num": track_num, "playlist_num": offset + 1,
93-
"cover": cover, "genre": genre, "spotify_id": spotify_id})
94-
offset += 1
84+
for item in items['items']:
85+
track_name = item.get('name')
86+
track_artist = ", ".join([artist['name'] for artist in item['artists']])
87+
track_num = item['track_number']
88+
spotify_id = item.get('id')
89+
songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year,
90+
"num_tracks": album_total, "num": track_num, "playlist_num": offset + 1,
91+
"cover": cover, "genre": genre, "spotify_id": spotify_id})
92+
offset += 1
9593

96-
log.info(f"Fetched {offset}/{total_songs} songs in the album")
97-
if total_songs == offset:
98-
log.info('All pages fetched, time to leave. Added %s songs in total', offset)
99-
break
94+
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)
95+
if total_songs == offset:
96+
break
10097

10198
elif item_type == 'track':
10299
items = sp.track(track_id=url)

spotify_dl/spotify_dl.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
import sys
66
from logging import DEBUG
77
from pathlib import Path, PurePath
8-
98
import spotipy
109
from spotipy.oauth2 import SpotifyClientCredentials
1110

1211
from spotify_dl.constants import VERSION
13-
from spotify_dl.scaffold import log, check_for_tokens
12+
from spotify_dl.scaffold import log, check_for_tokens, console
1413
from spotify_dl.spotify import fetch_tracks, parse_spotify_url, validate_spotify_url, get_item_name
1514
from spotify_dl.youtube import download_songs, default_filename, playlist_num_filename
1615

@@ -42,7 +41,7 @@ def spotify_dl():
4241
args = parser.parse_args()
4342

4443
if args.version:
45-
print("spotify_dl v{}".format(VERSION))
44+
console.print(f"spotify_dl [bold green]v{VERSION}[/bold green]")
4645
sys.exit(0)
4746

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

6160
if not hasattr(args, 'url'):
6261
raise(Exception("No playlist url provided"))
63-
if not hasattr(args, 'o'):
62+
if not hasattr(args, 'output'):
6463
raise(Exception("No output folder configured"))
6564

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

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

8887
songs = fetch_tracks(sp, item_type, args.url)
8988
if args.download is True:

spotify_dl/youtube.py

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def download_songs(songs, download_directory, format_string, skip_mp3,
4747
'outtmpl': outtmpl,
4848
'default_search': 'ytsearch',
4949
'noplaylist': True,
50+
'no_color': False,
5051
'postprocessor_args': ['-metadata', 'title=' + song.get('name'),
5152
'-metadata', 'artist=' + song.get('artist'),
5253
'-metadata', 'album=' + song.get('album')]

0 commit comments

Comments
 (0)