Skip to content

Commit

Permalink
get spotify files back
Browse files Browse the repository at this point in the history
  • Loading branch information
Groovylein committed Dec 18, 2023
1 parent efbe969 commit bdace53
Show file tree
Hide file tree
Showing 16 changed files with 445 additions and 14 deletions.
2 changes: 1 addition & 1 deletion docker/docker-compose.linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
environment:
- PULSE_SERVER=unix:/tmp/pulse-sock
volumes:
- ../shared/audiofolders:/home/pi/RPi-Jukebox-RFID/shared/audiofolders
- ../shared/audio:/home/pi/RPi-Jukebox-RFID/shared/audio
- ../shared/playlists:/home/pi/.config/mpd/playlists
- ./config/docker.pulse.mpd.conf:/home/pi/.config/mpd/mpd.conf
- $XDG_RUNTIME_DIR/pulse/native:/tmp/pulse-sock
Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ services:
- PULSE_SERVER=tcp:host.docker.internal:4713
restart: unless-stopped
volumes:
- ../shared/audiofolders:/root/RPi-Jukebox-RFID/shared/audiofolders
- ../shared/audio:/root/RPi-Jukebox-RFID/shared/audio
- ../shared/playlists:/root/.config/mpd/playlists
- ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf

Expand Down
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ tornado
# for collecting audiofiles
pyyaml

# For spotify
websocket-client
urllib3

# RPi's GPIO packages:
RPi.GPIO
gpiozero
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
11 changes: 10 additions & 1 deletion resources/default-settings/jukebox.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ alsawave:
device: default
players:
content:
audiofile: /home/pi/RPi-Jukebox-RFID/shared/audiofolders/audiofiles.yaml
audiofile: /home/pi/RPi-Jukebox-RFID/shared/audio/audiofiles.yaml
playermpd:
host: localhost
status_file: ../../shared/settings/music_player_status.json
Expand All @@ -91,6 +91,15 @@ playermpd:
update_on_startup: true
check_user_rights: true
mpd_conf: ~/.config/mpd/mpd.conf
playerspot:
host: localhost
status_file: ../../shared/settings/spotify_player_status.json
collection_file: ../../shared/audio/spotify/spotify_collection.yaml
second_swipe_action:
# Note: Does not follow the RPC alias convention (yet)
# Must be one of: 'toggle', 'play', 'skip', 'rewind', 'replay', 'none'
alias: toggle
spot_conf: ../../shared/spotify/config.toml"
rpc:
tcp_port: 5555
websocket_port: 5556
Expand Down
84 changes: 84 additions & 0 deletions resources/default-settings/spotify.config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
deviceId = "" ### Device ID (40 chars, leave empty for random) ###
deviceName = "Phoniebox" ### Device name ###
deviceType = "SPEAKER" ### Device type (COMPUTER, TABLET, SMARTPHONE, SPEAKER, TV, AVR, STB, AUDIO_DONGLE, GAME_CONSOLE, CAST_VIDEO, CAST_AUDIO, AUTOMOBILE, WEARABLE, UNKNOWN_SPOTIFY, CAR_THING, UNKNOWN) ###
preferredLocale = "de" ### Preferred locale ###
logLevel = "TRACE" ### Log level (OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL) ###

[auth] ### Authentication ###
strategy = "USER_PASS" # Strategy (USER_PASS, ZEROCONF, BLOB, FACEBOOK, STORED)
username = "HERE_USERNAME" # Spotify username (BLOB, USER_PASS only)
password = "HERE_PASSWORD" # Spotify password (USER_PASS only)
blob = "" # Spotify authentication blob Base64-encoded (BLOB only)
storeCredentials = false # Whether to store reusable credentials on disk (not a plain password)
credentialsFile = "credentials.json" # Credentials file (JSON)

[zeroconf] ### Zeroconf ###
listenPort = 12345 # Listen on this TCP port (`-1` for random)
listenAll = true # Listen on all interfaces (overrides `zeroconf.interfaces`)
interfaces = "" # Listen on these interfaces (comma separated list of names)

