mirror of
https://github.com/owntone/owntone-server.git
synced 2025-03-31 01:33:44 -04:00
[input/pipe] Parse basic Shairport metadata using mxml
This commit is contained in:
parent
ea874154b2
commit
dc84294348
@ -464,7 +464,7 @@ input_flush(short *flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup)
|
input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime)
|
||||||
{
|
{
|
||||||
int type;
|
int type;
|
||||||
|
|
||||||
@ -490,7 +490,7 @@ input_metadata_get(struct input_metadata *metadata, struct player_source *ps, in
|
|||||||
if (!inputs[type]->metadata_get)
|
if (!inputs[type]->metadata_get)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return inputs[type]->metadata_get(metadata, ps);
|
return inputs[type]->metadata_get(metadata, ps, rtptime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -499,6 +499,7 @@ input_metadata_free(struct input_metadata *metadata, int content_only)
|
|||||||
free(metadata->artist);
|
free(metadata->artist);
|
||||||
free(metadata->title);
|
free(metadata->title);
|
||||||
free(metadata->album);
|
free(metadata->album);
|
||||||
|
free(metadata->genre);
|
||||||
free(metadata->artwork_url);
|
free(metadata->artwork_url);
|
||||||
|
|
||||||
if (!content_only)
|
if (!content_only)
|
||||||
|
@ -82,9 +82,13 @@ struct input_metadata
|
|||||||
uint64_t rtptime;
|
uint64_t rtptime;
|
||||||
uint64_t offset;
|
uint64_t offset;
|
||||||
|
|
||||||
|
// The player will update queue_item with the below
|
||||||
|
uint32_t song_length;
|
||||||
|
|
||||||
char *artist;
|
char *artist;
|
||||||
char *title;
|
char *title;
|
||||||
char *album;
|
char *album;
|
||||||
|
char *genre;
|
||||||
char *artwork_url;
|
char *artwork_url;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -112,7 +116,7 @@ struct input_definition
|
|||||||
int (*seek)(struct player_source *ps, int seek_ms);
|
int (*seek)(struct player_source *ps, int seek_ms);
|
||||||
|
|
||||||
// Return metadata
|
// Return metadata
|
||||||
int (*metadata_get)(struct input_metadata *metadata, struct player_source *ps);
|
int (*metadata_get)(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime);
|
||||||
|
|
||||||
// Initialization function called during startup
|
// Initialization function called during startup
|
||||||
int (*init)(void);
|
int (*init)(void);
|
||||||
@ -214,7 +218,7 @@ input_flush(short *flags);
|
|||||||
* Gets metadata from the input, returns 0 if metadata is set, otherwise -1
|
* Gets metadata from the input, returns 0 if metadata is set, otherwise -1
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup);
|
input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Free the entire struct
|
* Free the entire struct
|
||||||
|
@ -105,7 +105,7 @@ seek(struct player_source *ps, int seek_ms)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
metadata_get_http(struct input_metadata *metadata, struct player_source *ps)
|
metadata_get_http(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
|
||||||
{
|
{
|
||||||
struct http_icy_metadata *m;
|
struct http_icy_metadata *m;
|
||||||
int changed;
|
int changed;
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
#include <event2/buffer.h>
|
#include <event2/buffer.h>
|
||||||
|
#include <mxml.h>
|
||||||
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
@ -103,6 +104,8 @@ static struct pipe *pipelist;
|
|||||||
static struct pipe *pipe_metadata;
|
static struct pipe *pipe_metadata;
|
||||||
// We read metadata into this evbuffer
|
// We read metadata into this evbuffer
|
||||||
static struct evbuffer *pipe_metadata_buf;
|
static struct evbuffer *pipe_metadata_buf;
|
||||||
|
// Parsed metadata goes here
|
||||||
|
static struct input_metadata pipe_metadata_parsed;
|
||||||
// True if there is new metadata to push to the player
|
// True if there is new metadata to push to the player
|
||||||
static bool pipe_metadata_is_new;
|
static bool pipe_metadata_is_new;
|
||||||
|
|
||||||
@ -183,22 +186,143 @@ pipe_find(int id)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
// Convert to macro?
|
||||||
pipe_metadata_parse(struct evbuffer *evbuf)
|
static inline uint32_t
|
||||||
|
dmapval(const char s[4])
|
||||||
{
|
{
|
||||||
char *line;
|
return ((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0));
|
||||||
size_t size;
|
}
|
||||||
|
|
||||||
while ( (line = evbuffer_readln(evbuf, &size, EVBUFFER_EOL_CRLF)) )
|
static void
|
||||||
|
parse_progress(struct input_metadata *m, char *progress)
|
||||||
|
{
|
||||||
|
char *s;
|
||||||
|
char *ptr;
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t pos;
|
||||||
|
uint64_t end;
|
||||||
|
|
||||||
|
if (!(s = strtok_r(progress, "/", &ptr)))
|
||||||
|
return;
|
||||||
|
safe_atou64(s, &start);
|
||||||
|
|
||||||
|
if (!(s = strtok_r(NULL, "/", &ptr)))
|
||||||
|
return;
|
||||||
|
safe_atou64(s, &pos);
|
||||||
|
|
||||||
|
if (!(s = strtok_r(NULL, "/", &ptr)))
|
||||||
|
return;
|
||||||
|
safe_atou64(s, &end);
|
||||||
|
|
||||||
|
if (!start || !pos || !end)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m->rtptime = start; // Not actually used - we have our own rtptime
|
||||||
|
m->offset = (pos > start) ? (pos - start) : 0;
|
||||||
|
m->song_length = (end - start) * 10 / 441; // Convert to ms based on 44100
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns 0 on metadata found, otherwise -1
|
||||||
|
static int
|
||||||
|
parse_item(struct input_metadata *m, mxml_node_t *item)
|
||||||
|
{
|
||||||
|
mxml_node_t *needle;
|
||||||
|
const char *s;
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t code;
|
||||||
|
char *progress;
|
||||||
|
char **data;
|
||||||
|
|
||||||
|
type = 0;
|
||||||
|
if ( (needle = mxmlFindElement(item, item, "type", NULL, NULL, MXML_DESCEND)) &&
|
||||||
|
(s = mxmlGetText(needle, NULL)) )
|
||||||
|
sscanf(s, "%8x", &type);
|
||||||
|
|
||||||
|
code = 0;
|
||||||
|
if ( (needle = mxmlFindElement(item, item, "code", NULL, NULL, MXML_DESCEND)) &&
|
||||||
|
(s = mxmlGetText(needle, NULL)) )
|
||||||
|
sscanf(s, "%8x", &code);
|
||||||
|
|
||||||
|
if (!type || !code)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_PLAYER, "Parsing %s\n", line);
|
DPRINTF(E_WARN, L_PLAYER, "No type (%d) or code (%d) in pipe metadata\n", type, code);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
free(line);
|
if (code == dmapval("asal"))
|
||||||
|
data = &m->album;
|
||||||
|
else if (code == dmapval("asar"))
|
||||||
|
data = &m->artist;
|
||||||
|
else if (code == dmapval("minm"))
|
||||||
|
data = &m->title;
|
||||||
|
else if (code == dmapval("asgn"))
|
||||||
|
data = &m->genre;
|
||||||
|
else if (code == dmapval("prgr"))
|
||||||
|
data = &progress;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if ( (needle = mxmlFindElement(item, item, "data", NULL, NULL, MXML_DESCEND)) &&
|
||||||
|
(s = mxmlGetText(needle, NULL)) )
|
||||||
|
{
|
||||||
|
if (data != &progress)
|
||||||
|
free(*data);
|
||||||
|
|
||||||
|
*data = b64_decode(s);
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_PLAYER, "Read Shairport metadata (type=%8x, code=%8x): '%s'\n", type, code, *data);
|
||||||
|
|
||||||
|
if (data == &progress)
|
||||||
|
{
|
||||||
|
parse_progress(m, progress);
|
||||||
|
free(*data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pipe_metadata_parse(struct input_metadata *m, struct evbuffer *evbuf)
|
||||||
|
{
|
||||||
|
mxml_node_t *xml;
|
||||||
|
mxml_node_t *haystack;
|
||||||
|
mxml_node_t *item;
|
||||||
|
const char *s;
|
||||||
|
int found;
|
||||||
|
|
||||||
|
xml = mxmlNewXML("1.0");
|
||||||
|
if (!xml)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
s = (char *)evbuffer_pullup(evbuf, -1);
|
||||||
|
haystack = mxmlLoadString(xml, s, MXML_NO_CALLBACK);
|
||||||
|
if (!haystack)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Could not parse pipe metadata\n");
|
||||||
|
mxmlDelete(xml);
|
||||||
|
evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
|
evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
|
||||||
|
|
||||||
return 0;
|
// DPRINTF(E_DBG, L_PLAYER, "Parsing %s\n", s);
|
||||||
|
|
||||||
|
found = 0;
|
||||||
|
for (item = mxmlGetFirstChild(haystack); item; item = mxmlWalkNext(item, haystack, MXML_NO_DESCEND))
|
||||||
|
{
|
||||||
|
if (mxmlGetType(item) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (parse_item(m, item) == 0)
|
||||||
|
found = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mxmlDelete(xml);
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------- GENERAL PIPE I/O -------------------------- */
|
/* ---------------------------- GENERAL PIPE I/O -------------------------- */
|
||||||
@ -416,8 +540,6 @@ pipe_metadata_read_cb(evutil_socket_t fd, short event, void *arg)
|
|||||||
struct evbuffer_ptr evptr;
|
struct evbuffer_ptr evptr;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_PLAYER, "BANG\n");
|
|
||||||
|
|
||||||
ret = evbuffer_read(pipe_metadata_buf, pipe_metadata->fd, PIPE_READ_MAX);
|
ret = evbuffer_read(pipe_metadata_buf, pipe_metadata->fd, PIPE_READ_MAX);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -442,22 +564,23 @@ pipe_metadata_read_cb(evutil_socket_t fd, short event, void *arg)
|
|||||||
evptr = evbuffer_search(pipe_metadata_buf, "</item>", strlen("</item>"), NULL);
|
evptr = evbuffer_search(pipe_metadata_buf, "</item>", strlen("</item>"), NULL);
|
||||||
if (evptr.pos < 0)
|
if (evptr.pos < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_PLAYER, "Incomplete\n");
|
|
||||||
goto readd;
|
goto readd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NULL-terminate the buffer
|
// NULL-terminate the buffer
|
||||||
evbuffer_add(pipe_metadata_buf, "", 1);
|
evbuffer_add(pipe_metadata_buf, "", 1);
|
||||||
|
|
||||||
ret = pipe_metadata_parse(pipe_metadata_buf);
|
ret = pipe_metadata_parse(&pipe_metadata_parsed, pipe_metadata_buf);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
pipe_metadata_watch_del(NULL);
|
pipe_metadata_watch_del(NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (ret > 0)
|
||||||
// Trigger notification in playback loop
|
{
|
||||||
pipe_metadata_is_new = 1;
|
// Trigger notification in playback loop
|
||||||
|
pipe_metadata_is_new = 1;
|
||||||
|
}
|
||||||
|
|
||||||
readd:
|
readd:
|
||||||
if (pipe_metadata && pipe_metadata->ev)
|
if (pipe_metadata && pipe_metadata->ev)
|
||||||
@ -629,6 +752,34 @@ stop(struct player_source *ps)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME Thread safety of pipe_metadata_parsed
|
||||||
|
static int
|
||||||
|
metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
|
||||||
|
{
|
||||||
|
if (pipe_metadata_parsed.artist)
|
||||||
|
swap_pointers(&metadata->artist, &pipe_metadata_parsed.artist);
|
||||||
|
if (pipe_metadata_parsed.title)
|
||||||
|
swap_pointers(&metadata->title, &pipe_metadata_parsed.title);
|
||||||
|
if (pipe_metadata_parsed.album)
|
||||||
|
swap_pointers(&metadata->album, &pipe_metadata_parsed.album);
|
||||||
|
if (pipe_metadata_parsed.genre)
|
||||||
|
swap_pointers(&metadata->genre, &pipe_metadata_parsed.genre);
|
||||||
|
if (pipe_metadata_parsed.artwork_url)
|
||||||
|
swap_pointers(&metadata->artwork_url, &pipe_metadata_parsed.artwork_url);
|
||||||
|
|
||||||
|
if (pipe_metadata_parsed.song_length)
|
||||||
|
{
|
||||||
|
if (rtptime > ps->stream_start)
|
||||||
|
metadata->rtptime = rtptime - pipe_metadata_parsed.offset;
|
||||||
|
metadata->offset = pipe_metadata_parsed.offset;
|
||||||
|
metadata->song_length = pipe_metadata_parsed.song_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
input_metadata_free(&pipe_metadata_parsed, 1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Thread: main
|
// Thread: main
|
||||||
static int
|
static int
|
||||||
init(void)
|
init(void)
|
||||||
@ -665,6 +816,7 @@ struct input_definition input_pipe =
|
|||||||
.setup = setup,
|
.setup = setup,
|
||||||
.start = start,
|
.start = start,
|
||||||
.stop = stop,
|
.stop = stop,
|
||||||
|
.metadata_get = metadata_get,
|
||||||
.init = init,
|
.init = init,
|
||||||
.deinit = deinit,
|
.deinit = deinit,
|
||||||
};
|
};
|
||||||
|
@ -488,8 +488,12 @@ metadata_update_cb(void *arg)
|
|||||||
swap_pointers(&queue_item->title, &metadata->title);
|
swap_pointers(&queue_item->title, &metadata->title);
|
||||||
if (metadata->album)
|
if (metadata->album)
|
||||||
swap_pointers(&queue_item->album, &metadata->album);
|
swap_pointers(&queue_item->album, &metadata->album);
|
||||||
|
if (metadata->genre)
|
||||||
|
swap_pointers(&queue_item->genre, &metadata->genre);
|
||||||
if (metadata->artwork_url)
|
if (metadata->artwork_url)
|
||||||
swap_pointers(&queue_item->artwork_url, &metadata->artwork_url);
|
swap_pointers(&queue_item->artwork_url, &metadata->artwork_url);
|
||||||
|
if (metadata->song_length)
|
||||||
|
queue_item->song_length = metadata->song_length;
|
||||||
|
|
||||||
ret = db_queue_update_item(queue_item);
|
ret = db_queue_update_item(queue_item);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@ -520,7 +524,7 @@ metadata_trigger(int startup)
|
|||||||
struct input_metadata metadata;
|
struct input_metadata metadata;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = input_metadata_get(&metadata, cur_streaming, startup);
|
ret = input_metadata_get(&metadata, cur_streaming, startup, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user