[xiph-commits] r12693 - in trunk/ezstream: . doc src
moritz at svn.xiph.org
moritz at svn.xiph.org
Thu Mar 8 18:30:34 PST 2007
Author: moritz
Date: 2007-03-08 18:30:29 -0800 (Thu, 08 Mar 2007)
New Revision: 12693
Modified:
trunk/ezstream/NEWS
trunk/ezstream/doc/ezstream.1.in
trunk/ezstream/src/configfile.c
trunk/ezstream/src/configfile.h
trunk/ezstream/src/ezstream.c
trunk/ezstream/src/metadata.c
trunk/ezstream/src/metadata.h
Log:
Add new <metadata_progname> configuration option, which specifies an external
program/script to get metadata from. Also include SIGUSR2 handling that triggers
metadata updates from the external program mid-stream.
Modified: trunk/ezstream/NEWS
===================================================================
--- trunk/ezstream/NEWS 2007-03-09 02:24:04 UTC (rev 12692)
+++ trunk/ezstream/NEWS 2007-03-09 02:30:29 UTC (rev 12693)
@@ -19,6 +19,12 @@
* various:
- [ADD] Allow ezstream to use TagLib for reading metadata from media
files. TagLib (libtag_c) is now an optional dependency.
+ - [ADD] New <metadata_progname> configuration option, which causes
+ metadata to be read from the output of an external program or
+ script.
+ - [ADD] New runtime control via the SIGUSR2 signal, which triggers reading
+ of fresh metadata information from <metadata_progname> (metadata
+ is always read at song changes.)
Modified: trunk/ezstream/doc/ezstream.1.in
===================================================================
--- trunk/ezstream/doc/ezstream.1.in 2007-03-09 02:24:04 UTC (rev 12692)
+++ trunk/ezstream/doc/ezstream.1.in 2007-03-09 02:30:29 UTC (rev 12693)
@@ -8,7 +8,7 @@
.Os
.Sh NAME
.Nm ezstream
-.Nd source client for Icecast with external en-/decoder support
+.Nd source client for Icecast with external de-/encoder support
.Sh SYNOPSIS
.Nm
.Bk -words
@@ -59,8 +59,7 @@
.Ss Runtime control
On POSIX systems,
.Nm
-offers limited runtime control via signals when it is not streaming data from
-standard input.
+offers limited runtime control via signals.
By sending a signal to the ezstream process, e.g. with the
.Xr kill 1
utility, a certain action will be triggered.
@@ -74,6 +73,11 @@
.It Cd SIGUSR1
Skips the currently playing track and moves on to the next in playlist mode, or
restarts the current track when streaming a single file.
+.It Cd SIGUSR2
+Triggers rereading of metadata for the stream by running the program or script
+specified in \&<metadata_progname/\&>
+.Pq see below.
+This is the only meaningful signal when streaming from standard input.
.El
.Pp
.Sh CONFIGURATION FILE SYNTAX
@@ -103,7 +107,7 @@
.Em start tag + content + end tag ,
like in the introductory example shown above.
.Ss Root element
-.Bl -ohang
+.Bl -tag -width -Ds
.It Sy \&<ezstream\ /\&>
.Pq Mandatory.
The configuration file's root element.
@@ -112,7 +116,7 @@
.Ss Global configuration elements
Each of the global configuration elements have the \&<ezstream/\&> element as
their parent.
-.Bl -ohang
+.Bl -tag -width -Ds
.It Sy \&<url\ /\&>
.Pq Mandatory.
Specifies the location and mountpoint of the Icecast server, to which the
@@ -170,10 +174,6 @@
.Pq one
to indicate that the file in \&<filename/\&> is actually an executable program
or script.
-This program is supposed to print
-.Pq to standard output
-one line with the name of a file that should be streamed next and then exit.
-.Pp
If set to
.Sy 0
.Pq zero ,
@@ -181,6 +181,10 @@
keyword
.Pa stdin
.Pq the default .
+.Pp
+See the
+.Sy SCRIPTING
+section for details on how the playlist program must behave.
.It Sy \&<shuffle\ /\&>
.Pq Optional.
Set to
@@ -193,6 +197,24 @@
or when the \&<shuffle/\&> element is absent.
This option will be ignored if \&<playlist_program/\&> is set to 1
.Pq one.
+.It Sy \&<metadata_progname\ /\&>
+.Pq Optional.
+Set the path and name of an executable program or script that should be used by
+.Nm
+to set the metadata of the stream.
+The program is automatically queried when a new track is streamed, or whenever
+the
+.Sy SIGUSR2
+signal is received.
+.Pp
+If the \&<metadata_progname/\&> element is present in the configuration, no
+attempts will be made to read metadata from files that are being streamed.
+If this behavior is not desired, it should be removed or commented out in the
+configuration file.
+.Pp
+See the
+.Sy SCRIPTING
+section for details on how the metadata program must behave.
.It Sy \&<stream_once\ /\&>
Set to
.Sy 1
@@ -268,7 +290,7 @@
.Ss Reencoding settings
Each of the reencoding configuration elements have the \&<reencode/\&>
element as their parent.
-.Bl -ohang
+.Bl -tag -width -Ds
.It Sy \&<enable\ /\&>
Set to
.Sy 1
@@ -287,7 +309,7 @@
.Ss Decoder/Encoder settings
Each of the decoder/encoder configuration elements have the \&<encdec/\&>
element as their parent.
-.Bl -ohang
+.Bl -tag -width -Ds
.It Sy \&<format\ /\&>
This element is used by
.Nm
@@ -353,6 +375,54 @@
.Pp
.Dl \&<encode\&>oggenc -r -q 1.5 -t \&"@M@\&" -\&</encode\&>
.El
+.Sh SCRIPTING
+The
+.Nm
+utility provides hooks for externally controlled playlist and metadata
+management.
+This is done by running external programs or scripts that need to behave in
+ways explained here.
+.Ss Common Rules
+.Bl -dash -compact
+.It
+The program must be an executable file.
+.It
+The program must write one line to standard output and exit.
+.It
+The program must not require arbitary command line options to function.
+A wrapper script must be used if there is no other way.
+.El
+.Ss Playlist Programs
+.Bl -dash -compact
+.It
+The program must return only filenames, with one filename per execution.
+.It
+The program should not return an empty line unless
+.Nm
+is supposed to know that the end of the playlist has been reached.
+This is significant when the \&<stream_once/\&> option is enabled.
+.El
+.Ss Metadata Programs
+.Bl -dash -compact
+.It
+The program must not return anything (just a newline character is okay) if it
+is called by
+.Nm
+with a command line parameter that the program does not support.
+.It
+When called without command line parameters, the program should return a
+complete string that should be used for metadata.
+.It
+When called with the command line parameter
+.Qq artist ,
+the program should return only the artist information of the metadata.
+.Pq Optional.
+.It
+When called with the command line parameter
+.Qq title ,
+the program should return only the title information of the metadata.
+.Pq Optional.
+.El
.Sh FILES
.Bl -tag -width "!!EXAMPLES_DIR!!" -compact
.It Pa !!EXAMPLES_DIR!!
Modified: trunk/ezstream/src/configfile.c
===================================================================
--- trunk/ezstream/src/configfile.c 2007-03-09 02:24:04 UTC (rev 12692)
+++ trunk/ezstream/src/configfile.c 2007-03-09 02:30:29 UTC (rev 12693)
@@ -98,6 +98,8 @@
xfree(cfg->format);
if (cfg->fileName != NULL)
xfree(cfg->fileName);
+ if (cfg->metadataProgram != NULL)
+ xfree(cfg->metadataProgram);
if (cfg->serverName != NULL)
xfree(cfg->serverName);
if (cfg->serverURL != NULL)
@@ -228,6 +230,25 @@
xmlFree(ls_xmlContentPtr);
}
}
+ if (!xmlStrcmp(cur->name, BAD_CAST "metadata_progname")) {
+ if (ezConfig.metadataProgram != NULL) {
+ printf("%s[%ld]: Error: Cannot have multiple <metadata_progname> elements\n",
+ fileName, xmlGetLineNo(cur));
+ config_error++;
+ continue;
+ }
+ if (cur->xmlChildrenNode != NULL) {
+ ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+ if (strlen(ls_xmlContentPtr) > PATH_MAX - 1) {
+ printf("%s[%ld]: Error: Path or filename in <metadata_progname> is too long\n",
+ fileName, xmlGetLineNo(cur));
+ config_error++;
+ continue;
+ }
+ ezConfig.metadataProgram = xstrdup(ls_xmlContentPtr);
+ xmlFree(ls_xmlContentPtr);
+ }
+ }
if (!xmlStrcmp(cur->name, BAD_CAST "playlist_program")) {
if (program_set) {
printf("%s[%ld]: Error: Cannot have multiple <playlist_program> elements\n",
Modified: trunk/ezstream/src/configfile.h
===================================================================
--- trunk/ezstream/src/configfile.h 2007-03-09 02:24:04 UTC (rev 12692)
+++ trunk/ezstream/src/configfile.h 2007-03-09 02:30:29 UTC (rev 12693)
@@ -44,6 +44,7 @@
char *password;
char *format;
char *fileName;
+ char *metadataProgram;
char *serverName;
char *serverURL;
char *serverGenre;
Modified: trunk/ezstream/src/ezstream.c
===================================================================
--- trunk/ezstream/src/ezstream.c 2007-03-09 02:24:04 UTC (rev 12692)
+++ trunk/ezstream/src/ezstream.c 2007-03-09 02:30:29 UTC (rev 12693)
@@ -69,6 +69,7 @@
#define STREAM_CONT 1
#define STREAM_SKIP 2
#define STREAM_SERVERR 3
+#define STREAM_UPDMDATA 4
#ifdef HAVE___PROGNAME
extern char *__progname;
@@ -78,6 +79,7 @@
int qFlag;
int vFlag;
+int metadataFromProgram;
EZCONFIG *pezConfig = NULL;
static const char *blankString = "";
@@ -85,15 +87,17 @@
int playlistMode = 0;
#ifdef HAVE_SIGNALS
-const int ezstream_signals[] = { SIGHUP, SIGUSR1 };
+const int ezstream_signals[] = { SIGHUP, SIGUSR1, SIGUSR2 };
volatile sig_atomic_t rereadPlaylist = 0;
volatile sig_atomic_t rereadPlaylist_notify = 0;
volatile sig_atomic_t skipTrack = 0;
+volatile sig_atomic_t queryMetadata = 0;
#else
int rereadPlaylist = 0;
int rereadPlaylist_notify = 0;
int skipTrack = 0;
+int queryMetadata = 0;
#endif /* HAVE_SIGNALS */
typedef struct tag_ID3Tag {
@@ -133,6 +137,9 @@
case SIGUSR1:
skipTrack = 1;
break;
+ case SIGUSR2:
+ queryMetadata = 1;
+ break;
default:
break;
}
@@ -291,16 +298,26 @@
shout_metadata_t *shout_mdata = NULL;
metadata_t *mdata = NULL;
- if ((mdata = metadata_file(fileName)) == NULL) {
- songInfo = xstrdup(blankString);
- return (songInfo);
+ if (metadataFromProgram) {
+ if ((mdata = metadata_program(fileName)) == NULL)
+ return (NULL);
+
+ if (!metadata_program_update(mdata, METADATA_STRING)) {
+ metadata_free(&mdata);
+ songInfo = xstrdup(blankString);
+ return (songInfo);
+ }
+ } else {
+ if ((mdata = metadata_file(fileName)) == NULL)
+ return (NULL);
+
+ if (!metadata_file_update(mdata)) {
+ metadata_free(&mdata);
+ songInfo = xstrdup(blankString);
+ return (songInfo);
+ }
}
- if (!metadata_file_update(mdata)) {
- metadata_free(&mdata);
- songInfo = xstrdup(blankString);
- return (songInfo);
- }
songInfo = xstrdup(metadata_get_string(mdata));
metadata_free(&mdata);
@@ -327,6 +344,10 @@
char *pCommandString = NULL;
if (strcmp(fileName, "stdin") == 0) {
+ if (metadataFromProgram &&
+ processMetadata(shout, pezConfig->metadataProgram) == NULL)
+ return (filep);
+
if (vFlag)
printf("%s: Reading from standard input\n",
__progname);
@@ -354,7 +375,12 @@
return (filep);
}
- pMetadata = processMetadata(shout, fileName);
+ if (metadataFromProgram)
+ pMetadata = processMetadata(shout, pezConfig->metadataProgram);
+ else
+ pMetadata = processMetadata(shout, fileName);
+ if (pMetadata == NULL)
+ return (filep);
if (metaCopy != NULL)
*metaCopy = xstrdup(pMetadata);
@@ -487,6 +513,19 @@
break;
}
+ shout_sync(shout);
+
+ if (shout_send(shout, buff, read) != SHOUTERR_SUCCESS) {
+ printf("%s: shout_send(): %s\n", __progname,
+ shout_get_error(shout));
+ if (reconnectServer(shout, 1))
+ break;
+ else {
+ ret = STREAM_SERVERR;
+ break;
+ }
+ }
+
if (rereadPlaylist_notify) {
rereadPlaylist_notify = 0;
if (!pezConfig->fileNameIsProgram)
@@ -498,17 +537,11 @@
ret = STREAM_SKIP;
break;
}
-
- shout_sync(shout);
-
- if (shout_send(shout, buff, read) != SHOUTERR_SUCCESS) {
- printf("%s: shout_send(): %s\n", __progname,
- shout_get_error(shout));
- if (reconnectServer(shout, 1))
+ if (queryMetadata) {
+ queryMetadata = 0;
+ if (metadataFromProgram) {
+ ret = STREAM_UPDMDATA;
break;
- else {
- ret = STREAM_SERVERR;
- break;
}
}
@@ -609,10 +642,15 @@
ret = sendStream(shout, filepstream, fileName, isStdin, NULL);
#endif
if (ret != STREAM_DONE) {
- if (skipTrack && rereadPlaylist) {
+ if ((skipTrack && rereadPlaylist) ||
+ (skipTrack && queryMetadata)) {
skipTrack = 0;
- ret = 1;
+ ret = STREAM_CONT;
}
+ if (queryMetadata && rereadPlaylist) {
+ queryMetadata = 0;
+ ret = STREAM_CONT;
+ }
if (ret == STREAM_SKIP || skipTrack) {
skipTrack = 0;
if (!isStdin && vFlag)
@@ -621,13 +659,30 @@
retval = 1;
ret = STREAM_DONE;
}
+ if (ret == STREAM_UPDMDATA || queryMetadata) {
+ queryMetadata = 0;
+ if (metadataFromProgram) {
+ char *mdataStr;
+
+ if (vFlag > 1)
+ printf("%s: Querying '%s' for fresh metadata\n",
+ __progname, pezConfig->metadataProgram);
+ if ((mdataStr = processMetadata(shout, pezConfig->metadataProgram)) == NULL) {
+ retval = 0;
+ ret = STREAM_DONE;
+ }
+ printf("%s: New metadata: ``%s''\n",
+ __progname, mdataStr);
+ xfree(mdataStr);
+ }
+ }
if (ret == STREAM_SERVERR) {
retval = 0;
ret = STREAM_DONE;
}
} else
retval = 1;
- } while (ret);
+ } while (ret != STREAM_DONE);
if (popenFlag)
pclose(filepstream);
@@ -683,10 +738,7 @@
}
}
- if (pezConfig->streamOnce)
- return (0);
- else
- return (1);
+ return (1);
}
/* Borrowed from OpenNTPd-portable's compat-openbsd/bsd-misc.c */
@@ -976,6 +1028,11 @@
return (1);
}
+ if (pezConfig->metadataProgram != NULL)
+ metadataFromProgram = 1;
+ else
+ metadataFromProgram = 0;
+
#ifdef HAVE_SIGNALS
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
Modified: trunk/ezstream/src/metadata.c
===================================================================
--- trunk/ezstream/src/metadata.c 2007-03-09 02:24:04 UTC (rev 12692)
+++ trunk/ezstream/src/metadata.c 2007-03-09 02:30:29 UTC (rev 12693)
@@ -352,6 +352,11 @@
metadata_program(const char *program)
{
metadata_t *md;
+#ifdef HAVE_STAT
+ struct stat st;
+#else
+ FILE *filep;
+#endif
if (program == NULL || strlen(program) == 0) {
printf("%s: metadata_program(): Internal error: Bad arguments\n",
@@ -361,7 +366,28 @@
md = metadata_create(program);
md->program = 1;
+ md->string = xstrdup("");
+#ifdef HAVE_STAT
+ if (stat(program, &st) == -1) {
+ printf("%s: %s: %s\n", __progname, program, strerror(errno));
+ metadata_free(&md);
+ return (NULL);
+ }
+ if (!(st.st_mode & (S_IEXEC | S_IXGRP | S_IXOTH))) {
+ printf("%s: %s: Not an executable program\n", __progname, program);
+ metadata_free(&md);
+ return (NULL);
+ }
+#else
+ if ((filep = fopen(program, "r")) == NULL) {
+ printf("%s: %s: %s\n", __progname, program, strerror(errno));
+ metadata_free(&md);
+ return (NULL);
+ }
+ fclose(filep);
+#endif /* HAVE_STAT */
+
return (md);
}
@@ -418,8 +444,110 @@
int
metadata_program_update(metadata_t *md, enum metadata_request md_req)
{
- /* XXX not implemented */
- return (0);
+ FILE *filep;
+ char buf[METADATA_MAX + 1];
+ char command[PATH_MAX + strlen(" artist") + 1];
+
+ if (md == NULL) {
+ printf("%s: metadata_program_update(): Internal error: NULL argument\n",
+ __progname);
+ abort();
+ }
+
+ if (!md->program) {
+ printf("%s: metadata_program_update(): Internal error: Received file handle\n",
+ __progname);
+ abort();
+ }
+
+ switch (md_req) {
+ case METADATA_ALL:
+ metadata_clean_md(md);
+ if (!metadata_program_update(md, METADATA_STRING) ||
+ !metadata_program_update(md, METADATA_ARTIST) ||
+ !metadata_program_update(md, METADATA_TITLE))
+ return (0);
+ break;
+ case METADATA_STRING:
+ strlcpy(command, md->filename, sizeof(command));
+ if (md->string != NULL)
+ xfree(md->string);
+ break;
+ case METADATA_ARTIST:
+ snprintf(command, sizeof(command), "%s artist", md->filename);
+ if (md->artist != NULL)
+ xfree(md->artist);
+ break;
+ case METADATA_TITLE:
+ snprintf(command, sizeof(command), "%s title", md->filename);
+ if (md->title != NULL)
+ xfree(md->title);
+ break;
+ default:
+ printf("%s: metadata_program_update(): Internal error: Unknown md_req\n",
+ __progname);
+ abort();
+ }
+
+ fflush(NULL);
+ errno = 0;
+ if ((filep = popen(command, "r")) == NULL) {
+ printf("%s: playlist_run_program(): Error while executing '%s'",
+ __progname, command);
+ /* popen() does not set errno reliably ... */
+ if (errno)
+ printf(": %s\n", strerror(errno));
+ else
+ printf("\n");
+ return (0);
+ }
+
+ if (fgets(buf, sizeof(buf), filep) == NULL) {
+ if (ferror(filep))
+ printf("%s: Error while reading output from program '%s': %s\n",
+ __progname, md->filename, strerror(errno));
+ pclose(filep);
+ printf("%s: FATAL: External program '%s' not (or no longer) usable.\n",
+ __progname, md->filename);
+ exit(1);
+ }
+
+ pclose(filep);
+
+ if (strlen(buf) == sizeof(buf) - 1)
+ printf("%s: Warning: Metadata string received via '%s' is too long and has been truncated\n",
+ __progname, command);
+
+ if (buf[0] != '\0' && buf[strlen(buf) - 1] == '\n')
+ buf[strlen(buf) - 1] = '\0';
+ if (buf[0] != '\0' && buf[strlen(buf) - 1] == '\r')
+ buf[strlen(buf) - 1] = '\0';
+
+ switch (md_req) {
+ case METADATA_STRING:
+ if (strlen(buf) == 0) {
+ printf("%s: Warning: Empty metadata string received from '%s'\n",
+ __progname, md->filename);
+ md->string = xstrdup("");
+ } else
+ md->string = xstrdup(buf);
+ break;
+ case METADATA_ARTIST:
+ if (strlen(buf) > 0)
+ md->artist = xstrdup(buf);
+ break;
+ case METADATA_TITLE:
+ if (strlen(buf) > 0)
+ md->title = xstrdup(buf);
+ break;
+ case METADATA_ALL:
+ default:
+ printf("%s: metadata_program_update(): Internal error: METADATA_ALL in code unreachable by METADATA_ALL\n",
+ __progname);
+ abort();
+ }
+
+ return (1);
}
const char *
Modified: trunk/ezstream/src/metadata.h
===================================================================
--- trunk/ezstream/src/metadata.h 2007-03-09 02:24:04 UTC (rev 12692)
+++ trunk/ezstream/src/metadata.h 2007-03-09 02:30:29 UTC (rev 12693)
@@ -45,8 +45,7 @@
* - Accept no command line parameter and return a complete metadata string
* (for metadata_get_string()). The program *should* always return
* something in this case (e.g. something based on the filename in case no
- * metadata is available.) This string will default to "[unknown]"
- * otherwise.
+ * metadata is available.)
* - Accept the command line parameter "artist" and return only the artist
* metadata, or an empty string if no artist information is available.
* - Accept the command line parameter "title" and return only the song title
More information about the commits
mailing list