Don't leave the silly debugging malloc enabled
[xiph/unicode.git] / ezstream / src / ezstream.c
blob0ab64aff09417c9e61912bde994844df3b7ea97e
1 /*
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
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
24 #ifdef HAVE_SYS_TYPES_H
25 # include <sys/types.h>
26 #endif
27 #ifdef HAVE_SYS_STAT_H
28 # include <sys/stat.h>
29 #endif
30 #ifdef HAVE_SYS_TIME_H
31 # include <sys/time.h>
32 #endif
33 #include <ctype.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #ifdef HAVE_LIBGEN_H
37 # include <libgen.h>
38 #endif
39 #include <limits.h>
40 #ifdef HAVE_PATHS_H
41 # include <paths.h>
42 #endif
43 #ifdef HAVE_SIGNAL_H
44 # include <signal.h>
45 #endif
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #ifdef HAVE_UNISTD_H
50 # include <unistd.h>
51 #endif
52 #include <shout/shout.h>
54 #include "compat.h"
55 #include "configfile.h"
56 #ifndef HAVE_GETOPT
57 # include "getopt.h"
58 #endif
59 #include "metadata.h"
60 #include "playlist.h"
61 #include "strfctns.h"
62 #include "util.h"
63 #include "xalloc.h"
65 #define STREAM_DONE 0
66 #define STREAM_CONT 1
67 #define STREAM_SKIP 2
68 #define STREAM_SERVERR 3
69 #define STREAM_UPDMDATA 4
71 #ifdef HAVE___PROGNAME
72 extern char *__progname;
73 #else
74 char *__progname;
75 #endif /* HAVE___PROGNAME */
77 int nFlag;
78 int qFlag;
79 int vFlag;
80 int metadataFromProgram;
82 EZCONFIG *pezConfig = NULL;
83 playlist_t *playlist = NULL;
84 int playlistMode = 0;
86 #ifdef HAVE_SIGNALS
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;
96 #else
97 int rereadPlaylist = 0;
98 int rereadPlaylist_notify = 0;
99 int skipTrack = 0;
100 int queryMetadata = 0;
101 int quit = 0;
102 #endif /* HAVE_SIGNALS */
104 typedef struct tag_ID3Tag {
105 char tag[3];
106 char trackName[30];
107 char artistName[30];
108 char albumName[30];
109 char year[3];
110 char comment[30];
111 char genre;
112 } ID3Tag;
114 int urlParse(const char *, char **, int *, char **);
115 void replaceString(const char *, char *, size_t, const char *,
116 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 **,
122 int *, int *);
123 int reconnectServer(shout_t *, int);
124 const char * getTimeString(int);
125 int sendStream(shout_t *, FILE *, const char *, int, const char *,
126 struct timeval *);
127 int streamFile(shout_t *, const char *);
128 int streamPlaylist(shout_t *, const char *);
129 char * getProgname(const char *);
130 void usage(void);
131 void usageHelp(void);
132 int ez_shutdown(int);
134 #ifdef HAVE_SIGNALS
135 void sig_handler(int);
137 # ifndef SIG_IGN
138 # define SIG_IGN (void (*)(int))1
139 # endif /* !SIG_IGN */
141 void
142 sig_handler(int sig)
144 switch (sig) {
145 case SIGTERM:
146 case SIGINT:
147 quit = 1;
148 break;
149 case SIGHUP:
150 rereadPlaylist = 1;
151 rereadPlaylist_notify = 1;
152 break;
153 case SIGUSR1:
154 skipTrack = 1;
155 break;
156 case SIGUSR2:
157 queryMetadata = 1;
158 break;
159 default:
160 break;
163 #endif /* HAVE_SIGNALS */
166 urlParse(const char *url, char **hostname, int *port, char **mountname)
168 char *p1, *p2, *p3;
169 char tmpPort[6] = "";
170 size_t hostsiz, mountsiz;
171 const char *errstr;
173 if (hostname == NULL || port == NULL || mountname == NULL) {
174 printf("%s: urlParse(): Internal error: Bad arguments\n",
175 __progname);
176 exit(1);
179 if (strncmp(url, "http://", strlen("http://")) != 0) {
180 printf("%s: Error: Invalid <url>: Not an HTTP address\n",
181 __progname);
182 return (0);
185 p1 = (char *)(url) + strlen("http://");
186 p2 = strchr(p1, ':');
187 if (p2 == NULL) {
188 printf("%s: Error: Invalid <url>: Missing port\n",
189 __progname);
190 return (0);
192 hostsiz = (p2 - p1) + 1;
193 *hostname = xmalloc(hostsiz);
194 strlcpy(*hostname, p1, hostsiz);
196 p2++;
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",
200 __progname);
201 xfree(*hostname);
202 return (0);
205 strlcpy(tmpPort, p2, (p3 - p2) + 1);
206 *port = (int)strtonum(tmpPort, 1, 65535, &errstr);
207 if (errstr) {
208 printf("%s: Error: Invalid <url>: Port '%s' is %s\n",
209 __progname, tmpPort, errstr);
210 xfree(*hostname);
211 return (0);
214 mountsiz = strlen(p3) + 1;
215 *mountname = xmalloc(mountsiz);
216 strlcpy(*mountname, p3, mountsiz);
218 return (1);
221 void
222 replaceString(const char *source, char *dest, size_t size,
223 const char *from, const char *to)
225 char *p1 = (char *)source;
226 char *p2;
228 p2 = strstr(p1, from);
229 if (p2 != NULL) {
230 if ((unsigned int)(p2 - p1) >= size) {
231 printf("%s: replaceString(): Internal error: p2 - p1 >= size\n",
232 __progname);
233 abort();
235 strncat(dest, p1, p2 - p1);
236 strlcat(dest, to, size);
237 p1 = p2 + strlen(from);
239 strlcat(dest, p1, size);
242 char *
243 buildCommandString(const char *extension, const char *fileName,
244 metadata_t *mdata)
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);
259 xfree(decoder);
260 return (NULL);
262 newDecoderLen = strlen(decoder) + strlen(fileName) + 1;
263 newDecoder = xcalloc(newDecoderLen, sizeof(char));
264 replaceString(decoder, newDecoder, newDecoderLen, TRACK_PLACEHOLDER,
265 fileName);
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));
271 xfree(newDecoder);
272 newDecoder = tmpStr;
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));
279 xfree(newDecoder);
280 newDecoder = tmpStr;
283 * if meta
284 * if (prog && format)
285 * metatoformat
286 * else
287 * if (!prog && title)
288 * emptymeta
289 * else
290 * replacemeta
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);
299 xfree(newDecoder);
300 xfree(mdataString);
301 newDecoder = tmpStr;
302 } else {
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, "");
308 xfree(newDecoder);
309 newDecoder = tmpStr;
310 } else {
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));
316 xfree(newDecoder);
317 newDecoder = tmpStr;
322 encoder = xstrdup(getFormatEncoder(pezConfig->format));
323 if (strlen(encoder) == 0) {
324 if (vFlag)
325 printf("%s: Passing through%s%s data from the decoder\n",
326 __progname,
327 (strcmp(pezConfig->format, THEORA_FORMAT) != 0) ? " (unsupported) " : " ",
328 pezConfig->format);
329 commandStringLen = strlen(newDecoder) + 1;
330 commandString = xcalloc(commandStringLen, sizeof(char));
331 strlcpy(commandString, newDecoder, commandStringLen);
332 xfree(decoder);
333 xfree(encoder);
334 xfree(newDecoder);
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));
347 xfree(newEncoder);
348 newEncoder = tmpStr;
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);
357 xfree(newEncoder);
358 xfree(mdataString);
359 newEncoder = tmpStr;
360 } else {
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, "");
366 xfree(newEncoder);
367 newEncoder = tmpStr;
368 } else {
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));
374 xfree(newEncoder);
375 newEncoder = tmpStr;
380 commandStringLen = strlen(newDecoder) + strlen(" | ") +
381 strlen(newEncoder) + 1;
382 commandString = xcalloc(commandStringLen, sizeof(char));
383 snprintf(commandString, commandStringLen, "%s | %s", newDecoder,
384 newEncoder);
386 xfree(decoder);
387 xfree(encoder);
388 xfree(newDecoder);
389 xfree(newEncoder);
391 return (commandString);
394 char *
395 getMetadataString(const char *format, metadata_t *mdata)
397 char *tmp, *str;
398 size_t len;
400 if (mdata == NULL) {
401 printf("%s: getMetadataString(): Internal error: NULL metadata_t\n",
402 __progname);
403 abort();
406 if (format == NULL)
407 return (NULL);
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));
416 xfree(str);
417 str = tmp;
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));
424 xfree(str);
425 str = tmp;
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));
432 xfree(str);
433 str = tmp;
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));
440 xfree(str);
441 str = tmp;
444 return (str);
447 metadata_t *
448 getMetadata(const char *fileName)
450 metadata_t *mdata;
452 if (metadataFromProgram) {
453 if ((mdata = metadata_program(fileName, nFlag)) == NULL)
454 return (NULL);
456 if (!metadata_program_update(mdata, METADATA_ALL)) {
457 metadata_free(&mdata);
458 return (NULL);
460 } else {
461 if ((mdata = metadata_file(fileName, nFlag)) == NULL)
462 return (NULL);
464 if (!metadata_file_update(mdata)) {
465 metadata_free(&mdata);
466 return (NULL);
470 return (mdata);
474 setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
476 shout_metadata_t *shout_mdata = NULL;
477 char *songInfo;
478 const char *artist, *title;
479 int ret = SHOUTERR_SUCCESS;
481 if (shout == NULL) {
482 printf("%s: setMetadata(): Internal error: NULL shout_t\n",
483 __progname);
484 abort();
487 if (mdata == NULL)
488 return 1;
490 if ((shout_mdata = shout_metadata_new()) == NULL) {
491 printf("%s: shout_metadata_new(): %s\n", __progname,
492 strerror(ENOMEM));
493 exit(1);
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,
508 strerror(ENOMEM));
509 exit(1);
512 if ((songInfo = getMetadataString(pezConfig->metadataFormat, mdata)) == NULL) {
513 if (artist[0] == '\0' && title[0] == '\0')
514 songInfo = xstrdup(metadata_get_string(mdata));
515 else
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,
520 strerror(ENOMEM));
521 exit(1);
523 if (shout_metadata_add(shout_mdata, "title", title) != SHOUTERR_SUCCESS) {
524 printf("%s: shout_metadata_add(): %s\n", __progname,
525 strerror(ENOMEM));
526 exit(1);
528 } else {
529 if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
530 printf("%s: shout_metadata_add(): %s\n", __progname,
531 strerror(ENOMEM));
532 exit(1);
535 } else if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
536 printf("%s: shout_metadata_add(): %s\n", __progname,
537 strerror(ENOMEM));
538 exit(1);
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);
552 xfree(songInfo);
553 return (ret);
556 FILE *
557 openResource(shout_t *shout, const char *fileName, int *popenFlag,
558 metadata_t **mdata_p, int *isStdin, int *songLen)
560 FILE *filep = NULL;
561 char extension[25];
562 char *p = NULL;
563 char *pCommandString = NULL;
564 metadata_t *mdata;
566 if (mdata_p != NULL)
567 *mdata_p = NULL;
568 if (songLen != NULL)
569 *songLen = 0;
571 if (strcmp(fileName, "stdin") == 0) {
572 if (metadataFromProgram) {
573 if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
574 return (NULL);
575 if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) {
576 metadata_free(&mdata);
577 return (NULL);
579 if (mdata_p != NULL)
580 *mdata_p = mdata;
581 else
582 metadata_free(&mdata);
585 if (isStdin != NULL)
586 *isStdin = 1;
587 #ifdef WIN32
588 _setmode(_fileno(stdin), _O_BINARY);
589 #endif
590 filep = stdin;
591 return (filep);
594 if (isStdin != NULL)
595 *isStdin = 0;
597 extension[0] = '\0';
598 p = strrchr(fileName, '.');
599 if (p != NULL)
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);
607 return (filep);
610 if (metadataFromProgram) {
611 if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
612 return (NULL);
613 } else {
614 if ((mdata = getMetadata(fileName)) == NULL)
615 return (NULL);
617 if (songLen != NULL)
618 *songLen = metadata_get_length(mdata);
620 *popenFlag = 0;
621 if (pezConfig->reencode) {
622 int stderr_fd = -1;
624 pCommandString = buildCommandString(extension, fileName, mdata);
625 if (mdata_p != NULL)
626 *mdata_p = mdata;
627 else
628 metadata_free(&mdata);
629 if (vFlag > 1)
630 printf("%s: Running command `%s`\n", __progname,
631 pCommandString);
633 if (qFlag) {
634 int fd;
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));
640 exit(1);
643 dup2(fd, fileno(stderr));
644 if (fd > 2)
645 close(fd);
648 fflush(NULL);
649 errno = 0;
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 ... */
654 if (errno)
655 printf(": %s\n", strerror(errno));
656 else
657 printf("\n");
658 } else {
659 *popenFlag = 1;
660 #ifdef WIN32
661 _setmode(_fileno(filep), _O_BINARY );
662 #endif
664 xfree(pCommandString);
666 if (qFlag)
667 dup2(stderr_fd, fileno(stderr));
669 if (stderr_fd > 2)
670 close(stderr_fd);
672 return (filep);
675 if (mdata_p != NULL)
676 *mdata_p = mdata;
677 else
678 metadata_free(&mdata);
680 if ((filep = fopen(fileName, "rb")) == NULL)
681 printf("%s: %s: %s\n", __progname, fileName,
682 strerror(errno));
684 return (filep);
688 reconnectServer(shout_t *shout, int closeConn)
690 unsigned int i;
691 int close_conn = closeConn;
693 printf("%s: Connection to %s lost\n", __progname, pezConfig->URL);
695 i = 0;
696 while (++i) {
697 printf("%s: Attempting reconnection #", __progname);
698 if (pezConfig->reconnectAttempts > 0)
699 printf("%u/%u: ", i,
700 pezConfig->reconnectAttempts);
701 else
702 printf("%u: ", i);
704 if (close_conn == 0)
705 close_conn = 1;
706 else
707 shout_close(shout);
708 if (shout_open(shout) == SHOUTERR_SUCCESS) {
709 printf("OK\n%s: Reconnect to %s successful\n",
710 __progname, pezConfig->URL);
711 return (1);
714 printf("FAILED: %s\n", shout_get_error(shout));
716 if (pezConfig->reconnectAttempts > 0 &&
717 i >= pezConfig->reconnectAttempts)
718 break;
720 printf("%s: Waiting 5s for %s to come back ...\n",
721 __progname, pezConfig->URL);
722 if (quit)
723 return (0);
724 else
725 sleep(5);
728 printf("%s: Giving up\n", __progname);
729 return (0);
732 const char *
733 getTimeString(int seconds)
735 static char str[20];
736 int secs, mins, hours;
738 if (seconds < 0)
739 return (NULL);
741 secs = seconds;
742 hours = secs / 3600;
743 secs %= 3600;
744 mins = secs / 60;
745 secs %= 60;
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;
757 int ret;
758 double kbps = -1.0;
759 struct timeval timeStamp, *startTime = tv;
761 if (startTime == NULL) {
762 printf("%s: sendStream(): Internal error: startTime is NULL\n",
763 __progname);
764 abort();
767 timeStamp.tv_sec = startTime->tv_sec;
768 timeStamp.tv_usec = startTime->tv_usec;
770 total = oldTotal = 0;
771 ret = STREAM_DONE;
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;
776 break;
779 shout_sync(shout);
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))
785 break;
786 else {
787 ret = STREAM_SERVERR;
788 break;
792 if (quit)
793 break;
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",
798 __progname);
800 if (skipTrack) {
801 skipTrack = 0;
802 ret = STREAM_SKIP;
803 break;
805 if (queryMetadata) {
806 queryMetadata = 0;
807 if (metadataFromProgram) {
808 ret = STREAM_UPDMDATA;
809 break;
813 total += read;
814 if (qFlag && vFlag) {
815 struct timeval tv;
816 double oldTime, newTime;
818 if (!isStdin && playlistMode) {
819 if (pezConfig->fileNameIsProgram) {
820 char *tmp = xstrdup(pezConfig->fileName);
821 printf(" [%s]",
822 basename(tmp));
823 xfree(tmp);
824 } else
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)
836 printf(" [ %s]",
837 getTimeString(tv.tv_sec - startTime->tv_sec));
838 else
839 printf(" [ %s/%s]",
840 getTimeString(tv.tv_sec - startTime->tv_sec),
841 songLenStr);
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;
846 oldTotal = total;
848 if (kbps < 0)
849 printf(" ");
850 else
851 printf(" [%8.2f kbps]", kbps);
853 printf(" \r");
854 fflush(stdout);
857 if (ferror(filepstream)) {
858 if (errno == EINTR) {
859 clearerr(filepstream);
860 ret = STREAM_CONT;
861 } else if (errno == EBADF && isStdin)
862 printf("%s: No (more) data available on standard input\n",
863 __progname);
864 else
865 printf("%s: sendStream(): Error while reading '%s': %s\n",
866 __progname, fileName, strerror(errno));
869 return (ret);
873 streamFile(shout_t *shout, const char *fileName)
875 FILE *filepstream = NULL;
876 int popenFlag = 0;
877 char *songLenStr = NULL;
878 int isStdin = 0;
879 int ret, retval = 0, songLen;
880 metadata_t *mdata;
881 struct timeval startTime;
883 if ((filepstream = openResource(shout, fileName, &popenFlag,
884 &mdata, &isStdin, &songLen))
885 == NULL) {
886 return (retval);
889 if (mdata != NULL) {
890 char *tmp, *metaData;
892 tmp = metadata_assemble_string(mdata);
893 if ((metaData = UTF8toCHAR(tmp, ICONV_REPLACE)) == NULL)
894 metaData = xstrdup("(unknown title)");
895 xfree(tmp);
896 printf("%s: Streaming ``%s''", __progname, metaData);
897 if (vFlag)
898 printf(" (file: %s)\n", fileName);
899 else
900 printf("\n");
901 xfree(metaData);
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);
908 } else if (isStdin)
909 printf("%s: Streaming from standard input\n", __progname);
911 if (songLen > 0)
912 songLenStr = xstrdup(getTimeString(songLen));
913 ez_gettimeofday((void *)&startTime);
914 do {
915 ret = sendStream(shout, filepstream, fileName, isStdin,
916 songLenStr, &startTime);
917 if (quit)
918 break;
919 if (ret != STREAM_DONE) {
920 if ((skipTrack && rereadPlaylist) ||
921 (skipTrack && queryMetadata)) {
922 skipTrack = 0;
923 ret = STREAM_CONT;
925 if (queryMetadata && rereadPlaylist) {
926 queryMetadata = 0;
927 ret = STREAM_CONT;
929 if (ret == STREAM_SKIP || skipTrack) {
930 skipTrack = 0;
931 if (!isStdin && vFlag)
932 printf("%s: SIGUSR1 signal received, skipping current track\n",
933 __progname);
934 retval = 1;
935 ret = STREAM_DONE;
937 if (ret == STREAM_UPDMDATA || queryMetadata) {
938 queryMetadata = 0;
939 if (metadataFromProgram) {
940 char *mdataStr = NULL;
941 metadata_t *prog_mdata;
943 if (vFlag > 1)
944 printf("%s: Querying '%s' for fresh metadata\n",
945 __progname, pezConfig->metadataProgram);
946 if ((prog_mdata = getMetadata(pezConfig->metadataProgram)) == NULL) {
947 retval = 0;
948 ret = STREAM_DONE;
949 continue;
951 if (setMetadata(shout, prog_mdata, &mdataStr) != SHOUTERR_SUCCESS) {
952 retval = 0;
953 ret = STREAM_DONE;
954 continue;
956 metadata_free(&prog_mdata);
957 printf("%s: New metadata: ``%s''\n",
958 __progname, mdataStr);
959 xfree(mdataStr);
962 if (ret == STREAM_SERVERR) {
963 retval = 0;
964 ret = STREAM_DONE;
966 } else
967 retval = 1;
968 } while (ret != STREAM_DONE);
970 if (popenFlag)
971 pclose(filepstream);
972 else
973 fclose(filepstream);
975 if (songLenStr != NULL)
976 xfree(songLenStr);
978 return (retval);
982 streamPlaylist(shout_t *shout, const char *fileName)
984 const char *song;
985 char lastSong[PATH_MAX];
987 if (playlist == NULL) {
988 if (pezConfig->fileNameIsProgram) {
989 if ((playlist = playlist_program(fileName)) == NULL)
990 return (0);
991 } else {
992 if ((playlist = playlist_read(fileName)) == NULL)
993 return (0);
994 if (vFlag && playlist_get_num_items(playlist) == 0)
995 printf("%s: Warning: Playlist '%s' is empty\n",
996 __progname, fileName);
998 } else {
1000 * XXX: This preserves traditional behavior, however,
1001 * rereading the playlist after each walkthrough seems a
1002 * bit more logical.
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))
1016 return (0);
1017 if (quit)
1018 break;
1019 if (rereadPlaylist) {
1020 rereadPlaylist = rereadPlaylist_notify = 0;
1021 if (pezConfig->fileNameIsProgram)
1022 continue;
1023 printf("%s: Rereading playlist\n", __progname);
1024 if (!playlist_reread(&playlist))
1025 return (0);
1026 if (pezConfig->shuffle)
1027 playlist_shuffle(playlist);
1028 else {
1029 playlist_goto_entry(playlist, lastSong);
1030 playlist_skip_next(playlist);
1032 continue;
1036 return (1);
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.
1044 char *
1045 getProgname(const char *argv0)
1047 #ifdef HAVE___PROGNAME
1048 return (strdup(__progname));
1049 #else
1050 char *p;
1052 if (argv0 == NULL)
1053 return ((char *)"ezstream");
1054 p = strrchr(argv0, PATH_SEPARATOR);
1055 if (p == NULL)
1056 p = (char *)argv0;
1057 else
1058 p++;
1060 return (strdup(p));
1061 #endif /* HAVE___PROGNAME */
1065 ez_shutdown(int exitval)
1067 shout_shutdown();
1068 playlist_shutdown();
1069 freeConfig(pezConfig);
1070 xalloc_shutdown();
1072 return (exitval);
1075 void
1076 usage(void)
1078 printf("usage: %s [-hnqVv] -c configfile\n", __progname);
1081 void
1082 usageHelp(void)
1084 printf("\n");
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");
1091 printf("\n");
1092 printf("See the ezstream(1) manual for detailed information.\n");
1096 main(int argc, char *argv[])
1098 int c;
1099 char *configFile = NULL;
1100 char *host = NULL;
1101 int port = 0;
1102 char *mount = NULL;
1103 shout_t *shout;
1104 extern char *optarg;
1105 extern int optind;
1106 #ifdef HAVE_SIGNALS
1107 struct sigaction act;
1108 unsigned int i;
1109 #endif
1111 #ifdef XALLOC_DEBUG
1112 xalloc_initialize_debug(2, NULL);
1113 #else
1114 xalloc_initialize();
1115 #endif /* XALLOC_DEBUG */
1116 playlist_init();
1117 shout_init();
1119 __progname = getProgname(argv[0]);
1120 pezConfig = getEZConfig();
1122 nFlag = 0;
1123 qFlag = 0;
1124 vFlag = 0;
1126 while ((c = getopt(argc, argv, "c:hnqVv")) != -1) {
1127 switch (c) {
1128 case 'c':
1129 if (configFile != NULL) {
1130 printf("Error: multiple -c arguments given\n");
1131 usage();
1132 return (ez_shutdown(2));
1134 configFile = xstrdup(optarg);
1135 break;
1136 case 'h':
1137 usage();
1138 usageHelp();
1139 return (ez_shutdown(0));
1140 case 'n':
1141 nFlag = 1;
1142 break;
1143 case 'q':
1144 qFlag = 1;
1145 break;
1146 case 'V':
1147 printf("%s\n", PACKAGE_STRING);
1148 return (ez_shutdown(0));
1149 case 'v':
1150 vFlag++;
1151 break;
1152 case '?':
1153 usage();
1154 return (ez_shutdown(2));
1155 default:
1156 break;
1159 argc -= optind;
1160 argv += optind;
1162 if (configFile == NULL) {
1163 printf("You must supply a config file with the -c argument.\n");
1164 usage();
1165 return (ez_shutdown(2));
1166 } else {
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.
1172 #ifdef HAVE_STAT
1173 struct stat st;
1175 if (stat(configFile, &st) == -1) {
1176 printf("%s: %s\n", configFile, strerror(errno));
1177 usage();
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));
1188 #else
1189 FILE *tmp;
1191 if ((tmp = fopen(configFile, "r")) == NULL) {
1192 printf("%s: %s\n", configFile, strerror(errno));
1193 usage();
1194 return (ez_shutdown(2));
1196 fclose(tmp);
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");
1234 xfree(configFile);
1236 if ((shout = stream_setup(host, port, mount)) == NULL)
1237 return (ez_shutdown(1));
1239 if (pezConfig->metadataProgram != NULL)
1240 metadataFromProgram = 1;
1241 else
1242 metadataFromProgram = 0;
1244 #ifdef HAVE_SIGNALS
1245 memset(&act, 0, sizeof(act));
1246 act.sa_handler = sig_handler;
1247 # ifdef SA_RESTART
1248 act.sa_flags = SA_RESTART;
1249 # endif
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) {
1270 int ret;
1272 printf("%s: Connected to http://%s:%d%s\n", __progname,
1273 host, port, mount);
1275 if (pezConfig->fileNameIsProgram ||
1276 strrcasecmp(pezConfig->fileName, ".m3u") == 0 ||
1277 strrcasecmp(pezConfig->fileName, ".txt") == 0)
1278 playlistMode = 1;
1279 else
1280 playlistMode = 0;
1282 if (vFlag && pezConfig->fileNameIsProgram)
1283 printf("%s: Using program '%s' to get filenames for streaming\n",
1284 __progname, pezConfig->fileName);
1286 ret = 1;
1287 do {
1288 if (playlistMode) {
1289 ret = streamPlaylist(shout,
1290 pezConfig->fileName);
1291 } else {
1292 ret = streamFile(shout, pezConfig->fileName);
1294 if (quit)
1295 break;
1296 if (pezConfig->streamOnce)
1297 break;
1298 } while (ret);
1300 shout_close(shout);
1301 } else
1302 printf("%s: Connection to http://%s:%d%s failed: %s\n", __progname,
1303 host, port, mount, shout_get_error(shout));
1305 if (quit)
1306 printf("\r%s: SIGINT or SIGTERM received\n", __progname);
1308 if (vFlag)
1309 printf("%s: Exiting ...\n", __progname);
1311 xfree(host);
1312 xfree(mount);
1313 playlist_free(&playlist);
1315 return (ez_shutdown(0));