2 * ezstream - source client for Icecast with external en-/decoder support
3 * Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski <oddsock@oddsock.org>
4 * Copyright (C) 2007 Moritz Grimm <mdgrimm@gmx.net>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #ifdef HAVE_SYS_TYPES_H
25 # include <sys/types.h>
27 #ifdef HAVE_SYS_STAT_H
28 # include <sys/stat.h>
30 #ifdef HAVE_SYS_TIME_H
31 # include <sys/time.h>
52 #include <shout/shout.h>
55 #include "configfile.h"
68 #define STREAM_SERVERR 3
69 #define STREAM_UPDMDATA 4
71 #ifdef HAVE___PROGNAME
72 extern char *__progname
;
75 #endif /* HAVE___PROGNAME */
80 int metadataFromProgram
;
82 EZCONFIG
*pezConfig
= NULL
;
83 playlist_t
*playlist
= NULL
;
87 const int ezstream_signals
[] = {
88 SIGTERM
, SIGINT
, SIGHUP
, SIGUSR1
, SIGUSR2
91 volatile sig_atomic_t rereadPlaylist
= 0;
92 volatile sig_atomic_t rereadPlaylist_notify
= 0;
93 volatile sig_atomic_t skipTrack
= 0;
94 volatile sig_atomic_t queryMetadata
= 0;
95 volatile sig_atomic_t quit
= 0;
97 int rereadPlaylist
= 0;
98 int rereadPlaylist_notify
= 0;
100 int queryMetadata
= 0;
102 #endif /* HAVE_SIGNALS */
104 typedef struct tag_ID3Tag
{
114 int urlParse(const char *, char **, int *, char **);
115 void replaceString(const char *, char *, size_t, const char *,
117 char * buildCommandString(const char *, const char *, metadata_t
*);
118 char * getMetadataString(const char *, metadata_t
*);
119 metadata_t
* getMetadata(const char *);
120 int setMetadata(shout_t
*, metadata_t
*, char **);
121 FILE * openResource(shout_t
*, const char *, int *, metadata_t
**,
123 int reconnectServer(shout_t
*, int);
124 const char * getTimeString(int);
125 int sendStream(shout_t
*, FILE *, const char *, int, const char *,
127 int streamFile(shout_t
*, const char *);
128 int streamPlaylist(shout_t
*, const char *);
129 char * getProgname(const char *);
131 void usageHelp(void);
132 int ez_shutdown(int);
135 void sig_handler(int);
138 # define SIG_IGN (void (*)(int))1
139 # endif /* !SIG_IGN */
151 rereadPlaylist_notify
= 1;
163 #endif /* HAVE_SIGNALS */
166 urlParse(const char *url
, char **hostname
, int *port
, char **mountname
)
169 char tmpPort
[6] = "";
170 size_t hostsiz
, mountsiz
;
173 if (hostname
== NULL
|| port
== NULL
|| mountname
== NULL
) {
174 printf("%s: urlParse(): Internal error: Bad arguments\n",
179 if (strncmp(url
, "http://", strlen("http://")) != 0) {
180 printf("%s: Error: Invalid <url>: Not an HTTP address\n",
185 p1
= (char *)(url
) + strlen("http://");
186 p2
= strchr(p1
, ':');
188 printf("%s: Error: Invalid <url>: Missing port\n",
192 hostsiz
= (p2
- p1
) + 1;
193 *hostname
= xmalloc(hostsiz
);
194 strlcpy(*hostname
, p1
, hostsiz
);
197 p3
= strchr(p2
, '/');
198 if (p3
== NULL
|| p3
- p2
>= (int)sizeof(tmpPort
)) {
199 printf("%s: Error: Invalid <url>: Missing mountpoint or too long port number\n",
205 strlcpy(tmpPort
, p2
, (p3
- p2
) + 1);
206 *port
= (int)strtonum(tmpPort
, 1, 65535, &errstr
);
208 printf("%s: Error: Invalid <url>: Port '%s' is %s\n",
209 __progname
, tmpPort
, errstr
);
214 mountsiz
= strlen(p3
) + 1;
215 *mountname
= xmalloc(mountsiz
);
216 strlcpy(*mountname
, p3
, mountsiz
);
222 replaceString(const char *source
, char *dest
, size_t size
,
223 const char *from
, const char *to
)
225 char *p1
= (char *)source
;
228 p2
= strstr(p1
, from
);
230 if ((unsigned int)(p2
- p1
) >= size
) {
231 printf("%s: replaceString(): Internal error: p2 - p1 >= size\n",
235 strncat(dest
, p1
, p2
- p1
);
236 strlcat(dest
, to
, size
);
237 p1
= p2
+ strlen(from
);
239 strlcat(dest
, p1
, size
);
243 buildCommandString(const char *extension
, const char *fileName
,
246 char *commandString
= NULL
;
247 size_t commandStringLen
= 0;
248 char *encoder
= NULL
;
249 char *decoder
= NULL
;
250 char *newDecoder
= NULL
;
251 size_t newDecoderLen
= 0;
252 char *newEncoder
= NULL
;
253 size_t newEncoderLen
= 0;
255 decoder
= xstrdup(getFormatDecoder(extension
));
256 if (strlen(decoder
) == 0) {
257 printf("%s: Unknown extension '%s', cannot decode '%s'\n",
258 __progname
, extension
, fileName
);
262 newDecoderLen
= strlen(decoder
) + strlen(fileName
) + 1;
263 newDecoder
= xcalloc(newDecoderLen
, sizeof(char));
264 replaceString(decoder
, newDecoder
, newDecoderLen
, TRACK_PLACEHOLDER
,
266 if (strstr(decoder
, ARTIST_PLACEHOLDER
) != NULL
) {
267 size_t tmpLen
= strlen(newDecoder
) + strlen(metadata_get_artist(mdata
)) + 1;
268 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
269 replaceString(newDecoder
, tmpStr
, tmpLen
, ARTIST_PLACEHOLDER
,
270 metadata_get_artist(mdata
));
274 if (strstr(decoder
, TITLE_PLACEHOLDER
) != NULL
) {
275 size_t tmpLen
= strlen(newDecoder
) + strlen(metadata_get_title(mdata
)) + 1;
276 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
277 replaceString(newDecoder
, tmpStr
, tmpLen
, TITLE_PLACEHOLDER
,
278 metadata_get_title(mdata
));
284 * if (prog && format)
287 * if (!prog && title)
292 if (strstr(decoder
, METADATA_PLACEHOLDER
) != NULL
) {
293 if (metadataFromProgram
&& pezConfig
->metadataFormat
!= NULL
) {
294 char *mdataString
= getMetadataString(pezConfig
->metadataFormat
, mdata
);
295 size_t tmpLen
= strlen(newDecoder
) + strlen(mdataString
) + 1;
296 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
297 replaceString(newDecoder
, tmpStr
, tmpLen
,
298 METADATA_PLACEHOLDER
, mdataString
);
303 if (!metadataFromProgram
&& strstr(decoder
, TITLE_PLACEHOLDER
) != NULL
) {
304 size_t tmpLen
= strlen(newDecoder
) + 1;
305 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
306 replaceString(newDecoder
, tmpStr
, tmpLen
,
307 METADATA_PLACEHOLDER
, "");
311 size_t tmpLen
= strlen(newDecoder
) + strlen(metadata_get_string(mdata
)) + 1;
312 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
313 replaceString(newDecoder
, tmpStr
, tmpLen
,
314 METADATA_PLACEHOLDER
,
315 metadata_get_string(mdata
));
322 encoder
= xstrdup(getFormatEncoder(pezConfig
->format
));
323 if (strlen(encoder
) == 0) {
325 printf("%s: Passing through%s%s data from the decoder\n",
327 (strcmp(pezConfig
->format
, THEORA_FORMAT
) != 0) ? " (unsupported) " : " ",
329 commandStringLen
= strlen(newDecoder
) + 1;
330 commandString
= xcalloc(commandStringLen
, sizeof(char));
331 strlcpy(commandString
, newDecoder
, commandStringLen
);
335 return (commandString
);
338 newEncoderLen
= strlen(encoder
) + strlen(metadata_get_artist(mdata
)) + 1;
339 newEncoder
= xcalloc(newEncoderLen
, sizeof(char));
340 replaceString(encoder
, newEncoder
, newEncoderLen
, ARTIST_PLACEHOLDER
,
341 metadata_get_artist(mdata
));
342 if (strstr(encoder
, TITLE_PLACEHOLDER
) != NULL
) {
343 size_t tmpLen
= strlen(newEncoder
) + strlen(metadata_get_title(mdata
)) + 1;
344 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
345 replaceString(newEncoder
, tmpStr
, tmpLen
, TITLE_PLACEHOLDER
,
346 metadata_get_title(mdata
));
350 if (strstr(encoder
, METADATA_PLACEHOLDER
) != NULL
) {
351 if (metadataFromProgram
&& pezConfig
->metadataFormat
!= NULL
) {
352 char *mdataString
= getMetadataString(pezConfig
->metadataFormat
, mdata
);
353 size_t tmpLen
= strlen(newEncoder
) + strlen(mdataString
) + 1;
354 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
355 replaceString(newEncoder
, tmpStr
, tmpLen
,
356 METADATA_PLACEHOLDER
, mdataString
);
361 if (!metadataFromProgram
&& strstr(encoder
, TITLE_PLACEHOLDER
) != NULL
) {
362 size_t tmpLen
= strlen(newEncoder
) + 1;
363 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
364 replaceString(newEncoder
, tmpStr
, tmpLen
,
365 METADATA_PLACEHOLDER
, "");
369 size_t tmpLen
= strlen(newEncoder
) + strlen(metadata_get_string(mdata
)) + 1;
370 char *tmpStr
= xcalloc(tmpLen
, sizeof(char));
371 replaceString(newEncoder
, tmpStr
, tmpLen
,
372 METADATA_PLACEHOLDER
,
373 metadata_get_string(mdata
));
380 commandStringLen
= strlen(newDecoder
) + strlen(" | ") +
381 strlen(newEncoder
) + 1;
382 commandString
= xcalloc(commandStringLen
, sizeof(char));
383 snprintf(commandString
, commandStringLen
, "%s | %s", newDecoder
,
391 return (commandString
);
395 getMetadataString(const char *format
, metadata_t
*mdata
)
401 printf("%s: getMetadataString(): Internal error: NULL metadata_t\n",
409 str
= xstrdup(format
);
411 if (strstr(format
, ARTIST_PLACEHOLDER
) != NULL
) {
412 len
= strlen(str
) + strlen(metadata_get_artist(mdata
)) + 1;
413 tmp
= xcalloc(len
, sizeof(char));
414 replaceString(str
, tmp
, len
, ARTIST_PLACEHOLDER
,
415 metadata_get_artist(mdata
));
419 if (strstr(format
, TITLE_PLACEHOLDER
) != NULL
) {
420 len
= strlen(str
) + strlen(metadata_get_title(mdata
)) + 1;
421 tmp
= xcalloc(len
, sizeof(char));
422 replaceString(str
, tmp
, len
, TITLE_PLACEHOLDER
,
423 metadata_get_title(mdata
));
427 if (strstr(format
, STRING_PLACEHOLDER
) != NULL
) {
428 len
= strlen(str
) + strlen(metadata_get_string(mdata
)) + 1;
429 tmp
= xcalloc(len
, sizeof(char));
430 replaceString(str
, tmp
, len
, STRING_PLACEHOLDER
,
431 metadata_get_string(mdata
));
435 if (strstr(format
, TRACK_PLACEHOLDER
) != NULL
) {
436 len
= strlen(str
) + strlen(metadata_get_filename(mdata
)) + 1;
437 tmp
= xcalloc(len
, sizeof(char));
438 replaceString(str
, tmp
, len
, TRACK_PLACEHOLDER
,
439 metadata_get_filename(mdata
));
448 getMetadata(const char *fileName
)
452 if (metadataFromProgram
) {
453 if ((mdata
= metadata_program(fileName
, nFlag
)) == NULL
)
456 if (!metadata_program_update(mdata
, METADATA_ALL
)) {
457 metadata_free(&mdata
);
461 if ((mdata
= metadata_file(fileName
, nFlag
)) == NULL
)
464 if (!metadata_file_update(mdata
)) {
465 metadata_free(&mdata
);
474 setMetadata(shout_t
*shout
, metadata_t
*mdata
, char **mdata_copy
)
476 shout_metadata_t
*shout_mdata
= NULL
;
478 const char *artist
, *title
;
479 int ret
= SHOUTERR_SUCCESS
;
482 printf("%s: setMetadata(): Internal error: NULL shout_t\n",
490 if ((shout_mdata
= shout_metadata_new()) == NULL
) {
491 printf("%s: shout_metadata_new(): %s\n", __progname
,
496 artist
= metadata_get_artist(mdata
);
497 title
= metadata_get_title(mdata
);
500 * We can do this, because we know how libshout works. This adds
501 * "charset=UTF-8" to the HTTP metadata update request and has the
502 * desired effect of letting newer-than-2.3.1 versions of Icecast know
503 * which encoding we're using.
505 if (shout_metadata_add(shout_mdata
, "charset", "UTF-8") != SHOUTERR_SUCCESS
) {
506 /* Assume SHOUTERR_MALLOC */
507 printf("%s: shout_metadata_add(): %s\n", __progname
,
512 if ((songInfo
= getMetadataString(pezConfig
->metadataFormat
, mdata
)) == NULL
) {
513 if (artist
[0] == '\0' && title
[0] == '\0')
514 songInfo
= xstrdup(metadata_get_string(mdata
));
516 songInfo
= metadata_assemble_string(mdata
);
517 if (artist
[0] != '\0' && title
[0] != '\0') {
518 if (shout_metadata_add(shout_mdata
, "artist", artist
) != SHOUTERR_SUCCESS
) {
519 printf("%s: shout_metadata_add(): %s\n", __progname
,
523 if (shout_metadata_add(shout_mdata
, "title", title
) != SHOUTERR_SUCCESS
) {
524 printf("%s: shout_metadata_add(): %s\n", __progname
,
529 if (shout_metadata_add(shout_mdata
, "song", songInfo
) != SHOUTERR_SUCCESS
) {
530 printf("%s: shout_metadata_add(): %s\n", __progname
,
535 } else if (shout_metadata_add(shout_mdata
, "song", songInfo
) != SHOUTERR_SUCCESS
) {
536 printf("%s: shout_metadata_add(): %s\n", __progname
,
541 if ((ret
= shout_set_metadata(shout
, shout_mdata
)) != SHOUTERR_SUCCESS
)
542 printf("%s: shout_set_metadata(): %s\n",
543 __progname
, shout_get_error(shout
));
545 shout_metadata_free(shout_mdata
);
547 if (ret
== SHOUTERR_SUCCESS
) {
548 if (mdata_copy
!= NULL
&& *mdata_copy
== NULL
)
549 *mdata_copy
= xstrdup(songInfo
);
557 openResource(shout_t
*shout
, const char *fileName
, int *popenFlag
,
558 metadata_t
**mdata_p
, int *isStdin
, int *songLen
)
563 char *pCommandString
= NULL
;
571 if (strcmp(fileName
, "stdin") == 0) {
572 if (metadataFromProgram
) {
573 if ((mdata
= getMetadata(pezConfig
->metadataProgram
)) == NULL
)
575 if (setMetadata(shout
, mdata
, NULL
) != SHOUTERR_SUCCESS
) {
576 metadata_free(&mdata
);
582 metadata_free(&mdata
);
588 _setmode(_fileno(stdin
), _O_BINARY
);
598 p
= strrchr(fileName
, '.');
600 strlcpy(extension
, p
, sizeof(extension
));
601 for (p
= extension
; *p
!= '\0'; p
++)
602 *p
= tolower((int)*p
);
604 if (strlen(extension
) == 0) {
605 printf("%s: Error: Cannot determine file type of '%s'\n",
606 __progname
, fileName
);
610 if (metadataFromProgram
) {
611 if ((mdata
= getMetadata(pezConfig
->metadataProgram
)) == NULL
)
614 if ((mdata
= getMetadata(fileName
)) == NULL
)
618 *songLen
= metadata_get_length(mdata
);
621 if (pezConfig
->reencode
) {
624 pCommandString
= buildCommandString(extension
, fileName
, mdata
);
628 metadata_free(&mdata
);
630 printf("%s: Running command `%s`\n", __progname
,
636 stderr_fd
= dup(fileno(stderr
));
637 if ((fd
= open(_PATH_DEVNULL
, O_RDWR
, 0)) == -1) {
638 printf("%s: Cannot open %s for redirecting STDERR output: %s\n",
639 __progname
, _PATH_DEVNULL
, strerror(errno
));
643 dup2(fd
, fileno(stderr
));
650 if ((filep
= popen(pCommandString
, "r")) == NULL
) {
651 printf("%s: popen(): Error while executing '%s'",
652 __progname
, pCommandString
);
653 /* popen() does not set errno reliably ... */
655 printf(": %s\n", strerror(errno
));
661 _setmode(_fileno(filep
), _O_BINARY
);
664 xfree(pCommandString
);
667 dup2(stderr_fd
, fileno(stderr
));
678 metadata_free(&mdata
);
680 if ((filep
= fopen(fileName
, "rb")) == NULL
)
681 printf("%s: %s: %s\n", __progname
, fileName
,
688 reconnectServer(shout_t
*shout
, int closeConn
)
691 int close_conn
= closeConn
;
693 printf("%s: Connection to %s lost\n", __progname
, pezConfig
->URL
);
697 printf("%s: Attempting reconnection #", __progname
);
698 if (pezConfig
->reconnectAttempts
> 0)
700 pezConfig
->reconnectAttempts
);
708 if (shout_open(shout
) == SHOUTERR_SUCCESS
) {
709 printf("OK\n%s: Reconnect to %s successful\n",
710 __progname
, pezConfig
->URL
);
714 printf("FAILED: %s\n", shout_get_error(shout
));
716 if (pezConfig
->reconnectAttempts
> 0 &&
717 i
>= pezConfig
->reconnectAttempts
)
720 printf("%s: Waiting 5s for %s to come back ...\n",
721 __progname
, pezConfig
->URL
);
728 printf("%s: Giving up\n", __progname
);
733 getTimeString(int seconds
)
736 int secs
, mins
, hours
;
747 snprintf(str
, sizeof(str
), "%uh%02um%02us", hours
, mins
, secs
);
748 return ((const char *)str
);
752 sendStream(shout_t
*shout
, FILE *filepstream
, const char *fileName
,
753 int isStdin
, const char *songLenStr
, struct timeval
*tv
)
755 unsigned char buff
[4096];
756 size_t read
, total
, oldTotal
;
759 struct timeval timeStamp
, *startTime
= tv
;
761 if (startTime
== NULL
) {
762 printf("%s: sendStream(): Internal error: startTime is NULL\n",
767 timeStamp
.tv_sec
= startTime
->tv_sec
;
768 timeStamp
.tv_usec
= startTime
->tv_usec
;
770 total
= oldTotal
= 0;
772 while ((read
= fread(buff
, 1, sizeof(buff
), filepstream
)) > 0) {
773 if (shout_get_connected(shout
) != SHOUTERR_CONNECTED
&&
774 reconnectServer(shout
, 0) == 0) {
775 ret
= STREAM_SERVERR
;
781 if (shout_send(shout
, buff
, read
) != SHOUTERR_SUCCESS
) {
782 printf("%s: shout_send(): %s\n", __progname
,
783 shout_get_error(shout
));
784 if (reconnectServer(shout
, 1))
787 ret
= STREAM_SERVERR
;
794 if (rereadPlaylist_notify
) {
795 rereadPlaylist_notify
= 0;
796 if (!pezConfig
->fileNameIsProgram
)
797 printf("%s: SIGHUP signal received, will reread playlist after this file\n",
807 if (metadataFromProgram
) {
808 ret
= STREAM_UPDMDATA
;
814 if (qFlag
&& vFlag
) {
816 double oldTime
, newTime
;
818 if (!isStdin
&& playlistMode
) {
819 if (pezConfig
->fileNameIsProgram
) {
820 char *tmp
= xstrdup(pezConfig
->fileName
);
825 printf(" [%4lu/%-4lu]",
826 playlist_get_position(playlist
),
827 playlist_get_num_items(playlist
));
830 oldTime
= (double)timeStamp
.tv_sec
831 + (double)timeStamp
.tv_usec
/ 1000000.0;
832 ez_gettimeofday((void *)&tv
);
833 newTime
= (double)tv
.tv_sec
834 + (double)tv
.tv_usec
/ 1000000.0;
835 if (songLenStr
== NULL
)
837 getTimeString(tv
.tv_sec
- startTime
->tv_sec
));
840 getTimeString(tv
.tv_sec
- startTime
->tv_sec
),
842 if (newTime
- oldTime
>= 1.0) {
843 kbps
= (((double)(total
- oldTotal
) / (newTime
- oldTime
)) * 8.0) / 1000.0;
844 timeStamp
.tv_sec
= tv
.tv_sec
;
845 timeStamp
.tv_usec
= tv
.tv_usec
;
851 printf(" [%8.2f kbps]", kbps
);
857 if (ferror(filepstream
)) {
858 if (errno
== EINTR
) {
859 clearerr(filepstream
);
861 } else if (errno
== EBADF
&& isStdin
)
862 printf("%s: No (more) data available on standard input\n",
865 printf("%s: sendStream(): Error while reading '%s': %s\n",
866 __progname
, fileName
, strerror(errno
));
873 streamFile(shout_t
*shout
, const char *fileName
)
875 FILE *filepstream
= NULL
;
877 char *songLenStr
= NULL
;
879 int ret
, retval
= 0, songLen
;
881 struct timeval startTime
;
883 if ((filepstream
= openResource(shout
, fileName
, &popenFlag
,
884 &mdata
, &isStdin
, &songLen
))
890 char *tmp
, *metaData
;
892 tmp
= metadata_assemble_string(mdata
);
893 if ((metaData
= UTF8toCHAR(tmp
, ICONV_REPLACE
)) == NULL
)
894 metaData
= xstrdup("(unknown title)");
896 printf("%s: Streaming ``%s''", __progname
, metaData
);
898 printf(" (file: %s)\n", fileName
);
903 /* MP3 streams are special, so set the metadata explicitly: */
904 if (strcmp(pezConfig
->format
, MP3_FORMAT
) == 0)
905 setMetadata(shout
, mdata
, NULL
);
907 metadata_free(&mdata
);
909 printf("%s: Streaming from standard input\n", __progname
);
912 songLenStr
= xstrdup(getTimeString(songLen
));
913 ez_gettimeofday((void *)&startTime
);
915 ret
= sendStream(shout
, filepstream
, fileName
, isStdin
,
916 songLenStr
, &startTime
);
919 if (ret
!= STREAM_DONE
) {
920 if ((skipTrack
&& rereadPlaylist
) ||
921 (skipTrack
&& queryMetadata
)) {
925 if (queryMetadata
&& rereadPlaylist
) {
929 if (ret
== STREAM_SKIP
|| skipTrack
) {
931 if (!isStdin
&& vFlag
)
932 printf("%s: SIGUSR1 signal received, skipping current track\n",
937 if (ret
== STREAM_UPDMDATA
|| queryMetadata
) {
939 if (metadataFromProgram
) {
940 char *mdataStr
= NULL
;
941 metadata_t
*prog_mdata
;
944 printf("%s: Querying '%s' for fresh metadata\n",
945 __progname
, pezConfig
->metadataProgram
);
946 if ((prog_mdata
= getMetadata(pezConfig
->metadataProgram
)) == NULL
) {
951 if (setMetadata(shout
, prog_mdata
, &mdataStr
) != SHOUTERR_SUCCESS
) {
956 metadata_free(&prog_mdata
);
957 printf("%s: New metadata: ``%s''\n",
958 __progname
, mdataStr
);
962 if (ret
== STREAM_SERVERR
) {
968 } while (ret
!= STREAM_DONE
);
975 if (songLenStr
!= NULL
)
982 streamPlaylist(shout_t
*shout
, const char *fileName
)
985 char lastSong
[PATH_MAX
];
987 if (playlist
== NULL
) {
988 if (pezConfig
->fileNameIsProgram
) {
989 if ((playlist
= playlist_program(fileName
)) == NULL
)
992 if ((playlist
= playlist_read(fileName
)) == NULL
)
994 if (vFlag
&& playlist_get_num_items(playlist
) == 0)
995 printf("%s: Warning: Playlist '%s' is empty\n",
996 __progname
, fileName
);
1000 * XXX: This preserves traditional behavior, however,
1001 * rereading the playlist after each walkthrough seems a
1004 playlist_rewind(playlist
);
1005 if (vFlag
&& playlist_get_num_items(playlist
) == 0)
1006 printf("%s: Warning: Playlist '%s' is empty\n",
1007 __progname
, fileName
);
1010 if (!pezConfig
->fileNameIsProgram
&& pezConfig
->shuffle
)
1011 playlist_shuffle(playlist
);
1013 while ((song
= playlist_get_next(playlist
)) != NULL
) {
1014 strlcpy(lastSong
, song
, sizeof(lastSong
));
1015 if (!streamFile(shout
, song
))
1019 if (rereadPlaylist
) {
1020 rereadPlaylist
= rereadPlaylist_notify
= 0;
1021 if (pezConfig
->fileNameIsProgram
)
1023 printf("%s: Rereading playlist\n", __progname
);
1024 if (!playlist_reread(&playlist
))
1026 if (pezConfig
->shuffle
)
1027 playlist_shuffle(playlist
);
1029 playlist_goto_entry(playlist
, lastSong
);
1030 playlist_skip_next(playlist
);
1040 * Borrowed from OpenNTPd-portable's compat-openbsd/bsd-misc.c.
1041 * Does not use xalloc on purpose, as the 9 bytes of memory that don't get
1042 * cleaned up in the end really don't matter.
1045 getProgname(const char *argv0
)
1047 #ifdef HAVE___PROGNAME
1048 return (strdup(__progname
));
1053 return ((char *)"ezstream");
1054 p
= strrchr(argv0
, PATH_SEPARATOR
);
1061 #endif /* HAVE___PROGNAME */
1065 ez_shutdown(int exitval
)
1068 playlist_shutdown();
1069 freeConfig(pezConfig
);
1078 printf("usage: %s [-hnqVv] -c configfile\n", __progname
);
1085 printf(" -c configfile use XML configuration in configfile (mandatory)\n");
1086 printf(" -h display this additional help and exit\n");
1087 printf(" -n normalize metadata strings\n");
1088 printf(" -q suppress STDERR output from external en-/decoders\n");
1089 printf(" -V print the version number and exit\n");
1090 printf(" -v verbose output (use twice for more effect)\n");
1092 printf("See the ezstream(1) manual for detailed information.\n");
1096 main(int argc
, char *argv
[])
1099 char *configFile
= NULL
;
1104 extern char *optarg
;
1107 struct sigaction act
;
1112 xalloc_initialize_debug(2, NULL
);
1114 xalloc_initialize();
1115 #endif /* XALLOC_DEBUG */
1119 __progname
= getProgname(argv
[0]);
1120 pezConfig
= getEZConfig();
1126 while ((c
= getopt(argc
, argv
, "c:hnqVv")) != -1) {
1129 if (configFile
!= NULL
) {
1130 printf("Error: multiple -c arguments given\n");
1132 return (ez_shutdown(2));
1134 configFile
= xstrdup(optarg
);
1139 return (ez_shutdown(0));
1147 printf("%s\n", PACKAGE_STRING
);
1148 return (ez_shutdown(0));
1154 return (ez_shutdown(2));
1162 if (configFile
== NULL
) {
1163 printf("You must supply a config file with the -c argument.\n");
1165 return (ez_shutdown(2));
1168 * Attempt to open configFile here for a more meaningful error
1169 * message. Where possible, do it with stat() and check for
1170 * safe config file permissions.
1175 if (stat(configFile
, &st
) == -1) {
1176 printf("%s: %s\n", configFile
, strerror(errno
));
1178 return (ez_shutdown(2));
1180 if (vFlag
&& (st
.st_mode
& (S_IRGRP
| S_IROTH
)))
1181 printf("%s: Warning: %s is group and/or world readable\n",
1182 __progname
, configFile
);
1183 if (st
.st_mode
& (S_IWGRP
| S_IWOTH
)) {
1184 printf("%s: Error: %s is group and/or world writeable\n",
1185 __progname
, configFile
);
1186 return (ez_shutdown(2));
1191 if ((tmp
= fopen(configFile
, "r")) == NULL
) {
1192 printf("%s: %s\n", configFile
, strerror(errno
));
1194 return (ez_shutdown(2));
1197 #endif /* HAVE_STAT */
1200 if (!parseConfig(configFile
))
1201 return (ez_shutdown(2));
1203 if (pezConfig
->URL
== NULL
) {
1204 printf("%s: Error: Missing <url>\n", configFile
);
1205 return (ez_shutdown(2));
1207 if (!urlParse(pezConfig
->URL
, &host
, &port
, &mount
)) {
1208 printf("Must be of the form ``http://server:port/mountpoint''\n");
1209 return (ez_shutdown(2));
1211 if (strlen(host
) == 0) {
1212 printf("%s: Error: Invalid <url>: Missing server:\n", configFile
);
1213 printf("Must be of the form ``http://server:port/mountpoint''\n");
1214 return (ez_shutdown(2));
1216 if (strlen(mount
) == 0) {
1217 printf("%s: Error: Invalid <url>: Missing mountpoint:\n", configFile
);
1218 printf("Must be of the form ``http://server:port/mountpoint''\n");
1219 return (ez_shutdown(2));
1221 if (pezConfig
->password
== NULL
) {
1222 printf("%s: Error: Missing <sourcepassword>\n", configFile
);
1223 return (ez_shutdown(2));
1225 if (pezConfig
->fileName
== NULL
) {
1226 printf("%s: Error: Missing <filename>\n", configFile
);
1227 return (ez_shutdown(2));
1229 if (pezConfig
->format
== NULL
) {
1230 printf("%s: Warning: Missing <format>:\n", configFile
);
1231 printf("Specify a stream format of either MP3, VORBIS or THEORA\n");
1236 if ((shout
= stream_setup(host
, port
, mount
)) == NULL
)
1237 return (ez_shutdown(1));
1239 if (pezConfig
->metadataProgram
!= NULL
)
1240 metadataFromProgram
= 1;
1242 metadataFromProgram
= 0;
1245 memset(&act
, 0, sizeof(act
));
1246 act
.sa_handler
= sig_handler
;
1248 act
.sa_flags
= SA_RESTART
;
1250 for (i
= 0; i
< sizeof(ezstream_signals
) / sizeof(int); i
++) {
1251 if (sigaction(ezstream_signals
[i
], &act
, NULL
) == -1) {
1252 printf("%s: sigaction(): %s\n",
1253 __progname
, strerror(errno
));
1254 return (ez_shutdown(1));
1258 * Ignore SIGPIPE, which has been seen to give a long-running ezstream
1259 * process trouble. EOF and/or EPIPE are also easier to handle.
1261 act
.sa_handler
= SIG_IGN
;
1262 if (sigaction(SIGPIPE
, &act
, NULL
) == -1) {
1263 printf("%s: sigaction(): %s\n",
1264 __progname
, strerror(errno
));
1265 return (ez_shutdown(1));
1267 #endif /* HAVE_SIGNALS */
1269 if (shout_open(shout
) == SHOUTERR_SUCCESS
) {
1272 printf("%s: Connected to http://%s:%d%s\n", __progname
,
1275 if (pezConfig
->fileNameIsProgram
||
1276 strrcasecmp(pezConfig
->fileName
, ".m3u") == 0 ||
1277 strrcasecmp(pezConfig
->fileName
, ".txt") == 0)
1282 if (vFlag
&& pezConfig
->fileNameIsProgram
)
1283 printf("%s: Using program '%s' to get filenames for streaming\n",
1284 __progname
, pezConfig
->fileName
);
1289 ret
= streamPlaylist(shout
,
1290 pezConfig
->fileName
);
1292 ret
= streamFile(shout
, pezConfig
->fileName
);
1296 if (pezConfig
->streamOnce
)
1302 printf("%s: Connection to http://%s:%d%s failed: %s\n", __progname
,
1303 host
, port
, mount
, shout_get_error(shout
));
1306 printf("\r%s: SIGINT or SIGTERM received\n", __progname
);
1309 printf("%s: Exiting ...\n", __progname
);
1313 playlist_free(&playlist
);
1315 return (ez_shutdown(0));