ezstream.c 25.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
char *		shellQuote(const char *);
char *		replaceString(const char *, const char *, const char *);
80
char *		buildReencodeCommand(const char *, const char *, metadata_t *);
81
char *		getMetadataString(const char *, metadata_t *);
82
83
metadata_t *	getMetadata(const char *);
int		setMetadata(shout_t *, metadata_t *, char **);
84
FILE *		openResource(shout_t *, const char *, int *, metadata_t **,
85
			     int *, long *);
86
int		reconnectServer(shout_t *, int);
87
const char *	getTimeString(long);
88
int		sendStream(shout_t *, FILE *, const char *, int, const char *,
89
			   struct timespec *);
90
int		streamFile(shout_t *, const char *);
91
int		streamPlaylist(shout_t *);
92
int		ez_shutdown(int);
oddsock's avatar
oddsock committed
93

94
#ifdef HAVE_SIGNALS
95
96
97
98
99
void		sig_handler(int);

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

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

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

134
	if (strncmp(url, "http://", strlen("http://")) != 0) {
135
		log_error("invalid <url>: not an HTTP address");
136
		return (0);
137
	}
138

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

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

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

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

	return (1);
oddsock's avatar
oddsock committed
174
175
}

176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#define SHELLQUOTE_INLEN_MAX	8191UL

char *
shellQuote(const char *in)
{
	char		*out, *out_p;
	size_t		 out_len;
	const char	*in_p;

	out_len = (strlen(in) > SHELLQUOTE_INLEN_MAX)
	    ? SHELLQUOTE_INLEN_MAX
	    : strlen(in);
	out_len = out_len * 2 + 2;
	out = xcalloc(out_len + 1, sizeof(char));

	out_p = out;
	in_p = in;

	*out_p++ = '\'';
	out_len--;
	while (*in_p && out_len > 2) {
		switch (*in_p) {
		case '\'':
		case '\\':
			*out_p++ = '\\';
			out_len--;
			break;
		default:
			break;
		}
		*out_p++ = *in_p++;
		out_len--;
	}
	*out_p++ = '\'';

	return (out);
}

char *
replaceString(const char *source, const char *from, const char *to)
216
{
217
218
219
220
221
222
223
	char		*to_quoted, *dest;
	size_t		 dest_size;
	const char	*p1, *p2;

	to_quoted = shellQuote(to);
	dest_size = strlen(source) + strlen(to_quoted) + 1;
	dest = xcalloc(dest_size, sizeof(char));
224

225
	p1 = source;
226
227
	p2 = strstr(p1, from);
	if (p2 != NULL) {
228
		strncat(dest, p1, (size_t)(p2 - p1));
229
		strlcat(dest, to_quoted, dest_size);
230
		p1 = p2 + strlen(from);
231
	}
232
233
234
235
236
	strlcat(dest, p1, dest_size);

	xfree(to_quoted);

	return (dest);
237
238
}

239
char *
240
241
buildReencodeCommand(const char *extension, const char *fileName,
    metadata_t *mdata)
242
{
243
244
245
246
247
248
249
250
251
252
	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",
253
		    fileName, extension);
254
		return (NULL);
255
	}
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
	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,
272
		    localArtist);
273
274
		xfree(dec_str);
		dec_str = tmpStr;
275
	}
276
277
	if (strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
		char *tmpStr = replaceString(dec_str, PLACEHOLDER_TITLE,
278
		    localTitle);
279
280
		xfree(dec_str);
		dec_str = tmpStr;
281
	}
282
283
284
285
286
287
288
289
290
291
	/*
	 * if meta
	 *   if (prog && format)
	 *      metatoformat
	 *   else
	 *     if (!prog && title)
	 *       emptymeta
	 *     else
	 *       replacemeta
	 */
