Skip to content
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
<img src="https://user-images.githubusercontent.com/35679186/141209937-049e8a52-95fd-4028-aa6c-d70670cd0171.png">
</p>

<!---
[Discord Server](https://discord.gg/skVNQKtyFq) - [Matrix Server](https://matrix.to/#/#zspotify:matrix.org) - [Gitea Mirror](https://git.robinsmediateam.dev/Footsiefat/zspotify) - [Main Site](https://footsiefat.github.io/)
-->



Expand Down Expand Up @@ -76,6 +78,7 @@ MAC:

<h3>Command Line Usage</h3>

```
Basic command line usage:
python zspotify <track/album/playlist/episode/artist url> Downloads the track, album, playlist or podcast episode specified as a command line argument. If an artist url is given, all albums by specified artist will be downloaded. Can take multiple urls.

Expand All @@ -88,7 +91,6 @@ Different usage modes:

Extra command line options:
-ns, --no-splash Suppress the splash screen when loading.
<<<<<<< HEAD

Options that can be configured in zs_config.json:
ROOT_PATH Change this path if you don't like the default directory where ZSpotify saves the music
Expand All @@ -105,26 +107,28 @@ Options that can be configured in zs_config.json:

=======
--config-location Use a different zs_config.json, defaults to the one in the program directory
```
```



### Options:

All these options can either be configured in the zs_config or via the commandline, in case of both the commandline-option has higher priority.
Be aware you have to set boolean values in the commandline like this: `--download-real-time=True`

```
| Key (zs-config) | commandline parameter | Description
|------------------------------|----------------------------------|---------------------------------------------------------------------|
| ROOT_PATH | --root-path | directory where ZSpotify saves the music
| ROOT_PODCAST_PATH | --root-podcast-path | directory where ZSpotify saves the podcasts
| ROOT_PATH | --root-path | Directory where ZSpotify saves the music
| ROOT_PODCAST_PATH | --root-podcast-path | Directory where ZSpotify saves the podcasts
| SKIP_EXISTING_FILES | --skip-existing-files | Skip songs with the same name
| SKIP_PREVIOUSLY_DOWNLOADED | --skip-previously-downloaded | Create a .song_archive file and skip previously downloaded songs
| DOWNLOAD_FORMAT | --download-format | The download audio format (aac, fdk_aac, m4a, mp3, ogg, opus, vorbis)
| FORCE_PREMIUM | --force-premium | Force the use of high quality downloads (only with premium accounts)
| ANTI_BAN_WAIT_TIME | --anti-ban-wait-time | The wait time between bulk downloads
| OVERRIDE_AUTO_WAIT | --override-auto-wait | Totally disable wait time between songs with the risk of instability
| CHUNK_SIZE | --chunk-size | chunk size for downloading
| SPLIT_ALBUM_DISCS | --split-album-discs | split downloaded albums by disc
| DOWNLOAD_REAL_TIME | --download-real-time | only downloads songs as fast as they would be played, can prevent account bans
| SPLIT_ALBUM_DISCS | --split-album-discs | Split downloaded albums by disc
| DOWNLOAD_REAL_TIME | --download-real-time | Only downloads songs as fast as they would be played, can prevent account bans
| LANGUAGE | --language | Language for spotify metadata
| BITRATE | --bitrate | Overwrite the bitrate for ffmpeg encoding
| SONG_ARCHIVE | --song-archive | The song_archive file for SKIP_PREVIOUSLY_DOWNLOADED
Expand All @@ -136,12 +140,14 @@ Be aware you have to set boolean values in the commandline like this: `--downloa
| PRINT_ERRORS | --print-errors | Print errors
| PRINT_DOWNLOADS | --print-downloads | Print messages when a song is finished downloading
| TEMP_DOWNLOAD_DIR | --temp-download-dir | Download tracks to a temporary directory first

| ENABLE_MEDIA_KEYS | --enable-media-keys | Allows use of the media keys on your keyboard to play/pause, change track
| RELATIVE_TIME | --relative-time | Changes the song duration time to time remaining
```
### Output format:

With the option `OUTPUT` (or the commandline parameter `--output`) you can specify the output location and format.
The value is relative to the `ROOT_PATH`/`ROOT_PODCAST_PATH` directory and can contain the following placeholder:

```
| Placeholder | Description
|-----------------|--------------------------------
| {artist} | The song artist
Expand All @@ -157,7 +163,7 @@ The value is relative to the `ROOT_PATH`/`ROOT_PODCAST_PATH` directory and can c
| {album_num} | (only when downloading albums) Incrementing track number
| {playlist} | (only when downloading playlists) Name of the playlist
| {playlist_num} | (only when downloading playlists) Incrementing track number

```
Example values could be:
~~~~
{playlist}/{artist} - {song_name}.{ext}
Expand All @@ -169,10 +175,9 @@ Liked Songs/{artist} - {song_name}.{ext}
~~~~

### Docker Usage
>>>>>>> 1585133e70ad6ab21c70e07f5c9d98b1127eca3e
`>>>>>>> 1585133e70ad6ab21c70e07f5c9d98b1127eca3e`

<h4>FAQ<h4/>
```
## FAQ

### Will my account get banned if I use this tool?

Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pyqtdarktheme
python-vlc
requests
tqdm
pydub
pydub
keyboard
3 changes: 2 additions & 1 deletion source/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from getpass import getpass
import os
from album import download_album, download_artist_albums
from const import TRACK, NAME, ID, ARTIST, ARTISTS, ITEMS, TRACKS, EXPLICIT, ALBUM, ALBUMS, \
from const import TRACK, NAME, ID, ALBUM_ID, ARTIST, ARTISTS, ITEMS, TRACKS, EXPLICIT, ALBUM, ALBUMS, \
OWNER, PLAYLIST, PLAYLISTS, DISPLAY_NAME
from playlist import get_playlist_songs, get_playlist_info, download_from_user_playlist, download_playlist
from podcast import download_episode, get_show_episodes
Expand Down Expand Up @@ -182,6 +182,7 @@ def search(search_term):
dics.append({
ID: track[ID],
NAME: track[NAME],
ALBUM_ID: track[ALBUM][ID],
'type': TRACK,
})

Expand Down
43 changes: 32 additions & 11 deletions source/appGui.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
import requests
import logging
from mutagen.id3 import ID3
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QThreadPool
from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog, QTreeWidgetItem, QLineEdit
Expand Down Expand Up @@ -76,6 +77,7 @@ def __init__(self, parent=None):
self.download_tree.focus()
self.reconnecting = False


def show(self):
super().show()
if not ZSpotify.login():
Expand Down Expand Up @@ -144,8 +146,11 @@ def display_results(self, results):
def update_item_info(self, item, headers, labels):
if not item: return
self.selected_item = item
worker = Worker(self._cover_art_loader, item)
QThreadPool.globalInstance().start(worker)
if item.downloaded:
self.extract_cover_art(item)
else:
worker = Worker(self._cover_art_loader, item)
QThreadPool.globalInstance().start(worker)
[lbl.setText("") for lbl in self.info_labels]
if "Index" in headers:
labels.pop(headers.index("Index"))
Expand Down Expand Up @@ -183,6 +188,21 @@ def request_cover_art(self, url):
lbl.setScaledContents(True)
lbl.show()

def extract_cover_art(self, item):
if item.path == "": return
tags = ID3(item.path)
pict = [v for v in tags.values() if v.FrameID == "APIC"]
if not len(pict):
pass
if not pict[0]:
pass
pixmap = QPixmap()
pixmap.loadFromData(pict[0].data)
lbl = self.coverArtLabel
lbl.setPixmap(pixmap)
lbl.setScaledContents(True)
lbl.show()

def update_result_amount(self, index):
amount = int(self.resultAmountCombo.itemText(index))
Config.set(TOTAL_SEARCH_RESULTS, amount)
Expand Down Expand Up @@ -239,6 +259,7 @@ def init_signals(self):
tree.signals.onSelected.connect(self.update_item_labels)
tree.signals.doubleClicked.connect(self.on_try_play_item)
tree.signals.onListenQueued.connect(self.music_controller.queue_track)
tree.signals.onListenUnqueued.connect(self.music_controller.remove_track)
tree.signals.onDownloadQueued.connect(self.download_controller.on_click_download)
return_shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Return,
tree.tree,
Expand Down Expand Up @@ -339,32 +360,32 @@ def init_tree_views(self):
self.songs_tree.set_header_item(
Track("Index", 0, "Title", "Artists", "Album", duration="Duration", release_date="Release Date"))

self.artists_tree = ItemTree(self.artistsTree, lambda artist: QTreeWidgetItem([str(artist.index), artist.name]))
self.artists_tree = ItemTree(self.artistsTree, lambda artist: QTreeWidgetItem([str(artist.index), artist.name]), "artists")
self.artists_tree.set_header_item(Artist("Index", 0, "Name"))

self.albums_tree = ItemTree(self.albumsTree,
lambda album: QTreeWidgetItem([str(album.index), album.title, album.artists, \
str(album.total_tracks), str(album.release_date)]))
str(album.total_tracks), str(album.release_date)]), "albums")
self.albums_tree.set_header_item(Album("Index", 0, "Title", "Artists", "Total Tracks", "Release Date"))

self.playlists_tree = ItemTree(self.playlistsTree,
lambda playlist: QTreeWidgetItem([str(playlist.index), playlist.title, \
str(playlist.creator),
str(playlist.total_tracks)]))
str(playlist.total_tracks)]), "playlists")
self.playlists_tree.set_header_item(Playlist("Index", 0, "Title", "Author", "Total Tracks"))

self.download_tree = ItemTree(self.downloadedTree,
lambda track: QTreeWidgetItem([track.title, track.artists, track.album]))
self.download_tree.set_header_item(Track("", 0, "Title", "Artists", "Albums"))
lambda track: QTreeWidgetItem([track.title, track.artists, track.album]), "download")
self.download_tree.set_header_item(Track("", 0, "Title", "Artists", "Album"))

self.liked_tree = ItemTree(self.likedTree,
lambda track: QTreeWidgetItem([track.title, track.artists, track.album]))
self.liked_tree.set_header_item(Track("", 0, "Title", "Artists", "Albums"))
lambda track: QTreeWidgetItem([track.title, track.artists, track.album]), "liked")
self.liked_tree.set_header_item(Track("", 0, "Title", "Artists", "Album"))
self.liked_tree.load_function = self.init_liked_view

self.queue_tree = ItemTree(self.queueTree,
lambda track: QTreeWidgetItem([track.title, track.artists, track.album]), False)
self.queue_tree.set_header_item(Track("", 0, "Title", "Artists", "Albums"))
lambda track: QTreeWidgetItem([track.title, track.artists, track.album]), "queue", False)
self.queue_tree.set_header_item(Track("", 0, "Title", "Artists", "Album"))
self.queue_tree.load_function = self.init_queue_view

self.search_trees = [self.songs_tree, self.artists_tree, self.albums_tree, self.playlists_tree]
Expand Down
Loading