0
0
mirror of https://gitlab.xiph.org/xiph/ezstream.git synced 2025-06-30 22:18:27 -04: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: * New features:
- Playlist shuffling support, enabled via the new <shuffle> configuration - Playlist shuffling support, enabled via the new <shuffle> configuration
option. 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 - Add feature to skip the currently streaming track, done by sending the
SIGUSR1 signal to the ezstream process. SIGUSR1 signal to the ezstream process.
- New command line option `-q': Suppress standard error output from external - 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. on the command line.
- Thorough configuration file checks with helpful error messages. - Thorough configuration file checks with helpful error messages.
- The @M@ metadata placeholder is now supported in <decode>. - The @M@ metadata placeholder is now supported in <decode>.
- Playlists may now have the '.txt' filename extension in addition to
'.m3u'.
* Fixes: * Fixes:
- At least one stack and one heap overflow have been fixed. - 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 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 files. Ezstream will use external decoders to read the media files, and
@ -14,12 +14,12 @@
output format of the stream. output format of the stream.
--> -->
<format>MP3</format> <format>MP3</format>
<filename>playlist.m3u</filename> <filename>playlist.pl</filename>
<!-- <!--
Explicitly disable playlist shuffling. Sequential playback is also the Indicate that <filename> contains an executable program or script,
default. 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. 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 It's up to you to make sure that the bitrate/samplerate/channels

View File

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

View File

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

View File

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

View File

@ -21,6 +21,9 @@
#ifdef HAVE_SYS_TYPES_H #ifdef HAVE_SYS_TYPES_H
# include <sys/types.h> # include <sys/types.h>
#endif #endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
@ -33,6 +36,9 @@
#ifdef WIN32 #ifdef WIN32
# define snprintf _snprintf # define snprintf _snprintf
# define popen _popen
# define pclose _pclose
# define stat _stat
#endif #endif
#ifndef SIZE_T_MAX #ifndef SIZE_T_MAX
@ -47,21 +53,24 @@ extern int errno;
extern char *__progname; extern char *__progname;
struct playlist { struct playlist {
char *filename; char *filename;
char **list; char **list;
size_t size; size_t size;
size_t num; size_t num;
size_t index; size_t index;
int program;
char *prog_track;
}; };
playlist_t * playlist_create(const char *); playlist_t * playlist_create(const char *);
int playlist_add(playlist_t *, const char *); int playlist_add(playlist_t *, const char *);
unsigned int playlist_random(void); unsigned int playlist_random(void);
const char * playlist_run_program(playlist_t *);
playlist_t * playlist_t *
playlist_create(const char *filename) playlist_create(const char *filename)
{ {
playlist_t *pl; playlist_t *pl;
pl = xcalloc(1, sizeof(playlist_t)); pl = xcalloc(1, sizeof(playlist_t));
pl->filename = xstrdup(filename); pl->filename = xstrdup(filename);
@ -145,16 +154,15 @@ void playlist_shutdown(void) {}
playlist_t * playlist_t *
playlist_read(const char *filename) playlist_read(const char *filename)
{ {
playlist_t *pl; playlist_t *pl;
unsigned long line; unsigned long line;
FILE *filep; FILE *filep;
char buf[PATH_MAX + 1]; char buf[PATH_MAX + 1];
pl = playlist_create(filename); pl = playlist_create(filename);
if ((filep = fopen(filename, "r")) == NULL) { if ((filep = fopen(filename, "r")) == NULL) {
printf("%s: playlist_read(): %s: %s\n", __progname, filename, printf("%s: %s: %s\n", __progname, filename, strerror(errno));
strerror(errno));
playlist_free(pl); playlist_free(pl);
return (NULL); return (NULL);
} }
@ -213,6 +221,42 @@ playlist_read(const char *filename)
return (pl); 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 void
playlist_free(playlist_t *pl) playlist_free(playlist_t *pl)
{ {
@ -239,6 +283,9 @@ playlist_free(playlist_t *pl)
pl->list = NULL; pl->list = NULL;
} }
if (pl->prog_track != NULL)
xfree(pl->prog_track);
xfree(pl); xfree(pl);
pl = NULL; pl = NULL;
} }
@ -253,6 +300,9 @@ playlist_get_next(playlist_t *pl)
exit(1); exit(1);
} }
if (pl->program)
return (playlist_run_program(pl));
return ((const char *)pl->list[pl->index++]); return ((const char *)pl->list[pl->index++]);
} }
@ -265,6 +315,9 @@ playlist_peek_next(playlist_t *pl)
exit(1); exit(1);
} }
if (pl->program)
return (NULL);
return ((const char *)pl->list[pl->index]); return ((const char *)pl->list[pl->index]);
} }
@ -277,6 +330,9 @@ playlist_skip_next(playlist_t *pl)
exit(1); exit(1);
} }
if (pl->program)
return;
if (pl->list[pl->index] != NULL) if (pl->list[pl->index] != NULL)
pl->index++; pl->index++;
} }
@ -290,6 +346,9 @@ playlist_get_num_items(playlist_t *pl)
exit(1); exit(1);
} }
if (pl->program)
return (0);
return ((unsigned long)pl->num); return ((unsigned long)pl->num);
} }
@ -302,6 +361,9 @@ playlist_get_position(playlist_t *pl)
exit(1); exit(1);
} }
if (pl->program)
return (0);
return ((unsigned long)pl->index); return ((unsigned long)pl->index);
} }
@ -314,7 +376,7 @@ playlist_set_position(playlist_t *pl, unsigned long index)
exit(1); exit(1);
} }
if (index > pl->num - 1) if (pl->program || index > pl->num - 1)
return (0); return (0);
pl->index = (size_t)index; pl->index = (size_t)index;
@ -333,7 +395,7 @@ playlist_goto_entry(playlist_t *pl, const char *entry)
exit(1); exit(1);
} }
if (pl->num == 0) if (pl->program || pl->num == 0)
return (0); return (0);
for (i = 0; i < pl->num; i++) { for (i = 0; i < pl->num; i++) {
@ -355,6 +417,9 @@ playlist_rewind(playlist_t *pl)
exit(1); exit(1);
} }
if (pl->program)
return;
pl->index = 0; pl->index = 0;
} }
@ -371,6 +436,9 @@ playlist_reread(playlist_t **plist)
pl = *plist; pl = *plist;
if (pl->program)
return (0);
if ((new_pl = playlist_read(pl->filename)) == NULL) if ((new_pl = playlist_read(pl->filename)) == NULL)
return (0); return (0);
@ -386,8 +454,8 @@ playlist_reread(playlist_t **plist)
void void
playlist_shuffle(playlist_t *pl) playlist_shuffle(playlist_t *pl)
{ {
unsigned long d, i, range; unsigned long d, i, range;
char *temp; char *temp;
if (pl == NULL) { if (pl == NULL) {
printf("%s: playlist_shuffle(): Internal error: NULL argument\n", printf("%s: playlist_shuffle(): Internal error: NULL argument\n",
@ -395,7 +463,7 @@ playlist_shuffle(playlist_t *pl)
exit(1); exit(1);
} }
if (pl->num < 2) if (pl->program || pl->num < 2)
return; return;
for (i = 0; i < pl->num; i++) { for (i = 0; i < pl->num; i++) {
@ -420,3 +488,66 @@ playlist_shuffle(playlist_t *pl)
pl->list[i] = temp; 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 */); 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 * Free all memory used by a playlist handler that was created with
* playlist_read(). * playlist_read().
@ -48,6 +55,11 @@ void playlist_free(playlist_t *);
*/ */
const char * playlist_get_next(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. * 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 * Returns a NUL-terminated string of the next playlist entry, or NULL if the