From 1392e81d00f26c5f425731cac3fbbac9a219b2aa Mon Sep 17 00:00:00 2001 From: awiouy Date: Thu, 5 Dec 2019 13:52:10 +0100 Subject: [PATCH] librespot: adapt to Python3 --- .../addons/service/librespot/changelog.txt | 7 ++ packages/addons/service/librespot/package.mk | 18 +++-- .../patches/librespot-01_notify_kodi.patch | 69 +++++++++++------ ...librespot-02_use_own_pulseaudio_sink.patch | 15 ++-- .../service/librespot/source/default.py | 76 +++++++++++-------- .../service.librespot-alsa.service | 30 ++++++++ 6 files changed, 145 insertions(+), 70 deletions(-) create mode 100644 packages/addons/service/librespot/source/miscellaneous/service.librespot-alsa.service diff --git a/packages/addons/service/librespot/changelog.txt b/packages/addons/service/librespot/changelog.txt index 4006c25cff..b245258fcb 100644 --- a/packages/addons/service/librespot/changelog.txt +++ b/packages/addons/service/librespot/changelog.txt @@ -1,3 +1,10 @@ +119 +- Update to f610436 (2020-01-02) +- Hook audiotype +- Use vorbis, which is more responsive than lewton +- Build alsa backend and provide sample alsa service +- Python 3 compliance + 118 - Update to 0.1.0 diff --git a/packages/addons/service/librespot/package.mk b/packages/addons/service/librespot/package.mk index 8733f2f1d3..2efbec529f 100644 --- a/packages/addons/service/librespot/package.mk +++ b/packages/addons/service/librespot/package.mk @@ -3,14 +3,15 @@ # Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv) PKG_NAME="librespot" -PKG_VERSION="0.1.0" -PKG_SHA256="4e03c69d1893ed14414d5a76ecdb5ea139ddfcce47fd57cc4e77b696001badb7" -PKG_REV="118" +PKG_VERSION="f610436641e957750fad35e7da4933b8308ddd6d" +PKG_SHA256="8c15c33a66602715b8bfc8e84d9457e70135d37079eda0df153a2ed57b4f1a33" +PKG_VERSION_DATE="2020-01-02" +PKG_REV="119" PKG_ARCH="any" PKG_LICENSE="MIT" PKG_SITE="https://github.com/librespot-org/librespot/" -PKG_URL="https://github.com/librespot-org/librespot/archive/v$PKG_VERSION.zip" -PKG_DEPENDS_TARGET="toolchain pulseaudio rust" +PKG_URL="https://github.com/librespot-org/librespot/archive/$PKG_VERSION.zip" +PKG_DEPENDS_TARGET="toolchain alsa-lib libvorbis pulseaudio rust" PKG_SECTION="service" PKG_SHORTDESC="Librespot: play Spotify through Kodi using a Spotify app as a remote" PKG_LONGDESC="Librespot ($PKG_VERSION_DATE) lets you play Spotify through Kodi using a Spotify app as a remote." @@ -19,12 +20,15 @@ PKG_TOOLCHAIN="manual" PKG_IS_ADDON="yes" PKG_ADDON_NAME="Librespot" PKG_ADDON_TYPE="xbmc.service" +PKG_ADDON_REQUIRES="script.module.requests:0.0.0" PKG_MAINTAINER="Anton Voyl (awiouy)" make_target() { - cd src . "$(get_build_dir rust)/cargo/env" - $CARGO_BUILD --no-default-features --features "pulseaudio-backend with-dns-sd" + cargo build \ + --release \ + --no-default-features \ + --features "alsa-backend pulseaudio-backend with-dns-sd with-vorbis" "$STRIP" $PKG_BUILD/.$TARGET_NAME/*/release/librespot } diff --git a/packages/addons/service/librespot/patches/librespot-01_notify_kodi.patch b/packages/addons/service/librespot/patches/librespot-01_notify_kodi.patch index e756a9c346..0afb08bf2e 100644 --- a/packages/addons/service/librespot/patches/librespot-01_notify_kodi.patch +++ b/packages/addons/service/librespot/patches/librespot-01_notify_kodi.patch @@ -1,3 +1,26 @@ +commit 6a9b509f4f85427f6392c08fc642afb9fc7bb8f2 +Author: awiouy +Date: Tue Dec 3 23:21:35 2019 +0100 + + Notify Kodi + +diff --git a/core/src/spotify_id.rs b/core/src/spotify_id.rs +index e6f0cdd..03f8912 100644 +--- a/core/src/spotify_id.rs ++++ b/core/src/spotify_id.rs +@@ -8,6 +8,12 @@ pub enum SpotifyAudioType { + NonPlayable, + } + ++impl fmt::Display for SpotifyAudioType { ++ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ++ write!(f, "{:?}", self) ++ } ++} ++ + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct SpotifyId { + pub id: u128, diff --git a/playback/src/config.rs b/playback/src/config.rs index 0f71110..931167d 100644 --- a/playback/src/config.rs @@ -8,7 +31,7 @@ index 0f71110..931167d 100644 pub normalisation_pregain: f32, + pub notify_kodi: bool, } - + impl Default for PlayerConfig { @@ -38,6 +39,7 @@ impl Default for PlayerConfig { bitrate: Bitrate::default(), @@ -19,35 +42,35 @@ index 0f71110..931167d 100644 } } diff --git a/playback/src/player.rs b/playback/src/player.rs -index ab1a8ab..19d6394 100644 +index a54a577..4e006ab 100644 --- a/playback/src/player.rs +++ b/playback/src/player.rs -@@ -4,7 +4,8 @@ use futures::sync::oneshot; - use futures::{future, Future}; +@@ -5,7 +5,8 @@ use futures::{future, Future}; use std; use std::borrow::Cow; + use std::cmp::max; -use std::io::{Read, Result, Seek, SeekFrom}; +use std::fs::OpenOptions; +use std::io::{Read, Result, Seek, SeekFrom, Write}; use std::mem; use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError}; use std::thread; -@@ -394,6 +395,14 @@ impl PlayerInternal { +@@ -427,6 +428,14 @@ impl PlayerInternal { } } - + + fn notify_kodi(&mut self, id: &str, track_id: &SpotifyId) { + // println!("fifo = {} {}", id, track_id.to_base62()); + if self.config.notify_kodi { + let mut file = OpenOptions::new().write(true).open("/tmp/librespot").unwrap(); -+ writeln!(&mut file, "{}\n{}", id, track_id.to_base62()).unwrap(); ++ writeln!(&mut file, "{}\n{} {}", id, track_id.audio_type.to_string(), track_id.to_base62()).unwrap(); + } + } + fn handle_command(&mut self, cmd: PlayerCommand) { debug!("command={:?}", cmd); match cmd { -@@ -413,11 +422,17 @@ impl PlayerInternal { +@@ -451,11 +460,17 @@ impl PlayerInternal { | PlayerState::EndOfTrack { track_id: old_track_id, .. @@ -68,9 +91,9 @@ index ab1a8ab..19d6394 100644 + self.notify_kodi("2", &track_id) + } } - + self.start_sink(); -@@ -443,13 +458,17 @@ impl PlayerInternal { +@@ -485,13 +500,17 @@ impl PlayerInternal { | PlayerState::EndOfTrack { track_id: old_track_id, .. @@ -91,24 +114,24 @@ index ab1a8ab..19d6394 100644 + self.notify_kodi("4", &track_id) } } - -@@ -476,6 +495,7 @@ impl PlayerInternal { - + +@@ -547,6 +566,7 @@ impl PlayerInternal { + self.send_event(PlayerEvent::Started { track_id }); self.start_sink(); + self.notify_kodi("5", &track_id) } else { warn!("Player::play called from invalid state"); } -@@ -487,6 +507,7 @@ impl PlayerInternal { - +@@ -558,6 +578,7 @@ impl PlayerInternal { + self.stop_sink_if_running(); self.send_event(PlayerEvent::Stopped { track_id }); + self.notify_kodi("6", &track_id) } else { warn!("Player::pause called from invalid state"); } -@@ -499,6 +520,7 @@ impl PlayerInternal { +@@ -570,6 +591,7 @@ impl PlayerInternal { self.stop_sink_if_running(); self.send_event(PlayerEvent::Stopped { track_id }); self.state = PlayerState::Stopped; @@ -117,10 +140,10 @@ index ab1a8ab..19d6394 100644 PlayerState::Stopped => { warn!("Player::stop called from invalid state"); diff --git a/src/main.rs b/src/main.rs -index 36cd1b5..502cac8 100644 +index e3718fb..a480480 100644 --- a/src/main.rs +++ b/src/main.rs -@@ -168,6 +168,11 @@ fn setup(args: &[String]) -> Setup { +@@ -184,6 +184,11 @@ fn setup(args: &[String]) -> Setup { "Pregain (dB) applied by volume normalisation", "PREGAIN", ) @@ -132,20 +155,20 @@ index 36cd1b5..502cac8 100644 .optflag( "", "linear-volume", -@@ -248,6 +253,8 @@ fn setup(args: &[String]) -> Setup { +@@ -276,6 +281,8 @@ fn setup(args: &[String]) -> Setup { ) }; - + + let notify_kodi = matches.opt_present("notify-kodi"); + let session_config = { let device_id = device_id(&name); - -@@ -291,6 +298,7 @@ fn setup(args: &[String]) -> Setup { + +@@ -319,6 +326,7 @@ fn setup(args: &[String]) -> Setup { .opt_str("normalisation-pregain") .map(|pregain| pregain.parse::().expect("Invalid pregain float value")) .unwrap_or(PlayerConfig::default().normalisation_pregain), + notify_kodi: notify_kodi, } }; - + diff --git a/packages/addons/service/librespot/patches/librespot-02_use_own_pulseaudio_sink.patch b/packages/addons/service/librespot/patches/librespot-02_use_own_pulseaudio_sink.patch index e6f4ce2405..5f8f899455 100644 --- a/packages/addons/service/librespot/patches/librespot-02_use_own_pulseaudio_sink.patch +++ b/packages/addons/service/librespot/patches/librespot-02_use_own_pulseaudio_sink.patch @@ -1,18 +1,15 @@ -From b87d18c6513cebc31118ffb447e2b7ae9255e6bd Mon Sep 17 00:00:00 2001 -From: awiouy -Date: Wed, 7 Nov 2018 07:51:46 +0100 -Subject: [PATCH] libreelec: pulseaudio sink +commit 0f3141a0874566ce867139aeadbefdb076706baf +Author: awiouy +Date: Tue Dec 3 23:22:55 2019 +0100 ---- - playback/src/audio_backend/pulseaudio.rs | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) + Use own pulseaudio sink diff --git a/playback/src/audio_backend/pulseaudio.rs b/playback/src/audio_backend/pulseaudio.rs -index 88f6280..4e7186b 100644 +index 88f6280..702be66 100644 --- a/playback/src/audio_backend/pulseaudio.rs +++ b/playback/src/audio_backend/pulseaudio.rs @@ -76,6 +76,7 @@ impl Open for PulseAudioSink { - + impl Sink for PulseAudioSink { fn start(&mut self) -> io::Result<()> { + let sink = CString::new("librespot_sink").unwrap(); diff --git a/packages/addons/service/librespot/source/default.py b/packages/addons/service/librespot/source/default.py index 1185cf4bc7..d984f53c80 100644 --- a/packages/addons/service/librespot/source/default.py +++ b/packages/addons/service/librespot/source/default.py @@ -2,14 +2,12 @@ # Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv) import base64 -import json import os import stat import subprocess import threading import time -import urllib -import urllib2 +import requests import xbmc import xbmcaddon import xbmcgui @@ -21,8 +19,6 @@ FIFO = '/tmp/librespot' LOG_LEVEL = xbmc.LOGNOTICE LOG_MESSAGE = ADDON.getAddonInfo('name') + ': {}' SINK_NAME = "librespot_sink" -SPOTIFY_ID = '169df5532dee47a59913f8528e83ae71' -SPOTIFY_SECRET = '1f3d8b507bbe4f68beb3a4472e8ad411' STREAM_CODEC = 'pcm_s16be' STREAM_PORT = '6666' STREAM_URL = 'rtp://127.0.0.1:{}'.format(STREAM_PORT) @@ -100,10 +96,11 @@ class Player(threading.Thread, xbmc.Player): log('pausing librespot playback') self.pause() - def playLibrespot(self, track_id): - track = self.spotify.getTrack(track_id) - self.listitem.setArt(track.getArt()) - self.listitem.setInfo('music', track.getInfo()) + def playLibrespot(self, spotify_id): + type, id = spotify_id.split() + info = self.spotify.getInfo(type, id) + self.listitem.setArt(info.getArt()) + self.listitem.setInfo('music', info.getInfo()) if not self.isPlaying(): subprocess.call(['pactl', 'suspend-sink', SINK_NAME, '0']) log('starting librespot playback') @@ -158,46 +155,51 @@ class Spotify(): def __init__(self): self.headers = None self.expiration = time.time() - self.request = [ - 'https://accounts.spotify.com/api/token', - urllib.urlencode({'grant_type': 'client_credentials'}), - {'Authorization': 'Basic {}'.format(base64.b64encode( - '{}:{}'.format(SPOTIFY_ID, SPOTIFY_SECRET)))} - ] def getHeaders(self): if time.time() > self.expiration: log('token expired') - token = json.loads(urllib2.urlopen( - urllib2.Request(*self.request)).read()) + token = requests.post( + url='https://accounts.spotify.com/api/token', + data={'grant_type': 'client_credentials'}, + headers={ + 'Authorization': 'Basic MTY5ZGY1NTMyZGVlNDdhNTk5MTNmODUyOGU4M2FlNzE6MWYzZDhiNTA3YmJlNGY2OGJlYjNhNDQ3MmU4YWQ0MTE='} + ).json() + print(token) log('new token expires in {} seconds'.format(token['expires_in'])) - self.expiration = time.time() + float(token['expires_in']) - 60 + self.expiration = time.time() + float(token['expires_in']) - 15 self.headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(token['access_token']) } - def getTrack(self, track_id): - log('getting track') + def getEndpoint(self, url): try: self.getHeaders() - track = json.loads(urllib2.urlopen(urllib2.Request( - 'https://api.spotify.com/v1/tracks/{}'.format(track_id), None, - self.headers)).read()) + return requests.get( + url=url, + headers=self.headers + ).json() except Exception as e: - log('failed to get track from Spotify: {}'.format(e)) - track = dict() - return Track(track) + log('failed to get endpoint from Spotify {}'.format(e)) + return {} + + def getInfo(self, type, id): + if type == 'Track': + return TrackInfo(self.getEndpoint('https://api.spotify.com/v1/tracks/{}'.format(id))) + else: + return UnknownInfo(type, id) -class Track(): +class UnknownInfo: - def __init__(self, track): - self.track = track + def __init__(self, type, id): + self.id = id + self.type = type def get(self, default, *indices): - tree = self.track + tree = self.info try: for index in indices: tree = tree[index] @@ -205,6 +207,18 @@ class Track(): tree = default return tree + def getArt(self): + return {'thumb': ''} + + def getInfo(self): + return {'album': '', 'artist': self.type, 'title': self.id} + + +class TrackInfo(UnknownInfo): + + def __init__(self, info): + self.info = info + def getArt(self): return { 'thumb': self.get('', 'album', 'images', 0, 'url') @@ -214,7 +228,7 @@ class Track(): return { 'album': self.get('', 'album', 'name'), 'artist': self.get('', 'artists', 0, 'name'), - 'title': self.get('', 'name'), + 'title': self.get('', 'name') } diff --git a/packages/addons/service/librespot/source/miscellaneous/service.librespot-alsa.service b/packages/addons/service/librespot/source/miscellaneous/service.librespot-alsa.service new file mode 100644 index 0000000000..35fc6a0d2a --- /dev/null +++ b/packages/addons/service/librespot/source/miscellaneous/service.librespot-alsa.service @@ -0,0 +1,30 @@ +# Librespot for ALSA +# Copy this file to '/storage/.config/system.d/service.librespot-alsa.service' and adapt it to your needs +# Enable the service with 'systemctl enable /storage/.config/system.d/service.librespot-alsa.service' +# Start the service with 'systemctl start service.librespot-alsa.service' +# If you update the file, reload units with 'systemctl daemon-reload' and restart the service + +[Unit] +Description=librespot alsa backend +After=network-online.target +Wants=network-online.target + +[Service] +Environment=LD_LIBRARY_PATH=/storage/.kodi/addons/service.librespot/lib +#Enable Raspberry Pi onboard audio +#ExecStartPre=-dtparam audio=on +#Set Raspberry Pi playback route +#ExecStartPre=-amixer -c 0 cset name="PCM Playback Route" 1 +ExecStart=/storage/.kodi/addons/service.librespot/bin/librespot \ + --backend alsa \ + --bitrate 320 \ + --cache "/storage/.config/lsa_cache" \ +# Use 'aplay -L' to list available devices +# --device "default:CARD=ALSA" \ + --device-type computer \ + --disable-audio-cache \ + --name "Librespot ALSA" \ +Restart=always + +[Install] +WantedBy=network-online.target