Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
Loading items

Target

Select target project
  • deadlocker8/SpotifyBackup
1 result
Select Git revision
Loading items
Show changes

Commits on Source 2

import json
import os.path
import time
from typing import List, Dict
import click
import spotipy
......@@ -25,27 +24,21 @@ class SpotifyRecorder:
redirectUrl: str,
openBrowser: bool,
cacheFilePath: str,
playlist: Dict[str, str],
playlists: list[dict[str, str | int]],
spotifyDeviceName: str,
audioDeviceName: str,
destinationFolder: str,
startNumber: int = 0,
limit: int = -1):
skipConfirmation: bool):
self._clientID = clientID
self._clientSecret = clientSecret
self._redirectUrl = redirectUrl
self._openBrowser = openBrowser
self._cacheFilePath = cacheFilePath
self._playlist = playlist
self._playlists = playlists
self._spotifyDeviceName = spotifyDeviceName
self._audioDeviceName = audioDeviceName
self._destinationFolder = destinationFolder
if startNumber <= 0:
raise ValueError(f'startNumber must be greater than 0')
self._startNumber = startNumber
self._limit = limit
self._skipConfirmation = skipConfirmation
os.makedirs(self._destinationFolder, exist_ok=True)
......@@ -61,33 +54,48 @@ class SpotifyRecorder:
return spotipy.Spotify(client_credentials_manager=client_credentials_manager)
def run(self):
LOGGER.info(f'Fetching all tracks for playlist {self._playlist["name"]}...')
playlist = self.__get_playlist(self._playlist['user'], self._playlist['id'])
for playlist in self._playlists:
self.__record_playlist(playlist)
def __record_playlist(self, playlistSettings: dict[str, str | int]):
LOGGER.info(f'Fetching all tracks for playlist "{playlistSettings["name"]}"...')
startNumber = playlistSettings['startNumber']
limit = playlistSettings['limit']
if startNumber <= 0:
raise ValueError(f'startNumber must be greater than 0')
playlist = self.__get_playlist(playlistSettings['user'], playlistSettings['id'])
tracks = self.__get_tracks(playlist)
if self._limit == -1:
LOGGER.info(f'Recording track #{self._startNumber} to end of playlist')
tracks = tracks[self._startNumber - 1:]
if limit == -1:
LOGGER.info(f'Recording track #{startNumber} to end of playlist')
tracks = tracks[startNumber - 1:]
else:
LOGGER.info(f'Recording track #{self._startNumber} to (including) #{self._startNumber + self._limit - 1}')
tracks = tracks[self._startNumber - 1:self._startNumber - 1 + self._limit]
LOGGER.info(f'Recording track #{startNumber} to (including) #{startNumber + limit - 1}')
tracks = tracks[startNumber - 1:startNumber - 1 + limit]
totalDurationInSeconds = sum([track['track']['duration_ms'] // 1000 for track in tracks if not track['is_local']])
totalDurationInSeconds = sum(
[track['track']['duration_ms'] // 1000 for track in tracks if not track['is_local']])
LOGGER.info(f'Total duration: {self.__convert_seconds_to_duration(totalDurationInSeconds)}')
if click.confirm('Do you want to start recording?', default=True):
self.__record_tracks(tracks)
destinationFolder = os.path.join(self._destinationFolder, playlistSettings['name'])
os.makedirs(destinationFolder, exist_ok=True)
if self._skipConfirmation:
self.__record_tracks(tracks, startNumber, destinationFolder)
elif click.confirm('Do you want to start recording?', default=True):
self.__record_tracks(tracks, startNumber, destinationFolder)
else:
LOGGER.warning('Aborted')
def __get_playlist(self, username: str, playlistID: str) -> Dict:
def __get_playlist(self, username: str, playlistID: str) -> dict:
LOGGER.info(f'Fetching playlist with ID: {playlistID} by {username}...')
identifier = f'spotify:user:{username}:playlist:{playlistID}'
playlist = self._spotify.playlist(identifier)
LOGGER.info(f'Found playlist "{playlist["name"]}"')
return playlist
def __get_tracks(self, playlist) -> List[Dict]:
def __get_tracks(self, playlist) -> list[dict]:
tracks = playlist['tracks']
results = tracks['items']
......@@ -98,7 +106,7 @@ class SpotifyRecorder:
LOGGER.info(f'Found {len(results)} tracks in playlist')
return results
def __record_tracks(self, tracks: list):
def __record_tracks(self, tracks: list, startNumber: int, destinationFolder: str):
deviceId = self.__get_device_id_by_name(self._spotifyDeviceName)
recordedTrackNumbers = []
......@@ -107,7 +115,7 @@ class SpotifyRecorder:
errorTrackNumbers = []
for index, track in enumerate(tracks):
indexInPlaylist = self._startNumber + index
indexInPlaylist = startNumber + index
if track['is_local']:
# It's not possible to add a local track to a playlist using the web API.
......@@ -119,7 +127,7 @@ class SpotifyRecorder:
LOGGER.info(f'>>> Recording track {index + 1}/{len(tracks)}: '
f'#{indexInPlaylist} "{track["track"]["name"]}"...')
try:
recordingCreated = self.__record_single_track(deviceId, indexInPlaylist, track)
recordingCreated = self.__record_single_track(deviceId, indexInPlaylist, track, destinationFolder)
if recordingCreated:
recordedTrackNumbers.append(indexInPlaylist)
else:
......@@ -128,12 +136,13 @@ class SpotifyRecorder:
LOGGER.error(f'An error occurred while recording track "{track["track"]["name"]}"', exc_info=e)
errorTrackNumbers.append(indexInPlaylist)
self.__print_end_statistics(errorTrackNumbers, recordedTrackNumbers, skippedTrackNumbers, alreadyExistingTrackNumbers, len(tracks))
self.__print_end_statistics(errorTrackNumbers, recordedTrackNumbers, skippedTrackNumbers,
alreadyExistingTrackNumbers, len(tracks))
def __record_single_track(self, deviceId, indexInPlaylist, track) -> bool:
def __record_single_track(self, deviceId, indexInPlaylist, track, destinationFolder: str) -> bool:
self.__stop_playback_if_playing(deviceId)
fileName = self.__determine_file_name(indexInPlaylist, track)
fileName = self.__determine_file_name(indexInPlaylist, track, destinationFolder)
filePathWav = f'{fileName}.wav'
filePathMp3 = f'{fileName}.mp3'
......@@ -181,7 +190,7 @@ class SpotifyRecorder:
f'{len(alreadyExistingTrackNumbers)} existing tracks skipped, '
f'{len(errorTrackNumbers)} errors ###')
def __determine_file_name(self, index: int, track) -> str:
def __determine_file_name(self, index: int, track, destinationFolder: str) -> str:
artists = self.__join_artists(track)
fileName = f'{index} - {artists} - {track["track"]["name"]}'
fileName = fileName.replace('/', '_')
......@@ -193,7 +202,7 @@ class SpotifyRecorder:
fileName = fileName.replace('>', '_')
fileName = fileName.replace('<', '_')
fileName = fileName.replace('|', '_')
return os.path.join(self._destinationFolder, fileName)
return os.path.join(destinationFolder, fileName)
@staticmethod
def __join_artists(track) -> str:
......@@ -291,12 +300,12 @@ if __name__ == '__main__':
SETTINGS['redirectUrl'],
SETTINGS['openBrowser'],
SETTINGS['cacheFilePath'],
SETTINGS['playlist'],
SETTINGS['playlists'],
SETTINGS['spotifyDeviceName'],
SETTINGS['audioDeviceName'],
SETTINGS['destinationFolder'],
SETTINGS['startNumber'],
SETTINGS['limit'], )
SETTINGS['skipConfirmation'],
)
spotifyBackup.run()
......
......@@ -3,16 +3,20 @@
"clientID": "",
"clientSecret": ""
},
"playlist": {
"playlists": [
{
"user": "",
"id": ""
},
"name": "",
"id": "",
"startNumber": 1,
"limit": -1
}
],
"spotifyDeviceName": "MYDEVICE",
"audioDeviceName": "3/4 - Musik (2- GIGAPort HD Audio driver) [Loopback]",
"destinationFolder": "",
"startNumber": 1,
"limit": -1,
"redirectUrl": "http://localhost:8080",
"openBrowser": false,
"cacheFilePath": ".cache"
"cacheFilePath": ".cache",
"skipConfirmation": false
}
\ No newline at end of file