1
0
mirror of https://gitlab.xiph.org/xiph/ezstream.git synced 2024-12-04 14:46:31 -05:00
ezstream/src/ezstream.c
2009-08-30 21:55:24 +00:00

1350 lines
33 KiB
C

/* $Id$ */
/*
* ezstream - source client for Icecast with external en-/decoder support
* Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski <oddsock@oddsock.org>
* Copyright (C) 2007, 2009 Moritz Grimm <mdgrimm@gmx.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "ezstream.h"
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <shout/shout.h>
#include "configfile.h"
#include "metadata.h"
#include "playlist.h"
#include "strfctns.h"
#include "util.h"
#include "xalloc.h"
#define STREAM_DONE 0
#define STREAM_CONT 1
#define STREAM_SKIP 2
#define STREAM_SERVERR 3
#define STREAM_UPDMDATA 4
#ifdef HAVE___PROGNAME
extern char *__progname;
#else
char *__progname;
#endif /* HAVE___PROGNAME */
int nFlag;
int qFlag;
int sFlag;
int vFlag;
int metadataFromProgram;
EZCONFIG *pezConfig = NULL;
playlist_t *playlist = NULL;
int playlistMode = 0;
unsigned int resource_errors = 0;
#ifdef HAVE_SIGNALS
const int ezstream_signals[] = {
SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2
};
volatile sig_atomic_t rereadPlaylist = 0;
volatile sig_atomic_t rereadPlaylist_notify = 0;
volatile sig_atomic_t skipTrack = 0;
volatile sig_atomic_t queryMetadata = 0;
volatile sig_atomic_t quit = 0;
#else
int rereadPlaylist = 0;
int rereadPlaylist_notify = 0;
int skipTrack = 0;
int queryMetadata = 0;
int quit = 0;
#endif /* HAVE_SIGNALS */
typedef struct tag_ID3Tag {
char tag[3];
char trackName[30];
char artistName[30];
char albumName[30];
char year[3];
char comment[30];
char genre;
} ID3Tag;
int urlParse(const char *, char **, unsigned short *, char **);
void replaceString(const char *, char *, size_t, const char *,
const char *);
char * buildCommandString(const char *, const char *, metadata_t *);
char * getMetadataString(const char *, metadata_t *);
metadata_t * getMetadata(const char *);
int setMetadata(shout_t *, metadata_t *, char **);
FILE * openResource(shout_t *, const char *, int *, metadata_t **,
int *, long *);
int reconnectServer(shout_t *, int);
const char * getTimeString(long);
int sendStream(shout_t *, FILE *, const char *, int, const char *,
struct timeval *);
int streamFile(shout_t *, const char *);
int streamPlaylist(shout_t *, const char *);
char * getProgname(const char *);
void usage(void);
void usageHelp(void);
int ez_shutdown(int);
#ifdef HAVE_SIGNALS
void sig_handler(int);
# ifndef SIG_IGN
# define SIG_IGN (void (*)(int))1
# endif /* !SIG_IGN */
void
sig_handler(int sig)
{
switch (sig) {
case SIGTERM:
case SIGINT:
quit = 1;
break;
case SIGHUP:
rereadPlaylist = 1;
rereadPlaylist_notify = 1;
break;
case SIGUSR1:
skipTrack = 1;
break;
case SIGUSR2:
queryMetadata = 1;
break;
default:
break;
}
}
#endif /* HAVE_SIGNALS */
int
urlParse(const char *url, char **hostname, unsigned short *port,
char **mountname)
{
const char *p1, *p2, *p3;
char tmpPort[6] = "";
size_t hostsiz, mountsiz;
const char *errstr;
if (hostname == NULL || port == NULL || mountname == NULL) {
printf("%s: urlParse(): Internal error: Bad arguments\n",
__progname);
exit(1);
}
if (strncmp(url, "http://", strlen("http://")) != 0) {
printf("%s: Error: Invalid <url>: Not an HTTP address\n",
__progname);
return (0);
}
p1 = url + strlen("http://");
p2 = strchr(p1, ':');
if (p2 == NULL) {
printf("%s: Error: Invalid <url>: Missing port\n",
__progname);
return (0);
}
hostsiz = (p2 - p1) + 1;
*hostname = xmalloc(hostsiz);
strlcpy(*hostname, p1, hostsiz);
p2++;
p3 = strchr(p2, '/');
if (p3 == NULL || p3 - p2 >= (int)sizeof(tmpPort)) {
printf("%s: Error: Invalid <url>: Missing mountpoint or too long port number\n",
__progname);
xfree(*hostname);
return (0);
}
strlcpy(tmpPort, p2, (p3 - p2) + 1UL);
*port = (unsigned short)strtonum(tmpPort, 1LL, (long long)USHRT_MAX, &errstr);
if (errstr) {
printf("%s: Error: Invalid <url>: Port '%s' is %s\n",
__progname, tmpPort, errstr);
xfree(*hostname);
return (0);
}
mountsiz = strlen(p3) + 1;
*mountname = xmalloc(mountsiz);
strlcpy(*mountname, p3, mountsiz);
return (1);
}
void
replaceString(const char *source, char *dest, size_t size,
const char *from, const char *to)
{
const char *p1 = source;
const char *p2;
p2 = strstr(p1, from);
if (p2 != NULL) {
if ((unsigned int)(p2 - p1) >= size) {
printf("%s: replaceString(): Internal error: p2 - p1 >= size\n",
__progname);
abort();
}
strncat(dest, p1, (size_t)(p2 - p1));
strlcat(dest, to, size);
p1 = p2 + strlen(from);
}
strlcat(dest, p1, size);
}
char *
buildCommandString(const char *extension, const char *fileName,
metadata_t *mdata)
{
char *commandString = NULL;
size_t commandStringLen = 0;
char *encoder = NULL;
char *decoder = NULL;
char *newDecoder = NULL;
size_t newDecoderLen = 0;
char *newEncoder = NULL;
size_t newEncoderLen = 0;
char *localTitle = UTF8toCHAR(metadata_get_title(mdata),
ICONV_REPLACE);
char *localArtist = UTF8toCHAR(metadata_get_artist(mdata),
ICONV_REPLACE);
char *localMetaString = UTF8toCHAR(metadata_get_string(mdata),
ICONV_REPLACE);
decoder = xstrdup(getFormatDecoder(extension));
if (strlen(decoder) == 0) {
printf("%s: Unknown extension '%s', cannot decode '%s'\n",
__progname, extension, fileName);
xfree(localTitle);
xfree(localArtist);
xfree(localMetaString);
xfree(decoder);
return (NULL);
}
newDecoderLen = strlen(decoder) + strlen(fileName) + 1;
newDecoder = xcalloc(newDecoderLen, sizeof(char));
replaceString(decoder, newDecoder, newDecoderLen, TRACK_PLACEHOLDER,
fileName);
if (strstr(decoder, ARTIST_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newDecoder) + strlen(localArtist) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newDecoder, tmpStr, tmpLen, ARTIST_PLACEHOLDER,
localArtist);
xfree(newDecoder);
newDecoder = tmpStr;
}
if (strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newDecoder) + strlen(localTitle) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newDecoder, tmpStr, tmpLen, TITLE_PLACEHOLDER,
localTitle);
xfree(newDecoder);
newDecoder = tmpStr;
}
/*
* if meta
* if (prog && format)
* metatoformat
* else
* if (!prog && title)
* emptymeta
* else
* replacemeta
*/
if (strstr(decoder, METADATA_PLACEHOLDER) != NULL) {
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
size_t tmpLen = strlen(newDecoder) + strlen(mdataString) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newDecoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER, mdataString);
xfree(newDecoder);
xfree(mdataString);
newDecoder = tmpStr;
} else {
if (!metadataFromProgram && strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newDecoder) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newDecoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER, "");
xfree(newDecoder);
newDecoder = tmpStr;
} else {
size_t tmpLen = strlen(newDecoder) + strlen(localMetaString) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newDecoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER,
localMetaString);
xfree(newDecoder);
newDecoder = tmpStr;
}
}
}
encoder = xstrdup(getFormatEncoder(pezConfig->format));
if (strlen(encoder) == 0) {
if (vFlag)
printf("%s: Passing through%s%s data from the decoder\n",
__progname,
(strcmp(pezConfig->format, THEORA_FORMAT) != 0) ? " (unsupported) " : " ",
pezConfig->format);
commandStringLen = strlen(newDecoder) + 1;
commandString = xcalloc(commandStringLen, sizeof(char));
strlcpy(commandString, newDecoder, commandStringLen);
xfree(localTitle);
xfree(localArtist);
xfree(localMetaString);
xfree(decoder);
xfree(encoder);
xfree(newDecoder);
return (commandString);
}
newEncoderLen = strlen(encoder) + strlen(localArtist) + 1;
newEncoder = xcalloc(newEncoderLen, sizeof(char));
replaceString(encoder, newEncoder, newEncoderLen, ARTIST_PLACEHOLDER,
localArtist);
if (strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newEncoder) + strlen(localTitle) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newEncoder, tmpStr, tmpLen, TITLE_PLACEHOLDER,
localTitle);
xfree(newEncoder);
newEncoder = tmpStr;
}
if (strstr(encoder, METADATA_PLACEHOLDER) != NULL) {
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
size_t tmpLen = strlen(newEncoder) + strlen(mdataString) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newEncoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER, mdataString);
xfree(newEncoder);
xfree(mdataString);
newEncoder = tmpStr;
} else {
if (!metadataFromProgram && strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newEncoder) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newEncoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER, "");
xfree(newEncoder);
newEncoder = tmpStr;
} else {
size_t tmpLen = strlen(newEncoder) + strlen(localMetaString) + 1;
char *tmpStr = xcalloc(tmpLen, sizeof(char));
replaceString(newEncoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER,
localMetaString);
xfree(newEncoder);
newEncoder = tmpStr;
}
}
}
commandStringLen = strlen(newDecoder) + strlen(" | ") +
strlen(newEncoder) + 1;
commandString = xcalloc(commandStringLen, sizeof(char));
snprintf(commandString, commandStringLen, "%s | %s", newDecoder,
newEncoder);
xfree(localTitle);
xfree(localArtist);
xfree(localMetaString);
xfree(decoder);
xfree(encoder);
xfree(newDecoder);
xfree(newEncoder);
return (commandString);
}
char *
getMetadataString(const char *format, metadata_t *mdata)
{
char *tmp, *str;
size_t len;
if (mdata == NULL) {
printf("%s: getMetadataString(): Internal error: NULL metadata_t\n",
__progname);
abort();
}
if (format == NULL)
return (NULL);
str = xstrdup(format);
if (strstr(format, ARTIST_PLACEHOLDER) != NULL) {
len = strlen(str) + strlen(metadata_get_artist(mdata)) + 1;
tmp = xcalloc(len, sizeof(char));
replaceString(str, tmp, len, ARTIST_PLACEHOLDER,
metadata_get_artist(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, TITLE_PLACEHOLDER) != NULL) {
len = strlen(str) + strlen(metadata_get_title(mdata)) + 1;
tmp = xcalloc(len, sizeof(char));
replaceString(str, tmp, len, TITLE_PLACEHOLDER,
metadata_get_title(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, STRING_PLACEHOLDER) != NULL) {
len = strlen(str) + strlen(metadata_get_string(mdata)) + 1;
tmp = xcalloc(len, sizeof(char));
replaceString(str, tmp, len, STRING_PLACEHOLDER,
metadata_get_string(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, TRACK_PLACEHOLDER) != NULL) {
len = strlen(str) + strlen(metadata_get_filename(mdata)) + 1;
tmp = xcalloc(len, sizeof(char));
replaceString(str, tmp, len, TRACK_PLACEHOLDER,
metadata_get_filename(mdata));
xfree(str);
str = tmp;
}
return (str);
}
metadata_t *
getMetadata(const char *fileName)
{
metadata_t *mdata;
if (metadataFromProgram) {
if ((mdata = metadata_program(fileName, nFlag)) == NULL)
return (NULL);
if (!metadata_program_update(mdata, METADATA_ALL)) {
metadata_free(&mdata);
return (NULL);
}
} else {
if ((mdata = metadata_file(fileName, nFlag)) == NULL)
return (NULL);
if (!metadata_file_update(mdata)) {
metadata_free(&mdata);
return (NULL);
}
}
return (mdata);
}
int
setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
{
shout_metadata_t *shout_mdata = NULL;
char *songInfo;
const char *artist, *title;
int ret = SHOUTERR_SUCCESS;
if (shout == NULL) {
printf("%s: setMetadata(): Internal error: NULL shout_t\n",
__progname);
abort();
}
if (mdata == NULL)
return 1;
if ((shout_mdata = shout_metadata_new()) == NULL) {
printf("%s: shout_metadata_new(): %s\n", __progname,
strerror(ENOMEM));
exit(1);
}
artist = metadata_get_artist(mdata);
title = metadata_get_title(mdata);
/*
* We can do this, because we know how libshout works. This adds
* "charset=UTF-8" to the HTTP metadata update request and has the
* desired effect of letting newer-than-2.3.1 versions of Icecast know
* which encoding we're using.
*/
if (shout_metadata_add(shout_mdata, "charset", "UTF-8") != SHOUTERR_SUCCESS) {
/* Assume SHOUTERR_MALLOC */
printf("%s: shout_metadata_add(): %s\n", __progname,
strerror(ENOMEM));
exit(1);
}
if ((songInfo = getMetadataString(pezConfig->metadataFormat, mdata)) == NULL) {
if (artist[0] == '\0' && title[0] == '\0')
songInfo = xstrdup(metadata_get_string(mdata));
else
songInfo = metadata_assemble_string(mdata);
if (artist[0] != '\0' && title[0] != '\0') {
if (shout_metadata_add(shout_mdata, "artist", artist) != SHOUTERR_SUCCESS) {
printf("%s: shout_metadata_add(): %s\n", __progname,
strerror(ENOMEM));
exit(1);
}
if (shout_metadata_add(shout_mdata, "title", title) != SHOUTERR_SUCCESS) {
printf("%s: shout_metadata_add(): %s\n", __progname,
strerror(ENOMEM));
exit(1);
}
} else {
if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
printf("%s: shout_metadata_add(): %s\n", __progname,
strerror(ENOMEM));
exit(1);
}
}
} else if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
printf("%s: shout_metadata_add(): %s\n", __progname,
strerror(ENOMEM));
exit(1);
}
if ((ret = shout_set_metadata(shout, shout_mdata)) != SHOUTERR_SUCCESS)
printf("%s: shout_set_metadata(): %s\n",
__progname, shout_get_error(shout));
shout_metadata_free(shout_mdata);
if (ret == SHOUTERR_SUCCESS) {
if (mdata_copy != NULL && *mdata_copy == NULL)
*mdata_copy = xstrdup(songInfo);
}
xfree(songInfo);
return (ret);
}
FILE *
openResource(shout_t *shout, const char *fileName, int *popenFlag,
metadata_t **mdata_p, int *isStdin, long *songLen)
{
FILE *filep = NULL;
char extension[25];
char *p = NULL;
char *pCommandString = NULL;
metadata_t *mdata;
if (mdata_p != NULL)
*mdata_p = NULL;
if (songLen != NULL)
*songLen = 0;
if (strcmp(fileName, "stdin") == 0) {
if (metadataFromProgram) {
if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
return (NULL);
if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) {
metadata_free(&mdata);
return (NULL);
}
if (mdata_p != NULL)
*mdata_p = mdata;
else
metadata_free(&mdata);
}
if (isStdin != NULL)
*isStdin = 1;
#ifdef WIN32
_setmode(_fileno(stdin), _O_BINARY);
#endif
filep = stdin;
return (filep);
}
if (isStdin != NULL)
*isStdin = 0;
extension[0] = '\0';
p = strrchr(fileName, '.');
if (p != NULL)
strlcpy(extension, p, sizeof(extension));
for (p = extension; *p != '\0'; p++)
*p = tolower((int)*p);
if (strlen(extension) == 0) {
printf("%s: Error: Cannot determine file type of '%s'\n",
__progname, fileName);
return (filep);
}
if (metadataFromProgram) {
if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
return (NULL);
} else {
if ((mdata = getMetadata(fileName)) == NULL)
return (NULL);
}
if (songLen != NULL)
*songLen = metadata_get_length(mdata);
*popenFlag = 0;
if (pezConfig->reencode) {
int stderr_fd = -1;
pCommandString = buildCommandString(extension, fileName, mdata);
if (mdata_p != NULL)
*mdata_p = mdata;
else
metadata_free(&mdata);
if (vFlag > 1)
printf("%s: Running command `%s`\n", __progname,
pCommandString);
if (qFlag) {
int fd;
stderr_fd = dup(fileno(stderr));
if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
printf("%s: Cannot open %s for redirecting STDERR output: %s\n",
__progname, _PATH_DEVNULL, strerror(errno));
exit(1);
}
dup2(fd, fileno(stderr));
if (fd > 2)
close(fd);
}
fflush(NULL);
errno = 0;
if ((filep = popen(pCommandString, "r")) == NULL) {
printf("%s: popen(): Error while executing '%s'",
__progname, pCommandString);
/* popen() does not set errno reliably ... */
if (errno)
printf(": %s\n", strerror(errno));
else
printf("\n");
} else {
*popenFlag = 1;
#ifdef WIN32
_setmode(_fileno(filep), _O_BINARY );
#endif
}
xfree(pCommandString);
if (qFlag)
dup2(stderr_fd, fileno(stderr));
if (stderr_fd > 2)
close(stderr_fd);
return (filep);
}
if (mdata_p != NULL)
*mdata_p = mdata;
else
metadata_free(&mdata);
if ((filep = fopen(fileName, "rb")) == NULL)
printf("%s: %s: %s\n", __progname, fileName,
strerror(errno));
return (filep);
}
int
reconnectServer(shout_t *shout, int closeConn)
{
unsigned int i;
int close_conn = closeConn;
printf("%s: Connection to %s lost\n", __progname, pezConfig->URL);
i = 0;
while (++i) {
printf("%s: Attempting reconnection #", __progname);
if (pezConfig->reconnectAttempts > 0)
printf("%u/%u: ", i,
pezConfig->reconnectAttempts);
else
printf("%u: ", i);
if (close_conn == 0)
close_conn = 1;
else
shout_close(shout);
if (shout_open(shout) == SHOUTERR_SUCCESS) {
printf("OK\n%s: Reconnect to %s successful\n",
__progname, pezConfig->URL);
return (1);
}
printf("FAILED: %s\n", shout_get_error(shout));
if (pezConfig->reconnectAttempts > 0 &&
i >= pezConfig->reconnectAttempts)
break;
printf("%s: Waiting 5s for %s to come back ...\n",
__progname, pezConfig->URL);
if (quit)
return (0);
else
sleep(5);
};
printf("%s: Giving up\n", __progname);
return (0);
}
const char *
getTimeString(long seconds)
{
static char str[20];
long secs, mins, hours;
if (seconds < 0)
return (NULL);
secs = seconds;
hours = secs / 3600;
secs %= 3600;
mins = secs / 60;
secs %= 60;
snprintf(str, sizeof(str), "%ldh%02ldm%02lds", hours, mins, secs);
return ((const char *)str);
}
int
sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
int isStdin, const char *songLenStr, struct timeval *tv)
{
unsigned char buff[4096];
size_t bytes_read, total, oldTotal;
int ret;
double kbps = -1.0;
struct timeval timeStamp, *startTime = tv;
if (startTime == NULL) {
printf("%s: sendStream(): Internal error: startTime is NULL\n",
__progname);
abort();
}
timeStamp.tv_sec = startTime->tv_sec;
timeStamp.tv_usec = startTime->tv_usec;
total = oldTotal = 0;
ret = STREAM_DONE;
while ((bytes_read = fread(buff, 1UL, sizeof(buff), filepstream)) > 0) {
if (shout_get_connected(shout) != SHOUTERR_CONNECTED &&
reconnectServer(shout, 0) == 0) {
ret = STREAM_SERVERR;
break;
}
shout_sync(shout);
if (shout_send(shout, buff, bytes_read) != SHOUTERR_SUCCESS) {
printf("%s: shout_send(): %s\n", __progname,
shout_get_error(shout));
if (reconnectServer(shout, 1))
break;
else {
ret = STREAM_SERVERR;
break;
}
}
if (quit)
break;
if (rereadPlaylist_notify) {
rereadPlaylist_notify = 0;
if (!pezConfig->fileNameIsProgram)
printf("%s: SIGHUP signal received, will reread playlist after this file\n",
__progname);
}
if (skipTrack) {
skipTrack = 0;
ret = STREAM_SKIP;
break;
}
if (queryMetadata) {
queryMetadata = 0;
if (metadataFromProgram) {
ret = STREAM_UPDMDATA;
break;
}
}
total += bytes_read;
if (qFlag && vFlag) {
struct timeval tval;
double oldTime, newTime;
if (!isStdin && playlistMode) {
if (pezConfig->fileNameIsProgram) {
char *tmp = xstrdup(pezConfig->fileName);
printf(" [%s]",
local_basename(tmp));
xfree(tmp);
} else
printf(" [%4lu/%-4lu]",
playlist_get_position(playlist),
playlist_get_num_items(playlist));
}
oldTime = (double)timeStamp.tv_sec
+ (double)timeStamp.tv_usec / 1000000.0;
ez_gettimeofday((void *)&tval);
newTime = (double)tval.tv_sec
+ (double)tval.tv_usec / 1000000.0;
if (songLenStr == NULL)
printf(" [ %s]",
getTimeString(tval.tv_sec - startTime->tv_sec));
else
printf(" [ %s/%s]",
getTimeString(tval.tv_sec - startTime->tv_sec),
songLenStr);
if (newTime - oldTime >= 1.0) {
kbps = (((double)(total - oldTotal) / (newTime - oldTime)) * 8.0) / 1000.0;
timeStamp.tv_sec = tval.tv_sec;
timeStamp.tv_usec = tval.tv_usec;
oldTotal = total;
}
if (kbps < 0)
printf(" ");
else
printf(" [%8.2f kbps]", kbps);
printf(" \r");
fflush(stdout);
}
}
if (ferror(filepstream)) {
if (errno == EINTR) {
clearerr(filepstream);
ret = STREAM_CONT;
} else if (errno == EBADF && isStdin)
printf("%s: No (more) data available on standard input\n",
__progname);
else
printf("%s: sendStream(): Error while reading '%s': %s\n",
__progname, fileName, strerror(errno));
}
return (ret);
}
int
streamFile(shout_t *shout, const char *fileName)
{
FILE *filepstream = NULL;
int popenFlag = 0;
char *songLenStr = NULL;
int isStdin = 0;
int ret, retval = 0;
long songLen;
metadata_t *mdata;
struct timeval startTime;
if ((filepstream = openResource(shout, fileName, &popenFlag,
&mdata, &isStdin, &songLen))
== NULL) {
if (++resource_errors > 100) {
printf("%s: Too many errors -- giving up.\n", __progname);
return (0);
}
/* Continue with next resource on failure: */
return (1);
}
resource_errors = 0;
if (mdata != NULL) {
char *tmp, *metaData;
tmp = metadata_assemble_string(mdata);
if ((metaData = UTF8toCHAR(tmp, ICONV_REPLACE)) == NULL)
metaData = xstrdup("(unknown title)");
xfree(tmp);
printf("%s: Streaming ``%s''", __progname, metaData);
if (vFlag)
printf(" (file: %s)\n", fileName);
else
printf("\n");
xfree(metaData);
/* MP3 streams are special, so set the metadata explicitly: */
if (strcmp(pezConfig->format, MP3_FORMAT) == 0)
setMetadata(shout, mdata, NULL);
metadata_free(&mdata);
} else if (isStdin)
printf("%s: Streaming from standard input\n", __progname);
if (songLen > 0)
songLenStr = xstrdup(getTimeString(songLen));
ez_gettimeofday((void *)&startTime);
do {
ret = sendStream(shout, filepstream, fileName, isStdin,
songLenStr, &startTime);
if (quit)
break;
if (ret != STREAM_DONE) {
if ((skipTrack && rereadPlaylist) ||
(skipTrack && queryMetadata)) {
skipTrack = 0;
ret = STREAM_CONT;
}
if (queryMetadata && rereadPlaylist) {
queryMetadata = 0;
ret = STREAM_CONT;
}
if (ret == STREAM_SKIP || skipTrack) {
skipTrack = 0;
if (!isStdin && vFlag)
printf("%s: SIGUSR1 signal received, skipping current track\n",
__progname);
retval = 1;
ret = STREAM_DONE;
}
if (ret == STREAM_UPDMDATA || queryMetadata) {
queryMetadata = 0;
if (metadataFromProgram) {
char *mdataStr = NULL;
metadata_t *prog_mdata;
if (vFlag > 1)
printf("%s: Querying '%s' for fresh metadata\n",
__progname, pezConfig->metadataProgram);
if ((prog_mdata = getMetadata(pezConfig->metadataProgram)) == NULL) {
retval = 0;
ret = STREAM_DONE;
continue;
}
if (setMetadata(shout, prog_mdata, &mdataStr) != SHOUTERR_SUCCESS) {
retval = 0;
ret = STREAM_DONE;
continue;
}
metadata_free(&prog_mdata);
printf("%s: New metadata: ``%s''\n",
__progname, mdataStr);
xfree(mdataStr);
}
}
if (ret == STREAM_SERVERR) {
retval = 0;
ret = STREAM_DONE;
}
} else
retval = 1;
} while (ret != STREAM_DONE);
if (popenFlag)
pclose(filepstream);
else
fclose(filepstream);
if (songLenStr != NULL)
xfree(songLenStr);
return (retval);
}
int
streamPlaylist(shout_t *shout, const char *fileName)
{
const char *song;
char lastSong[PATH_MAX];
if (playlist == NULL) {
if (pezConfig->fileNameIsProgram) {
if ((playlist = playlist_program(fileName)) == NULL)
return (0);
} else {
if ((playlist = playlist_read(fileName)) == NULL)
return (0);
if (vFlag && playlist_get_num_items(playlist) == 0)
printf("%s: Warning: Playlist '%s' is empty\n",
__progname, fileName);
}
} else {
/*
* XXX: This preserves traditional behavior, however,
* rereading the playlist after each walkthrough seems a
* bit more logical.
*/
playlist_rewind(playlist);
}
if (!pezConfig->fileNameIsProgram && pezConfig->shuffle)
playlist_shuffle(playlist);
while ((song = playlist_get_next(playlist)) != NULL) {
strlcpy(lastSong, song, sizeof(lastSong));
if (!streamFile(shout, song))
return (0);
if (quit)
break;
if (rereadPlaylist) {
rereadPlaylist = rereadPlaylist_notify = 0;
if (pezConfig->fileNameIsProgram)
continue;
printf("%s: Rereading playlist\n", __progname);
if (!playlist_reread(&playlist))
return (0);
if (pezConfig->shuffle)
playlist_shuffle(playlist);
else {
playlist_goto_entry(playlist, lastSong);
playlist_skip_next(playlist);
}
continue;
}
}
return (1);
}
/*
* Borrowed from OpenNTPd-portable's compat-openbsd/bsd-misc.c.
* Does not use xalloc on purpose, as the 9 bytes of memory that don't get
* cleaned up in the end really don't matter.
*/
char *
getProgname(const char *argv0)
{
#ifdef HAVE___PROGNAME
(void)argv0;
return (strdup(__progname));
#else
char *p;
if (argv0 == NULL)
return ((char *)"ezstream");
p = strrchr(argv0, path_separators[0]);
if (p == NULL)
p = (char *)argv0;
else
p++;
return (strdup(p));
#endif /* HAVE___PROGNAME */
}
int
ez_shutdown(int exitval)
{
shout_shutdown();
playlist_shutdown();
freeConfig(pezConfig);
xalloc_shutdown();
return (exitval);
}
void
usage(void)
{
printf("usage: %s [-hnqVv] -c configfile\n", __progname);
printf(" %s -s [playlist]\n", __progname);
}
void
usageHelp(void)
{
printf("\n");
printf(" -c configfile use XML configuration in configfile (mandatory)\n");
printf(" -h display this additional help and exit\n");
printf(" -n normalize metadata strings\n");
printf(" -q suppress STDERR output from external en-/decoders\n");
printf(" -s [playlist] read lines from playlist (or STDIN), shuffle and print them to\n");
printf(" STDOUT, then exit\n");
printf(" -V print the version number and exit\n");
printf(" -v verbose output (use twice for more effect)\n");
printf("\n");
printf("See the ezstream(1) manual for detailed information.\n");
}
int
main(int argc, char *argv[])
{
int c;
char *configFile = NULL;
char *host = NULL;
unsigned short port = 0;
char *mount = NULL;
shout_t *shout;
extern char *optarg;
extern int optind;
#ifdef HAVE_SIGNALS
struct sigaction act;
unsigned int i;
#endif
#ifdef XALLOC_DEBUG
xalloc_initialize_debug(2, NULL);
#else
xalloc_initialize();
#endif /* XALLOC_DEBUG */
playlist_init();
shout_init();
__progname = getProgname(argv[0]);
pezConfig = getEZConfig();
nFlag = 0;
qFlag = 0;
vFlag = 0;
while ((c = local_getopt(argc, argv, "c:hnqsVv")) != -1) {
switch (c) {
case 'c':
if (configFile != NULL) {
printf("Error: multiple -c arguments given\n");
usage();
return (ez_shutdown(2));
}
configFile = xstrdup(optarg);
break;
case 'h':
usage();
usageHelp();
return (ez_shutdown(0));
case 'n':
nFlag = 1;
break;
case 'q':
qFlag = 1;
break;
case 's':
sFlag = 1;
break;
case 'V':
printf("%s\n", PACKAGE_STRING);
return (ez_shutdown(0));
case 'v':
vFlag++;
break;
case '?':
usage();
return (ez_shutdown(2));
default:
break;
}
}
argc -= optind;
argv += optind;
if (sFlag) {
playlist_t *pl;
const char *entry;
switch (argc) {
case 0:
pl = playlist_read(NULL);
if (pl == NULL)
return (ez_shutdown(1));
break;
case 1:
pl = playlist_read(argv[0]);
if (pl == NULL)
return (ez_shutdown(1));
break;
default:
printf("Error: Too many arguments.\n");
return (ez_shutdown(2));
}
playlist_shuffle(pl);
while ((entry = playlist_get_next(pl)) != NULL)
printf("%s\n", entry);
playlist_free(&pl);
return (ez_shutdown(0));
}
if (configFile == NULL) {
printf("You must supply a config file with the -c argument.\n");
usage();
return (ez_shutdown(2));
} else {
/*
* Attempt to open configFile here for a more meaningful error
* message. Where possible, do it with stat() and check for
* safe config file permissions.
*/
#ifdef HAVE_STAT
struct stat st;
if (stat(configFile, &st) == -1) {
printf("%s: %s\n", configFile, strerror(errno));
usage();
return (ez_shutdown(2));
}
if (vFlag && (st.st_mode & (S_IRGRP | S_IROTH)))
printf("%s: Warning: %s is group and/or world readable\n",
__progname, configFile);
if (st.st_mode & (S_IWGRP | S_IWOTH)) {
printf("%s: Error: %s is group and/or world writeable\n",
__progname, configFile);
return (ez_shutdown(2));
}
#else
FILE *tmp;
if ((tmp = fopen(configFile, "r")) == NULL) {
printf("%s: %s\n", configFile, strerror(errno));
usage();
return (ez_shutdown(2));
}
fclose(tmp);
#endif /* HAVE_STAT */
}
if (!parseConfig(configFile))
return (ez_shutdown(2));
if (pezConfig->URL == NULL) {
printf("%s: Error: Missing <url>\n", configFile);
return (ez_shutdown(2));
}
if (!urlParse(pezConfig->URL, &host, &port, &mount)) {
printf("Must be of the form ``http://server:port/mountpoint''\n");
return (ez_shutdown(2));
}
if (strlen(host) == 0) {
printf("%s: Error: Invalid <url>: Missing server:\n", configFile);
printf("Must be of the form ``http://server:port/mountpoint''\n");
return (ez_shutdown(2));
}
if (strlen(mount) == 0) {
printf("%s: Error: Invalid <url>: Missing mountpoint:\n", configFile);
printf("Must be of the form ``http://server:port/mountpoint''\n");
return (ez_shutdown(2));
}
if (pezConfig->password == NULL) {
printf("%s: Error: Missing <sourcepassword>\n", configFile);
return (ez_shutdown(2));
}
if (pezConfig->fileName == NULL) {
printf("%s: Error: Missing <filename>\n", configFile);
return (ez_shutdown(2));
}
if (pezConfig->format == NULL) {
printf("%s: Warning: Missing <format>:\n", configFile);
printf("Specify a stream format of either MP3, VORBIS or THEORA\n");
}
xfree(configFile);
if ((shout = stream_setup(host, port, mount)) == NULL)
return (ez_shutdown(1));
if (pezConfig->metadataProgram != NULL)
metadataFromProgram = 1;
else
metadataFromProgram = 0;
#ifdef HAVE_SIGNALS
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
# ifdef SA_RESTART
act.sa_flags = SA_RESTART;
# endif
for (i = 0; i < sizeof(ezstream_signals) / sizeof(int); i++) {
if (sigaction(ezstream_signals[i], &act, NULL) == -1) {
printf("%s: sigaction(): %s\n",
__progname, strerror(errno));
return (ez_shutdown(1));
}
}
/*
* Ignore SIGPIPE, which has been seen to give a long-running ezstream
* process trouble. EOF and/or EPIPE are also easier to handle.
*/
act.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &act, NULL) == -1) {
printf("%s: sigaction(): %s\n",
__progname, strerror(errno));
return (ez_shutdown(1));
}
#endif /* HAVE_SIGNALS */
if (shout_open(shout) == SHOUTERR_SUCCESS) {
int ret;
printf("%s: Connected to http://%s:%hu%s\n", __progname,
host, port, mount);
if (pezConfig->fileNameIsProgram ||
strrcasecmp(pezConfig->fileName, ".m3u") == 0 ||
strrcasecmp(pezConfig->fileName, ".txt") == 0)
playlistMode = 1;
else
playlistMode = 0;
if (vFlag && pezConfig->fileNameIsProgram)
printf("%s: Using program '%s' to get filenames for streaming\n",
__progname, pezConfig->fileName);
ret = 1;
do {
if (playlistMode) {
ret = streamPlaylist(shout,
pezConfig->fileName);
} else {
ret = streamFile(shout, pezConfig->fileName);
}
if (quit)
break;
if (pezConfig->streamOnce)
break;
} while (ret);
shout_close(shout);
} else
printf("%s: Connection to http://%s:%hu%s failed: %s\n", __progname,
host, port, mount, shout_get_error(shout));
if (quit)
printf("\r%s: SIGINT or SIGTERM received\n", __progname);
if (vFlag)
printf("%s: Exiting ...\n", __progname);
xfree(host);
xfree(mount);
playlist_free(&playlist);
return (ez_shutdown(0));
}