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:
parent
1712f4f88f
commit
7d51c20fb8
5
NEWS
5
NEWS
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
165
src/playlist.c
165
src/playlist.c
@ -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);
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user