1
0
mirror of https://gitlab.xiph.org/xiph/ezstream.git synced 2024-11-03 04:17:18 -05:00

Add new playlist scripting feature (works similar to Ices 2.x.)

git-svn-id: https://svn.xiph.org/trunk/ezstream@12591 0101bb08-14d6-0310-b084-bc0e0c8e3800
This commit is contained in:
moritz 2007-02-28 21:26:16 +00:00
parent 1712f4f88f
commit 7d51c20fb8
9 changed files with 266 additions and 65 deletions

5
NEWS
View File

@ -6,6 +6,9 @@ Changes in 0.3.0, (SVN trunk):
* New features:
- Playlist shuffling support, enabled via the new <shuffle> configuration
option.
- Playlist scripting support: Indicate that the executable in <filename>
should be run each time to get a new media filename to stream, by setting
the new <playlist_program> configuration option to 1.
- Add feature to skip the currently streaming track, done by sending the
SIGUSR1 signal to the ezstream process.
- New command line option `-q': Suppress standard error output from external
@ -15,6 +18,8 @@ Changes in 0.3.0, (SVN trunk):
on the command line.
- Thorough configuration file checks with helpful error messages.
- The @M@ metadata placeholder is now supported in <decode>.
- Playlists may now have the '.txt' filename extension in addition to
'.m3u'.
* Fixes:
- At least one stack and one heap overflow have been fixed.

View File

@ -1,5 +1,5 @@
<!--
EXAMPLE: MP3 playlist stream WITH reencoding and sequential playback
EXAMPLE: MP3 stream using an external playlist program, WITH reencoding
This example streams a playlist that may contain MP3, Ogg Vorbis and FLAC
files. Ezstream will use external decoders to read the media files, and
@ -14,12 +14,12 @@
output format of the stream.
-->
<format>MP3</format>
<filename>playlist.m3u</filename>
<filename>playlist.pl</filename>
<!--
Explicitly disable playlist shuffling. Sequential playback is also the
default.
Indicate that <filename> contains an executable program or script,
which prints a single line with a filename to standard output:
-->
<shuffle>0</shuffle>
<playlist_program>1</playlist_program>
<!--
The following settings are used to describe your stream to the server.
It's up to you to make sure that the bitrate/samplerate/channels

View File

@ -17,6 +17,11 @@
<filename>playlist.m3u</filename>
<!-- Enable playlist shuffling: -->
<shuffle>1</shuffle>
<!--
The file in <filename> is a regular playlist and not a program.
For demonstrational purposes, explicitly state this here:
-->
<playlist_program>0</playlist_program>
<!--
The following settings are used to describe your stream to the server.
It's up to you to make sure that the bitrate/quality/samplerate/channels

View File

@ -106,7 +106,7 @@ The configuration file's root element.
It contains all other configuration elements.
.El
.Ss Global configuration elements
Each of the global configuration elements have the \&<ezstream\ /\&> element as
Each of the global configuration elements have the \&<ezstream/\&> element as
their parent.
.Bl -ohang
.It Sy \&<url\ /\&>
@ -143,7 +143,10 @@ Other values will be ignored and cause
to simply pass through the data, which may or may not work.
.It Sy \&<filename\ /\&>
.Pq Mandatory.
Set the path and name of a single media file, a playlist or the keyword
Set the path and name of a single media file, a playlist, the name of an
external program
.Pq see below ,
or the keyword
.Pa stdin
for streaming from standard input.
Playlists are recognized by their filename extension and end with either
@ -156,16 +159,24 @@ Comments in playlists are introduced by a
.Sq Li #
sign at the beginning of a line and ignored by
.Nm .
.It Sy \&<playlist_program\ /\&>
Indicates 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.
.It Sy \&<shuffle\ /\&>
.Pq Optional.
Set to
.Sy 1
.Pq one
to randomly shuffle the entries of the playlist specified in \&<filename\ /\&>.
to randomly shuffle the entries of the playlist specified in \&<filename/\&>.
Files are played sequentially if set to
.Sy 0
.Pq zero
or when the \&<shuffle\ /\&> element is absent.
or when the \&<shuffle/\&> element is absent.
This option will be ignored if \&<playlist_program/\&> is set to 1
.Pq one.
.It Sy \&<svrinfoname\ /\&>
.Pq Optional.
Set the name of the broadcast.
@ -216,14 +227,14 @@ If set to
.Sy 0
.Pq zero ,
the Icecast server will not submit this stream to a YP directory, which is also
the default if the \&<svrinfopublic\ /\&> element is absent.
the default if the \&<svrinfopublic/\&> element is absent.
.It Sy \&<reencode\ /\&>
.Pq Optional.
Element that contains child elements, which specify if and how reencoding
should be done.
.El
.Ss Reencoding settings
Each of the reencoding configuration elements have the \&<reencode\ /\&>
Each of the reencoding configuration elements have the \&<reencode/\&>
element as their parent.
.Bl -ohang
.It Sy \&<enable\ /\&>
@ -234,22 +245,22 @@ to enable reencoding.
If set to
.Sy 0
.Pq zero ,
no reencoding will be done, which is also the default if the \&<enable\ /\&>
no reencoding will be done, which is also the default if the \&<enable/\&>
element is absent.
.It Sy \&<encdec\ /\&>
Element that contains child elements, which specify how to decode and encode
a certain media file format for streaming.
Each format is described by a separate \&<encdec\ /\&> element.
Each format is described by a separate \&<encdec/\&> element.
.El
.Ss Dencoder/Encoder settings
Each of the decoder/encoder configuration elements have the \&<encdec\ /\&>
Each of the decoder/encoder configuration elements have the \&<encdec/\&>
element as their parent.
.Bl -ohang
.It Sy \&<format\ /\&>
This element is used by
.Nm
to find the appropriate encoder for the output stream format specified in the
\&<format\ /\&> element inside the global configuration.
\&<format/\&> element inside the global configuration.
It is recommended that this element is always supplied, even for currently
unsupported output formats, with content such as
.Sy VORBIS ,
@ -275,13 +286,13 @@ it to standard output.
During runtime, the placeholder
.Sq Li @T@
is replaced with the fully qualified name of the media file, as specified in
the \&<filename\ /\&> element or a playlist file.
the \&<filename/\&> element or a playlist file.
It should always be enclosed in quotes, to prevent problems with filenames that
contain whitespaces.
.Pp
The metadata placeholder,
.Sq @M@ ,
is also available in the \&<decode\ /\&> element.
is also available in the \&<decode/\&> element.
That way it can be used for combined de-/encoder programs that produce readily
streamable data.
.Pp

View File

@ -140,7 +140,7 @@ parseConfig(const char *fileName)
xmlDocPtr doc;
xmlNodePtr cur;
char *ls_xmlContentPtr;
int shuffle_set, svrinfopublic_set;
int shuffle_set, svrinfopublic_set, program_set;
xmlLineNumbersDefault(1);
if ((doc = xmlParseFile(fileName)) == NULL) {
@ -160,6 +160,7 @@ parseConfig(const char *fileName)
shuffle_set = 0;
svrinfopublic_set = 0;
program_set = 0;
cur = cur->xmlChildrenNode;
while (cur != NULL) {
if (!xmlStrcmp(cur->name, BAD_CAST "url")) {
@ -219,6 +220,22 @@ parseConfig(const char *fileName)
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",
fileName, xmlGetLineNo(cur));
goto config_error;
}
if (cur->xmlChildrenNode != NULL) {
int tmp;
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
tmp = atoi(ls_xmlContentPtr);
ezConfig.fileNameIsProgram = (tmp == 0) ? 0 : 1;
xmlFree(ls_xmlContentPtr);
program_set = 1;
}
}
if (!xmlStrcmp(cur->name, BAD_CAST "shuffle")) {
if (shuffle_set) {
printf("%s[%ld]: Error: Cannot have multiple <shuffle> elements.\n",

View File

@ -57,6 +57,7 @@ typedef struct tag_EZCONFIG {
FORMAT_ENCDEC *encoderDecoders[MAX_FORMAT_ENCDEC];
int numEncoderDecoders;
int shuffle;
int fileNameIsProgram;
} EZCONFIG;
EZCONFIG * getEZConfig(void);

View File

@ -550,7 +550,7 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
{
unsigned char buff[4096];
size_t read, total, oldTotal;
int retval = 0;
int ret = 0;
#ifdef HAVE_GETTIMEOFDAY
double kbps = -1.0;
struct timeval timeStamp, *startTime = (struct timeval *)tv;
@ -567,21 +567,21 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
total = oldTotal = 0;
while ((read = fread(buff, 1, sizeof(buff), filepstream)) > 0) {
int ret;
if (rereadPlaylist_notify) {
rereadPlaylist_notify = 0;
printf("%s: SIGHUP signal received, will reread playlist after this file.\n",
__progname);
if (!pezConfig->fileNameIsProgram)
printf("%s: SIGHUP signal received, will reread playlist after this file.\n",
__progname);
}
if (skipTrack) {
skipTrack = 0;
retval = 2;
ret = 2;
break;
}
ret = shout_send(shout, buff, read);
if (ret != SHOUTERR_SUCCESS) {
shout_sync(shout);
if (shout_send(shout, buff, read) != SHOUTERR_SUCCESS) {
printf("%s: shout_send(): %s\n", __progname,
shout_get_error(shout));
while (1) {
@ -591,13 +591,11 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
if (shout_open(shout) == SHOUTERR_SUCCESS) {
printf("%s: Reconnect to server successful.\n",
__progname);
ret = shout_send(shout, buff, read);
if (ret != SHOUTERR_SUCCESS)
printf("%s: shout_send(): %s\n",
__progname,
shout_get_error(shout));
else
if (shout_send(shout, buff, read) == SHOUTERR_SUCCESS)
break;
printf("%s: shout_send(): %s\n",
__progname,
shout_get_error(shout));
} else {
printf("%s: Reconnect failed. Waiting 5 seconds ...\n",
__progname);
@ -610,8 +608,6 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
}
}
shout_sync(shout);
total += read;
if (qFlag && vFlag) {
#ifdef HAVE_GETTIMEOFDAY
@ -620,10 +616,17 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
unsigned int hrs, mins, secs;
#endif /* HAVE_GETTIMEOFDAY */
if (!isStdin && playlistMode)
printf(" [%4lu/%-4lu]",
playlist_get_position(playlist),
playlist_get_num_items(playlist));
if (!isStdin && playlistMode) {
if (pezConfig->fileNameIsProgram) {
char *tmp = xstrdup(pezConfig->fileName);
printf(" [%s]",
basename(tmp));
xfree(tmp);
} else
printf(" [%4lu/%-4lu]",
playlist_get_position(playlist),
playlist_get_num_items(playlist));
}
#ifdef HAVE_GETTIMEOFDAY
oldTime = (double)timeStamp.tv_sec
@ -656,13 +659,13 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
if (ferror(filepstream)) {
if (errno == EINTR) {
clearerr(filepstream);
retval = 1;
ret = 1;
} else
printf("%s: streamFile(): Error while reading '%s': %s\n",
__progname, fileName, strerror(errno));
}
return (retval);
return (ret);
}
int
@ -732,17 +735,23 @@ streamPlaylist(shout_t *shout, const char *fileName)
const char *song;
char lastSong[PATH_MAX + 1];
/*
* XXX: This preserves traditional behavior, however, rereading the
* playlist after each walkthrough seems a bit more logical.
*/
if (playlist == NULL) {
if ((playlist = playlist_read(fileName)) == NULL)
return (0);
if (pezConfig->fileNameIsProgram) {
if ((playlist = playlist_program(fileName)) == NULL)
return (0);
} else {
if ((playlist = playlist_read(fileName)) == NULL)
return (0);
}
} else
/*
* XXX: This preserves traditional behavior, however,
* rereading the playlist after each walkthrough seems a
* bit more logical.
*/
playlist_rewind(playlist);
if (pezConfig->shuffle)
if (!pezConfig->fileNameIsProgram && pezConfig->shuffle)
playlist_shuffle(playlist);
while ((song = playlist_get_next(playlist)) != NULL) {
@ -751,6 +760,8 @@ streamPlaylist(shout_t *shout, const char *fileName)
return (0);
if (rereadPlaylist) {
rereadPlaylist = rereadPlaylist_notify = 0;
if (pezConfig->fileNameIsProgram)
continue;
printf("%s: Rereading playlist\n", __progname);
if (!playlist_reread(&playlist))
return (0);
@ -1095,13 +1106,18 @@ main(int argc, char *argv[])
tmpFileName = xstrdup(pezConfig->fileName);
for (p = tmpFileName; *p != '\0'; p++)
*p = tolower((int)*p);
if (strrcmp(tmpFileName, ".m3u") == 0 ||
if (pezConfig->fileNameIsProgram ||
strrcmp(tmpFileName, ".m3u") == 0 ||
strrcmp(tmpFileName, ".txt") == 0)
playlistMode = 1;
else
playlistMode = 0;
xfree(tmpFileName);
if (vFlag && pezConfig->fileNameIsProgram)
printf("%s: Using program '%s' to get filenames for streaming\n",
__progname, pezConfig->fileName);
ret = 1;
do {
if (playlistMode)
@ -1114,6 +1130,9 @@ main(int argc, char *argv[])
printf("%s: Connection to http://%s:%d%s failed: %s\n", __progname,
host, port, mount, shout_get_error(shout));
if (vFlag)
printf("%s: Exiting ...\n", __progname);
shout_close(shout);
playlist_free(playlist);

View File

@ -21,6 +21,9 @@
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <errno.h>
#include <limits.h>
#include <stdio.h>
@ -33,6 +36,9 @@
#ifdef WIN32
# define snprintf _snprintf
# define popen _popen
# define pclose _pclose
# define stat _stat
#endif
#ifndef SIZE_T_MAX
@ -47,21 +53,24 @@ extern int errno;
extern char *__progname;
struct playlist {
char *filename;
char **list;
size_t size;
size_t num;
size_t index;
char *filename;
char **list;
size_t size;
size_t num;
size_t index;
int program;
char *prog_track;
};
playlist_t * playlist_create(const char *);
int playlist_add(playlist_t *, const char *);
unsigned int playlist_random(void);
const char * playlist_run_program(playlist_t *);
playlist_t *
playlist_create(const char *filename)
{
playlist_t *pl;
playlist_t *pl;
pl = xcalloc(1, sizeof(playlist_t));
pl->filename = xstrdup(filename);
@ -145,16 +154,15 @@ void playlist_shutdown(void) {}
playlist_t *
playlist_read(const char *filename)
{
playlist_t *pl;
unsigned long line;
FILE *filep;
char buf[PATH_MAX + 1];
playlist_t *pl;
unsigned long line;
FILE *filep;
char buf[PATH_MAX + 1];
pl = playlist_create(filename);
if ((filep = fopen(filename, "r")) == NULL) {
printf("%s: playlist_read(): %s: %s\n", __progname, filename,
strerror(errno));
printf("%s: %s: %s\n", __progname, filename, strerror(errno));
playlist_free(pl);
return (NULL);
}
@ -213,6 +221,42 @@ playlist_read(const char *filename)
return (pl);
}
playlist_t *
playlist_program(const char *filename)
{
playlist_t *pl;
#ifdef HAVE_STAT
struct stat st;
#else
FILE *filep;
#endif
pl = playlist_create(filename);
pl->program = 1;
#ifdef HAVE_STAT
if (stat(filename, &st) == -1) {
printf("%s: %s: %s\n", __progname, filename, strerror(errno));
playlist_free(pl);
return (NULL);
}
if (!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
printf("%s: %s: Not an executable program\n", __progname, filename);
playlist_free(pl);
return (NULL);
}
#else
if ((filep = fopen(filename, "r")) == NULL) {
printf("%s: %s: %s\n", __progname, filename, strerror(errno));
playlist_free(pl);
return (NULL);
}
fclose(filep);
#endif /* HAVE_STAT */
return (pl);
}
void
playlist_free(playlist_t *pl)
{
@ -239,6 +283,9 @@ playlist_free(playlist_t *pl)
pl->list = NULL;
}
if (pl->prog_track != NULL)
xfree(pl->prog_track);
xfree(pl);
pl = NULL;
}
@ -253,6 +300,9 @@ playlist_get_next(playlist_t *pl)
exit(1);
}
if (pl->program)
return (playlist_run_program(pl));
return ((const char *)pl->list[pl->index++]);
}
@ -265,6 +315,9 @@ playlist_peek_next(playlist_t *pl)
exit(1);
}
if (pl->program)
return (NULL);
return ((const char *)pl->list[pl->index]);
}
@ -277,6 +330,9 @@ playlist_skip_next(playlist_t *pl)
exit(1);
}
if (pl->program)
return;
if (pl->list[pl->index] != NULL)
pl->index++;
}
@ -290,6 +346,9 @@ playlist_get_num_items(playlist_t *pl)
exit(1);
}
if (pl->program)
return (0);
return ((unsigned long)pl->num);
}
@ -302,6 +361,9 @@ playlist_get_position(playlist_t *pl)
exit(1);
}
if (pl->program)
return (0);
return ((unsigned long)pl->index);
}
@ -314,7 +376,7 @@ playlist_set_position(playlist_t *pl, unsigned long index)
exit(1);
}
if (index > pl->num - 1)
if (pl->program || index > pl->num - 1)
return (0);
pl->index = (size_t)index;
@ -333,7 +395,7 @@ playlist_goto_entry(playlist_t *pl, const char *entry)
exit(1);
}
if (pl->num == 0)
if (pl->program || pl->num == 0)
return (0);
for (i = 0; i < pl->num; i++) {
@ -355,6 +417,9 @@ playlist_rewind(playlist_t *pl)
exit(1);
}
if (pl->program)
return;
pl->index = 0;
}
@ -371,6 +436,9 @@ playlist_reread(playlist_t **plist)
pl = *plist;
if (pl->program)
return (0);
if ((new_pl = playlist_read(pl->filename)) == NULL)
return (0);
@ -386,8 +454,8 @@ playlist_reread(playlist_t **plist)
void
playlist_shuffle(playlist_t *pl)
{
unsigned long d, i, range;
char *temp;
unsigned long d, i, range;
char *temp;
if (pl == NULL) {
printf("%s: playlist_shuffle(): Internal error: NULL argument\n",
@ -395,7 +463,7 @@ playlist_shuffle(playlist_t *pl)
exit(1);
}
if (pl->num < 2)
if (pl->program || pl->num < 2)
return;
for (i = 0; i < pl->num; i++) {
@ -420,3 +488,66 @@ playlist_shuffle(playlist_t *pl)
pl->list[i] = temp;
}
}
const char *
playlist_run_program(playlist_t *pl)
{
FILE *filep;
char buf[PATH_MAX + 1];
if (pl == NULL) {
printf("%s: playlist_run_program(): Internal error: NULL argument\n",
__progname);
exit(1);
}
if (!pl->program)
return (NULL);
fflush(NULL);
errno = 0;
if ((filep = popen(pl->filename, "r")) == NULL) {
printf("%s: playlist_run_program(): Error while executing '%s'",
__progname, pl->filename);
/* popen() does not set errno reliably ... */
if (errno)
printf(": %s\n", strerror(errno));
else
printf("\n");
return (NULL);
}
if (fgets(buf, sizeof(buf), filep) == NULL) {
if (ferror(filep))
printf("%s: Error while reading output from program '%s': %s\n",
__progname, pl->filename, strerror(errno));
pclose(filep);
printf("%s: FATAL: External program '%s' not (or no longer) usable.\n",
__progname, pl->filename);
exit(1);
}
pclose(filep);
if (strlen(buf) == sizeof(buf) - 1) {
printf("%s: Output from program '%s' too long\n", __progname,
pl->filename);
return (NULL);
}
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';
if (buf[0] == '\0') {
printf("%s: Empty line received from program '%s'\n",
__progname, pl->filename);
return (NULL);
}
if (pl->prog_track != NULL)
xfree(pl->prog_track);
pl->prog_track = xstrdup(buf);
return ((const char *)pl->prog_track);
}

View File

@ -36,6 +36,13 @@ void playlist_shutdown(void);
*/
playlist_t * playlist_read(const char * /* filename */);
/*
* For each call to playlist_get_next(), the specified program is run. This
* program is supposed to print one line to standard output, containing the
* path and filename of a media file.
*/
playlist_t * playlist_program(const char * /* program name */);
/*
* Free all memory used by a playlist handler that was created with
* playlist_read().
@ -48,6 +55,11 @@ void playlist_free(playlist_t *);
*/
const char * playlist_get_next(playlist_t *);
/*
* The functions below work on playlist handlers obtained with playlist_read()
* and are no-ops (i.e. return failure) for playlists from playlist_program().
*/
/*
* Get the next item in the playlist without moving on to the following entry.
* Returns a NUL-terminated string of the next playlist entry, or NULL if the