ezstream.c 21.7 KB
Newer Older
1
2
3
/*
 *  ezstream - source client for Icecast with external en-/decoder support
 *  Copyright (C) 2003, 2004, 2005, 2006  Ed Zaleski <oddsock@oddsock.org>
4
 *  Copyright (C) 2007, 2009, 2015        Moritz Grimm <mgrimm@mrsserver.net>
5
6
 *
 *  This program is free software; you can redistribute it and/or modify
moritz's avatar
moritz committed
7
8
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
9
10
11
12
13
14
15
16
17
18
 *
 *  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.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
oddsock's avatar
oddsock committed
19

20
21
#include "compat.h"

22
23
#include "ezstream.h"

24
25
26
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
27

oddsock's avatar
oddsock committed
28
#include <shout/shout.h>
29

30
#include "cfg.h"
31
#include "cmdline.h"
32
#include "log.h"
33
#include "metadata.h"
34
#include "playlist.h"
35
#include "stream.h"
36
#include "util.h"
37
#include "xalloc.h"
38

39
40
41
42
#define STREAM_DONE	0
#define STREAM_CONT	1
#define STREAM_SKIP	2
#define STREAM_SERVERR	3
43
#define STREAM_UPDMDATA 4
44

45
46
47
playlist_t		 playlist;
int			 playlistMode;
unsigned int		 resource_errors;
48

49
#ifdef HAVE_SIGNALS
50
51
52
const int		 ezstream_signals[] = {
	SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2
};
53

54
55
56
57
58
volatile sig_atomic_t	 rereadPlaylist;
volatile sig_atomic_t	 rereadPlaylist_notify;
volatile sig_atomic_t	 skipTrack;
volatile sig_atomic_t	 queryMetadata;
volatile sig_atomic_t	 quit;
59
#else
60
61
62
63
64
int			 rereadPlaylist;
int			 rereadPlaylist_notify;
int			 skipTrack;
int			 queryMetadata;
int			 quit;
65
#endif /* HAVE_SIGNALS */
oddsock's avatar
oddsock committed
66
67

typedef struct tag_ID3Tag {
68
69
70
71
72
73
74
	char	tag[3];
	char	trackName[30];
	char	artistName[30];
	char	albumName[30];
	char	year[3];
	char	comment[30];
	char	genre;
oddsock's avatar
oddsock committed
75
76
} ID3Tag;

77
int		urlParse(const char *, char **, unsigned short *, char **);
78
79
80
char *		buildReencodeCommand(const char *, const char *, metadata_t);
metadata_t	getMetadata(const char *);
FILE *		openResource(stream_t, const char *, int *, metadata_t *,
81
			     int *, long *);
82
int		reconnectServer(shout_t *, int);
83
const char *	getTimeString(long);
84
int		sendStream(stream_t, FILE *, const char *, int, const char *,
85
			   struct timespec *);
86
87
int		streamFile(stream_t, const char *);
int		streamPlaylist(stream_t);
88
int		ez_shutdown(int);
oddsock's avatar
oddsock committed
89

90
#ifdef HAVE_SIGNALS
91
92
93
94
95
void		sig_handler(int);

# ifndef SIG_IGN
#  define SIG_IGN	 (void (*)(int))1
# endif /* !SIG_IGN */
96

97
98
99
100
void
sig_handler(int sig)
{
	switch (sig) {
101
102
103
104
	case SIGTERM:
	case SIGINT:
		quit = 1;
		break;
105
106
107
108
109
110
111
	case SIGHUP:
		rereadPlaylist = 1;
		rereadPlaylist_notify = 1;
		break;
	case SIGUSR1:
		skipTrack = 1;
		break;
112
113
114
	case SIGUSR2:
		queryMetadata = 1;
		break;
115
116
117
118
	default:
		break;
	}
}
119
#endif /* HAVE_SIGNALS */
120

121
int
122
123
urlParse(const char *url, char **hostname, unsigned short *port,
	 char **mountname)
