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:
parent
1712f4f88f
commit
7d51c20fb8
5
NEWS
5
NEWS
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -57,6 +57,7 @@ typedef struct tag_EZCONFIG {
|
||||
FORMAT_ENCDEC *encoderDecoders[MAX_FORMAT_ENCDEC];
|
||||
int numEncoderDecoders;
|
||||
int shuffle;
|
||||
int fileNameIsProgram;
|
||||
} EZCONFIG;
|
||||
|
||||
EZCONFIG * getEZConfig(void);
|
||||
|
@ -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);
|
||||
|
165
src/playlist.c
165
src/playlist.c
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user