From aac72613b6cfe7849c500b809593db41bca0601f Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Fri, 1 Nov 2013 22:24:05 +0100 Subject: [PATCH 01/25] Requesting progress and metadata in MDNS announcement. --- mdns.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdns.h b/mdns.h index b3f003912..9baaf59a0 100644 --- a/mdns.h +++ b/mdns.h @@ -14,7 +14,7 @@ typedef struct { } mdns_backend; #define MDNS_RECORD "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", \ - "ss=16", "sr=44100", "vn=3", "txtvers=1", \ + "ss=16", "sr=44100", "vn=3", "txtvers=1", "da=true", "md=0,1,2", \ config.password ? "pw=true" : "pw=false" #endif // _MDNS_H From f37e121d65302a243337f163ec61d36d51e0856a Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Fri, 1 Nov 2013 22:24:49 +0100 Subject: [PATCH 02/25] Switching to sub handlers in handle_set_parameter by content-type. --- rtsp.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/rtsp.c b/rtsp.c index ef79defbb..50374a0f5 100644 --- a/rtsp.c +++ b/rtsp.c @@ -453,6 +453,40 @@ static void handle_set_parameter(rtsp_conn_info *conn, resp->respcode = 200; } +static void handle_set_parameter(rtsp_conn_info *conn, + rtsp_message *req, rtsp_message *resp) { + if (!req->contentlength) + debug(1, "received empty SET_PARAMETER request\n"); + + char *ct = msg_get_header(req, "Content-Type"); + + if (ct) { + debug(1, "CT %s\n", ct); + + if (!strncmp(ct, "application/x-dmap-tagged", 25)) { + debug(1, "I have tagged stuff!\n"); + + handle_set_parameter_metadata(conn, req, resp); + } else if (!strncmp(ct, "image/jpeg", 10)) { + debug(1, "I have an image!\n"); + + handle_set_parameter_coverart(conn, req, resp); + } else if (!strncmp(ct, "image/none", 10)) { + debug(1, "I have an empty image!\n"); + + handle_set_parameter_coverart_empty(conn, req, resp); + } else if (!strncmp(ct, "text/parameters", 15)) { + debug(1, "I have parameters!\n"); + + handle_set_parameter_parameter(conn, req, resp); + } else { + debug(1, "I have got something else: %s\n", ct); + } + } else { + debug(1, "missing Content-Type header\n"); + } +} + static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { From fc3ebdd7e71f810ad74c5abcb2bc1eb5f65caa28 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Fri, 1 Nov 2013 22:25:24 +0100 Subject: [PATCH 03/25] Moved actual parameter handler. --- rtsp.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rtsp.c b/rtsp.c index 50374a0f5..3486a88c9 100644 --- a/rtsp.c +++ b/rtsp.c @@ -427,11 +427,8 @@ static void handle_ignore(rtsp_conn_info *conn, resp->respcode = 200; } -static void handle_set_parameter(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { - if (!req->contentlength) - debug(1, "received empty SET_PARAMETER request\n"); - +static void handle_set_parameter_parameter(rtsp_conn_info *conn, + rtsp_message *req, rtsp_message *resp) { char *cp = req->content; int cp_left = req->contentlength; char *next; @@ -449,6 +446,8 @@ static void handle_set_parameter(rtsp_conn_info *conn, cp = next; } + resp->respcode = 200; +} resp->respcode = 200; } From 87344f7f0054cad35aaa53a7aba33f278dc0ba59 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Fri, 1 Nov 2013 22:25:47 +0100 Subject: [PATCH 04/25] Added empty handlers for cover art. --- rtsp.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rtsp.c b/rtsp.c index 3486a88c9..67d5410ee 100644 --- a/rtsp.c +++ b/rtsp.c @@ -449,6 +449,16 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, resp->respcode = 200; } + +static void handle_set_parameter_coverart(rtsp_conn_info *conn, + rtsp_message *req, rtsp_message *resp) { + + resp->respcode = 200; +} + +static void handle_set_parameter_coverart_empty(rtsp_conn_info *conn, + rtsp_message *req, rtsp_message *resp) { + resp->respcode = 200; } From cbee548b310f9dd87ef03c26a2ee26221ab40a64 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Fri, 1 Nov 2013 22:26:28 +0100 Subject: [PATCH 05/25] Added preliminary parsing code for DMAP tags. --- rtsp.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/rtsp.c b/rtsp.c index 67d5410ee..f5dcab09a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -449,6 +449,40 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, resp->respcode = 200; } +static void handle_set_parameter_metadata(rtsp_conn_info *conn, + rtsp_message *req, rtsp_message *resp) { + + char *cp = req->content; + int cl = req->contentlength; + + // asal - album + // minm - title + // asar - track + // asgn - genre + // ascm - comment + + unsigned int off = 8; + + while (off < cl) { + char tag[5]; + strncpy(tag, cp+off, 4); + tag[4] = 0; + off += 4; + + uint32_t flength = ntohl(*(uint32_t *)(cp+off)); + off += sizeof(uint32_t); + + char *content = malloc(flength+1); + strncpy(content, cp+off, flength); + *(content+flength) = 0; + off += flength; + + debug(2, "Tag: %s Content: %s\n", tag, content); + free(content); + } + + resp->respcode = 200; +} static void handle_set_parameter_coverart(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { From 0058c5392518442d09afd60b0da2c72c194276e9 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:24:47 +0100 Subject: [PATCH 06/25] Added metadata structures and methods. --- metadata.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ metadata.h | 16 ++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 metadata.c create mode 100644 metadata.h diff --git a/metadata.c b/metadata.c new file mode 100644 index 000000000..6f21205b9 --- /dev/null +++ b/metadata.c @@ -0,0 +1,56 @@ +/* + * Metadate structure and utility methods. This file is part of Shairport. + * Copyright (c) Benjamin Maus 2013 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "common.h" +#include "metadata.h" + + +metadata * metadata_init(void) { + metadata *meta = malloc(sizeof(metadata)); + memset(meta, 0, sizeof(metadata)); + return meta; +} + +void metadata_free(metadata *meta) { + int i; + if (meta->artist) + free(meta->artist); + if (meta->title) + free(meta->title); + if (meta->album) + free(meta->album); + if (meta->comment) + free(meta->comment); + if (meta->genre) + free(meta->genre); + free(meta); +} diff --git a/metadata.h b/metadata.h new file mode 100644 index 000000000..730170092 --- /dev/null +++ b/metadata.h @@ -0,0 +1,16 @@ +#ifndef _METADATA_H +#define _METADATA_H + +typedef struct { + char *artist; + char *title; + char *album; + char *comment; + char *genre; +} metadata; + +metadata * metadata_init(void); + +void metadata_free(metadata *meta); + +#endif // _METADATA_H From ab63de4e854cd8c764e6d35c324217125d48056a Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:25:14 +0100 Subject: [PATCH 07/25] Added command line option to set the cover art output directory. --- common.h | 1 + shairport.c | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/common.h b/common.h index 451b815a7..8d7d8f712 100644 --- a/common.h +++ b/common.h @@ -29,6 +29,7 @@ typedef struct { int buffer_start_fill; int daemonise; char *cmd_start, *cmd_stop; + char *cover_dir; char *pidfile; char *logfile; char *errfile; diff --git a/shairport.c b/shairport.c index b05ace375..52ebcddfb 100644 --- a/shairport.c +++ b/shairport.c @@ -99,6 +99,7 @@ void usage(char *progname) { printf(" -e, --error=FILE redirect shairport's standard error output to FILE\n"); printf(" -B, --on-start=COMMAND run a shell command when playback begins\n"); printf(" -E, --on-stop=COMMAND run a shell command when playback ends\n"); + printf(" -C, --cover-dir=DIR set a directory to write the album cover art to\n"); printf(" -o, --output=BACKEND select audio output method\n"); printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service\n"); @@ -116,23 +117,24 @@ int parse_options(int argc, char **argv) { setenv("POSIXLY_CORRECT", "", 1); static struct option long_options[] = { - {"help", no_argument, NULL, 'h'}, - {"daemon", no_argument, NULL, 'd'}, - {"pidfile", required_argument, NULL, 'P'}, - {"log", required_argument, NULL, 'l'}, - {"error", required_argument, NULL, 'e'}, - {"port", required_argument, NULL, 'p'}, - {"name", required_argument, NULL, 'a'}, - {"output", required_argument, NULL, 'o'}, - {"on-start",required_argument, NULL, 'B'}, - {"on-stop", required_argument, NULL, 'E'}, - {"mdns", required_argument, NULL, 'm'}, - {NULL, 0, NULL, 0} + {"help", no_argument, NULL, 'h'}, + {"daemon", no_argument, NULL, 'd'}, + {"pidfile", required_argument, NULL, 'P'}, + {"log", required_argument, NULL, 'l'}, + {"error", required_argument, NULL, 'e'}, + {"port", required_argument, NULL, 'p'}, + {"name", required_argument, NULL, 'a'}, + {"output", required_argument, NULL, 'o'}, + {"on-start", required_argument, NULL, 'B'}, + {"on-stop", required_argument, NULL, 'E'}, + {"cover-dir", required_argument, NULL, 'C'}, + {"mdns", required_argument, NULL, 'm'}, + {NULL, 0, NULL, 0} }; int opt; while ((opt = getopt_long(argc, argv, - "+hdvP:l:e:p:a:o:b:B:E:m:", + "+hdvP:l:e:p:a:o:b:B:E:C:m:", long_options, NULL)) > 0) { switch (opt) { default: @@ -163,6 +165,9 @@ int parse_options(int argc, char **argv) { case 'E': config.cmd_stop = optarg; break; + case 'C': + config.cover_dir = optarg; + break; case 'P': config.pidfile = optarg; break; From 016e62e4835f13309d4814e17605c5e9ddcbbded Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:26:50 +0100 Subject: [PATCH 08/25] Added player methods for printing metadata and setting the artwork image. --- player.c | 14 ++++++++++++++ player.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/player.c b/player.c index 639354ce2..032862b86 100644 --- a/player.c +++ b/player.c @@ -495,6 +495,20 @@ void player_volume(double f) { pthread_mutex_unlock(&vol_mutex); } } + +void player_metadata(metadata *meta) { + printf("Metadata Artist: %s Title: %s Album: %s Genre: %s Comment: %s\n", + meta->artist, meta->title, meta->album, meta->genre, meta->comment); +} + +void player_cover_image(char *buf, int len, char *ext) { + printf("Cover Art set\n"); +} + +void player_cover_clear() { + printf("Cover Art cleared\n"); +} + void player_flush(void) { pthread_mutex_lock(&ab_mutex); ab_resync(); diff --git a/player.h b/player.h index 33b28c707..89e503e54 100644 --- a/player.h +++ b/player.h @@ -2,6 +2,7 @@ #define _PLAYER_H #include "audio.h" +#include "metadata.h" typedef struct { uint8_t aesiv[16], aeskey[16]; @@ -20,6 +21,9 @@ int player_play(stream_cfg *cfg); void player_stop(void); void player_volume(double f); +void player_metadata(metadata *meta); +void player_cover_image(char *buf, int len, char *ext); +void player_cover_clear(); void player_flush(void); void player_resync(void); From 16f49c69689b5fc5d386b6ecdd2de138047ce871 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:27:09 +0100 Subject: [PATCH 09/25] Added metadata.c to makefile. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1c640edb2..1fca4ea78 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ endif PREFIX ?= /usr/local -SRCS := shairport.c daemon.c rtsp.c mdns.c mdns_external.c mdns_tinysvcmdns.c common.c rtp.c player.c alac.c audio.c audio_dummy.c audio_pipe.c tinysvcmdns.c +SRCS := shairport.c daemon.c rtsp.c mdns.c mdns_external.c mdns_tinysvcmdns.c common.c rtp.c metadata.c player.c alac.c audio.c audio_dummy.c audio_pipe.c tinysvcmdns.c ifdef CONFIG_SNDIO SRCS += audio_sndio.c From cb5cc59abb9c61eb5c719e53e18451ced3cb0d6f Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:27:50 +0100 Subject: [PATCH 10/25] Reading the progress: parameter to prevent warnings. --- rtsp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rtsp.c b/rtsp.c index f5dcab09a..5571af5a6 100644 --- a/rtsp.c +++ b/rtsp.c @@ -440,6 +440,9 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, float volume = atof(cp + 8); debug(1, "volume: %f\n", volume); player_volume(volume); + } else if(!strncmp(cp, "progress: ", 10)) { + char *progress = cp + 10; + debug(1, "progress: %s\n", progress); } else { debug(1, "unrecognised parameter: >>%s<< (%d)\n", cp, strlen(cp)); } From 2562e2b2dc09bf36d64db215f9517d40e0949b3d Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:28:51 +0100 Subject: [PATCH 11/25] Settings response status in main handler. --- rtsp.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/rtsp.c b/rtsp.c index 5571af5a6..e172025a1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -484,19 +484,14 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, free(content); } - resp->respcode = 200; } static void handle_set_parameter_coverart(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - - resp->respcode = 200; } static void handle_set_parameter_coverart_empty(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - - resp->respcode = 200; } static void handle_set_parameter(rtsp_conn_info *conn, @@ -531,6 +526,8 @@ static void handle_set_parameter(rtsp_conn_info *conn, } else { debug(1, "missing Content-Type header\n"); } + + resp->respcode = 200; } static void handle_announce(rtsp_conn_info *conn, From 5c69dcb8a77834cecf48d6ae93c814bc625b47ee Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:30:40 +0100 Subject: [PATCH 12/25] Writing the cover image to a file. This only happens if the cover directory option is set. Using the MD5 hash with a prefix as the file name. Don't overwrite the file if it already exists. --- player.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/player.c b/player.c index 032862b86..c81cb1e78 100644 --- a/player.c +++ b/player.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -37,6 +39,7 @@ #include #include #include +#include #include "common.h" #include "player.h" @@ -503,6 +506,53 @@ void player_metadata(metadata *meta) { void player_cover_image(char *buf, int len, char *ext) { printf("Cover Art set\n"); + + if (config.cover_dir) { + uint8_t img_md5[16]; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, buf, len); + MD5_Final(img_md5, &ctx); + + char img_md5_str[33]; + for (int i = 0; i < 16; i++) + sprintf(&img_md5_str[i*2], "%02x", (uint8_t)img_md5[i]); + + char *dir = config.cover_dir; + char *prefix = "cover-"; + + int pl = strlen(dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 + strlen(ext); + + char *path = malloc(pl+1); + snprintf(path, pl+1, "%s/%s%s.%s", dir, prefix, img_md5_str, ext); + + int cover_fd = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + + int success = 0; + + if (cover_fd > -1) { + if (write(cover_fd, buf, len) < len) { + warn("writing failed\n"); + } else { + success = 1; + } + + close(cover_fd); + } else { + if (errno == EEXIST) { + debug(1, "file already exists. skipping.\n"); + success = 1; + } else { + warn("could not open file %s for writing cover art.\n", path); + } + } + + if (success) { + printf("Cover Art file is %s\n", path); + } + + free(path); + } } void player_cover_clear() { From f16f3972512cb46ea289e45c40d49088e0e72d21 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:32:10 +0100 Subject: [PATCH 13/25] Calls to the player for setting and clearing the album cover. Supported types are png and jpeg. Setting the file extension. --- rtsp.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rtsp.c b/rtsp.c index e172025a1..7f1310902 100644 --- a/rtsp.c +++ b/rtsp.c @@ -488,10 +488,23 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, static void handle_set_parameter_coverart(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + char *cp = req->content; + int cl = req->contentlength; + + char *ct = msg_get_header(req, "Content-Type"); + + if (!strncmp(ct, "image/jpeg", 10)) { + player_cover_image(cp, cl, "jpg"); + } + + if (!strncmp(ct, "image/png", 9)) { + player_cover_image(cp, cl, "png"); + } } static void handle_set_parameter_coverart_empty(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + player_cover_clear(); } static void handle_set_parameter(rtsp_conn_info *conn, From 04dea7ff5a9321d8b90de51fd8d9197e88efdddc Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:33:45 +0100 Subject: [PATCH 14/25] Removed comments. Naming... --- rtsp.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/rtsp.c b/rtsp.c index 7f1310902..2cbff5907 100644 --- a/rtsp.c +++ b/rtsp.c @@ -448,40 +448,31 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, } cp = next; } - - resp->respcode = 200; } static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - char *cp = req->content; int cl = req->contentlength; - // asal - album - // minm - title - // asar - track - // asgn - genre - // ascm - comment unsigned int off = 8; while (off < cl) { char tag[5]; strncpy(tag, cp+off, 4); - tag[4] = 0; + tag[4] = '\0'; off += 4; - uint32_t flength = ntohl(*(uint32_t *)(cp+off)); + uint32_t vl = ntohl(*(uint32_t *)(cp+off)); off += sizeof(uint32_t); - char *content = malloc(flength+1); - strncpy(content, cp+off, flength); - *(content+flength) = 0; - off += flength; + char *val = malloc(vl+1); + strncpy(val, cp+off, vl); + val[vl] = '\0'; + off += vl; - debug(2, "Tag: %s Content: %s\n", tag, content); - free(content); + free(val); } } From 646a5a2a1e2284965b9cc1aa95c4ea44bc41fbe2 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:34:30 +0100 Subject: [PATCH 15/25] Setting the metadata from parsed data and passing it to the player. --- rtsp.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/rtsp.c b/rtsp.c index 2cbff5907..55646f94b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -45,6 +45,7 @@ #include "player.h" #include "rtp.h" #include "mdns.h" +#include "metadata.h" #ifdef AF_INET6 #define INETx_ADDRSTRLEN INET6_ADDRSTRLEN @@ -455,6 +456,7 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, char *cp = req->content; int cl = req->contentlength; + metadata *meta = metadata_init(); unsigned int off = 8; @@ -472,9 +474,30 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, val[vl] = '\0'; off += vl; + debug(2, "Tag: %s Content: %s\n", tag, val); + + if (!strncmp(tag, "asal ", 4) && meta->album == NULL) { + debug(1, "META Album: %s\n", val); + meta->album = strdup(val); + } else if (!strncmp(tag, "asar ", 4) && meta->artist == NULL) { + debug(1, "META Artist: %s\n", val); + meta->artist = strdup(val); + } else if (!strncmp(tag, "ascm ", 4) && meta->comment == NULL) { + debug(1, "META Comment: %s\n", val); + meta->comment = strdup(val); + } else if (!strncmp(tag, "asgn ", 4) && meta->genre == NULL) { + debug(1, "META Genre: %s\n", val); + meta->genre = strdup(val); + } else if (!strncmp(tag, "minm ", 4) && meta->title == NULL) { + debug(1, "META Title: %s\n", val); + meta->title = strdup(val); + } + free(val); } + player_metadata(meta); + metadata_free(meta); } static void handle_set_parameter_coverart(rtsp_conn_info *conn, From a4f4f11cb4e989083cde1dcac554272479988932 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:35:29 +0100 Subject: [PATCH 16/25] Cleaned logging for SET_PARAMETER requests. --- rtsp.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rtsp.c b/rtsp.c index 55646f94b..89702d965 100644 --- a/rtsp.c +++ b/rtsp.c @@ -529,29 +529,29 @@ static void handle_set_parameter(rtsp_conn_info *conn, char *ct = msg_get_header(req, "Content-Type"); if (ct) { - debug(1, "CT %s\n", ct); + debug(2, "SET_PARAMETER Content-Type: %s\n", ct); if (!strncmp(ct, "application/x-dmap-tagged", 25)) { - debug(1, "I have tagged stuff!\n"); + debug(1, "received metadata tags in SET_PARAMETER request\n"); handle_set_parameter_metadata(conn, req, resp); } else if (!strncmp(ct, "image/jpeg", 10)) { - debug(1, "I have an image!\n"); + debug(1, "received image in SET_PARAMETER request\n"); handle_set_parameter_coverart(conn, req, resp); } else if (!strncmp(ct, "image/none", 10)) { - debug(1, "I have an empty image!\n"); + debug(1, "received empty image in SET_PARAMETER request\n"); handle_set_parameter_coverart_empty(conn, req, resp); } else if (!strncmp(ct, "text/parameters", 15)) { - debug(1, "I have parameters!\n"); + debug(1, "received parameters in SET_PARAMETER request\n"); handle_set_parameter_parameter(conn, req, resp); } else { - debug(1, "I have got something else: %s\n", ct); + debug(1, "received unknown Content-Type %s in SET_PARAMETER request\n", ct); } } else { - debug(1, "missing Content-Type header\n"); + debug(1, "missing Content-Type header in SET_PARAMETER request\n"); } resp->respcode = 200; From 0c23b3a1b4b4d833185092260d12af39ae6b0284 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:35:44 +0100 Subject: [PATCH 17/25] Allowing png as content-type. --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 89702d965..f8d525aaa 100644 --- a/rtsp.c +++ b/rtsp.c @@ -535,7 +535,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, debug(1, "received metadata tags in SET_PARAMETER request\n"); handle_set_parameter_metadata(conn, req, resp); - } else if (!strncmp(ct, "image/jpeg", 10)) { + } else if (!strncmp(ct, "image/jpeg", 10) || !strncmp(ct, "image/png", 9)) { debug(1, "received image in SET_PARAMETER request\n"); handle_set_parameter_coverart(conn, req, resp); From ceb88799579b38c6c489d242a23cce82417ea5f8 Mon Sep 17 00:00:00 2001 From: Jens de Boer Date: Sat, 2 Nov 2013 14:16:44 +0100 Subject: [PATCH 18/25] Fixed C99 declaration error. --- player.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index c81cb1e78..771a71940 100644 --- a/player.c +++ b/player.c @@ -515,7 +515,8 @@ void player_cover_image(char *buf, int len, char *ext) { MD5_Final(img_md5, &ctx); char img_md5_str[33]; - for (int i = 0; i < 16; i++) + int i; + for (i = 0; i < 16; i++) sprintf(&img_md5_str[i*2], "%02x", (uint8_t)img_md5[i]); char *dir = config.cover_dir; @@ -550,9 +551,9 @@ void player_cover_image(char *buf, int len, char *ext) { if (success) { printf("Cover Art file is %s\n", path); } - + free(path); - } + } } void player_cover_clear() { From 785fa091c0e77d44e7c900491007c1baa1b86d40 Mon Sep 17 00:00:00 2001 From: Weston Nielson Date: Sun, 23 Mar 2014 13:27:18 -0700 Subject: [PATCH 19/25] Added functionality to write metadata to disk. --- metadata.c | 27 +++++++++++++++++++++++++++ metadata.h | 9 ++++++--- player.c | 19 ++++++++++++++----- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/metadata.c b/metadata.c index 6f21205b9..6ef414471 100644 --- a/metadata.c +++ b/metadata.c @@ -54,3 +54,30 @@ void metadata_free(metadata *meta) { free(meta->genre); free(meta); } + +FILE* metadata_open(const char* mode) { + FILE* fh = NULL; + if (config.cover_dir) { + const char fn[] = "now_playing.txt"; + size_t pl = strlen(config.cover_dir) + 1 + strlen(fn); + + char* path = malloc(pl+1); + snprintf(path, pl+1, "%s/%s", config.cover_dir, fn); + + fh = fopen(path, mode); + free(path); + } + return fh; +} + +void metadata_write(metadata* meta, const char* dir) { + FILE* fh = metadata_open("w"); + if (fh) { + fprintf(fh, "%s\n", meta->artist); + fprintf(fh, "%s\n", meta->title); + fprintf(fh, "%s\n", meta->album); + fprintf(fh, "%s\n", meta->genre); + fprintf(fh, "%s\n", (meta->comment == NULL) ? "" : meta->comment); + fclose(fh); + } +} diff --git a/metadata.h b/metadata.h index 730170092..fc276bb05 100644 --- a/metadata.h +++ b/metadata.h @@ -1,6 +1,8 @@ #ifndef _METADATA_H #define _METADATA_H +#include + typedef struct { char *artist; char *title; @@ -9,8 +11,9 @@ typedef struct { char *genre; } metadata; -metadata * metadata_init(void); - -void metadata_free(metadata *meta); +metadata* metadata_init(void); +void metadata_free(metadata* meta); +FILE* metadata_open(const char* mode); +void metadata_write(metadata* meta, const char* dir); #endif // _METADATA_H diff --git a/player.c b/player.c index f98649631..8beb3aec8 100644 --- a/player.c +++ b/player.c @@ -500,12 +500,16 @@ void player_volume(double f) { } void player_metadata(metadata *meta) { - printf("Metadata Artist: %s Title: %s Album: %s Genre: %s Comment: %s\n", + debug(1, "Metadata Artist: %s Title: %s Album: %s Genre: %s Comment: %s\n", meta->artist, meta->title, meta->album, meta->genre, meta->comment); + + if (config.cover_dir) { + metadata_write(meta, config.cover_dir); + } } void player_cover_image(char *buf, int len, char *ext) { - printf("Cover Art set\n"); + debug(1, "Cover Art set\n"); if (config.cover_dir) { uint8_t img_md5[16]; @@ -522,7 +526,7 @@ void player_cover_image(char *buf, int len, char *ext) { char *dir = config.cover_dir; char *prefix = "cover-"; - int pl = strlen(dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 + strlen(ext); + size_t pl = strlen(dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 + strlen(ext); char *path = malloc(pl+1); snprintf(path, pl+1, "%s/%s%s.%s", dir, prefix, img_md5_str, ext); @@ -535,6 +539,11 @@ void player_cover_image(char *buf, int len, char *ext) { if (write(cover_fd, buf, len) < len) { warn("writing failed\n"); } else { + FILE* fh = metadata_open("a"); + if (fh) { + fprintf(fh, "%s%s.%s\n", prefix,img_md5_str,ext); + fclose(fh); + } success = 1; } @@ -549,7 +558,7 @@ void player_cover_image(char *buf, int len, char *ext) { } if (success) { - printf("Cover Art file is %s\n", path); + debug(1, "Cover Art file is %s\n", path); } free(path); @@ -557,7 +566,7 @@ void player_cover_image(char *buf, int len, char *ext) { } void player_cover_clear() { - printf("Cover Art cleared\n"); + debug(1, "Cover Art cleared\n"); } void player_flush(void) { From f31be9d952db9664929da9a1a6568df81417a858 Mon Sep 17 00:00:00 2001 From: Weston Nielson Date: Sun, 23 Mar 2014 14:36:06 -0700 Subject: [PATCH 20/25] Improved metadata output. --- README.md | 23 +++++++++++++++++++++++ metadata.c | 37 ++++++++++++------------------------- metadata.h | 10 ++++++---- player.c | 20 +++++++++----------- player.h | 2 +- rtsp.c | 36 +++++++++++++++++------------------- 6 files changed, 68 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index f68bd7fd1..a73cff027 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,29 @@ The backends prefixed by 'external' rely on external programs that should be pre By default, shairport will try all backends, in the order they are listed by `shairport -h`, until one works. You can force the use of a specific backend using `shairport -m tinysvcmdns` for example. +Metadata +-------- + +The following metadata can be sent to the player for the currently playing track: + + * artist + * title + * album + * artwork + * genre + * comment + +To enable the output of metadata, the `-C ` flag must be set to instruct `shairport` where +to save the `now_playing.txt` file and cover art. The `now_playing.txt` file will then contain the fields +listed above, in that order, with each field on its own line. Example output:: + + Tame Impala + Elephant + Lonerism + cover-0c7b6d4c572d0936a5b94e37b6ef0408.jpg + +In this example there is no `genre` or `comment` data, hence the last two lines are empty. + Thanks ------ diff --git a/metadata.c b/metadata.c index 6ef414471..e5143b501 100644 --- a/metadata.c +++ b/metadata.c @@ -33,26 +33,12 @@ #include "common.h" #include "metadata.h" +metadata player_meta; -metadata * metadata_init(void) { - metadata *meta = malloc(sizeof(metadata)); - memset(meta, 0, sizeof(metadata)); - return meta; -} - -void metadata_free(metadata *meta) { - int i; - if (meta->artist) - free(meta->artist); - if (meta->title) - free(meta->title); - if (meta->album) - free(meta->album); - if (meta->comment) - free(meta->comment); - if (meta->genre) - free(meta->genre); - free(meta); +void metadata_set(char** field, const char* value) { + if (*field) + free(*field); + *field = strdup(value); } FILE* metadata_open(const char* mode) { @@ -70,14 +56,15 @@ FILE* metadata_open(const char* mode) { return fh; } -void metadata_write(metadata* meta, const char* dir) { +void metadata_write(const char* dir) { FILE* fh = metadata_open("w"); if (fh) { - fprintf(fh, "%s\n", meta->artist); - fprintf(fh, "%s\n", meta->title); - fprintf(fh, "%s\n", meta->album); - fprintf(fh, "%s\n", meta->genre); - fprintf(fh, "%s\n", (meta->comment == NULL) ? "" : meta->comment); + fprintf(fh, "%s\n", player_meta.artist); + fprintf(fh, "%s\n", player_meta.title); + fprintf(fh, "%s\n", player_meta.album); + fprintf(fh, "%s\n", player_meta.artwork); + fprintf(fh, "%s\n", player_meta.genre); + fprintf(fh, "%s\n", (player_meta.comment == NULL) ? "" : player_meta.comment); fclose(fh); } } diff --git a/metadata.h b/metadata.h index fc276bb05..d99da83f8 100644 --- a/metadata.h +++ b/metadata.h @@ -7,13 +7,15 @@ typedef struct { char *artist; char *title; char *album; + char *artwork; char *comment; char *genre; } metadata; -metadata* metadata_init(void); -void metadata_free(metadata* meta); -FILE* metadata_open(const char* mode); -void metadata_write(metadata* meta, const char* dir); +void metadata_set(char** field, const char* value); +FILE* metadata_open(const char* mode); +void metadata_write(const char* dir); + +extern metadata player_meta; #endif // _METADATA_H diff --git a/player.c b/player.c index 8beb3aec8..38105c05a 100644 --- a/player.c +++ b/player.c @@ -499,13 +499,14 @@ void player_volume(double f) { } } -void player_metadata(metadata *meta) { - debug(1, "Metadata Artist: %s Title: %s Album: %s Genre: %s Comment: %s\n", - meta->artist, meta->title, meta->album, meta->genre, meta->comment); +void player_metadata() { + debug(1, "Artist: %s Title: %s Album: %s Genre: %s Comment: %s\n", + player_meta.artist, player_meta.title, player_meta.album, + player_meta.genre, player_meta.comment); - if (config.cover_dir) { - metadata_write(meta, config.cover_dir); - } + if (config.cover_dir) { + metadata_write(config.cover_dir); + } } void player_cover_image(char *buf, int len, char *ext) { @@ -539,11 +540,6 @@ void player_cover_image(char *buf, int len, char *ext) { if (write(cover_fd, buf, len) < len) { warn("writing failed\n"); } else { - FILE* fh = metadata_open("a"); - if (fh) { - fprintf(fh, "%s%s.%s\n", prefix,img_md5_str,ext); - fclose(fh); - } success = 1; } @@ -559,6 +555,8 @@ void player_cover_image(char *buf, int len, char *ext) { if (success) { debug(1, "Cover Art file is %s\n", path); + metadata_set(&player_meta.artwork, path+strlen(dir)+1); + metadata_write(config.cover_dir); } free(path); diff --git a/player.h b/player.h index 89e503e54..0d52cb458 100644 --- a/player.h +++ b/player.h @@ -21,7 +21,7 @@ int player_play(stream_cfg *cfg); void player_stop(void); void player_volume(double f); -void player_metadata(metadata *meta); +void player_metadata(); void player_cover_image(char *buf, int len, char *ext); void player_cover_clear(); void player_flush(void); diff --git a/rtsp.c b/rtsp.c index ecade9bf5..bd451f7e7 100644 --- a/rtsp.c +++ b/rtsp.c @@ -451,12 +451,11 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, } static void handle_set_parameter_metadata(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { + rtsp_message *req, + rtsp_message *resp) { char *cp = req->content; - int cl = req->contentlength; - - metadata *meta = metadata_init(); - + int cl = req->contentlength; + unsigned int off = 8; while (off < cl) { @@ -475,28 +474,27 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, debug(2, "Tag: %s Content: %s\n", tag, val); - if (!strncmp(tag, "asal ", 4) && meta->album == NULL) { + if (!strncmp(tag, "asal ", 4)) { debug(1, "META Album: %s\n", val); - meta->album = strdup(val); - } else if (!strncmp(tag, "asar ", 4) && meta->artist == NULL) { + metadata_set(&player_meta.album, val); + } else if (!strncmp(tag, "asar ", 4)) { debug(1, "META Artist: %s\n", val); - meta->artist = strdup(val); - } else if (!strncmp(tag, "ascm ", 4) && meta->comment == NULL) { + metadata_set(&player_meta.artist, val); + } else if (!strncmp(tag, "ascm ", 4)) { debug(1, "META Comment: %s\n", val); - meta->comment = strdup(val); - } else if (!strncmp(tag, "asgn ", 4) && meta->genre == NULL) { + metadata_set(&player_meta.comment, val); + } else if (!strncmp(tag, "asgn ", 4)) { debug(1, "META Genre: %s\n", val); - meta->genre = strdup(val); - } else if (!strncmp(tag, "minm ", 4) && meta->title == NULL) { + metadata_set(&player_meta.genre, val); + } else if (!strncmp(tag, "minm ", 4)) { debug(1, "META Title: %s\n", val); - meta->title = strdup(val); + metadata_set(&player_meta.title, val); } - free(val); + free(val); } - - player_metadata(meta); - metadata_free(meta); + + player_metadata(); } static void handle_set_parameter_coverart(rtsp_conn_info *conn, From c4992b52e8a734d95dcd929d76a88bfc99aa72c1 Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:25:14 +0100 Subject: [PATCH 21/25] Added command line option to set the cover art output directory. --- common.h | 254 ++++++++++++++++++++++++++++++++++++++++------------ shairport.c | 18 ++++ 2 files changed, 214 insertions(+), 58 deletions(-) diff --git a/common.h b/common.h index 811d3f9cf..8cf25b500 100644 --- a/common.h +++ b/common.h @@ -1,66 +1,204 @@ -#ifndef _COMMON_H -#define _COMMON_H +/* + * Utility routines. This file is part of Shairport. + * Copyright (c) James Laird 2013 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include #include -#include -#include -#include "audio.h" -#include "mdns.h" - -// struct sockaddr_in6 is bigger than struct sockaddr. derp -#ifdef AF_INET6 - #define SOCKADDR struct sockaddr_storage - #define SAFAMILY ss_family -#else - #define SOCKADDR struct sockaddr - #define SAFAMILY sa_family -#endif - - -typedef struct { - char *password; - char *apname; - uint8_t hw_addr[6]; - int port; - char *output_name; - audio_output *output; - char *mdns_name; - mdns_backend *mdns; - int buffer_start_fill; - int daemonise; - char *cmd_start, *cmd_stop; - int cmd_blocking; - char *meta_dir; - char *pidfile; - char *logfile; - char *errfile; -} shairport_cfg; - -extern int debuglev; -void die(char *format, ...); -void warn(char *format, ...); -void debug(int level, char *format, ...); - -/* functions that ignore return values without compiler warnings. - * for use only where return values really don't matter! - */ -#define write_unchecked(...) (void)(write(__VA_ARGS__)+1) -#define read_unchecked(...) (void)(read (__VA_ARGS__)+1) -#define lockf_unchecked(...) (void)(lockf(__VA_ARGS__)+1) +#include +#include +#include +#include +#include "common.h" +#include "daemon.h" + +shairport_cfg config; + +int debuglev = 0; + +void die(char *format, ...) { + fprintf(stderr, "FATAL: "); + + va_list args; + va_start(args, format); + + vfprintf(stderr, format, args); + if (config.daemonise) + daemon_fail(format, args); // Send error message to parent + + va_end(args); + + fprintf(stderr, "\n"); + shairport_shutdown(1); +} + +void warn(char *format, ...) { + fprintf(stderr, "WARNING: "); + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); +} + +void debug(int level, char *format, ...) { + if (level > debuglev) + return; + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + + +char *base64_enc(uint8_t *input, int length) { + BIO *bmem, *b64; + BUF_MEM *bptr; + b64 = BIO_new(BIO_f_base64()); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + BIO_write(b64, input, length); + BIO_flush(b64); + BIO_get_mem_ptr(b64, &bptr); + + char *buf = (char *)malloc(bptr->length); + if (bptr->length) { + memcpy(buf, bptr->data, bptr->length-1); + buf[bptr->length-1] = 0; + } + + BIO_free_all(bmem); + + return buf; +} + +uint8_t *base64_dec(char *input, int *outlen) { + BIO *bmem, *b64; + int inlen = strlen(input); + + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); + + // Apple cut the padding off their challenges; restore it + BIO_write(bmem, input, inlen); + while (inlen++ & 3) + BIO_write(bmem, "=", 1); + BIO_flush(bmem); + + int bufsize = strlen(input)*3/4 + 1; + uint8_t *buf = malloc(bufsize); + int nread; + + nread = BIO_read(b64, buf, bufsize); + + BIO_free_all(bmem); + + *outlen = nread; + return buf; +} + +static char super_secret_key[] = +"-----BEGIN RSA PRIVATE KEY-----\n" +"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n" +"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n" +"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n" +"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n" +"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n" +"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n" +"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n" +"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n" +"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n" +"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n" +"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n" +"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n" +"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n" +"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n" +"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n" +"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n" +"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n" +"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n" +"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n" +"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n" +"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n" +"-----END RSA PRIVATE KEY-----"; + +uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { + static RSA *rsa = NULL; + + if (!rsa) { + BIO *bmem = BIO_new_mem_buf(super_secret_key, -1); + rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL); + BIO_free(bmem); + } + + uint8_t *out = malloc(RSA_size(rsa)); + switch (mode) { + case RSA_MODE_AUTH: + *outlen = RSA_private_encrypt(inlen, input, out, rsa, + RSA_PKCS1_PADDING); + break; + case RSA_MODE_KEY: + *outlen = RSA_private_decrypt(inlen, input, out, rsa, + RSA_PKCS1_OAEP_PADDING); + break; + default: + die("bad rsa mode"); + } + return out; +} -uint8_t *base64_dec(char *input, int *outlen); -char *base64_enc(uint8_t *input, int length); +void command_start(void) { + if (!config.cmd_start) + return; + if (!config.cmd_blocking && fork()) + return; -#define RSA_MODE_AUTH (0) -#define RSA_MODE_KEY (1) -uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode); + debug(1, "running start command: %s", config.cmd_start); + if (system(config.cmd_start)) + warn("exec of external start command failed"); -void command_start(void); -void command_stop(void); + if (!config.cmd_blocking) + exit(0); +} -extern shairport_cfg config; +void command_stop(void) { + if (!config.cmd_stop) + return; + if (!config.cmd_blocking && fork()) + return; -void shairport_shutdown(int retval); -void shairport_startup_complete(void); + debug(1, "running stop command: %s", config.cmd_stop); + if (system(config.cmd_stop)) + warn("exec of external stop command failed"); -#endif // _COMMON_H + if (!config.cmd_blocking) + exit(0); +} \ No newline at end of file diff --git a/shairport.c b/shairport.c index 8a3f89d39..7e01b3474 100644 --- a/shairport.c +++ b/shairport.c @@ -131,6 +131,7 @@ int parse_options(int argc, char **argv) { {"error", required_argument, NULL, 'e'}, {"port", required_argument, NULL, 'p'}, {"name", required_argument, NULL, 'a'}, +<<<<<<< HEAD {"password", required_argument, NULL, 'k'}, {"output", required_argument, NULL, 'o'}, {"on-start", required_argument, NULL, 'B'}, @@ -139,11 +140,23 @@ int parse_options(int argc, char **argv) { {"meta-dir", required_argument, NULL, 'M'}, {"mdns", required_argument, NULL, 'm'}, {NULL, 0, NULL, 0} +======= + {"output", required_argument, NULL, 'o'}, + {"on-start", required_argument, NULL, 'B'}, + {"on-stop", required_argument, NULL, 'E'}, + {"cover-dir", required_argument, NULL, 'C'}, + {"mdns", required_argument, NULL, 'm'}, + {NULL, 0, NULL, 0} +>>>>>>> Added command line option to set the cover art output directory. }; int opt; while ((opt = getopt_long(argc, argv, +<<<<<<< HEAD "+hdvP:l:e:p:a:k:o:b:B:E:M:wm:", +======= + "+hdvP:l:e:p:a:o:b:B:E:C:m:", +>>>>>>> Added command line option to set the cover art output directory. long_options, NULL)) > 0) { switch (opt) { default: @@ -179,11 +192,16 @@ int parse_options(int argc, char **argv) { case 'E': config.cmd_stop = optarg; break; +<<<<<<< HEAD case 'w': config.cmd_blocking = 1; break; case 'M': config.meta_dir = optarg; +======= + case 'C': + config.cover_dir = optarg; +>>>>>>> Added command line option to set the cover art output directory. break; case 'P': config.pidfile = optarg; From 4281cb5566df4e2785db27fa1b470482e653778a Mon Sep 17 00:00:00 2001 From: Benjamin Maus Date: Sat, 2 Nov 2013 13:27:09 +0100 Subject: [PATCH 22/25] Added metadata.c to makefile. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dd4dc8b07..14c02b113 100644 --- a/Makefile +++ b/Makefile @@ -67,4 +67,4 @@ shairport: $(OBJS) clean: rm -f shairport version.h - rm -f $(OBJS) + rm -f $(OBJS) \ No newline at end of file From 632f46145b5778133219804b2f1cde98c51a8c58 Mon Sep 17 00:00:00 2001 From: Weston Nielson Date: Thu, 16 Oct 2014 16:38:15 -0700 Subject: [PATCH 23/25] Added FIFO for playback position. --- common.h | 2 +- metadata.c | 65 +++++++++++++++++++++++++++++++++++++++++++++- metadata.h | 9 ++++++- player.c | 74 ++--------------------------------------------------- rtp.c | 9 ++++++- rtsp.c | 10 ++++++-- shairport.c | 2 +- 7 files changed, 92 insertions(+), 79 deletions(-) diff --git a/common.h b/common.h index 746240fe2..811d3f9cf 100644 --- a/common.h +++ b/common.h @@ -63,4 +63,4 @@ extern shairport_cfg config; void shairport_shutdown(int retval); void shairport_startup_complete(void); -#endif // _COMMON_H \ No newline at end of file +#endif // _COMMON_H diff --git a/metadata.c b/metadata.c index 230141eb7..e9708e34f 100644 --- a/metadata.c +++ b/metadata.c @@ -41,6 +41,7 @@ metadata player_meta; static int fd = -1; static int dirty = 0; +static int fdp = -1; void metadata_set(char** field, const char* value) { if (*field) { @@ -77,6 +78,31 @@ static void metadata_close(void) { fd = -1; } +void metadata_position_open(void) { + if (!config.meta_dir) + return; + + const char fn[] = "position"; + size_t pl = strlen(config.meta_dir) + 1 + strlen(fn); + + char* path = malloc(pl+1); + snprintf(path, pl+1, "%s/%s", config.meta_dir, fn); + + if (mkfifo(path, 0644) && errno != EEXIST) + die("Could not create metadata FIFO %s", path); + + fdp = open(path, O_WRONLY | O_NONBLOCK); + if (fdp < 0) + debug(1, "Could not open metadata position FIFO %s. Will try again later.", path); + + free(path); +} + +static void metadata_position_close(void) { + close(fdp); + fdp = -1; +} + static void print_one(const char *name, const char *value) { write_unchecked(fd, name, strlen(name)); write_unchecked(fd, "=", 1); @@ -114,6 +140,43 @@ void metadata_write(void) { metadata_close(); } +void metadata_position_write(void) { + int ret; + + if (player_meta.paused) + return; + + // readers may go away and come back + if (fdp < 0) + metadata_position_open(); + if (fdp < 0) + return; + + char number[10]; + int chars; + + // + // XXX: The sample rate is always 44100 Hertz, right? + // + unsigned int position = (player_meta.position-player_meta.start)/44100; + unsigned int length = (player_meta.end-player_meta.start)/44100; + + if (length == 0 || position > length) + return; + + chars = sprintf(number, "%d", position); + write_unchecked(fdp, number, chars); + + write_unchecked(fdp, " ", 1); + + chars = sprintf(number, "%d", length); + write_unchecked(fdp, number, chars); + + ret = write(fdp, "\n", 1); + if (ret < 1) // no reader + metadata_position_close(); +} + void metadata_cover_image(const char *buf, int len, const char *ext) { if (!config.meta_dir) return; @@ -162,4 +225,4 @@ void metadata_cover_image(const char *buf, int len, const char *ext) { metadata_set(&player_meta.artwork, path+strlen(dir)+1); free(path); -} \ No newline at end of file +} diff --git a/metadata.h b/metadata.h index 356e5ab30..ea01d8966 100644 --- a/metadata.h +++ b/metadata.h @@ -10,13 +10,20 @@ typedef struct { char *artwork; char *comment; char *genre; + + unsigned int paused; + unsigned int position; + unsigned int start; + unsigned int curr; + unsigned int end; } metadata; void metadata_set(char** field, const char* value); void metadata_open(void); void metadata_write(void); void metadata_cover_image(const char *buf, int len, const char *ext); +void metadata_position_write(void); extern metadata player_meta; -#endif // _METADATA_H \ No newline at end of file +#endif // _METADATA_H diff --git a/player.c b/player.c index 38105c05a..9b6229ca4 100644 --- a/player.c +++ b/player.c @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include #include #include @@ -39,7 +37,6 @@ #include #include #include -#include #include "common.h" #include "player.h" @@ -479,6 +476,7 @@ static void *player_thread_func(void *arg) { #endif play_samples = stuff_buffer(bf_playback_rate, inbuf, outbuf); + player_meta.paused = 0; config.output->play(outbuf, play_samples); } @@ -498,76 +496,8 @@ void player_volume(double f) { pthread_mutex_unlock(&vol_mutex); } } - -void player_metadata() { - debug(1, "Artist: %s Title: %s Album: %s Genre: %s Comment: %s\n", - player_meta.artist, player_meta.title, player_meta.album, - player_meta.genre, player_meta.comment); - - if (config.cover_dir) { - metadata_write(config.cover_dir); - } -} - -void player_cover_image(char *buf, int len, char *ext) { - debug(1, "Cover Art set\n"); - - if (config.cover_dir) { - uint8_t img_md5[16]; - MD5_CTX ctx; - MD5_Init(&ctx); - MD5_Update(&ctx, buf, len); - MD5_Final(img_md5, &ctx); - - char img_md5_str[33]; - int i; - for (i = 0; i < 16; i++) - sprintf(&img_md5_str[i*2], "%02x", (uint8_t)img_md5[i]); - - char *dir = config.cover_dir; - char *prefix = "cover-"; - - size_t pl = strlen(dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 + strlen(ext); - - char *path = malloc(pl+1); - snprintf(path, pl+1, "%s/%s%s.%s", dir, prefix, img_md5_str, ext); - - int cover_fd = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); - - int success = 0; - - if (cover_fd > -1) { - if (write(cover_fd, buf, len) < len) { - warn("writing failed\n"); - } else { - success = 1; - } - - close(cover_fd); - } else { - if (errno == EEXIST) { - debug(1, "file already exists. skipping.\n"); - success = 1; - } else { - warn("could not open file %s for writing cover art.\n", path); - } - } - - if (success) { - debug(1, "Cover Art file is %s\n", path); - metadata_set(&player_meta.artwork, path+strlen(dir)+1); - metadata_write(config.cover_dir); - } - - free(path); - } -} - -void player_cover_clear() { - debug(1, "Cover Art cleared\n"); -} - void player_flush(void) { + player_meta.paused = 1; pthread_mutex_lock(&ab_mutex); ab_resync(); pthread_mutex_unlock(&ab_mutex); diff --git a/rtp.c b/rtp.c index 51b846d5d..5cd1e7f87 100644 --- a/rtp.c +++ b/rtp.c @@ -34,6 +34,7 @@ #include #include "common.h" #include "player.h" +#include "metadata.h" // only one RTP session can be active at a time. static int running = 0; @@ -57,8 +58,14 @@ static void *rtp_receiver(void *arg) { ssize_t plen = nread; uint8_t type = packet[1] & ~0x80; - if (type == 0x54) // sync + if (type == 0x54) { // sync + player_meta.position = (packet[4] << 24) | + (packet[5] << 16) | + (packet[6] << 8) | + (packet[7]); + metadata_position_write(); continue; + } if (type == 0x60 || type == 0x56) { // audio data / resend pktp = packet; if (type==0x56) { diff --git a/rtsp.c b/rtsp.c index a6ec86946..72d3365b0 100644 --- a/rtsp.c +++ b/rtsp.c @@ -410,7 +410,7 @@ static void handle_setup(rtsp_conn_info *conn, int sport = rtp_setup(&conn->remote, cport, tport); if (!sport) return; - + player_play(&conn->stream); char resphdr[100]; @@ -445,6 +445,12 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, } else if(!strncmp(cp, "progress: ", 10)) { char *progress = cp + 10; debug(1, "progress: %s\n", progress); + + if (sscanf(progress, "%u/%u/%u", &(player_meta.start), &(player_meta.curr), &(player_meta.end)) == 3) + { + player_meta.position = 0; + } + } else { debug(1, "unrecognised parameter: >>%s<< (%d)\n", cp, strlen(cp)); } @@ -968,4 +974,4 @@ void rtsp_listen_loop(void) { } perror("select"); die("fell out of the RTSP select loop"); -} \ No newline at end of file +} diff --git a/shairport.c b/shairport.c index 1a1b7d42e..8a3f89d39 100644 --- a/shairport.c +++ b/shairport.c @@ -320,4 +320,4 @@ int main(int argc, char **argv) { // should not. shairport_shutdown(1); return 1; -} \ No newline at end of file +} From 8451ea8f543ed956fe5a44654a74b1c42d687317 Mon Sep 17 00:00:00 2001 From: Weston Nielson Date: Thu, 16 Oct 2014 16:38:15 -0700 Subject: [PATCH 24/25] Fixed logic error. --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 72d3365b0..67b7fb21c 100644 --- a/rtsp.c +++ b/rtsp.c @@ -446,7 +446,7 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, char *progress = cp + 10; debug(1, "progress: %s\n", progress); - if (sscanf(progress, "%u/%u/%u", &(player_meta.start), &(player_meta.curr), &(player_meta.end)) == 3) + if (sscanf(progress, "%u/%u/%u", &(player_meta.start), &(player_meta.curr), &(player_meta.end)) != 3) { player_meta.position = 0; } From 56643d40a117621d3b2fbe13477ab6bf43d6eccc Mon Sep 17 00:00:00 2001 From: Weston Nielson Date: Fri, 17 Oct 2014 09:23:39 -0700 Subject: [PATCH 25/25] Updated README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d9304ec4c..306fd8831 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,11 @@ An example:: comment= +Additionally, a fifo named `position` will also be created and will be updated +roughly once a second with the position and the total duration of the +currently playing track, space-separated. + + Thanks ------ Big thanks to David Hammerton for releasing an ALAC decoder, which is reproduced here in full. @@ -100,7 +105,7 @@ Contributors to version 1.x * [Peter Körner](http://mazdermind.de) * [Muffinman](http://github.com/therealmuffin) * [Skaman](http://github.com/skaman) -* [Weston](http://github.com/wnielson) +* [Weston Nielson](http://github.com/wnielson) * [allesblinkt](http://github.com/allesblinkt) Contributors to version 0.x