oddsock's avatar
oddsock committed
124
{
125
	const char	*p1, *p2, *p3;
126
127
128
	char		 tmpPort[6] = "";
	size_t		 hostsiz, mountsiz;
	const char	*errstr;
oddsock's avatar
oddsock committed
129

130
	if (strncmp(url, "http://", strlen("http://")) != 0) {
131
		log_error("invalid <url>: not an HTTP address");
132
		return (0);
133
	}
134

135
	p1 = url + strlen("http://");
oddsock's avatar
oddsock committed
136
	p2 = strchr(p1, ':');
137
	if (p2 == NULL) {
138
		log_error("invalid <url>: missing port");
139
		return (0);
140
	}
141
	hostsiz = (p2 - p1) + 1;
142
143
144
145
	if (hostsiz <= 1) {
		log_error("invalid <url>: missing host");
		return (0);
	}
146
147
148
	*hostname = xmalloc(hostsiz);
	strlcpy(*hostname, p1, hostsiz);

oddsock's avatar
oddsock committed
149
150
	p2++;
	p3 = strchr(p2, '/');
151
	if (p3 == NULL || p3 - p2 >= (int)sizeof(tmpPort)) {
152
		log_error("invalid <url>: mountpoint missing, or port number too long");
153
		xfree(*hostname);
154
		return (0);
155
	}
156

157
158
	strlcpy(tmpPort, p2, (p3 - p2) + 1UL);
	*port = (unsigned short)strtonum(tmpPort, 1LL, (long long)USHRT_MAX, &errstr);
159
	if (errstr) {
160
		log_error("invalid <url>: port: %s is %s", tmpPort, errstr);
161
162
163
		xfree(*hostname);
		return (0);
	}
164
165
166
167
168
169

	mountsiz = strlen(p3) + 1;
	*mountname = xmalloc(mountsiz);
	strlcpy(*mountname, p3, mountsiz);

	return (1);
oddsock's avatar
oddsock committed
170
171
}

172
char *
173
buildReencodeCommand(const char *extension, const char *fileName,
174
    metadata_t mdata)
175
{
176
177
178
179
180
181
182
183
184
185
	cfg_decoder_t	 decoder;
	cfg_encoder_t	 encoder;
	char		*dec_str, *enc_str;
	char		*commandString;
	size_t		 commandStringLen;
	char		*localTitle, *localArtist, *localMetaString;

	decoder = cfg_decoder_find(extension);
	if (!decoder) {
		log_error("cannot decode: %s: unsupported file extension %s",
186
		    fileName, extension);
187
		return (NULL);
188
	}
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
	encoder = cfg_encoder_get(cfg_get_stream_encoder());
	if (!encoder) {
		log_error("cannot encode: %s: unknown encoder",
		    cfg_get_stream_encoder());
		return (NULL);
	}

	localTitle = UTF8toCHAR(metadata_get_title(mdata), ICONV_REPLACE);
	localArtist = UTF8toCHAR(metadata_get_artist(mdata), ICONV_REPLACE);
	localMetaString = UTF8toCHAR(metadata_get_string(mdata),
	    ICONV_REPLACE);

	dec_str = replaceString(cfg_decoder_get_program(decoder),
	    PLACEHOLDER_TRACK, fileName);
	if (strstr(dec_str, PLACEHOLDER_ARTIST) != NULL) {
		char *tmpStr = replaceString(dec_str, PLACEHOLDER_ARTIST,
205
		    localArtist);
206
207
		xfree(dec_str);
		dec_str = tmpStr;
208
	}
209
210
	if (strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
		char *tmpStr = replaceString(dec_str, PLACEHOLDER_TITLE,
211
		    localTitle);
212
213
		xfree(dec_str);
		dec_str = tmpStr;
214
	}
215
216
217
218
219
220
221
222
223
224
	/*
	 * if meta
	 *   if (prog && format)
	 *      metatoformat
	 *   else
	 *     if (!prog && title)
	 *       emptymeta
	 *     else
	 *       replacemeta
	 */
225
226
227
	if (strstr(dec_str, PLACEHOLDER_METADATA) != NULL) {
		if (cfg_get_metadata_program() &&
		    cfg_get_metadata_format_str()) {
228
			char *mdataString = stream_get_metadata_str(cfg_get_metadata_format_str(),
229
230
231
232
			    mdata);
			char *tmpStr = replaceString(dec_str,
			    PLACEHOLDER_METADATA, mdataString);
			xfree(dec_str);
233
			xfree(mdataString);
234
			dec_str = tmpStr;
235
		} else {
236
237
238
239
240
241
			if (!cfg_get_metadata_program() &&
			    strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
				char *tmpStr = replaceString(dec_str,
				    PLACEHOLDER_METADATA, "");
				xfree(dec_str);
				dec_str = tmpStr;
242
			} else {
243
244
245
246
				char *tmpStr = replaceString(dec_str,
				    PLACEHOLDER_METADATA, localMetaString);
				xfree(dec_str);
				dec_str = tmpStr;
247
248
249
			}
		}
	}
250

251
252
253
	if (!cfg_encoder_get_program(encoder))
		return (dec_str);

254
255
256
257
	enc_str = replaceString(cfg_encoder_get_program(encoder),
	    PLACEHOLDER_ARTIST, localArtist);
	if (strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
		char *tmpStr = replaceString(enc_str, PLACEHOLDER_TITLE,
258
		    localTitle);
259
260
		xfree(enc_str);
		enc_str = tmpStr;
261
	}
262
263
264
	if (strstr(enc_str, PLACEHOLDER_METADATA) != NULL) {
		if (cfg_get_metadata_program() &&
		    cfg_get_metadata_format_str()) {
265
			char *mdataString = stream_get_metadata_str(cfg_get_metadata_format_str(),
266
267
268
269
			    mdata);
			char *tmpStr = replaceString(enc_str,
			    PLACEHOLDER_METADATA, mdataString);
			xfree(enc_str);
270
			xfree(mdataString);
271
			enc_str = tmpStr;
272
		} else {
273
274
275
276
277
278
			if (!cfg_get_metadata_program() &&
			    strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
				char *tmpStr = replaceString(enc_str,
				    PLACEHOLDER_METADATA, "");
				xfree(enc_str);
				enc_str = tmpStr;
279
			} else {
280
281
282
283
				char *tmpStr = replaceString(enc_str,
				    PLACEHOLDER_METADATA, localMetaString);
				xfree(enc_str);
				enc_str = tmpStr;
284
285
286
			}
		}
	}
287

288
289
	commandStringLen = strlen(dec_str) + strlen(" | ") +
	    strlen(enc_str) + 1;
290
	commandString = xcalloc(commandStringLen, sizeof(char));
291
292
	snprintf(commandString, commandStringLen, "%s | %s", dec_str,
	    enc_str);
293

294
295
296
	xfree(localTitle);
	xfree(localArtist);
	xfree(localMetaString);
297
298
	xfree(dec_str);
	xfree(enc_str);
299
300

	return (commandString);
301
}
oddsock's avatar
oddsock committed
302

