Add playlist support and ditch sysbox
This commit is contained in:
1042
.gitignore
vendored
Normal file
1042
.gitignore
vendored
Normal file
File diff suppressed because it is too large
Load Diff
31
README.md
31
README.md
@@ -1,31 +0,0 @@
|
||||
## SUPPORT YOUR ARTISTS
|
||||
|
||||
As of 2025, Spotify pays an average of $0.005 per stream to the artist. That means that if you give the equivalent of $5 directly to them (like merch, buying cds, or just donating), you can """ethically""" listen to them a total of 1000 times. Of course, nobody thinks spotify payment is fair, so preferably you should give more, but $5 is the bare minimum. Big names prolly don't need those $5 dollars, but it might be _the_ difference between going out of business or not for that indie rock band you like.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone this repo
|
||||
2. Inside its directory, run `docker build -t zotifarr .`
|
||||
3. Setup the docker-compose.yaml file (for easier maintenance, preferably in a different directory from the installation)
|
||||
4. Deploy the container and access {ip of your server}:7070.
|
||||
5. Start ripping
|
||||
|
||||
## Adding accounts
|
||||
|
||||
Follow [this repo](https://github.com/dspearson/librespot-auth?tab=readme-ov-file) using a PC/laptop with spotify client installed, and once it generates the credentials.json, you need to modify it as follows:
|
||||
|
||||
- Replace `"auth_type": 1` with `"type":"AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS"`
|
||||
- Rename `"auth_data"` to `"credentials"`
|
||||
|
||||
Once you do that, you need to go where your docker-compose.yaml is and create the file `./credentials/{account_custom_name}/credentials.json` and paste the contents from the `credentials.json` you modified previously on your PC/laptop.
|
||||
|
||||
Added accounts should be catched automatically on the frontend after reloading it.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
[Zotify](https://github.com/zotify-dev/zotify), for basically being 90% of this project. Cool shi my guy, keep the great work.
|
||||
This project is NOT an NZBDrone fork, despite its -arr suffix. More info [here](https://www.reddit.com/r/radarr/comments/hbwnb2/a_list_of_all_companion_tools_and_software/)
|
||||
|
||||
## Roadmap:
|
||||
|
||||
* Before downloading from spotify, try to download from another service (deezer, tidal or qobuz) in order to at least try on getting best quality.
|
||||
2
app.py
2
app.py
@@ -15,4 +15,4 @@ def serve_frontend():
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=7070, debug=True, threaded=True)
|
||||
app.run(host='0.0.0.0', port=7071, debug=True, threaded=True)
|
||||
|
||||
38
build.sh
38
build.sh
@@ -1,38 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build the "zotify" image using ./builds as the build context
|
||||
echo "Building zotify image..."
|
||||
docker build -t zotify ./builds
|
||||
|
||||
# Check if the build was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "zotify image built successfully."
|
||||
|
||||
# Save the zotify image as a tar file
|
||||
echo "Saving zotify image to ./zotify.tar..."
|
||||
docker save -o ./zotify.tar zotify
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "zotify image saved to ./zotify.tar."
|
||||
else
|
||||
echo "Failed to save zotify image."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Failed to build zotify image."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the "zotifarr" image using ./ as the build context
|
||||
echo "Building zotifarr image..."
|
||||
docker build -t zotifarr ./
|
||||
|
||||
# Check if the build was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "zotifarr image built successfully."
|
||||
else
|
||||
echo "Failed to build zotifarr image."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All tasks completed successfully."
|
||||
@@ -1,15 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
zotifarr:
|
||||
image: zotifarr
|
||||
ports:
|
||||
- "7070:7070"
|
||||
runtime: sysbox-runc
|
||||
volumes:
|
||||
- "./credentials:/app/credentials"
|
||||
- "./downloads:/app/downloads" # <-- change this mapping for your music library directory
|
||||
environment:
|
||||
FLASK_APP: app.py
|
||||
FLASK_RUN_HOST: 0.0.0.0
|
||||
restart: unless-stopped
|
||||
@@ -106,6 +106,7 @@ export function initDownload() {
|
||||
let currentTrackName = '';
|
||||
let currentTrackArtists = '';
|
||||
let playlistName = '';
|
||||
let albumName = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
@@ -137,6 +138,14 @@ export function initDownload() {
|
||||
statusLine.textContent = `Starting playlist: ${playlistName} (${totalTracks} tracks)`;
|
||||
break;
|
||||
|
||||
case 'album_start':
|
||||
totalTracks = data.total_tracks;
|
||||
albumName = data.album;
|
||||
completedTracks = 0;
|
||||
currentTrackNumber = 0;
|
||||
statusLine.textContent = `Starting album: ${albumName} (${totalTracks} tracks)`;
|
||||
break;
|
||||
|
||||
case 'track_start':
|
||||
if (type === 'playlist') {
|
||||
currentTrackNumber++;
|
||||
@@ -146,10 +155,16 @@ export function initDownload() {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'album_track_start':
|
||||
currentTrackNumber = data.number;
|
||||
currentTrackName = data.track;
|
||||
statusLine.textContent = `Starting track ${currentTrackNumber}/${totalTracks}: ${currentTrackName}`;
|
||||
break;
|
||||
|
||||
case 'metadata':
|
||||
if (type === 'track') {
|
||||
statusLine.textContent = `Downloading: ${data.name} by ${data.artists[0]}`;
|
||||
} else if (type === 'playlist') {
|
||||
} else if (type === 'playlist' || type === 'album') {
|
||||
currentTrackName = data.name;
|
||||
currentTrackArtists = data.artists.join(', ');
|
||||
statusLine.textContent = `Downloading track ${currentTrackNumber}/${totalTracks}: ${currentTrackName} by ${currentTrackArtists}`;
|
||||
@@ -166,9 +181,7 @@ export function initDownload() {
|
||||
const progress = `${lastPercentage}% of ${formatBytes(data.total)}`;
|
||||
if (type === 'track') {
|
||||
statusLine.textContent = `Downloading: ${progress}`;
|
||||
} else if (type === 'album') {
|
||||
statusLine.textContent = `Downloading track ${currentTrackNumber}/${totalTracks}: ${currentTrackName} (${progress})`;
|
||||
} else if (type === 'playlist') {
|
||||
} else if (type === 'album' || type === 'playlist') {
|
||||
statusLine.textContent = `Downloading track ${currentTrackNumber}/${totalTracks}: ${currentTrackName} (${progress})`;
|
||||
} else {
|
||||
statusLine.textContent = `Downloading track ${completedTracks + 1}: ${progress}`;
|
||||
@@ -200,6 +213,11 @@ export function initDownload() {
|
||||
statusLine.classList.add('completed');
|
||||
break;
|
||||
|
||||
case 'album_complete':
|
||||
statusLine.textContent = `Album "${data.album}" complete! ${data.successful}/${data.total} tracks downloaded`;
|
||||
statusLine.classList.add('completed');
|
||||
break;
|
||||
|
||||
case 'track_error':
|
||||
completedTracks++;
|
||||
statusLine.textContent = `Error: ${data.error}`;
|
||||
@@ -233,4 +251,4 @@ function formatBytes(bytes) {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// Account loading functionality
|
||||
export async function loadAccounts() {
|
||||
try {
|
||||
const response = await fetch('/api/check');
|
||||
|
||||
@@ -51,4 +51,4 @@ def download():
|
||||
finally:
|
||||
proc.wait()
|
||||
|
||||
return Response(generate(), mimetype='application/x-ndjson')
|
||||
return Response(generate(), mimetype='application/x-ndjson')
|
||||
@@ -9,4 +9,4 @@ def zotify_command_builder(account: str, action: str, args: str) -> list:
|
||||
return [*base_cmd, args] # args is Spotify URL
|
||||
elif action == 'search':
|
||||
return [*base_cmd, '-s', args] # args is search query
|
||||
raise ValueError(f"Invalid action: {action}")
|
||||
raise ValueError(f"Invalid action: {action}")
|
||||
Reference in New Issue
Block a user