Skip to content
This repository has been archived by the owner on Feb 9, 2024. It is now read-only.

Update #59

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1bce289
Move JavaScript and Style Sheets to a file
Cyb10101 Feb 25, 2023
3787b88
Adjust line indentation
Cyb10101 Feb 25, 2023
fe1906e
Small code design adjustment
Cyb10101 Feb 25, 2023
84e5e25
Add Javascript class; Checks for Microsoft Internet Explorer and Serv…
Cyb10101 Feb 25, 2023
5c3bc08
Add json pretty printing
Cyb10101 Feb 25, 2023
c5c0aae
Change some links to https
Cyb10101 Feb 25, 2023
fc2988f
Improve authentification
Cyb10101 Feb 25, 2023
7aea67c
Add development context, filename template, exclude playlist or saved
Cyb10101 Feb 25, 2023
ea5c7f2
Fix date in filename; Add Interval; Move functions
Cyb10101 Feb 25, 2023
b8cbc3b
Webserver information added or changed
Cyb10101 Feb 26, 2023
776f287
Refactor export and download
Cyb10101 Feb 26, 2023
82de025
Update readme and ajust some code
Cyb10101 Feb 26, 2023
26288d2
dryrun, css styles, accept only json, api request, import updated
Cyb10101 Mar 13, 2023
ae110e4
Restyle website
Cyb10101 Mar 13, 2023
2896379
Link avatar with profile
Cyb10101 Mar 14, 2023
92732c3
Style track counter
Cyb10101 Mar 14, 2023
7bf95b8
Fix compare saved tracks for import; clear content if authentificated
Cyb10101 Mar 14, 2023
5341104
Filter playlist only if array not empty; Add Playlist description, pu…
Cyb10101 Mar 14, 2023
e3a4c98
Remove jquery and underscore.js; fix login button
Cyb10101 Mar 14, 2023
00fa2be
Log output moved
Cyb10101 Mar 14, 2023
fa2aebd
Cleanups
Cyb10101 Mar 14, 2023
b4f0310
Download class added
Cyb10101 Mar 31, 2023
801da83
Export 'is_playable' and parse it with jq
Cyb10101 Sep 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <https://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Expand Down
169 changes: 168 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,175 @@
# SpotMyBackup

Backup and Restore your Spotify Playlists and "My Music".

This javascript based app allows you to backup all your playlists and import them in any other Spotify Account. It uses the OAuth-Functionality of Spotify to be able to handle your personal playlists.

In consequence, no credentials or data is stored or processed on the Webserver itself.