303
metadata_t
304
getMetadata(const char *fileName)
moritz's avatar
moritz committed
305
{
306
	metadata_t	mdata;
307

308
309
310
	if (cfg_get_metadata_program()) {
		if (NULL == (mdata = metadata_program(fileName,
		    cfg_get_metadata_normalize_strings())))
311
312
			return (NULL);

313
		if (!metadata_program_update(mdata, METADATA_ALL)) {
314
			metadata_free(&mdata);
315
			return (NULL);
316
317
		}
	} else {
318
319
		if (NULL == (mdata = metadata_file(fileName,
		    cfg_get_metadata_normalize_strings())))
320
			return (NULL);
moritz's avatar
moritz committed
321

322
323
		if (!metadata_file_update(mdata)) {
			metadata_free(&mdata);
324
			return (NULL);
325
		}
326
	}
327

328
329
330
	return (mdata);
}

331
FILE *
332
333
openResource(stream_t stream, const char *fileName, int *popenFlag,
	     metadata_t *mdata_p, int *isStdin, long *songLen)
334
{
335
336
337
338
	FILE		*filep = NULL;
	char		 extension[25];
	char		*p = NULL;
	char		*pCommandString = NULL;
339
	metadata_t	 mdata;
oddsock's avatar
oddsock committed
340

341
342
	if (mdata_p != NULL)
		*mdata_p = NULL;
343
344
	if (songLen != NULL)
		*songLen = 0;
345

346
	if (strcmp(fileName, "stdin") == 0) {
347
348
		if (cfg_get_metadata_program()) {
			if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
349
				return (NULL);
350
			if (stream_set_metadata(stream, mdata, NULL) != SHOUTERR_SUCCESS) {
351
352
353
				metadata_free(&mdata);
				return (NULL);
			}
354
355
356
357
			if (mdata_p != NULL)
				*mdata_p = mdata;
			else
				metadata_free(&mdata);
358
		}
359

360
361
		if (isStdin != NULL)
			*isStdin = 1;
362
		filep = stdin;
363
		return (filep);
oddsock's avatar
oddsock committed
364
	}
365

366
367
368
369
	if (isStdin != NULL)
		*isStdin = 0;

	extension[0] = '\0';
370
371
372
373
374
375
376
	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) {
377
		log_error("%s: cannot determine file type", fileName);
378
379
380
		return (filep);
	}

