mirror of
https://github.com/LibreELEC/LibreELEC.tv.git
synced 2025-07-28 13:16:41 +00:00
librespot: adapt to Python3
This commit is contained in:
parent
8d5111c208
commit
1392e81d00
@ -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
|
118
|
||||||
- Update to 0.1.0
|
- Update to 0.1.0
|
||||||
|
|
||||||
|
@ -3,14 +3,15 @@
|
|||||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
PKG_NAME="librespot"
|
PKG_NAME="librespot"
|
||||||
PKG_VERSION="0.1.0"
|
PKG_VERSION="f610436641e957750fad35e7da4933b8308ddd6d"
|
||||||
PKG_SHA256="4e03c69d1893ed14414d5a76ecdb5ea139ddfcce47fd57cc4e77b696001badb7"
|
PKG_SHA256="8c15c33a66602715b8bfc8e84d9457e70135d37079eda0df153a2ed57b4f1a33"
|
||||||
PKG_REV="118"
|
PKG_VERSION_DATE="2020-01-02"
|
||||||
|
PKG_REV="119"
|
||||||
PKG_ARCH="any"
|
PKG_ARCH="any"
|
||||||
PKG_LICENSE="MIT"
|
PKG_LICENSE="MIT"
|
||||||
PKG_SITE="https://github.com/librespot-org/librespot/"
|
PKG_SITE="https://github.com/librespot-org/librespot/"
|
||||||
PKG_URL="https://github.com/librespot-org/librespot/archive/v$PKG_VERSION.zip"
|
PKG_URL="https://github.com/librespot-org/librespot/archive/$PKG_VERSION.zip"
|
||||||
PKG_DEPENDS_TARGET="toolchain pulseaudio rust"
|
PKG_DEPENDS_TARGET="toolchain alsa-lib libvorbis pulseaudio rust"
|
||||||
PKG_SECTION="service"
|
PKG_SECTION="service"
|
||||||
PKG_SHORTDESC="Librespot: play Spotify through Kodi using a Spotify app as a remote"
|
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."
|
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_IS_ADDON="yes"
|
||||||
PKG_ADDON_NAME="Librespot"
|
PKG_ADDON_NAME="Librespot"
|
||||||
PKG_ADDON_TYPE="xbmc.service"
|
PKG_ADDON_TYPE="xbmc.service"
|
||||||
|
PKG_ADDON_REQUIRES="script.module.requests:0.0.0"
|
||||||
PKG_MAINTAINER="Anton Voyl (awiouy)"
|
PKG_MAINTAINER="Anton Voyl (awiouy)"
|
||||||
|
|
||||||
make_target() {
|
make_target() {
|
||||||
cd src
|
|
||||||
. "$(get_build_dir rust)/cargo/env"
|
. "$(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
|
"$STRIP" $PKG_BUILD/.$TARGET_NAME/*/release/librespot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,26 @@
|
|||||||
|
commit 6a9b509f4f85427f6392c08fc642afb9fc7bb8f2
|
||||||
|
Author: awiouy <awiouy@gmail.com>
|
||||||
|
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
|
diff --git a/playback/src/config.rs b/playback/src/config.rs
|
||||||
index 0f71110..931167d 100644
|
index 0f71110..931167d 100644
|
||||||
--- a/playback/src/config.rs
|
--- a/playback/src/config.rs
|
||||||
@ -8,7 +31,7 @@ index 0f71110..931167d 100644
|
|||||||
pub normalisation_pregain: f32,
|
pub normalisation_pregain: f32,
|
||||||
+ pub notify_kodi: bool,
|
+ pub notify_kodi: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlayerConfig {
|
impl Default for PlayerConfig {
|
||||||
@@ -38,6 +39,7 @@ impl Default for PlayerConfig {
|
@@ -38,6 +39,7 @@ impl Default for PlayerConfig {
|
||||||
bitrate: Bitrate::default(),
|
bitrate: Bitrate::default(),
|
||||||
@ -19,35 +42,35 @@ index 0f71110..931167d 100644
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
diff --git a/playback/src/player.rs b/playback/src/player.rs
|
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
|
--- a/playback/src/player.rs
|
||||||
+++ b/playback/src/player.rs
|
+++ b/playback/src/player.rs
|
||||||
@@ -4,7 +4,8 @@ use futures::sync::oneshot;
|
@@ -5,7 +5,8 @@ use futures::{future, Future};
|
||||||
use futures::{future, Future};
|
|
||||||
use std;
|
use std;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::cmp::max;
|
||||||
-use std::io::{Read, Result, Seek, SeekFrom};
|
-use std::io::{Read, Result, Seek, SeekFrom};
|
||||||
+use std::fs::OpenOptions;
|
+use std::fs::OpenOptions;
|
||||||
+use std::io::{Read, Result, Seek, SeekFrom, Write};
|
+use std::io::{Read, Result, Seek, SeekFrom, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
|
use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
|
||||||
use std::thread;
|
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) {
|
+ fn notify_kodi(&mut self, id: &str, track_id: &SpotifyId) {
|
||||||
+ // println!("fifo = {} {}", id, track_id.to_base62());
|
+ // println!("fifo = {} {}", id, track_id.to_base62());
|
||||||
+ if self.config.notify_kodi {
|
+ if self.config.notify_kodi {
|
||||||
+ let mut file = OpenOptions::new().write(true).open("/tmp/librespot").unwrap();
|
+ 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) {
|
fn handle_command(&mut self, cmd: PlayerCommand) {
|
||||||
debug!("command={:?}", cmd);
|
debug!("command={:?}", cmd);
|
||||||
match cmd {
|
match cmd {
|
||||||
@@ -413,11 +422,17 @@ impl PlayerInternal {
|
@@ -451,11 +460,17 @@ impl PlayerInternal {
|
||||||
| PlayerState::EndOfTrack {
|
| PlayerState::EndOfTrack {
|
||||||
track_id: old_track_id,
|
track_id: old_track_id,
|
||||||
..
|
..
|
||||||
@ -68,9 +91,9 @@ index ab1a8ab..19d6394 100644
|
|||||||
+ self.notify_kodi("2", &track_id)
|
+ self.notify_kodi("2", &track_id)
|
||||||
+ }
|
+ }
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start_sink();
|
self.start_sink();
|
||||||
@@ -443,13 +458,17 @@ impl PlayerInternal {
|
@@ -485,13 +500,17 @@ impl PlayerInternal {
|
||||||
| PlayerState::EndOfTrack {
|
| PlayerState::EndOfTrack {
|
||||||
track_id: old_track_id,
|
track_id: old_track_id,
|
||||||
..
|
..
|
||||||
@ -91,24 +114,24 @@ index ab1a8ab..19d6394 100644
|
|||||||
+ self.notify_kodi("4", &track_id)
|
+ 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.send_event(PlayerEvent::Started { track_id });
|
||||||
self.start_sink();
|
self.start_sink();
|
||||||
+ self.notify_kodi("5", &track_id)
|
+ self.notify_kodi("5", &track_id)
|
||||||
} else {
|
} else {
|
||||||
warn!("Player::play called from invalid state");
|
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.stop_sink_if_running();
|
||||||
self.send_event(PlayerEvent::Stopped { track_id });
|
self.send_event(PlayerEvent::Stopped { track_id });
|
||||||
+ self.notify_kodi("6", &track_id)
|
+ self.notify_kodi("6", &track_id)
|
||||||
} else {
|
} else {
|
||||||
warn!("Player::pause called from invalid state");
|
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.stop_sink_if_running();
|
||||||
self.send_event(PlayerEvent::Stopped { track_id });
|
self.send_event(PlayerEvent::Stopped { track_id });
|
||||||
self.state = PlayerState::Stopped;
|
self.state = PlayerState::Stopped;
|
||||||
@ -117,10 +140,10 @@ index ab1a8ab..19d6394 100644
|
|||||||
PlayerState::Stopped => {
|
PlayerState::Stopped => {
|
||||||
warn!("Player::stop called from invalid state");
|
warn!("Player::stop called from invalid state");
|
||||||
diff --git a/src/main.rs b/src/main.rs
|
diff --git a/src/main.rs b/src/main.rs
|
||||||
index 36cd1b5..502cac8 100644
|
index e3718fb..a480480 100644
|
||||||
--- a/src/main.rs
|
--- a/src/main.rs
|
||||||
+++ b/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 (dB) applied by volume normalisation",
|
||||||
"PREGAIN",
|
"PREGAIN",
|
||||||
)
|
)
|
||||||
@ -132,20 +155,20 @@ index 36cd1b5..502cac8 100644
|
|||||||
.optflag(
|
.optflag(
|
||||||
"",
|
"",
|
||||||
"linear-volume",
|
"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 notify_kodi = matches.opt_present("notify-kodi");
|
||||||
+
|
+
|
||||||
let session_config = {
|
let session_config = {
|
||||||
let device_id = device_id(&name);
|
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")
|
.opt_str("normalisation-pregain")
|
||||||
.map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
|
.map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
|
||||||
.unwrap_or(PlayerConfig::default().normalisation_pregain),
|
.unwrap_or(PlayerConfig::default().normalisation_pregain),
|
||||||
+ notify_kodi: notify_kodi,
|
+ notify_kodi: notify_kodi,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
From b87d18c6513cebc31118ffb447e2b7ae9255e6bd Mon Sep 17 00:00:00 2001
|
commit 0f3141a0874566ce867139aeadbefdb076706baf
|
||||||
From: awiouy <awiouy@gmail.com>
|
Author: awiouy <awiouy@gmail.com>
|
||||||
Date: Wed, 7 Nov 2018 07:51:46 +0100
|
Date: Tue Dec 3 23:22:55 2019 +0100
|
||||||
Subject: [PATCH] libreelec: pulseaudio sink
|
|
||||||
|
|
||||||
---
|
Use own pulseaudio sink
|
||||||
playback/src/audio_backend/pulseaudio.rs | 3 ++-
|
|
||||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/playback/src/audio_backend/pulseaudio.rs b/playback/src/audio_backend/pulseaudio.rs
|
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
|
--- a/playback/src/audio_backend/pulseaudio.rs
|
||||||
+++ b/playback/src/audio_backend/pulseaudio.rs
|
+++ b/playback/src/audio_backend/pulseaudio.rs
|
||||||
@@ -76,6 +76,7 @@ impl Open for PulseAudioSink {
|
@@ -76,6 +76,7 @@ impl Open for PulseAudioSink {
|
||||||
|
|
||||||
impl Sink for PulseAudioSink {
|
impl Sink for PulseAudioSink {
|
||||||
fn start(&mut self) -> io::Result<()> {
|
fn start(&mut self) -> io::Result<()> {
|
||||||
+ let sink = CString::new("librespot_sink").unwrap();
|
+ let sink = CString::new("librespot_sink").unwrap();
|
||||||
|
@ -2,14 +2,12 @@
|
|||||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import urllib
|
import requests
|
||||||
import urllib2
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
@ -21,8 +19,6 @@ FIFO = '/tmp/librespot'
|
|||||||
LOG_LEVEL = xbmc.LOGNOTICE
|
LOG_LEVEL = xbmc.LOGNOTICE
|
||||||
LOG_MESSAGE = ADDON.getAddonInfo('name') + ': {}'
|
LOG_MESSAGE = ADDON.getAddonInfo('name') + ': {}'
|
||||||
SINK_NAME = "librespot_sink"
|
SINK_NAME = "librespot_sink"
|
||||||
SPOTIFY_ID = '169df5532dee47a59913f8528e83ae71'
|
|
||||||
SPOTIFY_SECRET = '1f3d8b507bbe4f68beb3a4472e8ad411'
|
|
||||||
STREAM_CODEC = 'pcm_s16be'
|
STREAM_CODEC = 'pcm_s16be'
|
||||||
STREAM_PORT = '6666'
|
STREAM_PORT = '6666'
|
||||||
STREAM_URL = 'rtp://127.0.0.1:{}'.format(STREAM_PORT)
|
STREAM_URL = 'rtp://127.0.0.1:{}'.format(STREAM_PORT)
|
||||||
@ -100,10 +96,11 @@ class Player(threading.Thread, xbmc.Player):
|
|||||||
log('pausing librespot playback')
|
log('pausing librespot playback')
|
||||||
self.pause()
|
self.pause()
|
||||||
|
|
||||||
def playLibrespot(self, track_id):
|
def playLibrespot(self, spotify_id):
|
||||||
track = self.spotify.getTrack(track_id)
|
type, id = spotify_id.split()
|
||||||
self.listitem.setArt(track.getArt())
|
info = self.spotify.getInfo(type, id)
|
||||||
self.listitem.setInfo('music', track.getInfo())
|
self.listitem.setArt(info.getArt())
|
||||||
|
self.listitem.setInfo('music', info.getInfo())
|
||||||
if not self.isPlaying():
|
if not self.isPlaying():
|
||||||
subprocess.call(['pactl', 'suspend-sink', SINK_NAME, '0'])
|
subprocess.call(['pactl', 'suspend-sink', SINK_NAME, '0'])
|
||||||
log('starting librespot playback')
|
log('starting librespot playback')
|
||||||
@ -158,46 +155,51 @@ class Spotify():
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.headers = None
|
self.headers = None
|
||||||
self.expiration = time.time()
|
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):
|
def getHeaders(self):
|
||||||
if time.time() > self.expiration:
|
if time.time() > self.expiration:
|
||||||
log('token expired')
|
log('token expired')
|
||||||
token = json.loads(urllib2.urlopen(
|
token = requests.post(
|
||||||
urllib2.Request(*self.request)).read())
|
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']))
|
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 = {
|
self.headers = {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': 'Bearer {}'.format(token['access_token'])
|
'Authorization': 'Bearer {}'.format(token['access_token'])
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTrack(self, track_id):
|
def getEndpoint(self, url):
|
||||||
log('getting track')
|
|
||||||
try:
|
try:
|
||||||
self.getHeaders()
|
self.getHeaders()
|
||||||
track = json.loads(urllib2.urlopen(urllib2.Request(
|
return requests.get(
|
||||||
'https://api.spotify.com/v1/tracks/{}'.format(track_id), None,
|
url=url,
|
||||||
self.headers)).read())
|
headers=self.headers
|
||||||
|
).json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log('failed to get track from Spotify: {}'.format(e))
|
log('failed to get endpoint from Spotify {}'.format(e))
|
||||||
track = dict()
|
return {}
|
||||||
return Track(track)
|
|
||||||
|
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):
|
def __init__(self, type, id):
|
||||||
self.track = track
|
self.id = id
|
||||||
|
self.type = type
|
||||||
|
|
||||||
def get(self, default, *indices):
|
def get(self, default, *indices):
|
||||||
tree = self.track
|
tree = self.info
|
||||||
try:
|
try:
|
||||||
for index in indices:
|
for index in indices:
|
||||||
tree = tree[index]
|
tree = tree[index]
|
||||||
@ -205,6 +207,18 @@ class Track():
|
|||||||
tree = default
|
tree = default
|
||||||
return tree
|
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):
|
def getArt(self):
|
||||||
return {
|
return {
|
||||||
'thumb': self.get('', 'album', 'images', 0, 'url')
|
'thumb': self.get('', 'album', 'images', 0, 'url')
|
||||||
@ -214,7 +228,7 @@ class Track():
|
|||||||
return {
|
return {
|
||||||
'album': self.get('', 'album', 'name'),
|
'album': self.get('', 'album', 'name'),
|
||||||
'artist': self.get('', 'artists', 0, 'name'),
|
'artist': self.get('', 'artists', 0, 'name'),
|
||||||
'title': self.get('', 'name'),
|
'title': self.get('', 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user