292
293
294
295
296
297
298
299
	if (strstr(dec_str, PLACEHOLDER_METADATA) != NULL) {
		if (cfg_get_metadata_program() &&
		    cfg_get_metadata_format_str()) {
			char *mdataString = getMetadataString(cfg_get_metadata_format_str(),
			    mdata);
			char *tmpStr = replaceString(dec_str,
			    PLACEHOLDER_METADATA, mdataString);
			xfree(dec_str);
300
			xfree(mdataString);
301
			dec_str = tmpStr;
302
		} else {
303
304
305
306
307
308
			if (!cfg_get_metadata_program() &&
			    strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
				char *tmpStr = replaceString(dec_str,
				    PLACEHOLDER_METADATA, "");
				xfree(dec_str);
				dec_str = tmpStr;
309
			} else {
310
311
312
313
				char *tmpStr = replaceString(dec_str,
				    PLACEHOLDER_METADATA, localMetaString);
				xfree(dec_str);
				dec_str = tmpStr;
314
315
316
			}
		}
	}
317

318
319
320
	if (!cfg_encoder_get_program(encoder))
		return (dec_str);

321
322
323
324
	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,
325
		    localTitle);
326
327
		xfree(enc_str);
		enc_str = tmpStr;
328
	}
329
330
331
332
333
334
335
336
	if (strstr(enc_str, PLACEHOLDER_METADATA) != NULL) {
		if (cfg_get_metadata_program() &&
		    cfg_get_metadata_format_str()) {
			char *mdataString = getMetadataString(cfg_get_metadata_format_str(),
			    mdata);
			char *tmpStr = replaceString(enc_str,
			    PLACEHOLDER_METADATA, mdataString);
			xfree(enc_str);
337
			xfree(mdataString);
338
			enc_str = tmpStr;
339
		} else {
340
341
342
343
344
345
			if (!cfg_get_metadata_program() &&
			    strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
				char *tmpStr = replaceString(enc_str,
				    PLACEHOLDER_METADATA, "");
				xfree(enc_str);
				enc_str = tmpStr;
346
			} else {
347
348
349
350
				char *tmpStr = replaceString(enc_str,
				    PLACEHOLDER_METADATA, localMetaString);
				xfree(enc_str);
				enc_str = tmpStr;
351
352
353
			}
		}
	}
354

355
356
	commandStringLen = strlen(dec_str) + strlen(" | ") +
	    strlen(enc_str) + 1;
357
	commandString = xcalloc(commandStringLen, sizeof(char));
358
359
	snprintf(commandString, commandStringLen, "%s | %s", dec_str,
	    enc_str);
360

361
362
363
	xfree(localTitle);
	xfree(localArtist);
	xfree(localMetaString);
364
365
	xfree(dec_str);
	xfree(enc_str);
366
367

	return (commandString);
368
}
oddsock's avatar
oddsock committed
369

370
371
372
373
374
375
376
377
378
379
char *
getMetadataString(const char *format, metadata_t *mdata)
{
	char	*tmp, *str;

	if (format == NULL)
		return (NULL);

	str = xstrdup(format);

380
381
	if (strstr(format, PLACEHOLDER_ARTIST) != NULL) {
		tmp = replaceString(str, PLACEHOLDER_ARTIST,
382
		    metadata_get_artist(mdata));
383
384
385
		xfree(str);
		str = tmp;
	}
386
387
	if (strstr(format, PLACEHOLDER_TITLE) != NULL) {
		tmp = replaceString(str, PLACEHOLDER_TITLE,
388
		    metadata_get_title(mdata));
389
390
391
		xfree(str);
		str = tmp;
	}
392
393
	if (strstr(format, PLACEHOLDER_STRING) != NULL) {
		tmp = replaceString(str, PLACEHOLDER_STRING,
394
		    metadata_get_string(mdata));
395
396
397
		xfree(str);
		str = tmp;
	}
398
399
	if (strstr(format, PLACEHOLDER_TRACK) != NULL) {
		tmp = replaceString(str, PLACEHOLDER_TRACK,
400
		    metadata_get_filename(mdata));
401
402
403
404
405
406
407
		xfree(str);
		str = tmp;
	}

	return (str);
}