381
382
	if (cfg_get_metadata_program()) {
		if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
383
384
385
386
387
			return (NULL);
	} else {
		if ((mdata = getMetadata(fileName)) == NULL)
			return (NULL);
	}
388
389
	if (songLen != NULL)
		*songLen = metadata_get_length(mdata);
390
391

	*popenFlag = 0;
392
	if (cfg_get_stream_encoder()) {
393
		int	stderr_fd = -1;
394

395
396
		pCommandString = buildReencodeCommand(extension, fileName,
		    mdata);
397
398
399
400
		if (mdata_p != NULL)
			*mdata_p = mdata;
		else
			metadata_free(&mdata);
401
		log_info("running command: %s", pCommandString);
402

403
		if (cfg_get_program_quiet_stderr()) {
404
405
			int fd;

406
			stderr_fd = dup(fileno(stderr));
407
			if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
408
409
				log_alert("%s: %s", _PATH_DEVNULL,
				    strerror(errno));
410
				exit(1);
411
412
			}

413
414
415
416
417
418
419
420
421
422
			dup2(fd, fileno(stderr));
			if (fd > 2)
				close(fd);
		}

		fflush(NULL);
		errno = 0;
		if ((filep = popen(pCommandString, "r")) == NULL) {
			/* popen() does not set errno reliably ... */
			if (errno)
423
424
				log_error("execution error: %s: %s",
				    pCommandString, strerror(errno));
425
			else
426
427
				log_error("execution error: %s",
				    pCommandString);
428
429
430
431
		} else {
			*popenFlag = 1;
		}
		xfree(pCommandString);
432

433
		if (cfg_get_program_quiet_stderr())
434
			dup2(stderr_fd, fileno(stderr));
435
436
437

		if (stderr_fd > 2)
			close(stderr_fd);
438

439
		return (filep);
440
	}
441

442
443
444
445
	if (mdata_p != NULL)
		*mdata_p = mdata;
	else
		metadata_free(&mdata);
446

447
448
449
450
	if ((filep = fopen(fileName, "rb")) == NULL) {
		log_error("%s: %s", fileName, strerror(errno));
		return (NULL);
	}
451
452

	return (filep);
453
454
}

455
456
457
458
459
460
int
reconnectServer(shout_t *shout, int closeConn)
{
	unsigned int	i;
	int		close_conn = closeConn;

461
	log_warning("%s: connection lost", cfg_get_server_hostname());
462
463
464

	i = 0;
	while (++i) {
465
		if (cfg_get_server_reconnect_attempts() > 0)
466
			log_notice("reconnect: %s: attempt #%u/%u ...",
467
468
			    cfg_get_server_hostname(), i,
			    cfg_get_server_reconnect_attempts());
469
		else
470
			log_notice("reconnect: %s: attempt #%u ...",
471
			    cfg_get_server_hostname(), i);
472
473
474
475
476
477

		if (close_conn == 0)
			close_conn = 1;
		else
			shout_close(shout);
		if (shout_open(shout) == SHOUTERR_SUCCESS) {
478
			log_notice("reconnect: %s: success",
479
			    cfg_get_server_hostname());
480
481
482
			return (1);
		}

483
		log_warning("reconnect failed: %s: %s",
484
		    cfg_get_server_hostname(), shout_get_error(shout));
485

486
487
		if (cfg_get_server_reconnect_attempts() > 0 &&
		    i >= cfg_get_server_reconnect_attempts())
488
489
			break;

490
491
492
493
		if (quit)
			return (0);
		else
			sleep(5);
494
495
	};

496
	log_warning("reconnect failed: giving up");
497
498
499
	return (0);
}