You can use it at www.spotmybackup.com or on your own webserver (see Q&A).
You can use it at [www.spotmybackup.com](http://www.spotmybackup.com) or on your own webserver (see Q&A).

## Own hosted

Configure `uri` in `config.json` to your host/ip/domain and port, if is different.
For example: `http://127.0.0.1:8888`

Create or edit a Spotify application:

* [Spotify: Developer Dashboard](https://developer.spotify.com/dashboard/)
* Edit settings
* Configure your redirect/callback uri for example to: `http://127.0.0.1:8888/callback-spotify.html`
* (Saved it)
* Copy your Cliend ID and store it in `config.json` file under `clientId`.

Run a webserver, for example:

* [XAMPP](https://www.apachefriends.org/)
* [Docker: Nginx](https://hub.docker.com/_/nginx)

```bash
# Python 3
python3 -m http.server -b 127.0.0.1 8888

# Python 2
python -m SimpleHTTPServer -b 127.0.0.1 8888

# Docker non detached
docker run --rm -v ${PWD}:/usr/share/nginx/html:ro -p '127.0.0.1:8888:80' --name spotify-nginx nginx
```

... and open your configured url [127.0.0.1:8888](http://127.0.0.1:8888) in a web browser.

If you run into a CORS error message:

* You should use your ip instead of localhost
* You should add SSL (https)
* [CORS request did not succeed](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSDidNotSucceed)

## Configuration

The `config.json` file is for overwriting existing default configuration.
So it is not necessary to write everything down.

*Note: Default JSON did not have comments. It is just easier to explain.*

The minimal configuration, is mostly your uri and clientId:

```json
{
// Required, your Spotify application client id
"clientId": "[YOUR_TOKEN_HERE]",

// Find the right window in callback-*.html
"uri": "http://127.0.0.1:8888",

// Spotify's callback uri
"redirectUri": "http://127.0.0.1:8888/callback-spotify.html"
}
```

Extended configuration, mostly because you want it pretty:

```json
{
// Change exported: {filename}.json
// %u = username
// %Y-%m-%d = Year-month-day = 2013-02-30
// %H-%i-%s = Hour-minutes-seconds = 14-59-01
"filename": "spotify_%u_%Y-%m-%d_%H-%i-%s",

"prettyPrint": 0, // Json pretty-printing spacing level
"extendedTrack": false, // More data, like track name, artist names, album name
"slowdownExport": 100, // Slow down api calls for tracks in milliseconds
"slowdownImport": 100, // Slow down api calls for tracks in milliseconds
"market": "US" // Track Relinking for is_playable https://developer.spotify.com/documentation/web-api/concepts/track-relinking
}
```

## Developers

* [Spotify: Web Api Reference](https://developer.spotify.com/documentation/web-api/reference/)

Developer configuration for `config.json`, you should know what you doing:

```json
{
"development": false, // A switch for some switches

// You might not want to loading all tracks, because this could be huge!
"devShortenSpotifyExportTracks": 0,

"dryrun": true, // Do not make any changes
"printPlaylists": false, // Just print playlist on website
"excludeSaved": false, // Exclude saved/my-music in export
"excludePlaylists": [] // Exclude playlist ids in export
}
```

Check export with jq:

```bash
sudo apt install jq

jq '{file: input_filename, savedCount: .saved? | length, playlistCount: .playlists? | length, playlists: [.playlists?[] | {name: .name, tracks: .tracks | length}]}' ~/Downloads/spotify_*.json
```

## Filter not playable tracks

Since availability is not always guaranteed in Spotify, I would like to see which songs can no longer be played.

First you need to add `market` in your `config.json` file:

```json
{
"market": "US" // Track Relinking for is_playable https://developer.spotify.com/documentation/web-api/concepts/track-relinking
}
```

Then create a new spotify backup. You will see a `is_playable` key in your track data.

Download and install [JQ](https://jqlang.github.io/jq/).

Execute this command:

```bash
jq '{
playlists: [.playlists[] | {
name: .name,
notPlayable: ([.tracks[] | select(.is_playable == false)] | length),
total: ([.tracks[]] | length),
tracks: ([.tracks[] | select(.is_playable == false) | {name: .name, artists: .artists}])
}],
saved: {
notPlayable: ([.saved[] | select(.is_playable == false)] | length),
total: ([.saved[]] | length),
tracks: ([.saved[] | select(.is_playable == false) | {name: .name, artists: .artists}])
}
}' backup.json > not-playable.json
```

If you want full track data, remove both " | {name: .name, artists: .artists}".

***Note: You can not import the "not-playable.json" file as a recovery!***

Example Output:

```json
{
"playlists": [
{
"name": "Games",
"notPlayable": 1,
"total": 24,
"tracks": [
{
"name": "Exile Vilify (From the Game Portal 2)",
"artists": [
"The National"
]
}
]
},
],
"saved": {...}
}
```
168 changes: 168 additions & 0 deletions app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
*, *::before, *::after {
box-sizing: border-box;
}

body {
margin: 0 0 40px 0;
padding: 0;
font-family: 'Lato', sans-serif;
font-size: 16px;
background: url(gplaypattern.png) repeat;
background-size: 188px 178px;
}

a {
color: #121F59;
text-decoration: none;
}
a:hover {
color: #A2C852;
}


.header,
.footer,
.container {
margin: 0 auto;
padding: 12px;
width: 640px;
background-color: rgba(255,255,255,0.9);
}

.header {
color: #444;
margin-bottom: 10px;
border-bottom: solid 1px #eaeaea;
}
.header h1 {
margin: 0 0 20px 0;
font-size: 44px;
font-weight: bold;
}
.header h1 span {
font-weight: 300;
color: #555;
}
.header p {
margin: 0;
font-size: 24px;
}

.footer-wrapper {
position: fixed;
bottom: 0;
width: 100%;
}
.footer {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0;
border-top: solid 1px #eaeaea;
}
.footer a {
display: inline-block;
flex: 0 1 auto;

font-size: 14px;
padding: 5px 15px;
color: #121F59;
text-decoration: none;
}
.footer a:hover {
color: #A2C852;
}

.container {
border-bottom: solid 1px #eaeaea;
}
.container > *:last-child > *:last-child,
.container > *:last-child {
margin-bottom: 0 !important;
}

.button {
display: block;
margin: 0 auto;
padding: 10px 47px;
min-width: 130px;

color: #fff;
background-color: #2ebd59;
background-image: none;
border: none;
border-radius: 999em;
font-size: 18px;
font-weight: 600;
line-height: 1.5em;
letter-spacing: 1.2px;
text-transform: uppercase;
text-align: center;
white-space: nowrap;
white-space: normal;
cursor: pointer;
}

.bg-red {
background-color: #bd2e2e;
}

.avatar {
padding-bottom: 10px;
font-weight: bold;
}

#login,
#btnDownload {
margin-bottom: 1em;
}

#pnlImport {
display: flex;
}
#pnlImport label {
display: inline-block;
cursor: pointer;
}
#pnlImport input {
display: none;
}

.badge-fork img {
position: absolute;
top: 0;
left: 0;
border: 0;
}

.alert {
position: relative;
padding: .75rem 1.25rem;
margin-bottom: 1rem;
border: 1px solid transparent;
border-radius: .25rem;
}
.alert > *:last-child {
margin-bottom: 0;
}
.alert-info {
color: #0c5460;
background-color: #d1ecf1;
border-color: #bee5eb;
}
.alert-warning {
color: #856404;
background-color: #fff3cd;
border-color: #ffeeba;
}
.alert-danger {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
}

.count-track {
display: inline-block;
min-width: 2.5em;
text-align: right;
}
Loading