[cache] ### Cache ###
enabled = false # Cache enabled
dir = "./cache/"
doCleanUp = true

[network] ### Network ###
connectionTimeout = 10 # If ping isn't received within this amount of seconds, reconnect

[preload] ### Preload ###
enabled = true # Preload enabled

[time] ### Time correction ###
synchronizationMethod = "NTP" # Time synchronization method (NTP, PING, MELODY, MANUAL)
manualCorrection = 0 # Manual time correction in millis

[player] ### Player ###
autoplayEnabled = false # Autoplay similar songs when your music ends
preferredAudioQuality = "NORMAL" # Preferred audio quality (NORMAL, HIGH, VERY_HIGH)
enableNormalisation = true # Whether to apply the Spotify loudness normalisation
normalisationPregain = +3.0 # Normalisation pregain in decibels (loud at +6, normal at +3, quiet at -5)
initialVolume = 65536 # Initial volume (0-65536)
volumeSteps = 64 # Number of volume notches
logAvailableMixers = true # Log available mixers
mixerSearchKeywords = "" # Mixer/backend search keywords (semicolon separated)
crossfadeDuration = 0 # Crossfade overlap time (in milliseconds)
output = "MIXER" # Audio output device (MIXER, PIPE, STDOUT, CUSTOM)
outputClass = "" # Audio output Java class name
releaseLineDelay = 20 # Release mixer line after set delay (in seconds)
pipe = "" # Output raw (signed) PCM to this file (`player.output` must be PIPE)
retryOnChunkError = true # Whether the player should retry fetching a chuck if it fails
metadataPipe = "" # Output metadata in Shairport Sync format (https://github.com/mikebrady/shairport-sync-metadata-reader)
bypassSinkVolume = false # Whether librespot-java should ignore volume events, sink volume is set to the max
localFilesPath = "" # Where librespot-java should search for local files

[api] ### API ###
port = 24879 # API port (`api` module only)
host = "0.0.0.0" # API listen interface (`api` module only)

[proxy] ### Proxy ###
enabled = false # Whether the proxy is enabled
type = "HTTP" # The proxy type (HTTP, SOCKS)
ssl = false # Connect to proxy using SSL (HTTP only)
address = "" # The proxy hostname
port = 0 # The proxy port
auth = false # Whether authentication is enabled on the server
username = "" # Basic auth username
password = "" # Basic auth password

[shell] ### Shell ###
enabled = false # Shell events enabled
executeWithBash = false # Execute the command with `bash -c`
onContextChanged = ""
onTrackChanged = ""
onPlaybackEnded = ""
onPlaybackPaused = ""
onPlaybackResumed = ""
onTrackSeeked = ""
onMetadataAvailable = ""
onVolumeChanged = ""
onInactiveSession = ""
onPanicState = ""
onConnectionDropped = ""
onConnectionEstablished = ""
onStartedLoading = ""
onFinishedLoading = ""
97 changes: 97 additions & 0 deletions src/jukebox/components/player/backends/spotify/http_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import json
import logging
import requests
from requests.adapters import HTTPAdapter
import urllib
from urllib3.util.retry import Retry

logger = logging.getLogger('jb.spotify.SpotifyHttpClient')


class SpotifyHttpClient:
def __init__(self, host: str, port=24879):
self.protocol = 'http'
self.host = host
self.port = port
self.authority = f'{self.protocol}://{self.host}:{self.port}'

self.session = requests.Session()
retries = Retry(
total=5,
backoff_factor=5,
status_forcelist=[500, 502, 503, 504]
)

self.session.mount(
self.protocol + '://',
HTTPAdapter(max_retries=retries)
)
self.session.headers.update({'content-type': 'application/json'})
logger.debug(f'Spotify HTTP Client initialized. Will connect to {self.authority}')

def close(self):
logger.debug("Exiting Spotify HTTP session")
self._post_request('/instance/close')

def _request(self, request_func, path: str):
try:
url = urllib.parse.urljoin(self.authority, path)
logger.debug(f'Requesting "{self.authority}"')