500
const char *
501
getTimeString(long seconds)
502
503
{
	static char	str[20];
504
	long		secs, mins, hours;
505
506
507
508
509
510
511
512
513
514

	if (seconds < 0)
		return (NULL);

	secs = seconds;
	hours = secs / 3600;
	secs %= 3600;
	mins = secs / 60;
	secs %= 60;

515
	snprintf(str, sizeof(str), "%ldh%02ldm%02lds", hours, mins, secs);
516
517
518
	return ((const char *)str);
}

519
int
520
sendStream(stream_t stream, FILE *filepstream, const char *fileName,
521
	   int isStdin, const char *songLenStr, struct timespec *tv)
522
{
523
524
525
526
527
528
529
	unsigned char	  buff[4096];
	size_t		  bytes_read, total, oldTotal;
	int		  ret;
	double		  kbps = -1.0;
	struct timespec	  timeStamp, *startTime = tv;
	struct timespec	  callTime, currentTime;
	shout_t 	 *shout = stream_get_shout(stream);
530

531
	clock_gettime(CLOCK_MONOTONIC, &callTime);
532

533
	timeStamp.tv_sec = startTime->tv_sec;
534
	timeStamp.tv_nsec = startTime->tv_nsec;
535
536

	total = oldTotal = 0;
537
	ret = STREAM_DONE;
538
	while ((bytes_read = fread(buff, 1UL, sizeof(buff), filepstream)) > 0) {
539
540
541
542
543
544
		if (shout_get_connected(shout) != SHOUTERR_CONNECTED &&
		    reconnectServer(shout, 0) == 0) {
			ret = STREAM_SERVERR;
			break;
		}

545
546
		shout_sync(shout);

547
		if (shout_send(shout, buff, bytes_read) != SHOUTERR_SUCCESS) {
548
			log_error("shout_send: %s", shout_get_error(shout));
549
550
551
552
553
554
555
556
			if (reconnectServer(shout, 1))
				break;
			else {
				ret = STREAM_SERVERR;
				break;
			}
		}

557
558
		if (quit)
			break;
559
560
		if (rereadPlaylist_notify) {
			rereadPlaylist_notify = 0;
561
			if (CFG_MEDIA_PLAYLIST == cfg_get_media_type())
562
				log_notice("HUP signal received: playlist re-read scheduled");
563
564
565
		}
		if (skipTrack) {
			skipTrack = 0;
566
			ret = STREAM_SKIP;
567
568
			break;
		}
569

570
		clock_gettime(CLOCK_MONOTONIC, &currentTime);
571
572

		if (queryMetadata ||
573
		    (0 <= cfg_get_metadata_refresh_interval() &&
574
575
			(currentTime.tv_sec - callTime.tv_sec >=
			    cfg_get_metadata_refresh_interval()))) {
576
			queryMetadata = 0;
577
			if (cfg_get_metadata_program()) {
578
				ret = STREAM_UPDMDATA;
579
				break;
oddsock's avatar
oddsock committed
580
			}
581
582
		}

583
		total += bytes_read;
584
		if (cfg_get_program_rtstatus_output()) {
585
			double	oldTime, newTime;
586

587
			if (!isStdin && playlistMode) {
588
589
				if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
					char *tmp = xstrdup(cfg_get_media_filename());
590
					printf("  [%s]", basename(tmp));
591
592
593
					xfree(tmp);
				} else
					printf("  [%4lu/%-4lu]",
594
595
					    playlist_get_position(playlist),
					    playlist_get_num_items(playlist));
596
			}
597
598

			oldTime = (double)timeStamp.tv_sec
599
			    + (double)timeStamp.tv_nsec / 1000000000.0;
600
			newTime = (double)currentTime.tv_sec
601
			    + (double)currentTime.tv_nsec / 1000000000.0;
602
603
			if (songLenStr == NULL)
				printf("  [ %s]",
604
605
				    getTimeString(currentTime.tv_sec -
					startTime->tv_sec));
606
607
			else
				printf("  [ %s/%s]",
608
609
610
				    getTimeString(currentTime.tv_sec -
					startTime->tv_sec),
				    songLenStr);
611
			if (newTime - oldTime >= 1.0) {
612
				kbps = (((double)(total - oldTotal)
613
				    / (newTime - oldTime)) * 8.0) / 1000.0;
614
				timeStamp.tv_sec = currentTime.tv_sec;
615
				timeStamp.tv_nsec = currentTime.tv_nsec;
616
617
618
619
620
621
622
623
624
625
				oldTotal = total;
			}
			if (kbps < 0)
				printf("                 ");
			else
				printf("  [%8.2f kbps]", kbps);

			printf("  \r");
			fflush(stdout);
		}
oddsock's avatar
oddsock committed
626
	}