408
409
metadata_t *
getMetadata(const char *fileName)
moritz's avatar
moritz committed
410
{
411
	metadata_t	*mdata;
412

413
414
415
	if (cfg_get_metadata_program()) {
		if (NULL == (mdata = metadata_program(fileName,
		    cfg_get_metadata_normalize_strings())))
416
417
			return (NULL);

418
		if (!metadata_program_update(mdata, METADATA_ALL)) {
419
			metadata_free(&mdata);
420
			return (NULL);
421
422
		}
	} else {
423
424
		if (NULL == (mdata = metadata_file(fileName,
		    cfg_get_metadata_normalize_strings())))
425
			return (NULL);
moritz's avatar
moritz committed
426

427
428
		if (!metadata_file_update(mdata)) {
			metadata_free(&mdata);
429
			return (NULL);
430
		}
431
	}
432

433
434
435
436
437
438
439
	return (mdata);
}

int
setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
{
	shout_metadata_t	*shout_mdata = NULL;
moritz's avatar
moritz committed
440
441
	char			*songInfo;
	const char		*artist, *title;
442
443
	int			 ret = SHOUTERR_SUCCESS;

444
	if (cfg_get_metadata_no_updates())
445
446
		return (SHOUTERR_SUCCESS);

447
448
	if (mdata == NULL)
		return 1;
449

450
	if ((shout_mdata = shout_metadata_new()) == NULL) {
451
		log_syserr(ALERT, ENOMEM, "shout_metadata_new");
moritz's avatar
moritz committed
452
		exit(1);
453
	}
454

moritz's avatar
moritz committed
455
456
457
458
459
460
461
462
463
464
465
	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 */
466
		log_syserr(ALERT, ENOMEM, "shout_metadata_add");
moritz's avatar
moritz committed
467
468
469
		exit(1);
	}

470
471
	songInfo = getMetadataString(cfg_get_metadata_format_str(), mdata);
	if (songInfo == NULL) {
moritz's avatar
moritz committed
472
		if (artist[0] == '\0' && title[0] == '\0')
473
474
475
			songInfo = xstrdup(metadata_get_string(mdata));
		else
			songInfo = metadata_assemble_string(mdata);
moritz's avatar
moritz committed
476
477
		if (artist[0] != '\0' && title[0] != '\0') {
			if (shout_metadata_add(shout_mdata, "artist", artist) != SHOUTERR_SUCCESS) {
478
479
				log_syserr(ALERT, ENOMEM,
				    "shout_metadata_add");
moritz's avatar
moritz committed
480
481
482
				exit(1);
			}
			if (shout_metadata_add(shout_mdata, "title", title) != SHOUTERR_SUCCESS) {
483
484
				log_syserr(ALERT, ENOMEM,
				    "shout_metadata_add");
moritz's avatar
moritz committed
485
486
487
488
				exit(1);
			}
		} else {
			if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
489
490
				log_syserr(ALERT, ENOMEM,
				    "shout_metadata_add");
moritz's avatar
moritz committed
491
492
493
494
				exit(1);
			}
		}
	} else if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
495
		log_syserr(ALERT, ENOMEM, "shout_metadata_add");
496
497
		exit(1);
	}
moritz's avatar
moritz committed
498

499
	if ((ret = shout_set_metadata(shout, shout_mdata)) != SHOUTERR_SUCCESS)
500
		log_warning("shout_set_metadata: %s", shout_get_error(shout));
moritz's avatar
moritz committed
501

502
	shout_metadata_free(shout_mdata);
moritz's avatar
moritz committed
503
504
505
506
507

	if (ret == SHOUTERR_SUCCESS) {
		if (mdata_copy != NULL && *mdata_copy == NULL)
			*mdata_copy = xstrdup(songInfo);
	}
moritz's avatar
moritz committed
508

509
510
	xfree(songInfo);
	return (ret);
511
512
}

513
514
FILE *
openResource(shout_t *shout, const char *fileName, int *popenFlag,
515
	     metadata_t **mdata_p, int *isStdin, long *songLen)
516
{
517
518
519
520
521
	FILE		*filep = NULL;
	char		 extension[25];
	char		*p = NULL;
	char		*pCommandString = NULL;
	metadata_t	*mdata;
oddsock's avatar
oddsock committed
522

523
524
	if (mdata_p != NULL)
		*mdata_p = NULL;
525
526
	if (songLen != NULL)
		*songLen = 0;
527

528
	if (strcmp(fileName, "stdin") == 0) {
529
530
		if (cfg_get_metadata_program()) {
			if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
531
				return (NULL);
532
			if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) {
533
534
535
				metadata_free(&mdata);
				return (NULL);
			}
536
537
538
539
			if (mdata_p != NULL)
				*mdata_p = mdata;
			else
				metadata_free(&mdata);
540
		}
541

542
543
		if (isStdin != NULL)
			*isStdin = 1;
544
		filep = stdin;
545
		return (filep);
oddsock's avatar
oddsock committed
546
	}
