mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-12 16:46:42 +02:00
Modified metadata interface to be tag independent, added JSON api.
This commit is contained in:
parent
67083975b0
commit
ce17b0010a
7 changed files with 116 additions and 33 deletions
|
@ -55,7 +55,7 @@ public:
|
||||||
|
|
||||||
std::string serialize()
|
std::string serialize()
|
||||||
{
|
{
|
||||||
return METADATA + ":" + _msg->dump() + "\n";
|
return METADATA + ":" + _msg->dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
void tag(std::string name, std::string value)
|
void tag(std::string name, std::string value)
|
||||||
|
|
|
@ -385,10 +385,14 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
{
|
{
|
||||||
if (request->method.find("Stream.SetMeta") == 0)
|
if (request->method.find("Stream.SetMeta") == 0)
|
||||||
{
|
{
|
||||||
/// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.SetMeta","params":{"id":"Spotify","meta": {"album": "some album", "artist": "some artist", "track": "some track"...}}}
|
/// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.SetMeta","params":{"stream_id":"Spotify",
|
||||||
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"stream 1"}}
|
/// "meta": {"album": "some album", "artist": "some artist", "track": "some track"...}}}
|
||||||
|
///
|
||||||
|
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"Spotify"}}
|
||||||
/// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications
|
/// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications
|
||||||
|
|
||||||
|
LOG(INFO) << "Stream.SetMeta(" << request->params.get("stream_id") << ")" << request->params.get("meta") <<"\n";
|
||||||
|
|
||||||
// Find stream
|
// Find stream
|
||||||
string streamId = request->params.get("stream_id");
|
string streamId = request->params.get("stream_id");
|
||||||
PcmStreamPtr stream = streamManager_->getStream(streamId);
|
PcmStreamPtr stream = streamManager_->getStream(streamId);
|
||||||
|
@ -396,14 +400,10 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
throw jsonrpcpp::InternalErrorException("Stream not found", request->id);
|
throw jsonrpcpp::InternalErrorException("Stream not found", request->id);
|
||||||
|
|
||||||
// Set metadata from request
|
// Set metadata from request
|
||||||
// stream->
|
stream->setMeta(request->params.get("meta"));
|
||||||
|
|
||||||
// Update clients
|
|
||||||
// Setup response
|
// Setup response
|
||||||
|
result["stream_id"] = streamId;
|
||||||
// Trigger notifications
|
|
||||||
// onMetaChanged(stream);
|
|
||||||
// notification.reset(new jsonrpcpp::Notification("Client.OnLatencyChanged", jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency)));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw jsonrpcpp::MethodNotFoundException(request->id);
|
throw jsonrpcpp::MethodNotFoundException(request->id);
|
||||||
|
|
31
server/streamreader/README.meta
Normal file
31
server/streamreader/README.meta
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
Metadata tags
|
||||||
|
=============
|
||||||
|
The metatag interface will propegate anything to the client, ie no restrictions on the tags.
|
||||||
|
But, to avoid total confusion in the tag display app (like Kodi), we need some groundrules.
|
||||||
|
|
||||||
|
My suggestion is to stick with the Vorbis standard as described in:
|
||||||
|
|
||||||
|
http://www.xiph.org/vorbis/doc/v-comment.html
|
||||||
|
https://wiki.xiph.org/Field_names
|
||||||
|
|
||||||
|
In addition we should accept unique identifiers of the stream such as Spotify track ID or
|
||||||
|
MusicBrainz id which can be used to lookup additional information online.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
ARTIST: Pink Floyd
|
||||||
|
ALBUM: Dark Side of the Moon
|
||||||
|
TITLE: Money
|
||||||
|
METADATA_BLOCK_PICTURE: base64 (https://xiph.org/flac/format.html#metadata_block_picture)
|
||||||
|
|
||||||
|
|
||||||
|
Parsing tags from streams
|
||||||
|
=========================
|
||||||
|
I've implemented parsing tags from Librespot, but, as Librespot doesn't export anything more than
|
||||||
|
track title I added a patch 'librespot-meta.patch' to apply to get artist/title.
|
||||||
|
|
||||||
|
TBH, the meta tag output from players are a mess, neither Librespot nor Shairport-sync associate
|
||||||
|
metadata with their audio interfaces, ie they don't even export metadata when the audio interface
|
||||||
|
supports it, at least Shairport-sync exports a ritch set of data.
|
||||||
|
|
||||||
|
So, to get artist/album apply the patch to librespot and recompile, otherwise you will only get the
|
||||||
|
track title.
|
31
server/streamreader/librespot-meta.patch
Normal file
31
server/streamreader/librespot-meta.patch
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
*** librespot/src/player.rs 2017-11-30 08:16:40.865939287 +0100
|
||||||
|
--- librespot.meta/src/player.rs 2017-11-30 08:14:39.232954039 +0100
|
||||||
|
***************
|
||||||
|
*** 13,19 ****
|
||||||
|
use audio_backend::Sink;
|
||||||
|
use audio::{AudioFile, AudioDecrypt};
|
||||||
|
use audio::{VorbisDecoder, VorbisPacket};
|
||||||
|
! use metadata::{FileFormat, Track, Metadata};
|
||||||
|
use mixer::AudioFilter;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
--- 13,19 ----
|
||||||
|
use audio_backend::Sink;
|
||||||
|
use audio::{AudioFile, AudioDecrypt};
|
||||||
|
use audio::{VorbisDecoder, VorbisPacket};
|
||||||
|
! use metadata::{Artist, FileFormat, Track, Metadata};
|
||||||
|
use mixer::AudioFilter;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
***************
|
||||||
|
*** 384,389 ****
|
||||||
|
--- 384,392 ----
|
||||||
|
|
||||||
|
info!("Track \"{}\" loaded", track.name);
|
||||||
|
|
||||||
|
+ let artist = Artist::get(&self.session, track.artists[0]).wait().unwrap();
|
||||||
|
+ info!("metadata:{{\"ARTIST\":\"{}\",\"TITLE\":\"{}\"}}", artist.name, track.name);
|
||||||
|
+
|
||||||
|
Some(decoder)
|
||||||
|
}
|
||||||
|
}
|
|
@ -179,3 +179,17 @@ json PcmStream::toJson() const
|
||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<msg::StreamTags> PcmStream::getMeta() const
|
||||||
|
{
|
||||||
|
return meta_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PcmStream::setMeta(json jtag)
|
||||||
|
{
|
||||||
|
meta_.reset(new msg::StreamTags(jtag));
|
||||||
|
|
||||||
|
// Trigger a stream update
|
||||||
|
if (pcmListener_)
|
||||||
|
pcmListener_->onMetaChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,15 +83,12 @@ public:
|
||||||
virtual const std::string& getId() const;
|
virtual const std::string& getId() const;
|
||||||
virtual const SampleFormat& getSampleFormat() const;
|
virtual const SampleFormat& getSampleFormat() const;
|
||||||
|
|
||||||
|
std::shared_ptr<msg::StreamTags> getMeta() const;
|
||||||
|
void setMeta(json j);
|
||||||
|
|
||||||
virtual ReaderState getState() const;
|
virtual ReaderState getState() const;
|
||||||
virtual json toJson() const;
|
virtual json toJson() const;
|
||||||
|
|
||||||
//const msg::StreamTags *getMeta()
|
|
||||||
std::shared_ptr<msg::StreamTags> getMeta() const
|
|
||||||
{
|
|
||||||
return meta_;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::condition_variable cv_;
|
std::condition_variable cv_;
|
||||||
|
@ -99,6 +96,7 @@ protected:
|
||||||
std::thread thread_;
|
std::thread thread_;
|
||||||
std::atomic<bool> active_;
|
std::atomic<bool> active_;
|
||||||
|
|
||||||
|
|
||||||
virtual void worker() = 0;
|
virtual void worker() = 0;
|
||||||
virtual bool sleep(int32_t ms);
|
virtual bool sleep(int32_t ms);
|
||||||
void setState(const ReaderState& newState);
|
void setState(const ReaderState& newState);
|
||||||
|
@ -112,8 +110,6 @@ protected:
|
||||||
std::unique_ptr<Encoder> encoder_;
|
std::unique_ptr<Encoder> encoder_;
|
||||||
std::string name_;
|
std::string name_;
|
||||||
ReaderState state_;
|
ReaderState state_;
|
||||||
|
|
||||||
// Stream metadata
|
|
||||||
std::shared_ptr<msg::StreamTags> meta_;
|
std::shared_ptr<msg::StreamTags> meta_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,9 @@ void SpotifyStream::initExeAndPath(const std::string& filename)
|
||||||
|
|
||||||
void SpotifyStream::onStderrMsg(const char* buffer, size_t n)
|
void SpotifyStream::onStderrMsg(const char* buffer, size_t n)
|
||||||
{
|
{
|
||||||
|
static bool libreelec_patched = false;
|
||||||
|
smatch m;
|
||||||
|
|
||||||
// Watch stderr for 'Loading track' messages and set the stream metadata
|
// Watch stderr for 'Loading track' messages and set the stream metadata
|
||||||
// For more than track name check: https://github.com/plietar/librespot/issues/154
|
// For more than track name check: https://github.com/plietar/librespot/issues/154
|
||||||
|
|
||||||
|
@ -124,29 +127,37 @@ void SpotifyStream::onStderrMsg(const char* buffer, size_t n)
|
||||||
LOG(INFO) << "(" << getName() << ") " << logmsg << "\n";
|
LOG(INFO) << "(" << getName() << ") " << logmsg << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track tags "Julia Michaels" "Issues - Acoustic"
|
// Librespot patch:
|
||||||
if (logmsg.find("Track tags") != string::npos)
|
// info!("metadata:{{\"ARTIST\":\"{}\",\"TITLE\":\"{}\"}}", artist.name, track.name);
|
||||||
{
|
// non patched:
|
||||||
// Traditional Libreelec meta interface, only track name
|
// info!("Track \"{}\" loaded", track.name);
|
||||||
regex re("Track tags \"(.*)\" \"(.*)\"");
|
|
||||||
smatch m;
|
|
||||||
|
|
||||||
if (regex_search(logmsg, m, re))
|
// If we detect a patched libreelec we don't want to bother with this anymoer
|
||||||
|
// to avoid duplicate metadata pushes
|
||||||
|
if (!libreelec_patched)
|
||||||
{
|
{
|
||||||
// Create a new meta struct?
|
static regex re_nonpatched("Track \"(.*)\" loaded");
|
||||||
LOG(INFO) << "Loading track <" << m[1] << "> <" << m[2] << ">\n";
|
if(regex_search(logmsg, m, re_nonpatched))
|
||||||
|
{
|
||||||
|
LOG(INFO) << "metadata: <" << m[1] << ">\n";
|
||||||
|
|
||||||
json jtag = {
|
json jtag = {
|
||||||
{"artist", (string)m[1]},
|
{"TITLE", (string)m[1]}
|
||||||
{"track", (string)m[2]}
|
|
||||||
};
|
};
|
||||||
meta_.reset(new msg::StreamTags(jtag));
|
setMeta(jtag);
|
||||||
|
|
||||||
// Trigger a stream update
|
|
||||||
if (pcmListener_)
|
|
||||||
pcmListener_->onMetaChanged(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the patched version
|
||||||
|
static regex re_patched("metadata:(.*)");
|
||||||
|
if (regex_search(logmsg, m, re_patched))
|
||||||
|
{
|
||||||
|
LOG(INFO) << "metadata: <" << m[1] << ">\n";
|
||||||
|
|
||||||
|
json jtag;
|
||||||
|
setMeta(jtag.parse(m[1]));
|
||||||
|
libreelec_patched = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue