Modified metadata interface to be tag independent, added JSON api.

This commit is contained in:
frafall 2017-11-30 11:07:23 +01:00
parent 67083975b0
commit ce17b0010a
7 changed files with 116 additions and 33 deletions

View file

@ -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)

View file

@ -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);

View 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.

View 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)
}
}

View file

@ -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);
}

View file

@ -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_;
}; };

View file

@ -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;
}
} }