547

548
549
550
551
	if (isStdin != NULL)
		*isStdin = 0;

	extension[0] = '\0';
552
553
554
555
556
557
558
	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) {
559
		log_error("%s: cannot determine file type", fileName);
560
561
562
		return (filep);
	}

563
564
	if (cfg_get_metadata_program()) {
		if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
565
566
567
568
569
			return (NULL);
	} else {
		if ((mdata = getMetadata(fileName)) == NULL)
			return (NULL);
	}
570
571
	if (songLen != NULL)
		*songLen = metadata_get_length(mdata);
572
573

	*popenFlag = 0;
574
	if (cfg_get_stream_encoder()) {
575
		int	stderr_fd = -1;
576

577
578
		pCommandString = buildReencodeCommand(extension, fileName,
		    mdata);
579
580
581
582
		if (mdata_p != NULL)
			*mdata_p = mdata;
		else
			metadata_free(&mdata);
583
		log_info("running command: %s", pCommandString);
584

585
		if (cfg_get_program_quiet_stderr()) {
586
587
			int fd;

588
			stderr_fd = dup(fileno(stderr));
589
			if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
590
591
				log_alert("%s: %s", _PATH_DEVNULL,
				    strerror(errno));
592
				exit(1);
593
594
			}

595
596
597
598
599
600
601
602
603
604
			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)
605
606
				log_error("execution error: %s: %s",
				    pCommandString, strerror(errno));
607
			else
608
609
				log_error("execution error: %s",
				    pCommandString);
610
611
612
613
		} else {
			*popenFlag = 1;
		}
		xfree(pCommandString);
614

615
		if (cfg_get_program_quiet_stderr())
616
			dup2(stderr_fd, fileno(stderr));
617
618
619

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

621
		return (filep);
622
	}
623

624
625
626
627
	if (mdata_p != NULL)
		*mdata_p = mdata;
	else
		metadata_free(&mdata);
628

629
630
631
632
	if ((filep = fopen(fileName, "rb")) == NULL) {
		log_error("%s: %s", fileName, strerror(errno));
		return (NULL);
	}
633
634

	return (filep);
635
636
}

637
638
639
640
641
642
int
reconnectServer(shout_t *shout, int closeConn)
{
	unsigned int	i;
	int		close_conn = closeConn;

643
	log_warning("%s: connection lost", cfg_get_server_hostname());
644
645
646

	i = 0;
	while (++i) {
647
		if (cfg_get_server_reconnect_attempts() > 0)
648
			log_notice("reconnect: %s: attempt #%u/%u ...",
649
650
			    cfg_get_server_hostname(), i,
			    cfg_get_server_reconnect_attempts());
651
		else
652
			log_notice("reconnect: %s: attempt #%u ...",
653
			    cfg_get_server_hostname(), i);
654
655
656
657
658
659

		if (close_conn == 0)
			close_conn = 1;
		else
			shout_close(shout);
		if (shout_open(shout) == SHOUTERR_SUCCESS) {
660
			log_notice("reconnect: %s: success",
661
			    cfg_get_server_hostname());
662
663
664
			return (1);
		}

665
		log_warning("reconnect failed: %s: %s",
666
		    cfg_get_server_hostname(), shout_get_error(shout));
667

668
669
		if (cfg_get_server_reconnect_attempts() > 0 &&
		    i >= cfg_get_server_reconnect_attempts())
670
671
			break;

672
673
674
675
		if (quit)
			return (0);
		else
			sleep(5);
676
677
	};

678
	log_warning("reconnect failed: giving up");
679
680
681
	return (0);
}