627
628
629
	if (ferror(filepstream)) {
		if (errno == EINTR) {
			clearerr(filepstream);
630
			ret = STREAM_CONT;
631
		} else if (errno == EBADF && isStdin)
632
			log_notice("no (more) data available on standard input");
633
		else
634
635
			log_error("sendStream: %s: %s", fileName,
			    strerror(errno));
636
637
	}

638
	return (ret);
639
640
641
}

int
642
streamFile(stream_t stream, const char *fileName)
643
644
645
{
	FILE		*filepstream = NULL;
	int		 popenFlag = 0;
646
	char		*songLenStr = NULL;
647
	int		 isStdin = 0;
648
649
	int		 ret, retval = 0;
	long		 songLen;
650
	metadata_t	 mdata;
651
	struct timespec	 startTime;
652

653
	if ((filepstream = openResource(stream, fileName, &popenFlag, &mdata, &isStdin, &songLen))
654
655
	    == NULL) {
		if (++resource_errors > 100) {
656
			log_error("too many errors; giving up");
657
658
			return (0);
		}
659
660
		/* Continue with next resource on failure: */
		return (1);
661
662
	}
	resource_errors = 0;
663

664
	if (mdata != NULL) {
665
		char	*tmp, *metaData;
666

667
		tmp = metadata_assemble_string(mdata);
668
669
		if ((metaData = UTF8toCHAR(tmp, ICONV_REPLACE)) == NULL)
			metaData = xstrdup("(unknown title)");
670
		xfree(tmp);
671
		log_notice("streaming: %s (%s)", metaData, fileName);
672
		xfree(metaData);
673
674

		/* MP3 streams are special, so set the metadata explicitly: */
675
		if (CFG_STREAM_MP3 == cfg_get_stream_format())
676
			stream_set_metadata(stream, mdata, NULL);
677
678

		metadata_free(&mdata);
679
	} else if (isStdin)
680
		log_notice("streaming: standard input");
681

moritz's avatar
moritz committed
682
	if (songLen > 0)
683
		songLenStr = xstrdup(getTimeString(songLen));
684
	clock_gettime(CLOCK_MONOTONIC, &startTime);
685
	do {
686
		ret = sendStream(stream, filepstream, fileName, isStdin,
687
		    songLenStr, &startTime);
688
689
		if (quit)
			break;
690
		if (ret != STREAM_DONE) {
691
692
			if ((skipTrack && rereadPlaylist) ||
			    (skipTrack && queryMetadata)) {
693
				skipTrack = 0;
694
695
696
697
698
				ret = STREAM_CONT;
			}
			if (queryMetadata && rereadPlaylist) {
				queryMetadata = 0;
				ret = STREAM_CONT;
699
			}
700
			if (ret == STREAM_SKIP || skipTrack) {
701
				skipTrack = 0;
702
703
				if (!isStdin)
					log_notice("USR1 signal received: skipping current track");
704
				retval = 1;
705
706
				ret = STREAM_DONE;
			}
707
708
			if (ret == STREAM_UPDMDATA || queryMetadata) {
				queryMetadata = 0;
709
				if (cfg_get_metadata_no_updates())
710
					continue;
711
				if (cfg_get_metadata_program()) {
712
					char		*mdataStr = NULL;
713
					metadata_t	 prog_mdata;
714

715
					log_info("running metadata program: %s",
716
717
					    cfg_get_metadata_program());
					if ((prog_mdata = getMetadata(cfg_get_metadata_program())) == NULL) {
718
719
720
721
						retval = 0;
						ret = STREAM_DONE;
						continue;
					}
722
					if (stream_set_metadata(stream, prog_mdata, &mdataStr) != SHOUTERR_SUCCESS) {
723
724
						retval = 0;
						ret = STREAM_DONE;
725
						continue;
726
					}
727
					metadata_free(&prog_mdata);
728
					log_info("new metadata: %s", mdataStr);
729
730
731
					xfree(mdataStr);
				}
			}
732
733
734
			if (ret == STREAM_SERVERR) {
				retval = 0;
				ret = STREAM_DONE;
735
736
737
			}
		} else
			retval = 1;
738
	} while (ret != STREAM_DONE);
739
740

	if (popenFlag)
oddsock's avatar
oddsock committed
741
		pclose(filepstream);
Moritz Grimm's avatar
Moritz Grimm committed
742
	else if (!isStdin)
oddsock's avatar
oddsock committed
743
		fclose(filepstream);
744

745
746
747
	if (songLenStr != NULL)
		xfree(songLenStr);

748
	return (retval);
oddsock's avatar
oddsock committed
749
}
750

