[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