682
const char *
683
getTimeString(long seconds)
684
685
{
	static char	str[20];
686
	long		secs, mins, hours;
687
688
689
690
691
692
693
694
695
696

	if (seconds < 0)
		return (NULL);

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

697
	snprintf(str, sizeof(str), "%ldh%02ldm%02lds", hours, mins, secs);
698
699
700
	return ((const char *)str);
}

701
int
702
sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
703
	   int isStdin, const char *songLenStr, struct timespec *tv)
704
705
{
	unsigned char	 buff[4096];
706
	size_t		 bytes_read, total, oldTotal;
707
	int		 ret;
708
	double		 kbps = -1.0;
709
710
	struct timespec	 timeStamp, *startTime = tv;
	struct timespec	 callTime, currentTime;
711

712
	clock_gettime(CLOCK_MONOTONIC, &callTime);
713

714
	timeStamp.tv_sec = startTime->tv_sec;
715
	timeStamp.tv_nsec = startTime->tv_nsec;
716
717

	total = oldTotal = 0;
718
	ret = STREAM_DONE;
719
	while ((bytes_read = fread(buff, 1UL, sizeof(buff), filepstream)) > 0) {
720
721
722
723
724
725
		if (shout_get_connected(shout) != SHOUTERR_CONNECTED &&
		    reconnectServer(shout, 0) == 0) {
			ret = STREAM_SERVERR;
			break;
		}

726
727
		shout_sync(shout);

728
		if (shout_send(shout, buff, bytes_read) != SHOUTERR_SUCCESS) {
729
			log_error("shout_send: %s", shout_get_error(shout));
730
731
732
733
734
735
736
737
			if (reconnectServer(shout, 1))
				break;
			else {
				ret = STREAM_SERVERR;
				break;
			}
		}

738
739
		if (quit)
			break;
740
741
		if (rereadPlaylist_notify) {
			rereadPlaylist_notify = 0;
742
			if (CFG_MEDIA_PLAYLIST == cfg_get_media_type())
743
				log_notice("HUP signal received: playlist re-read scheduled");
744
745
746
		}
		if (skipTrack) {
			skipTrack = 0;
747
			ret = STREAM_SKIP;
748
749
			break;
		}
750

751
		clock_gettime(CLOCK_MONOTONIC, &currentTime);
752
753

		if (queryMetadata ||
754
		    (0 <= cfg_get_metadata_refresh_interval() &&
755
756
			(currentTime.tv_sec - callTime.tv_sec >=
			    cfg_get_metadata_refresh_interval()))) {
757
			queryMetadata = 0;
758
			if (cfg_get_metadata_program()) {
759
				ret = STREAM_UPDMDATA;
760
				break;
oddsock's avatar
oddsock committed
761
			}
762
763
		}

764
		total += bytes_read;
765
		if (cfg_get_program_rtstatus_output()) {
766
			double	oldTime, newTime;
767

768
			if (!isStdin && playlistMode) {
769
770
				if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
					char *tmp = xstrdup(cfg_get_media_filename());
771
					printf("  [%s]", basename(tmp));
772
773
774
					xfree(tmp);
				} else
					printf("  [%4lu/%-4lu]",
775
776
					    playlist_get_position(playlist),
					    playlist_get_num_items(playlist));
777
			}
778
779

			oldTime = (double)timeStamp.tv_sec
780
			    + (double)timeStamp.tv_nsec / 1000000000.0;
781
			newTime = (double)currentTime.tv_sec
782
			    + (double)currentTime.tv_nsec / 1000000000.0;
783
784
			if (songLenStr == NULL)
				printf("  [ %s]",
785
786
				    getTimeString(currentTime.tv_sec -
					startTime->tv_sec));
787
788
			else
				printf("  [ %s/%s]",
789
790
791
				    getTimeString(currentTime.tv_sec -
					startTime->tv_sec),
				    songLenStr);
792
			if (newTime - oldTime >= 1.0) {
793
				kbps = (((double)(total - oldTotal)
794
				    / (newTime - oldTime)) * 8.0) / 1000.0;
795
				timeStamp.tv_sec = currentTime.tv_sec;
796
				timeStamp.tv_nsec = currentTime.tv_nsec;
797
798
799
800
801
802
803
804
805
806
				oldTotal = total;
			}
			if (kbps < 0)
				printf("                 ");
			else
				printf("  [%8.2f kbps]", kbps);

			printf("  \r");
			fflush(stdout);
		}
oddsock's avatar
oddsock committed
807
	}