response = request_func(url)
response.raise_for_status()

except requests.HTTPError as http_error:
response = {}
logger.error(f'HTTPError: {http_error}')

except Exception as error:
response = {}
logger.error(f'Error {error}')

if response.content:
logger.debug(f"Request response.content: {response.content}")
return json.loads(response.content)
else:
logger.debug("Request response.content empty")
return {}

# no JSON returned

def _get_request(self, path: str):
response = self._request(self.session.get, path)
return response

def _post_request(self, path: str):
response = self._request(self.session.post, path)
return response

def get_status(self):
# json = self._get_request('/web-api/v1//me/player')
response_json = self._post_request('/player/current')
logger.debug(response_json)
return response_json

def play_uri(self, uri: str, play: bool = True, shuffle: bool = False):
return self._post_request(f'/player/load?uri={uri}&play={play}&shuffle={shuffle}')

def play(self):
return self._post_request('/player/resume')

def pause(self):
return self._post_request('/player/pause')

def prev(self):
return self._post_request('/player/prev')

def next(self):
return self._post_request('/player/next')

def seek(self, new_time: int):
return self._post_request(f'/player/seek?pos={new_time}')

def shuffle(self, val: bool):
return self._post_request(f'/player/shuffle?val={val}')

def repeat(self, val: str):
return self._post_request(f'/player/repeat?val={val}')
104 changes: 104 additions & 0 deletions src/jukebox/components/player/backends/spotify/interfacing_spotify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright: 2022
# SPDX License Identifier: MIT License

import logging
import os.path

from ruamel import yaml

import jukebox.plugs as plugin
import jukebox.cfghandler
from components.player.backends.spotify.http_client import SpotifyHttpClient
from components.player.backends.spotify.ws_client import SpotifyWsClient

logger = logging.getLogger('jb.spotify')
cfg = jukebox.cfghandler.get_handler('jukebox')


def sanitize(path: str):
return os.path.normpath(path).lstrip('./')


class SPOTBackend:
def __init__(self, player_status):
host = cfg.getn('playerspot', 'host')
self.player_status = player_status

self.http_client = SpotifyHttpClient(host)

self.ws_client = SpotifyWsClient(
host=host,
player_status=self.player_status
)
self.ws_client.connect()

self.collection_file_location = cfg.setndefault('playerspot', 'collection_file',
value="../../shared/audio/spotify/spotify_collection.yaml")
self.spotify_collection_data = self._read_data_file()

def _read_data_file(self) -> dict:
try:
with open(self.collection_file_location, "r") as collection_file:
return yaml.safe_load(collection_file.read())
except Exception as err:
logger.error(f"Could not open spotify collection file {self.collection_file_location}")
logger.debug(f"Error: {err}")
logger.debug("Continuing with empty dictionary")
return {}

def play(self):
self.http_client.play()

def pause(self):
self.http_client.pause()

def stop(self):
try:
is_playing = self.http_client.get_status()['current']
logger.debug(f"Current player playing status: {is_playing}")
if is_playing:
self.http_client.pause()
except Exception as err:
logger.debug("No status information if Spotify is playing something.")

def prev(self):
self.http_client.prev()

def next(self):
self.http_client.next()

def toggle(self):
pass

def get_queue(self):
pass

@plugin.tag
def play_uri(self, uri: str, **kwargs):
"""Decode URI and forward play call
spotify:playlist:0
--> search in the yaml-file for the type "playlist" and play the first uri
"""
player_type, index = uri.split(':', 1)
if player_type != 'spotify':
raise KeyError(f"URI prefix must be 'spotify' not '{player_type}")

self.http_client.play_uri(uri)

@plugin.tag
def get_status(self):
self.http_client.get_status()

# -----------------------------------------------------
# Queue / URI state (save + restore e.g. random, resume, ...)

def save_state(self):
"""Save the configuration and state of the current URI playback to the URIs state file"""
pass

def _restore_state(self):
"""
Restore the configuration state and last played status for current active URI
"""
pass
Loading

0 comments on commit bdace53

Please sign in to comment.