751
int
752
streamPlaylist(stream_t stream)
753
754
{
	const char	*song;
moritz's avatar
moritz committed
755
	char		 lastSong[PATH_MAX];
756
757

	if (playlist == NULL) {
758
759
		switch (cfg_get_media_type()) {
		case CFG_MEDIA_PROGRAM:
760
			if ((playlist = playlist_program(cfg_get_media_filename())) == NULL)
761
				return (0);
762
763
764
765
766
767
			break;
		case CFG_MEDIA_STDIN:
			if ((playlist = playlist_read(NULL)) == NULL)
				return (0);
			break;
		default:
768
			if ((playlist = playlist_read(cfg_get_media_filename())) == NULL)
769
				return (0);
770
			if (playlist_get_num_items(playlist) == 0)
771
				log_warning("%s: playlist empty",
772
				    cfg_get_media_filename());
773
			break;
774
		}
775
	} else {
776
777
778
779
780
		/*
		 * XXX: This preserves traditional behavior, however,
		 *      rereading the playlist after each walkthrough seems a
		 *      bit more logical.
		 */
781
		playlist_rewind(playlist);
782
	}
783

784
785
	if (CFG_MEDIA_PROGRAM != cfg_get_media_type() &&
	    cfg_get_media_shuffle())
786
787
788
789
		playlist_shuffle(playlist);

	while ((song = playlist_get_next(playlist)) != NULL) {
		strlcpy(lastSong, song, sizeof(lastSong));
790
		if (!streamFile(stream, song))
791
			return (0);
792
793
		if (quit)
			break;
794
795
		if (rereadPlaylist) {
			rereadPlaylist = rereadPlaylist_notify = 0;
796
			if (CFG_MEDIA_PROGRAM == cfg_get_media_type())
797
				continue;
798
			log_notice("rereading playlist");
799
800
			if (!playlist_reread(&playlist))
				return (0);
801
			if (cfg_get_media_shuffle())
802
803
804
805
				playlist_shuffle(playlist);
			else {
				playlist_goto_entry(playlist, lastSong);
				playlist_skip_next(playlist);
oddsock's avatar
oddsock committed
806
			}
807
			continue;
808
		}
oddsock's avatar
oddsock committed
809
	}
810

811
	return (1);
oddsock's avatar
oddsock committed
812
813
}

814
int
815
ez_shutdown(int exitval)
816
{
817
	stream_exit();
818
819
820
	playlist_exit();
	cfg_encoder_exit();
	cfg_decoder_exit();
Moritz Grimm's avatar
Moritz Grimm committed
821
	log_exit();
822
	cfg_exit();
823
824
825
826

	return (exitval);
}

827
int
828
main(int argc, char *argv[])
oddsock's avatar
oddsock committed
829
{
Moritz Grimm's avatar
Moritz Grimm committed
830
	int		 ret;
831
	const char	*errstr;
832
	stream_t	 stream;
833
834
835
	shout_t 	*shout;
	extern char	*optarg;
	extern int	 optind;
836
837
#ifdef HAVE_SIGNALS
	struct sigaction act;
moritz's avatar
moritz committed
838
	unsigned int	 i;
839
#endif
840

Moritz Grimm's avatar
Moritz Grimm committed
841
	ret = 1;
842
843
	if (0 > cfg_init() ||
	    0 > cmdline_parse(argc, argv, &ret) ||
844
845
846
847
	    0 > log_init() ||
	    0 > cfg_decoder_init() ||
	    0 > cfg_encoder_init() ||
	    0 > playlist_init() ||
848
849
	    0 > cfg_reload() ||
	    0 > stream_init())
Moritz Grimm's avatar
Moritz Grimm committed
850