808
809
810
	if (ferror(filepstream)) {
		if (errno == EINTR) {
			clearerr(filepstream);
811
			ret = STREAM_CONT;
812
		} else if (errno == EBADF && isStdin)
813
			log_notice("no (more) data available on standard input");
814
		else
815
816
			log_error("sendStream: %s: %s", fileName,
			    strerror(errno));
817
818
	}

819
	return (ret);
820
821
822
823
824
825
826
}

int
streamFile(shout_t *shout, const char *fileName)
{
	FILE		*filepstream = NULL;
	int		 popenFlag = 0;
827
	char		*songLenStr = NULL;
828
	int		 isStdin = 0;
829
830
	int		 ret, retval = 0;
	long		 songLen;
831
	metadata_t	*mdata;
832
	struct timespec	 startTime;
833

834
	if ((filepstream = openResource(shout, fileName, &popenFlag, &mdata, &isStdin, &songLen))
835
836
	    == NULL) {
		if (++resource_errors > 100) {
837
			log_error("too many errors; giving up");
838
839
			return (0);
		}
840
841
		/* Continue with next resource on failure: */
		return (1);
842
843
	}
	resource_errors = 0;
844

845
	if (mdata != NULL) {
846
		char	*tmp, *metaData;
847

848
		tmp = metadata_assemble_string(mdata);
849
850
		if ((metaData = UTF8toCHAR(tmp, ICONV_REPLACE)) == NULL)
			metaData = xstrdup("(unknown title)");
851
		xfree(tmp);
852
		log_notice("streaming: %s (%s)", metaData, fileName);
853
		xfree(metaData);
854
855

		/* MP3 streams are special, so set the metadata explicitly: */
856
		if (CFG_STREAM_MP3 == cfg_get_stream_format())
857
858
859
			setMetadata(shout, mdata, NULL);

		metadata_free(&mdata);
860
	} else if (isStdin)
861
		log_notice("streaming: standard input");
862

moritz's avatar
moritz committed
863
	if (songLen > 0)
864
		songLenStr = xstrdup(getTimeString(songLen));
865
	clock_gettime(CLOCK_MONOTONIC, &startTime);
866
867
	do {
		ret = sendStream(shout, filepstream, fileName, isStdin,
868
		    songLenStr, &startTime);
869
870
		if (quit)
			break;
871
		if (ret != STREAM_DONE) {
872
873
			if ((skipTrack && rereadPlaylist) ||
			    (skipTrack && queryMetadata)) {
874
				skipTrack = 0;
875
876
877
878
879
				ret = STREAM_CONT;
			}
			if (queryMetadata && rereadPlaylist) {
				queryMetadata = 0;
				ret = STREAM_CONT;
880
			}
881
			if (ret == STREAM_SKIP || skipTrack) {
882
				skipTrack = 0;
883
884
				if (!isStdin)
					log_notice("USR1 signal received: skipping current track");
885
				retval = 1;
886
887
				ret = STREAM_DONE;
			}
888
889
			if (ret == STREAM_UPDMDATA || queryMetadata) {
				queryMetadata = 0;
890
				if (cfg_get_metadata_no_updates())
891
					continue;
892
				if (cfg_get_metadata_program()) {
893
					char		*mdataStr = NULL;
894
					metadata_t	*prog_mdata;
895

896
					log_info("running metadata program: %s",
897
898
					    cfg_get_metadata_program());
					if ((prog_mdata = getMetadata(cfg_get_metadata_program())) == NULL) {
899
900
901
902
						retval = 0;
						ret = STREAM_DONE;
						continue;
					}
903
					if (setMetadata(shout, prog_mdata, &mdataStr) != SHOUTERR_SUCCESS) {
904
905
						retval = 0;
						ret = STREAM_DONE;
906
						continue;
907
					}
908
					metadata_free(&prog_mdata);
909
					log_info("new metadata: %s", mdataStr);
910
911
912
					xfree(mdataStr);
				}
			}