mirror of
https://gitlab.xiph.org/xiph/ezstream.git
synced 2025-02-02 15:07:45 -05:00
Refactor configuration (work in progress)
* Move -m and -n command line options into the config file * Restructure configuration file: - Group into "server", "stream", "media", "metadata", "decoders", and "encoders" - Untangle decoder and encoder: o Decoders match on file extensions and convert to a canonical "internal" format o Encoders create one of the supported stream formats, potentially using different parameters (like bitrate) - Consistently specify stream format - Enable reencoding by selecting an encoder * Architecturally separate configuration file storage from parsing - Allows for different configuration back-ends in the future, like YAML, SQL, REST API, ... * Support roll-back in case of error on (re)load * Anticipate HTTPS support
This commit is contained in:
parent
ffa2a01c96
commit
43e48648fa
3
NEWS
3
NEWS
@ -5,6 +5,9 @@ Changes in X.X.X, released on XXXX-XX-XX:
|
||||
* The behaviour of the -s command line argument was changed:
|
||||
To shuffle lines from standard input, the special file name "-" needs
|
||||
to be provided.
|
||||
* The command line options -m and -n have been removed, and new configuration
|
||||
file settings have been added accordingly.
|
||||
* The configuration file structure has changed.
|
||||
|
||||
|
||||
Changes in 0.6.0, released on 2015-01-18:
|
||||
|
67
examples/ezstream-full.xml
Normal file
67
examples/ezstream-full.xml
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<ezstream>
|
||||
|
||||
<server>
|
||||
<protocol></protocol>
|
||||
<hostname></hostname>
|
||||
<port></port>
|
||||
<user></user>
|
||||
<password></password>
|
||||
<ca_dir></ca_dir>
|
||||
<ca_file></ca_file>
|
||||
<client_cert></client_cert>
|
||||
<client_key></client_key>
|
||||
<reconnect_attempts></reconnect_attempts>
|
||||
</server>
|
||||
|
||||
<stream>
|
||||
<mountpoint></mountpoint>
|
||||
<name></name>
|
||||
<url></url>
|
||||
<genre></genre>
|
||||
<description></description>
|
||||
<quality></quality>
|
||||
<bitrate></bitrate>
|
||||
<samplerate></samplerate>
|
||||
<channels></channels>
|
||||
<server_public></server_public>
|
||||
<format></format>
|
||||
<encoder></encoder>
|
||||
</stream>
|
||||
|
||||
<media>
|
||||
<type></type>
|
||||
<filename></filename>
|
||||
<shuffle></shuffle>
|
||||
<stream_once></stream_once>
|
||||
</media>
|
||||
|
||||
<metadata>
|
||||
<program></program>
|
||||
<format_str></format_str>
|
||||
<refresh_interval></refresh_interval>
|
||||
<normalize_strings></normalize_strings>
|
||||
<no_updates></no_updates>
|
||||
</metadata>
|
||||
|
||||
<decoders>
|
||||
<decoder>
|
||||
<name></name>
|
||||
<program></program>
|
||||
<file_ext></file_ext>
|
||||
<!-- ... -->
|
||||
</decoder>
|
||||
<!-- ... -->
|
||||
</decoders>
|
||||
|
||||
<encoders>
|
||||
<encoder>
|
||||
<name></name>
|
||||
<format></format>
|
||||
<program></program>
|
||||
</encoder>
|
||||
<!-- ... -->
|
||||
</encoders>
|
||||
|
||||
</ezstream>
|
@ -6,7 +6,11 @@ bin_SCRIPTS = ezstream-file.sh
|
||||
noinst_HEADERS = \
|
||||
attributes.h \
|
||||
cfg.h \
|
||||
configfile.h \
|
||||
cfg_decoder.h \
|
||||
cfg_encoder.h \
|
||||
cfg_private.h \
|
||||
cfg_xmlfile.h \
|
||||
cmdline.h \
|
||||
ezstream.h \
|
||||
log.h \
|
||||
metadata.h \
|
||||
@ -15,7 +19,10 @@ noinst_HEADERS = \
|
||||
xalloc.h
|
||||
ezstream_SOURCES = \
|
||||
cfg.c \
|
||||
configfile.c \
|
||||
cfg_decoder.c \
|
||||
cfg_encoder.c \
|
||||
cfg_xmlfile.c \
|
||||
cmdline.c \
|
||||
ezstream.c \
|
||||
log.c \
|
||||
metadata.c \
|
||||
|
171
src/cfg.h
171
src/cfg.h
@ -17,19 +17,170 @@
|
||||
#ifndef __CFG_H__
|
||||
#define __CFG_H__
|
||||
|
||||
int cfg_cmdline_parse(int, char *[], int *);
|
||||
#define CFG_SFMT_VORBIS "VORBIS"
|
||||
#define CFG_SFMT_MP3 "MP3"
|
||||
#define CFG_SFMT_THEORA "THEORA"
|
||||
|
||||
#define PLACEHOLDER_TRACK "@T@"
|
||||
#define PLACEHOLDER_METADATA "@M@"
|
||||
#define PLACEHOLDER_ARTIST "@a@"
|
||||
#define PLACEHOLDER_TITLE "@t@"
|
||||
#define PLACEHOLDER_STRING "@s@"
|
||||
|
||||
enum cfg_config_type {
|
||||
CFG_TYPE_XMLFILE = 0,
|
||||
CFG_TYPE_MIN = CFG_TYPE_XMLFILE,
|
||||
CFG_TYPE_MAX = CFG_TYPE_XMLFILE,
|
||||
};
|
||||
|
||||
enum cfg_server_protocol {
|
||||
CFG_PROTO_HTTP = 0,
|
||||
CFG_PROTO_HTTPS,
|
||||
CFG_PROTO_MIN = CFG_PROTO_HTTP,
|
||||
CFG_PROTO_MAX = CFG_PROTO_HTTPS,
|
||||
};
|
||||
|
||||
enum cfg_media_type {
|
||||
CFG_MEDIA_AUTODETECT = 0,
|
||||
CFG_MEDIA_FILE,
|
||||
CFG_MEDIA_PLAYLIST,
|
||||
CFG_MEDIA_PROGRAM,
|
||||
CFG_MEDIA_STDIN,
|
||||
CFG_MEDIA_MIN = CFG_MEDIA_AUTODETECT,
|
||||
CFG_MEDIA_MAX = CFG_MEDIA_STDIN,
|
||||
};
|
||||
|
||||
enum cfg_stream_format {
|
||||
CFG_STREAM_INVALID = 0,
|
||||
CFG_STREAM_VORBIS,
|
||||
CFG_STREAM_MP3,
|
||||
CFG_STREAM_THEORA,
|
||||
CFG_STREAM_MIN = CFG_STREAM_VORBIS,
|
||||
CFG_STREAM_MAX = CFG_STREAM_THEORA,
|
||||
};
|
||||
|
||||
#include "cfg_decoder.h"
|
||||
#include "cfg_encoder.h"
|
||||
|
||||
int cfg_reload(void);
|
||||
void cfg_exit(void);
|
||||
|
||||
int cfg_stream_str2fmt(const char *, enum cfg_stream_format *);
|
||||
const char *
|
||||
cfg_stream_fmt2str(enum cfg_stream_format);
|
||||
|
||||
int cfg_set_program_name(const char *, const char **);
|
||||
int cfg_set_program_config_type(enum cfg_config_type, const char **);
|
||||
int cfg_set_program_config_file(const char *, const char **);
|
||||
int cfg_set_program_quiet_stderr(int, const char **);
|
||||
int cfg_set_program_verbosity(unsigned int, const char **);
|
||||
|
||||
int cfg_set_server_protocol(const char *, const char **);
|
||||
int cfg_set_server_hostname(const char *, const char **);
|
||||
int cfg_set_server_port(const char *, const char **);
|
||||
int cfg_set_server_user(const char *, const char **);
|
||||
int cfg_set_server_password(const char *, const char **);
|
||||
int cfg_set_server_ca_dir(const char *, const char **);
|
||||
int cfg_set_server_ca_file(const char *, const char **);
|
||||
int cfg_set_server_client_cert(const char *, const char **);
|
||||
int cfg_set_server_client_key(const char *, const char **);
|
||||
int cfg_set_server_reconnect_attempts(const char *, const char **);
|
||||
|
||||
int cfg_set_stream_mountpoint(const char *, const char **);
|
||||
int cfg_set_stream_name(const char *, const char **);
|
||||
int cfg_set_stream_url(const char *, const char **);
|
||||
int cfg_set_stream_genre(const char *, const char **);
|
||||
int cfg_set_stream_description(const char *, const char **);
|
||||
int cfg_set_stream_quality(const char *, const char **);
|
||||
int cfg_set_stream_bitrate(const char *, const char **);
|
||||
int cfg_set_stream_samplerate(const char *, const char **);
|
||||
int cfg_set_stream_channels(const char *, const char **);
|
||||
int cfg_set_stream_server_public(const char *, const char **);
|
||||
int cfg_set_stream_format(const char *, const char **);
|
||||
int cfg_set_stream_encoder(const char *, const char **);
|
||||
|
||||
int cfg_set_media_type(const char *, const char **);
|
||||
int cfg_set_media_filename(const char *, const char **);
|
||||
int cfg_set_media_shuffle(const char *, const char **);
|
||||
int cfg_set_media_stream_once(const char *, const char **);
|
||||
|
||||
int cfg_set_metadata_program(const char *, const char **);
|
||||
int cfg_set_metadata_format_str(const char *, const char **);
|
||||
int cfg_set_metadata_refresh_interval(const char *, const char **);
|
||||
int cfg_set_metadata_normalize_strings(const char *, const char **);
|
||||
int cfg_set_metadata_no_updates(const char *, const char **);
|
||||
|
||||
const char *
|
||||
cfg_progname(void);
|
||||
|
||||
cfg_get_program_name(void);
|
||||
enum cfg_config_type
|
||||
cfg_get_program_config_type(void);
|
||||
const char *
|
||||
cfg_config_file(void);
|
||||
int cfg_no_metadata_updates(void);
|
||||
int cfg_normalize_strings(void);
|
||||
int cfg_quiet_stderr(void);
|
||||
const char *
|
||||
cfg_shuffle_file(void);
|
||||
cfg_get_program_config_file(void);
|
||||
int cfg_get_program_quiet_stderr(void);
|
||||
unsigned int
|
||||
cfg_verbosity(void);
|
||||
cfg_get_program_verbosity(void);
|
||||
|
||||
enum cfg_server_protocol
|
||||
cfg_get_server_protocol(void);
|
||||
const char *
|
||||
cfg_get_server_protocol_str(void);
|
||||
const char *
|
||||
cfg_get_server_hostname(void);
|
||||
unsigned int
|
||||
cfg_get_server_port(void);
|
||||
const char *
|
||||
cfg_get_server_user(void);
|
||||
const char *
|
||||
cfg_get_server_password(void);
|
||||
const char *
|
||||
cfg_get_server_ca_dir(void);
|
||||
const char *
|
||||
cfg_get_server_ca_file(void);
|
||||
const char *
|
||||
cfg_get_server_client_cert(void);
|
||||
const char *
|
||||
cfg_get_server_client_key(void);
|
||||
unsigned int
|
||||
cfg_get_server_reconnect_attempts(void);
|
||||
|
||||
const char *
|
||||
cfg_get_stream_mountpoint(void);
|
||||
const char *
|
||||
cfg_get_stream_name(void);
|
||||
const char *
|
||||
cfg_get_stream_url(void);
|
||||
const char *
|
||||
cfg_get_stream_genre(void);
|
||||
const char *
|
||||
cfg_get_stream_description(void);
|
||||
const char *
|
||||
cfg_get_stream_quality(void);
|
||||
const char *
|
||||
cfg_get_stream_bitrate(void);
|
||||
const char *
|
||||
cfg_get_stream_samplerate(void);
|
||||
const char *
|
||||
cfg_get_stream_channels(void);
|
||||
int cfg_get_stream_server_public(void);
|
||||
enum cfg_stream_format
|
||||
cfg_get_stream_format(void);
|
||||
const char *
|
||||
cfg_get_stream_encoder(void);
|
||||
|
||||
enum cfg_media_type
|
||||
cfg_get_media_type(void);
|
||||
const char *
|
||||
cfg_get_media_filename(void);
|
||||
int cfg_get_media_shuffle(void);
|
||||
int cfg_get_media_stream_once(void);
|
||||
|
||||
const char *
|
||||
cfg_get_metadata_program(void);
|
||||
const char *
|
||||
cfg_get_metadata_format_str(void);
|
||||
unsigned int
|
||||
cfg_get_metadata_refresh_interval(void);
|
||||
int cfg_get_metadata_normalize_strings(void);
|
||||
int cfg_get_metadata_no_updates(void);
|
||||
|
||||
#endif /* __CFG_H__ */
|
||||
|
219
src/cfg_decoder.c
Normal file
219
src/cfg_decoder.c
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <sys/queue.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg_private.h"
|
||||
#include "cfg_decoder.h"
|
||||
#include "log.h"
|
||||
#include "xalloc.h"
|
||||
|
||||
struct file_ext {
|
||||
TAILQ_ENTRY(file_ext) entry;
|
||||
char *ext;
|
||||
};
|
||||
TAILQ_HEAD(file_ext_list, file_ext);
|
||||
|
||||
struct cfg_decoder {
|
||||
TAILQ_ENTRY(cfg_decoder) entry;
|
||||
char *name;
|
||||
char *program;
|
||||
struct file_ext_list exts;
|
||||
};
|
||||
TAILQ_HEAD(cfg_decoder_list, cfg_decoder);
|
||||
|
||||
static struct cfg_decoder_list cfg_decoders;
|
||||
|
||||
int
|
||||
cfg_decoder_init(void)
|
||||
{
|
||||
TAILQ_INIT(&cfg_decoders);
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
cfg_decoder_exit(void)
|
||||
{
|
||||
struct cfg_decoder *d;
|
||||
|
||||
while (NULL != (d = TAILQ_FIRST(&cfg_decoders))) {
|
||||
struct file_ext *e;
|
||||
|
||||
TAILQ_REMOVE(&cfg_decoders, d, entry);
|
||||
xfree(d->name);
|
||||
xfree(d->program);
|
||||
while (NULL != (e = TAILQ_FIRST(&d->exts))) {
|
||||
TAILQ_REMOVE(&d->exts, e, entry);
|
||||
xfree(e->ext);
|
||||
xfree(e);
|
||||
}
|
||||
xfree(d);
|
||||
}
|
||||
}
|
||||
|
||||
struct cfg_decoder *
|
||||
cfg_decoder_get(const char *name)
|
||||
{
|
||||
struct cfg_decoder *d;
|
||||
|
||||
if (!name || !name[0])
|
||||
return (NULL);
|
||||
|
||||
TAILQ_FOREACH(d, &cfg_decoders, entry) {
|
||||
if (0 == strcasecmp(d->name, name))
|
||||
return (d);
|
||||
}
|
||||
|
||||
d = xcalloc(1UL, sizeof(*d));
|
||||
d->name = xstrdup(name);
|
||||
TAILQ_INIT(&d->exts);
|
||||
|
||||
return (d);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_decoder_set_name(struct cfg_decoder *d, const char *name,
|
||||
const char **errstrp)
|
||||
{
|
||||
if (!name || !name[0]) {
|
||||
if (errstrp)
|
||||
*errstrp = "empty";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
xfree(d->name);
|
||||
d->name = xstrdup(name);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_decoder_set_program(struct cfg_decoder *d, const char *program,
|
||||
const char **errstrp)
|
||||
{
|
||||
if (!program || !program[0]) {
|
||||
if (errstrp)
|
||||
*errstrp = "empty";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
xfree(d->program);
|
||||
d->program = xstrdup(program);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_decoder_add_match(struct cfg_decoder *d, const char *ext,
|
||||
const char **errstrp)
|
||||
{
|
||||
struct cfg_decoder *d2;
|
||||
struct file_ext *e, *e2;
|
||||
|
||||
if (!ext || !ext[0]) {
|
||||
if (errstrp)
|
||||
*errstrp = "empty";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
d2 = cfg_decoder_find(ext);
|
||||
e = NULL;
|
||||
if (d2) {
|
||||
while (NULL != (e2 = TAILQ_FIRST(&d2->exts))) {
|
||||
if (0 == strcasecmp(e2->ext, ext)) {
|
||||
log_notice("%s: relocating match from %s to %s",
|
||||
ext, d2->name, d->name);
|
||||
TAILQ_REMOVE(&d2->exts, e2, entry);
|
||||
e = e2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!e) {
|
||||
e = xcalloc(1UL, sizeof(*e));
|
||||
e->ext = xstrdup(ext);
|
||||
}
|
||||
TAILQ_INSERT_TAIL(&d->exts, e, entry);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_decoder_validate(struct cfg_decoder *d, const char **errstrp)
|
||||
{
|
||||
struct file_ext *e;
|
||||
unsigned int num_exts;
|
||||
|
||||
if (!d->program) {
|
||||
if (errstrp)
|
||||
*errstrp = "program not set";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
num_exts = 0;
|
||||
TAILQ_FOREACH(e, &d->exts, entry) {
|
||||
num_exts++;
|
||||
}
|
||||
if (!num_exts) {
|
||||
if (errstrp)
|
||||
*errstrp = "no file extensions registered";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
CHECKPH_PROHIBITED(d->program, PLACEHOLDER_STRING);
|
||||
CHECKPH_DUPLICATE(d->program, PLACEHOLDER_TRACK);
|
||||
CHECKPH_DUPLICATE(d->program, PLACEHOLDER_METADATA);
|
||||
CHECKPH_DUPLICATE(d->program, PLACEHOLDER_ARTIST);
|
||||
CHECKPH_DUPLICATE(d->program, PLACEHOLDER_TITLE);
|
||||
CHECKPH_REQUIRED(d->program, PLACEHOLDER_TRACK);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
struct cfg_decoder *
|
||||
cfg_decoder_find(const char *ext)
|
||||
{
|
||||
struct cfg_decoder *d;
|
||||
|
||||
TAILQ_FOREACH(d, &cfg_decoders, entry) {
|
||||
struct file_ext *e;
|
||||
|
||||
TAILQ_FOREACH(e, &d->exts, entry) {
|
||||
if (0 == strcasecmp(e->ext, ext))
|
||||
return (d);
|
||||
}
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
const char *
|
||||
cfg_decoder_get_name(struct cfg_decoder *d)
|
||||
{
|
||||
return (d->name);
|
||||
}
|
||||
|
||||
const char *
|
||||
cfg_decoder_get_program(struct cfg_decoder *d)
|
||||
{
|
||||
return (d->program);
|
||||
}
|
42
src/cfg_decoder.h
Normal file
42
src/cfg_decoder.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __CFG_INPUT_H__
|
||||
#define __CFG_INPUT_H__
|
||||
|
||||
typedef struct cfg_decoder * cfg_decoder_t;
|
||||
|
||||
int cfg_decoder_init(void);
|
||||
void cfg_decoder_exit(void);
|
||||
|
||||
cfg_decoder_t
|
||||
cfg_decoder_get(const char *);
|
||||
|
||||
int cfg_decoder_set_name(cfg_decoder_t, const char *, const char **);
|
||||
int cfg_decoder_set_program(cfg_decoder_t, const char *, const char **);
|
||||
int cfg_decoder_add_match(cfg_decoder_t, const char *, const char **);
|
||||
|
||||
int cfg_decoder_validate(cfg_decoder_t, const char **);
|
||||
|
||||
cfg_decoder_t
|
||||
cfg_decoder_find(const char *);
|
||||
|
||||
const char *
|
||||
cfg_decoder_get_name(cfg_decoder_t);
|
||||
const char *
|
||||
cfg_decoder_get_program(cfg_decoder_t);
|
||||
|
||||
#endif /* __CFG_INPUT_H__ */
|
184
src/cfg_encoder.c
Normal file
184
src/cfg_encoder.c
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <sys/queue.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg_private.h"
|
||||
#include "cfg_encoder.h"
|
||||
#include "xalloc.h"
|
||||
|
||||
struct cfg_encoder {
|
||||
TAILQ_ENTRY(cfg_encoder) entry;
|
||||
char *name;
|
||||
enum cfg_stream_format format;
|
||||
char *program;
|
||||
};
|
||||
TAILQ_HEAD(cfg_encoder_list, cfg_encoder);
|
||||
|
||||
static struct cfg_encoder_list cfg_encoders;
|
||||
|
||||
int
|
||||
cfg_encoder_init(void)
|
||||
{
|
||||
TAILQ_INIT(&cfg_encoders);
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
cfg_encoder_exit(void)
|
||||
{
|
||||
struct cfg_encoder *e;
|
||||
|
||||
while (NULL != (e = TAILQ_FIRST(&cfg_encoders))) {
|
||||
TAILQ_REMOVE(&cfg_encoders, e, entry);
|
||||
xfree(e->name);
|
||||
xfree(e->program);
|
||||
xfree(e);
|
||||
}
|
||||
}
|
||||
|
||||
struct cfg_encoder *
|
||||
cfg_encoder_get(const char *name)
|
||||
{
|
||||
struct cfg_encoder *e;
|
||||
|
||||
if (!name || !name[0])
|
||||
return (NULL);
|
||||
|
||||
TAILQ_FOREACH(e, &cfg_encoders, entry) {
|
||||
if (0 == strcasecmp(e->name, name))
|
||||
return (e);
|
||||
}
|
||||
|
||||
e = xcalloc(1UL, sizeof(*e));
|
||||
e->name = xstrdup(name);
|
||||
|
||||
return (e);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_encoder_set_name(struct cfg_encoder *e, const char *name,
|
||||
const char **errstrp)
|
||||
{
|
||||
if (!name || !name[0]) {
|
||||
if (errstrp)
|
||||
*errstrp = "empty";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
xfree(e->name);
|
||||
e->name = xstrdup(name);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_encoder_set_format(struct cfg_encoder *e, enum cfg_stream_format fmt,
|
||||
const char **not_used)
|
||||
{
|
||||
(void)not_used;
|
||||
assert(CFG_STREAM_MIN <= fmt && CFG_STREAM_MAX >= fmt);
|
||||
e->format = fmt;
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_encoder_set_format_str(struct cfg_encoder *e, const char *fmt_str,
|
||||
const char **errstrp)
|
||||
{
|
||||
enum cfg_stream_format fmt;
|
||||
|
||||
if (!fmt_str) {
|
||||
if (errstrp)
|
||||
*errstrp = "empty";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (0 > cfg_stream_str2fmt(fmt_str, &fmt)) {
|
||||
if (errstrp)
|
||||
*errstrp = "unsupported stream format";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
cfg_encoder_set_format(e, fmt, errstrp);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_encoder_set_program(struct cfg_encoder *e, const char *program,
|
||||
const char **errstrp)
|
||||
{
|
||||
if (!program || !program[0]) {
|
||||
if (errstrp)
|
||||
*errstrp = "empty";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
xfree(e->program);
|
||||
e->program = xstrdup(program);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
cfg_encoder_validate(struct cfg_encoder *e, const char **errstrp)
|
||||
{
|
||||
if (!e->program) {
|
||||
if (errstrp)
|
||||
*errstrp = "program not set";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (CFG_STREAM_INVALID == e->format) {
|
||||
if (errstrp)
|
||||
*errstrp = "format not set";
|
||||
return (-1);
|
||||
}
|
||||
|
||||
CHECKPH_PROHIBITED(e->program, PLACEHOLDER_TRACK);
|
||||
CHECKPH_PROHIBITED(e->program, PLACEHOLDER_STRING);
|
||||
CHECKPH_DUPLICATE(e->program, PLACEHOLDER_METADATA);
|
||||
CHECKPH_DUPLICATE(e->program, PLACEHOLDER_ARTIST);
|
||||
CHECKPH_DUPLICATE(e->program, PLACEHOLDER_TITLE);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
const char *
|
||||
cfg_encoder_get_name(struct cfg_encoder *e)
|
||||
{
|
||||
return (e->name);
|
||||
}
|
||||
|
||||
enum cfg_stream_format
|
||||
cfg_encoder_get_format(struct cfg_encoder *e)
|
||||
{
|
||||
return (e->format);
|
||||
}
|
||||
|
||||
const char *
|
||||
cfg_encoder_get_program(struct cfg_encoder *e)
|
||||
{
|
||||
return (e->program);
|
||||
}
|
43
src/cfg_encoder.h
Normal file
43
src/cfg_encoder.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __CFG_ENCODER_H__
|
||||
#define __CFG_ENCODER_H__
|
||||
|
||||
typedef struct cfg_encoder * cfg_encoder_t;
|
||||
|
||||
int cfg_encoder_init(void);
|
||||
void cfg_encoder_exit(void);
|
||||
|
||||
cfg_encoder_t
|
||||
cfg_encoder_get(const char *);
|
||||
|
||||
int cfg_encoder_set_name(cfg_encoder_t, const char *, const char **);
|
||||
int cfg_encoder_set_format(cfg_encoder_t, enum cfg_stream_format,
|
||||
const char **);
|
||||
int cfg_encoder_set_format_str(cfg_encoder_t, const char *, const char **);
|
||||
int cfg_encoder_set_program(cfg_encoder_t, const char *, const char **);
|
||||
|
||||
int cfg_encoder_validate(cfg_encoder_t, const char **);
|
||||
|
||||
const char *
|
||||
cfg_encoder_get_name(cfg_encoder_t);
|
||||
enum cfg_stream_format
|
||||
cfg_encoder_get_format(cfg_encoder_t);
|
||||
const char *
|
||||
cfg_encoder_get_program(cfg_encoder_t);
|
||||
|
||||
#endif /* __CFG_ENCODER_H__ */
|
146
src/cfg_private.h
Normal file
146
src/cfg_private.h
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __CFG_PRIVATE_H__
|
||||
#define __CFG_PRIVATE_H__
|
||||
|
||||
#include "cfg.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define EXTENSIONS_MAX 16
|
||||
#define UCREDS_SIZE 256
|
||||
|
||||
#define DEFAULT_USER "source"
|
||||
|
||||
struct cfg {
|
||||
struct program {
|
||||
char name[PATH_MAX];
|
||||
enum cfg_config_type config_type;
|
||||
char config_file[PATH_MAX];
|
||||
int quiet_stderr;
|
||||
unsigned int verbosity;
|
||||
} program;
|
||||
struct server {
|
||||
enum cfg_server_protocol protocol;
|
||||
char hostname[NI_MAXHOST];
|
||||
unsigned int port;
|
||||
char user[UCREDS_SIZE];
|
||||
char password[UCREDS_SIZE];
|
||||
char ca_dir[PATH_MAX];
|
||||
char ca_file[PATH_MAX];
|
||||
char client_cert[PATH_MAX];
|
||||
char client_key[PATH_MAX];
|
||||
unsigned int reconnect_attempts;
|
||||
} server;
|
||||
struct stream {
|
||||
char *mountpoint;
|
||||
char *name;
|
||||
char *url;
|
||||
char *genre;
|
||||
char *description;
|
||||
char *quality;
|
||||
char *bitrate;
|
||||
char *samplerate;
|
||||
char *channels;
|
||||
int server_public;
|
||||
enum cfg_stream_format format;
|
||||
char *encoder;
|
||||
} stream;
|
||||
struct media {
|
||||
enum cfg_media_type type;
|
||||
char filename[PATH_MAX];
|
||||
int shuffle;
|
||||
int stream_once;
|
||||
} media;
|
||||
struct metadata {
|
||||
char program[PATH_MAX];
|
||||
char *format_str;
|
||||
unsigned int refresh_interval;
|
||||
int normalize_strings;
|
||||
int no_updates;
|
||||
} metadata;
|
||||
};
|
||||
|
||||
#define SET_STRLCPY(t, s, e) do { \
|
||||
if (!(s) || !(s)[0]) { \
|
||||
if ((e)) \
|
||||
*(e) = "empty"; \
|
||||
return (-1); \
|
||||
} \
|
||||
if (sizeof((t)) <= \
|
||||
strlcpy((t), (s), sizeof((t)))) { \
|
||||
if ((e)) \
|
||||
*(e) = "too long"; \
|
||||
return (-1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SET_BOOLEAN(t, s, e) do { \
|
||||
int val; \
|
||||
if (!(s) || !(s)[0]) { \
|
||||
if ((e)) \
|
||||
*(e) = "empty"; \
|
||||
return (-1); \
|
||||
} \
|
||||
if (0 == strcasecmp((s), "true") || \
|
||||
0 == strcasecmp((s), "yes") || \
|
||||
0 == strcasecmp((s), "1")) { \
|
||||
val = 1; \
|
||||
} else if (0 == strcasecmp((s), "false") || \
|
||||
0 == strcasecmp((s), "no") || \
|
||||
0 == strcasecmp((s), "0")) { \
|
||||
val = 0; \
|
||||
} else { \
|
||||
if ((e)) \
|
||||
*(e) = "invalid"; \
|
||||
return (-1); \
|
||||
} \
|
||||
(t) = val; \
|
||||
} while (0)
|
||||
|
||||
#define CHECKPH_PROHIBITED(s, p) do { \
|
||||
if (NULL != strstr((s), (p))) { \
|
||||
if (errstrp) \
|
||||
*errstrp = "prohibited placeholder " p; \
|
||||
return (-1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECKPH_DUPLICATE(s, p) do { \
|
||||
char *c; \
|
||||
if (NULL != (c = strstr((s), (p)))) { \
|
||||
c += strlen((p)); \
|
||||
if (NULL != strstr(c, (p))) { \
|
||||
if (errstrp) \
|
||||
*errstrp = "duplicate placeholder " p; \
|
||||
return (-1); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECKPH_REQUIRED(s, p) do { \
|
||||
if (NULL == strstr((s), (p))) { \
|
||||
if (errstrp) \
|
||||
*errstrp = "missing placeholder " p; \
|
||||
return (-1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif /* __CFG_PRIVATE_H__ */
|
383
src/cfg_xmlfile.c
Normal file
383
src/cfg_xmlfile.c
Normal file
@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include "cfg.h"
|
||||
#include "cfg_xmlfile.h"
|
||||
#include "log.h"
|
||||
#include "xalloc.h"
|
||||
|
||||
#include <libxml/parser.h>
|
||||
|
||||
static unsigned int decoder_id, encoder_id;
|
||||
|
||||
static int _cfg_xmlfile_parse_server(xmlDocPtr, xmlNodePtr);
|
||||
static int _cfg_xmlfile_parse_stream(xmlDocPtr, xmlNodePtr);
|
||||
static int _cfg_xmlfile_parse_media(xmlDocPtr, xmlNodePtr);
|
||||
static int _cfg_xmlfile_parse_metadata(xmlDocPtr, xmlNodePtr);
|
||||
static int _cfg_xmlfile_parse_decoder(xmlDocPtr, xmlNodePtr);
|
||||
static int _cfg_xmlfile_parse_decoders(xmlDocPtr, xmlNodePtr);
|
||||
static int _cfg_xmlfile_parse_encoder(xmlDocPtr, xmlNodePtr);
|
||||
static int _cfg_xmlfile_parse_encoders(xmlDocPtr, xmlNodePtr);
|
||||
|
||||
#define XML_CHAR(s) (const xmlChar *)(s)
|
||||
#define XML_STRCONFIG(s, f, e) do { \
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR((e)))) { \
|
||||
xmlChar *val; \
|
||||
const char *err_str; \
|
||||
\
|
||||
val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); \
|
||||
if (0 > (f)(val, &err_str)) { \
|
||||
log_error("%s[%ld]: %s: %s: %s", doc->name, \
|
||||
xmlGetLineNo(cur), (s), (e), err_str); \
|
||||
error = 1; \
|
||||
} \
|
||||
xmlFree(val); \
|
||||
continue; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int
|
||||
_cfg_xmlfile_parse_server(xmlDocPtr doc, xmlNodePtr cur)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
XML_STRCONFIG("server", cfg_set_server_protocol, "protocol");
|
||||
XML_STRCONFIG("server", cfg_set_server_hostname, "hostname");
|
||||
XML_STRCONFIG("server", cfg_set_server_port, "port");
|
||||
XML_STRCONFIG("server", cfg_set_server_user, "user");
|
||||
XML_STRCONFIG("server", cfg_set_server_password, "password");
|
||||
XML_STRCONFIG("server", cfg_set_server_ca_dir, "ca_dir");
|
||||
XML_STRCONFIG("server", cfg_set_server_ca_file, "ca_file");
|
||||
XML_STRCONFIG("server", cfg_set_server_client_key, "client_key");
|
||||
XML_STRCONFIG("server", cfg_set_server_client_cert, "client_cert");
|
||||
XML_STRCONFIG("server", cfg_set_server_reconnect_attempts,
|
||||
"reconnect_attempts");
|
||||
}
|
||||
|
||||
if (error)
|
||||
return (-1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
_cfg_xmlfile_parse_stream(xmlDocPtr doc, xmlNodePtr cur)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
XML_STRCONFIG("stream", cfg_set_stream_mountpoint, "mountpoint");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_name, "name");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_url, "url");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_genre, "genre");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_description, "description");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_quality, "quality");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_bitrate, "bitrate");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_samplerate, "samplerate");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_channels, "channels");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_server_public, "server_public");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_format, "format");
|
||||
XML_STRCONFIG("stream", cfg_set_stream_encoder, "encoder");
|
||||
}
|
||||
|
||||
if (error)
|
||||
return (-1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
_cfg_xmlfile_parse_media(xmlDocPtr doc, xmlNodePtr cur)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
XML_STRCONFIG("media", cfg_set_media_type, "type");
|
||||
XML_STRCONFIG("media", cfg_set_media_filename, "filename");
|
||||
XML_STRCONFIG("media", cfg_set_media_shuffle, "shuffle");
|
||||
XML_STRCONFIG("media", cfg_set_media_stream_once, "stream_once");
|
||||
}
|
||||
|
||||
if (error)
|
||||
return (-1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
_cfg_xmlfile_parse_metadata(xmlDocPtr doc, xmlNodePtr cur)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
XML_STRCONFIG("metadata", cfg_set_metadata_program, "program");
|
||||
XML_STRCONFIG("metadata", cfg_set_metadata_format_str, "format_str");
|
||||
XML_STRCONFIG("metadata", cfg_set_metadata_refresh_interval,
|
||||
"refresh_interval");
|
||||
XML_STRCONFIG("metadata", cfg_set_metadata_normalize_strings,
|
||||
"normalize_strings");
|
||||
XML_STRCONFIG("metadata", cfg_set_metadata_no_updates, "no_updates");
|
||||
}
|
||||
|
||||
if (error)
|
||||
return (-1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
#define XML_DECODER_SET(c, f, e) do { \
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR((e)))) { \
|
||||
xmlChar *val; \
|
||||
const char *err_str; \
|
||||
int error = 0; \
|
||||
\
|
||||
val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); \
|
||||
if (0 > (f)((c), val, &err_str)) { \
|
||||
log_error("%s[%ld]: decoder: %s: %s: %s", \
|
||||
doc->name, xmlGetLineNo(cur), \
|
||||
cfg_decoder_get_name((c)), (e), err_str); \
|
||||
error = 1; \
|
||||
} \
|
||||
xmlFree(val); \
|
||||
if (error) \
|
||||
return (-1); \
|
||||
continue; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int
|
||||
_cfg_xmlfile_parse_decoder(xmlDocPtr doc, xmlNodePtr cur)
|
||||
{
|
||||
cfg_decoder_t d;
|
||||
char d_id[11];
|
||||
const char *errstr;
|
||||
long int line_no = xmlGetLineNo(cur);
|
||||
|
||||
(void)snprintf(d_id, sizeof(d_id), "%u", ++decoder_id);
|
||||
d = cfg_decoder_get(d_id);
|
||||
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
XML_DECODER_SET(d, cfg_decoder_set_name, "name");
|
||||
XML_DECODER_SET(d, cfg_decoder_set_program, "program");
|
||||
XML_DECODER_SET(d, cfg_decoder_add_match, "file_ext");
|
||||
}
|
||||
|
||||
if (0 > cfg_decoder_validate(d, &errstr)) {
|
||||
log_error("%s[%ld]: decoder: %s", doc->name, line_no, errstr);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
_cfg_xmlfile_parse_decoders(xmlDocPtr doc, xmlNodePtr cur)
|
||||
{
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("decoder")) &&
|
||||
0 > _cfg_xmlfile_parse_decoder(doc, cur))
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
#define XML_ENCODER_SET(c, f, e) do { \
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR((e)))) { \
|
||||
xmlChar *val; \
|
||||
const char *err_str; \
|
||||
int error = 0; \
|
||||
\
|
||||
val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); \
|
||||
if (0 > (f)((c), val, &err_str)) { \
|
||||
log_error("%s[%ld]: encoder: %s: %s: %s", \
|
||||
doc->name, xmlGetLineNo(cur), \
|
||||
cfg_encoder_get_name((c)), (e), err_str); \
|
||||
error = 1; \
|
||||
} \
|
||||
xmlFree(val); \
|
||||
if (error) \
|
||||
return (-1); \
|
||||
continue; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int
|
||||
_cfg_xmlfile_parse_encoder(xmlDocPtr doc, xmlNodePtr cur)
|
||||
{
|
||||
cfg_encoder_t e;
|
||||
char e_id[11];
|
||||
const char *errstr;
|
||||
long int line_no = xmlGetLineNo(cur);
|
||||
|
||||
(void)snprintf(e_id, sizeof(e_id), "%u", ++encoder_id);
|
||||
e = cfg_encoder_get(e_id);
|
||||
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
XML_ENCODER_SET(e, cfg_encoder_set_name, "name");
|
||||
XML_ENCODER_SET(e, cfg_encoder_set_format_str, "format");
|
||||
XML_ENCODER_SET(e, cfg_encoder_set_program, "program");
|
||||
}
|
||||
|
||||
if (0 > cfg_encoder_validate(e, &errstr)) {
|
||||
log_error("%s[%ld]: encoder: %s", doc->name, line_no, errstr);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
_cfg_xmlfile_parse_encoders(xmlDocPtr doc, xmlNodePtr cur)
|
||||
{
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("encoder")) &&
|
||||
0 > _cfg_xmlfile_parse_encoder(doc, cur))
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* XML configuration file structure:
|
||||
*
|
||||
* ezstream
|
||||
* server
|
||||
* protocol
|
||||
* hostname
|
||||
* port
|
||||
* user
|
||||
* password
|
||||
* ca_dir
|
||||
* ca_file
|
||||
* client_cert
|
||||
* client_key
|
||||
* reconnect_attempts
|
||||
* stream
|
||||
* mountpoint
|
||||
* name
|
||||
* url
|
||||
* genre
|
||||
* description
|
||||
* quality
|
||||
* bitrate
|
||||
* samplerate
|
||||
* channels
|
||||
* server_public
|
||||
* format
|
||||
* encoder
|
||||
* media
|
||||
* type
|
||||
* filename
|
||||
* shuffle
|
||||
* stream_once
|
||||
* metadata
|
||||
* program
|
||||
* format_str
|
||||
* refresh_interval
|
||||
* normalize_strings
|
||||
* no_updates
|
||||
* decoders
|
||||
* decoder
|
||||
* name
|
||||
* program
|
||||
* file_ext
|
||||
* ...
|
||||
* ...
|
||||
* encoders
|
||||
* encoder
|
||||
* name
|
||||
* format
|
||||
* program
|
||||
* ...
|
||||
*/
|
||||
int
|
||||
cfg_xmlfile_parse(const char *config_file)
|
||||
{
|
||||
xmlDocPtr doc = NULL;
|
||||
xmlNodePtr cur = NULL;
|
||||
int error = 0;
|
||||
|
||||
xmlLineNumbersDefault(1);
|
||||
|
||||
doc = xmlParseFile(config_file);
|
||||
if (!doc) {
|
||||
log_error("%s: not well-formed XML", config_file);
|
||||
goto error;
|
||||
}
|
||||
if (!doc->name)
|
||||
doc->name = xmlStrdup(config_file);
|
||||
cur = xmlDocGetRootElement(doc);
|
||||
if (!cur) {
|
||||
log_error("%s: empty document", config_file);
|
||||
goto error;
|
||||
}
|
||||
if (0 != xmlStrcasecmp(cur->name, XML_CHAR("ezstream"))) {
|
||||
log_error("%s: not ezstream config", config_file);
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("server"))) {
|
||||
if (0 > _cfg_xmlfile_parse_server(doc, cur))
|
||||
error = 1;
|
||||
continue;
|
||||
}
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("stream"))) {
|
||||
if (0 > _cfg_xmlfile_parse_stream(doc, cur))
|
||||
error = 1;
|
||||
continue;
|
||||
}
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("media"))) {
|
||||
if (0 > _cfg_xmlfile_parse_media(doc, cur))
|
||||
error = 1;
|
||||
continue;
|
||||
}
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("metadata"))) {
|
||||
if (0 > _cfg_xmlfile_parse_metadata(doc, cur))
|
||||
error = 1;
|
||||
continue;
|
||||
}
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("decoders"))) {
|
||||
if (0 > _cfg_xmlfile_parse_decoders(doc, cur))
|
||||
error = 1;
|
||||
continue;
|
||||
}
|
||||
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("encoders"))) {
|
||||
if (0 > _cfg_xmlfile_parse_encoders(doc, cur))
|
||||
error = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (error)
|
||||
goto error;
|
||||
|
||||
xmlFreeDoc(doc);
|
||||
|
||||
return (0);
|
||||
|
||||
error:
|
||||
if (doc)
|
||||
xmlFreeDoc(doc);
|
||||
|
||||
return (-1);
|
||||
}
|
22
src/cfg_xmlfile.h
Normal file
22
src/cfg_xmlfile.h
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __CFG_XMLFILE_H__
|
||||
#define __CFG_XMLFILE_H__
|
||||
|
||||
int cfg_xmlfile_parse(const char *);
|
||||
|
||||
#endif /* __CFG_XMLFILE_H__ */
|
183
src/cmdline.c
Normal file
183
src/cmdline.c
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#ifdef HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif /* HAVE_UNISTD_H */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "cmdline.h"
|
||||
#include "playlist.h"
|
||||
|
||||
#define OPTSTRING "c:hqs:Vv"
|
||||
enum opt_vals {
|
||||
OPT_CONFIGFILE = 'c',
|
||||
OPT_HELP = 'h',
|
||||
OPT_QUIETSTDERR = 'q',
|
||||
OPT_SHUFFLEFILE = 's',
|
||||
OPT_VERSION = 'V',
|
||||
OPT_VERBOSE = 'v',
|
||||
OPT_INVALID = '?'
|
||||
};
|
||||
|
||||
static void _usage(void);
|
||||
static void _usage_help(void);
|
||||
static void _set_program_name(const char *);
|
||||
|
||||
static void
|
||||
_usage(void)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-ghqVv] -c cfgfile\n",
|
||||
cfg_get_program_name());
|
||||
fprintf(stderr, " %s [-ghV] -s file\n",
|
||||
cfg_get_program_name());
|
||||
}
|
||||
|
||||
static void
|
||||
_usage_help(void)
|
||||
{
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " -c cfgfile use XML configuration in cfgfile\n");
|
||||
fprintf(stderr, " -h print this help and exit\n");
|
||||
fprintf(stderr, " -q suppress STDERR output from external en-/decoders\n");
|
||||
fprintf(stderr, " -s file read lines from file, shuffle, print to STDOUT, then exit\n");
|
||||
fprintf(stderr, " -V print the version number and exit\n");
|
||||
fprintf(stderr, " -v verbose output (use twice for more effect)\n");
|
||||
}
|
||||
|
||||
static void
|
||||
_set_program_name(const char *argv0)
|
||||
{
|
||||
#ifdef HAVE___PROGNAME
|
||||
extern char *__progname;
|
||||
(void)argv0;
|
||||
cfg_set_program_name(__progname, NULL);
|
||||
#else
|
||||
if (argv0 == NULL) {
|
||||
cfg_set_program_name("ezstream", NULL);
|
||||
} else {
|
||||
const char *p = strrchr(argv0, '/');
|
||||
if (p == NULL)
|
||||
p = argv0;
|
||||
else
|
||||
p++;
|
||||
cfg_set_program_name(p, NULL);
|
||||
}
|
||||
#endif /* HAVE___PROGNAME */
|
||||
}
|
||||
|
||||
int
|
||||
cmdline_parse(int argc, char *argv[], int *ret_p)
|
||||
{
|
||||
int ch;
|
||||
const char *playlistFile = NULL;
|
||||
unsigned int verbosity = 0;
|
||||
const char *err_str;
|
||||
|
||||
_set_program_name(argv[0]);
|
||||
|
||||
for (;;) {
|
||||
ch = getopt(argc, argv, OPTSTRING);
|
||||
if (0 > ch)
|
||||
break;
|
||||
|
||||
switch (ch) {
|
||||
case OPT_CONFIGFILE:
|
||||
if (0 > cfg_set_program_config_file(optarg, &err_str) ||
|
||||
0 > cfg_set_program_config_type(CFG_TYPE_XMLFILE, NULL)) {
|
||||
fprintf(stderr, "-%c: argument %s\n",
|
||||
OPT_CONFIGFILE, err_str);
|
||||
_usage();
|
||||
*ret_p = 2;
|
||||
return (-1);
|
||||
}
|
||||
break;
|
||||
case OPT_HELP:
|
||||
_usage();
|
||||
_usage_help();
|
||||
*ret_p = 0;
|
||||
return (-1);
|
||||
case OPT_QUIETSTDERR:
|
||||
cfg_set_program_quiet_stderr(1, NULL);
|
||||
break;
|
||||
case OPT_SHUFFLEFILE:
|
||||
playlistFile = optarg;
|
||||
break;
|
||||
case OPT_VERSION:
|
||||
fprintf(stdout, "%s version %s\n",
|
||||
PACKAGE_NAME, PACKAGE_VERSION);
|
||||
*ret_p = 0;
|
||||
return (-1);
|
||||
case OPT_VERBOSE:
|
||||
verbosity++;
|
||||
break;
|
||||
case OPT_INVALID:
|
||||
default:
|
||||
_usage();
|
||||
*ret_p = 2;
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
cfg_set_program_verbosity(verbosity, NULL);
|
||||
|
||||
if (playlistFile) {
|
||||
playlist_t *pl;
|
||||
const char *entry;
|
||||
|
||||
if (0 > playlist_init()) {
|
||||
*ret_p = 1;
|
||||
return (-1);
|
||||
}
|
||||
if (0 == strcmp(playlistFile, "-")) {
|
||||
pl = playlist_read(NULL);
|
||||
} else {
|
||||
pl = playlist_read(playlistFile);
|
||||
}
|
||||
if (pl == NULL) {
|
||||
*ret_p = 1;
|
||||
} else {
|
||||
playlist_shuffle(pl);
|
||||
while (NULL != (entry = playlist_get_next(pl)))
|
||||
printf("%s\n", entry);
|
||||
playlist_free(&pl);
|
||||
*ret_p = 0;
|
||||
}
|
||||
playlist_exit();
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (!cfg_get_program_config_file()) {
|
||||
fprintf(stderr, "either -%c or -%c must be provided\n",
|
||||
OPT_CONFIGFILE, OPT_SHUFFLEFILE);
|
||||
_usage();
|
||||
*ret_p = 2;
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
22
src/cmdline.h
Normal file
22
src/cmdline.h
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __CMDLINE_H__
|
||||
#define __CMDLINE_H__
|
||||
|
||||
int cmdline_parse(int, char *[], int *);
|
||||
|
||||
#endif /* __CMDLINE_H__ */
|
756
src/configfile.c
756
src/configfile.c
@ -1,756 +0,0 @@
|
||||
/*
|
||||
* ezstream - source client for Icecast with external en-/decoder support
|
||||
* Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski <oddsock@oddsock.org>
|
||||
* Copyright (C) 2007, 2009, 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
#include "ezstream.h"
|
||||
|
||||
#include <libxml/parser.h>
|
||||
|
||||
#include "configfile.h"
|
||||
#include "log.h"
|
||||
#include "util.h"
|
||||
#include "xalloc.h"
|
||||
|
||||
static EZCONFIG ezConfig;
|
||||
static const char *blankString = "";
|
||||
|
||||
unsigned int checkDecoderLine(const char *, const char *, long);
|
||||
unsigned int checkEncoderLine(const char *, const char *, long);
|
||||
unsigned int checkFormatLine(const char *, const char *, long);
|
||||
|
||||
EZCONFIG *
|
||||
getEZConfig(void)
|
||||
{
|
||||
return (&ezConfig);
|
||||
}
|
||||
|
||||
const char *
|
||||
getFormatEncoder(const char *format)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ezConfig.numEncoderDecoders; i++) {
|
||||
if (ezConfig.encoderDecoders[i] != NULL &&
|
||||
ezConfig.encoderDecoders[i]->format != NULL &&
|
||||
strcmp(ezConfig.encoderDecoders[i]->format, format) == 0) {
|
||||
if (ezConfig.encoderDecoders[i]->encoder != NULL)
|
||||
return (ezConfig.encoderDecoders[i]->encoder);
|
||||
else
|
||||
return (blankString);
|
||||
}
|
||||
}
|
||||
|
||||
return (blankString);
|
||||
}
|
||||
|
||||
const char *
|
||||
getFormatDecoder(const char *match)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ezConfig.numEncoderDecoders; i++) {
|
||||
if (ezConfig.encoderDecoders[i] != NULL &&
|
||||
ezConfig.encoderDecoders[i]->match != NULL &&
|
||||
strcmp(ezConfig.encoderDecoders[i]->match, match) == 0) {
|
||||
if (ezConfig.encoderDecoders[i]->decoder != NULL)
|
||||
return (ezConfig.encoderDecoders[i]->decoder);
|
||||
else
|
||||
return (blankString);
|
||||
}
|
||||
}
|
||||
|
||||
return (blankString);
|
||||
}
|
||||
|
||||
#define CFGERROR_TOO_MANY(x) \
|
||||
do { \
|
||||
log_error("%s[%ld]: more than one <%s> element", \
|
||||
fileName, xmlGetLineNo(cur), (x)); \
|
||||
config_error++; \
|
||||
} while (0)
|
||||
|
||||
int
|
||||
parseConfig(const char *fileName)
|
||||
{
|
||||
xmlDocPtr doc;
|
||||
xmlNodePtr cur;
|
||||
char *ls_xmlContentPtr;
|
||||
int program_set, reconnect_set, shuffle_set,
|
||||
streamOnce_set, svrinfopublic_set,
|
||||
refresh_set;
|
||||
unsigned int config_error;
|
||||
|
||||
xmlLineNumbersDefault(1);
|
||||
if ((doc = xmlParseFile(fileName)) == NULL) {
|
||||
log_error("%s: not well-formed", fileName);
|
||||
return (0);
|
||||
}
|
||||
|
||||
cur = xmlDocGetRootElement(doc);
|
||||
|
||||
if (cur == NULL) {
|
||||
log_error("%s: empty document", fileName);
|
||||
xmlFreeDoc(doc);
|
||||
return (0);
|
||||
}
|
||||
|
||||
memset(&ezConfig, 0, sizeof(ezConfig));
|
||||
ezConfig.metadataRefreshInterval = -1;
|
||||
|
||||
config_error = 0;
|
||||
program_set = 0;
|
||||
reconnect_set = 0;
|
||||
refresh_set = 0;
|
||||
shuffle_set = 0;
|
||||
streamOnce_set = 0;
|
||||
svrinfopublic_set = 0;
|
||||
|
||||
for (cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"url")) {
|
||||
if (ezConfig.URL != NULL) {
|
||||
CFGERROR_TOO_MANY("url");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.URL = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"sourceuser")) {
|
||||
if (ezConfig.username != NULL) {
|
||||
CFGERROR_TOO_MANY("sourceuser");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.username = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"sourcepassword")) {
|
||||
if (ezConfig.password != NULL) {
|
||||
CFGERROR_TOO_MANY("sourcepassword");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.password = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"format")) {
|
||||
if (ezConfig.format != NULL) {
|
||||
CFGERROR_TOO_MANY("format");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
char *p;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.format = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
for (p = ezConfig.format; *p != '\0'; p++)
|
||||
*p = toupper((int)*p);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"filename")) {
|
||||
if (ezConfig.fileName != NULL) {
|
||||
CFGERROR_TOO_MANY("filename");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
if (strlen(ls_xmlContentPtr) > PATH_MAX - 1) {
|
||||
log_error("%s[%ld]: path or filename in <filename> too long",
|
||||
fileName, xmlGetLineNo(cur));
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
ezConfig.fileName = UTF8toCHAR(ls_xmlContentPtr, ICONV_REPLACE);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"metadata_progname")) {
|
||||
if (ezConfig.metadataProgram != NULL) {
|
||||
CFGERROR_TOO_MANY("metadata_progname");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
if (strlen(ls_xmlContentPtr) > PATH_MAX - 1) {
|
||||
log_error("%s[%ld]: path or filename in <metadata_progname> too long",
|
||||
fileName, xmlGetLineNo(cur));
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
ezConfig.metadataProgram = UTF8toCHAR(ls_xmlContentPtr, ICONV_REPLACE);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"metadata_format")) {
|
||||
if (ezConfig.metadataFormat != NULL) {
|
||||
CFGERROR_TOO_MANY("metadata_format");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
unsigned int ret;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.metadataFormat = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
if ((ret = checkFormatLine(ezConfig.metadataFormat, fileName, xmlGetLineNo(cur)))
|
||||
> 0) {
|
||||
config_error += ret;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"metadata_refreshinterval")) {
|
||||
if (refresh_set) {
|
||||
CFGERROR_TOO_MANY("metadata_refreshinterval");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
const char *errstr;
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.metadataRefreshInterval = (int)strtonum(ls_xmlContentPtr, -1LL, (long long)INT_MAX, &errstr);
|
||||
if (errstr) {
|
||||
log_error("%s[%ld]: <metadata_refreshinterval>: %s: %s",
|
||||
fileName, xmlGetLineNo(cur), ls_xmlContentPtr, errstr);
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
refresh_set = 1;
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"playlist_program")) {
|
||||
if (program_set) {
|
||||
CFGERROR_TOO_MANY("playlist_program");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
const char *errstr;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.fileNameIsProgram = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
||||
if (errstr) {
|
||||
log_error("%s[%ld]: <playlist_program> may only contain 1 or 0",
|
||||
fileName, xmlGetLineNo(cur));
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
program_set = 1;
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"shuffle")) {
|
||||
if (shuffle_set) {
|
||||
CFGERROR_TOO_MANY("shuffle");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
const char *errstr;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.shuffle = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
||||
if (errstr) {
|
||||
log_error("%s[%ld]: <shuffle> may only contain 1 or 0",
|
||||
fileName, xmlGetLineNo(cur));
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
shuffle_set = 1;
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"stream_once")) {
|
||||
if (streamOnce_set) {
|
||||
CFGERROR_TOO_MANY("stream_once");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
const char *errstr;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.streamOnce = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
||||
if (errstr) {
|
||||
log_error("%s[%ld]: <stream_once> may only contain 1 or 0",
|
||||
fileName, xmlGetLineNo(cur));
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
streamOnce_set = 1;
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"reconnect_tries")) {
|
||||
if (reconnect_set) {
|
||||
CFGERROR_TOO_MANY("reconnect_tries");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
const char *errstr;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.reconnectAttempts = (unsigned int)strtonum(ls_xmlContentPtr, 0LL, (long long)UINT_MAX, &errstr);
|
||||
if (errstr) {
|
||||
log_error("%s[%ld]: <reconnect_tries>: %s: %s",
|
||||
fileName, xmlGetLineNo(cur), ls_xmlContentPtr, errstr);
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
reconnect_set = 1;
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfoname")) {
|
||||
if (ezConfig.serverName != NULL) {
|
||||
CFGERROR_TOO_MANY("svrinfoname");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverName = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfourl")) {
|
||||
if (ezConfig.serverURL != NULL) {
|
||||
CFGERROR_TOO_MANY("svrinfourl");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverURL = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfogenre")) {
|
||||
if (ezConfig.serverGenre != NULL) {
|
||||
CFGERROR_TOO_MANY("svrinfogenre");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverGenre = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfodescription")) {
|
||||
if (ezConfig.serverDescription != NULL) {
|
||||
CFGERROR_TOO_MANY("svrinfodescription");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverDescription = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfobitrate")) {
|
||||
if (ezConfig.serverBitrate != NULL) {
|
||||
CFGERROR_TOO_MANY("svrinfobitrate");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverBitrate = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfochannels")) {
|
||||
if (ezConfig.serverChannels != NULL) {
|
||||
CFGERROR_TOO_MANY("svrinfochannels");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverChannels = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfosamplerate")) {
|
||||
if (ezConfig.serverSamplerate != NULL) {
|
||||
CFGERROR_TOO_MANY("svrinfosamplerate");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverSamplerate = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfoquality")) {
|
||||
if (ezConfig.serverQuality != NULL) {
|
||||
CFGERROR_TOO_MANY("svrinfoquality");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverQuality = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfopublic")) {
|
||||
if (svrinfopublic_set) {
|
||||
CFGERROR_TOO_MANY("svrinfopublic");
|
||||
continue;
|
||||
}
|
||||
if (cur->xmlChildrenNode != NULL) {
|
||||
const char *errstr;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
||||
ezConfig.serverPublic = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
||||
if (errstr) {
|
||||
log_error("%s[%ld]: <svrinfopublic> may only contain 1 or 0",
|
||||
fileName, xmlGetLineNo(cur));
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
svrinfopublic_set = 1;
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"reencode")) {
|
||||
xmlNodePtr cur2;
|
||||
int enable_set;
|
||||
|
||||
enable_set = 0;
|
||||
for (cur2 = cur->xmlChildrenNode; cur2 != NULL;
|
||||
cur2 = cur2->next) {
|
||||
if (!xmlStrcmp(cur2->name, (const xmlChar *)"enable")) {
|
||||
if (enable_set) {
|
||||
CFGERROR_TOO_MANY("enable");
|
||||
continue;
|
||||
}
|
||||
if (cur2->xmlChildrenNode != NULL) {
|
||||
const char *errstr;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur2->xmlChildrenNode, 1);
|
||||
ezConfig.reencode = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
||||
if (errstr) {
|
||||
log_error("%s[%ld]: <enable> may only contain 1 or 0",
|
||||
fileName, xmlGetLineNo(cur));
|
||||
config_error++;
|
||||
continue;
|
||||
}
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
enable_set = 1;
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur2->name, (const xmlChar *)"encdec")) {
|
||||
xmlNodePtr cur3;
|
||||
FORMAT_ENCDEC *pformatEncDec;
|
||||
|
||||
pformatEncDec = xcalloc(1UL, sizeof(FORMAT_ENCDEC));
|
||||
|
||||
for (cur3 = cur2->xmlChildrenNode;
|
||||
cur3 != NULL; cur3 = cur3->next) {
|
||||
if (!xmlStrcmp(cur3->name, (const xmlChar *)"format")) {
|
||||
if (pformatEncDec->format != NULL) {
|
||||
CFGERROR_TOO_MANY("format");
|
||||
continue;
|
||||
}
|
||||
if (cur3->xmlChildrenNode != NULL) {
|
||||
char *p;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur3->xmlChildrenNode, 1);
|
||||
pformatEncDec->format = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
for (p = pformatEncDec->format; *p != '\0'; p++)
|
||||
*p = toupper((int)*p);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur3->name, (const xmlChar *)"match")) {
|
||||
if (pformatEncDec->match != NULL) {
|
||||
CFGERROR_TOO_MANY("match");
|
||||
continue;
|
||||
}
|
||||
if (cur3->xmlChildrenNode != NULL) {
|
||||
char *p;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur3->xmlChildrenNode, 1);
|
||||
pformatEncDec->match = xstrdup(ls_xmlContentPtr);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
for (p = pformatEncDec->match; *p != '\0'; p++)
|
||||
*p = tolower((int)*p);
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur3->name, (const xmlChar *)"decode")) {
|
||||
if (pformatEncDec->decoder != NULL) {
|
||||
CFGERROR_TOO_MANY("decode");
|
||||
continue;
|
||||
}
|
||||
if (cur3->xmlChildrenNode != NULL) {
|
||||
unsigned int ret;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur3->xmlChildrenNode, 1);
|
||||
pformatEncDec->decoder = UTF8toCHAR(ls_xmlContentPtr, ICONV_REPLACE);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
if ((ret = checkDecoderLine(pformatEncDec->decoder, fileName, xmlGetLineNo(cur3)))
|
||||
> 0) {
|
||||
config_error += ret;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!xmlStrcmp(cur3->name, (const xmlChar *)"encode")) {
|
||||
if (pformatEncDec->encoder != NULL) {
|
||||
CFGERROR_TOO_MANY("encode");
|
||||
continue;
|
||||
}
|
||||
if (cur3->xmlChildrenNode != NULL) {
|
||||
unsigned int ret;
|
||||
|
||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur3->xmlChildrenNode, 1);
|
||||
pformatEncDec->encoder = UTF8toCHAR(ls_xmlContentPtr, ICONV_REPLACE);
|
||||
xmlFree(ls_xmlContentPtr);
|
||||
if ((ret = checkEncoderLine(pformatEncDec->encoder, fileName, xmlGetLineNo(cur3)))
|
||||
> 0) {
|
||||
config_error += ret;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ezConfig.encoderDecoders[ezConfig.numEncoderDecoders] = pformatEncDec;
|
||||
ezConfig.numEncoderDecoders++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlFreeDoc(doc);
|
||||
|
||||
if (config_error == 0)
|
||||
return (1);
|
||||
|
||||
freeConfig(&ezConfig);
|
||||
log_error("%s: %u configuration error(s)", fileName, config_error);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
freeConfig(EZCONFIG *cfg)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (cfg == NULL)
|
||||
return;
|
||||
|
||||
if (cfg->URL != NULL)
|
||||
xfree(cfg->URL);
|
||||
if (cfg->password != NULL)
|
||||
xfree(cfg->password);
|
||||
if (cfg->format != NULL)
|
||||
xfree(cfg->format);
|
||||
if (cfg->fileName != NULL)
|
||||
xfree(cfg->fileName);
|
||||
if (cfg->metadataProgram != NULL)
|
||||
xfree(cfg->metadataProgram);
|
||||
if (cfg->metadataFormat != NULL)
|
||||
xfree(cfg->metadataFormat);
|
||||
if (cfg->serverName != NULL)
|
||||
xfree(cfg->serverName);
|
||||
if (cfg->serverURL != NULL)
|
||||
xfree(cfg->serverURL);
|
||||
if (cfg->serverGenre != NULL)
|
||||
xfree(cfg->serverGenre);
|
||||
if (cfg->serverDescription != NULL)
|
||||
xfree(cfg->serverDescription);
|
||||
if (cfg->serverBitrate != NULL)
|
||||
xfree(cfg->serverBitrate);
|
||||
if (cfg->serverChannels != NULL)
|
||||
xfree(cfg->serverChannels);
|
||||
if (cfg->serverSamplerate != NULL)
|
||||
xfree(cfg->serverSamplerate);
|
||||
if (cfg->serverQuality != NULL)
|
||||
xfree(cfg->serverQuality);
|
||||
if (cfg->encoderDecoders != NULL) {
|
||||
for (i = 0; i < MAX_FORMAT_ENCDEC; i++) {
|
||||
if (cfg->encoderDecoders[i] != NULL) {
|
||||
if (cfg->encoderDecoders[i]->format != NULL)
|
||||
xfree(cfg->encoderDecoders[i]->format);
|
||||
if (cfg->encoderDecoders[i]->match != NULL)
|
||||
xfree(cfg->encoderDecoders[i]->match);
|
||||
if (cfg->encoderDecoders[i]->encoder != NULL)
|
||||
xfree(cfg->encoderDecoders[i]->encoder);
|
||||
if (cfg->encoderDecoders[i]->decoder != NULL)
|
||||
xfree(cfg->encoderDecoders[i]->decoder);
|
||||
xfree(cfg->encoderDecoders[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memset(cfg, 0, sizeof(EZCONFIG));
|
||||
}
|
||||
|
||||
unsigned int
|
||||
checkDecoderLine(const char *str, const char *file, long line)
|
||||
{
|
||||
unsigned int errors;
|
||||
char *p;
|
||||
int have_track = 0;
|
||||
|
||||
errors = 0;
|
||||
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: '%s' placeholder not allowed in decoder command",
|
||||
file, line, STRING_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
if ((p = strstr(str, TRACK_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(TRACK_PLACEHOLDER);
|
||||
if ((p = strstr(p, TRACK_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in decoder command",
|
||||
file, line, TRACK_PLACEHOLDER);
|
||||
errors++;
|
||||
} else
|
||||
have_track = 1;
|
||||
}
|
||||
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(METADATA_PLACEHOLDER);
|
||||
if ((p = strstr(p, METADATA_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in decoder command",
|
||||
file, line, METADATA_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if ((p = strstr(str, ARTIST_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(ARTIST_PLACEHOLDER);
|
||||
if ((p = strstr(p, ARTIST_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in decoder command",
|
||||
file, line, ARTIST_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if ((p = strstr(str, TITLE_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(TITLE_PLACEHOLDER);
|
||||
if ((p = strstr(p, TITLE_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in decoder command",
|
||||
file, line, TITLE_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!have_track) {
|
||||
log_error("%s[%ld]: decoder command requires '%s' track placeholder",
|
||||
file, line, TRACK_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
|
||||
return (errors);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
checkEncoderLine(const char *str, const char *file, long line)
|
||||
{
|
||||
unsigned int errors;
|
||||
char *p;
|
||||
|
||||
errors = 0;
|
||||
if ((p = strstr(str, TRACK_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: '%s' placeholder not allowed in encoder command",
|
||||
file, line, TRACK_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: '%s' placeholder not allowed in encoder command",
|
||||
file, line, STRING_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(METADATA_PLACEHOLDER);
|
||||
if ((p = strstr(p, METADATA_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in encoder command",
|
||||
file, line, METADATA_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if ((p = strstr(str, ARTIST_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(ARTIST_PLACEHOLDER);
|
||||
if ((p = strstr(p, ARTIST_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in encoder command",
|
||||
file, line, ARTIST_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if ((p = strstr(str, TITLE_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(TITLE_PLACEHOLDER);
|
||||
if ((p = strstr(p, TITLE_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in encoder command",
|
||||
file, line, TITLE_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
return (errors);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
checkFormatLine(const char *str, const char *file, long line)
|
||||
{
|
||||
unsigned int errors;
|
||||
char *p;
|
||||
|
||||
errors = 0;
|
||||
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: '%s' placeholder not allowed in <metadata_format>",
|
||||
file, line, METADATA_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
if ((p = strstr(str, TRACK_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(TRACK_PLACEHOLDER);
|
||||
if ((p = strstr(p, TRACK_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in <metadata_format>",
|
||||
file, line, TRACK_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(STRING_PLACEHOLDER);
|
||||
if ((p = strstr(p, STRING_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in <metadata_format>",
|
||||
file, line, STRING_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if ((p = strstr(str, ARTIST_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(ARTIST_PLACEHOLDER);
|
||||
if ((p = strstr(p, ARTIST_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in <metadata_format>",
|
||||
file, line, ARTIST_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if ((p = strstr(str, TITLE_PLACEHOLDER)) != NULL) {
|
||||
p += strlen(TITLE_PLACEHOLDER);
|
||||
if ((p = strstr(p, TITLE_PLACEHOLDER)) != NULL) {
|
||||
log_error("%s[%ld]: more than one '%s' placeholder in <metadata_format>",
|
||||
file, line, TITLE_PLACEHOLDER);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
return (errors);
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* ezstream - source client for Icecast with external en-/decoder support
|
||||
* Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski <oddsock@oddsock.org>
|
||||
* Copyright (C) 2007, 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __CONFIGFILE_H__
|
||||
#define __CONFIGFILE_H__
|
||||
|
||||
#define MP3_FORMAT "MP3"
|
||||
#define VORBIS_FORMAT "VORBIS"
|
||||
#define THEORA_FORMAT "THEORA"
|
||||
|
||||
#define MAX_FORMAT_ENCDEC 15
|
||||
|
||||
#define TRACK_PLACEHOLDER "@T@"
|
||||
#define METADATA_PLACEHOLDER "@M@"
|
||||
#define ARTIST_PLACEHOLDER "@a@"
|
||||
#define TITLE_PLACEHOLDER "@t@"
|
||||
#define STRING_PLACEHOLDER "@s@"
|
||||
|
||||
typedef struct tag_FORMAT_ENCDEC {
|
||||
char *format;
|
||||
char *match;
|
||||
char *encoder;
|
||||
char *decoder;
|
||||
} FORMAT_ENCDEC;
|
||||
|
||||
typedef struct tag_EZCONFIG {
|
||||
char *URL;
|
||||
char *username;
|
||||
char *password;
|
||||
char *format;
|
||||
char *fileName;
|
||||
char *metadataProgram;
|
||||
char *metadataFormat;
|
||||
char *serverName;
|
||||
char *serverURL;
|
||||
char *serverGenre;
|
||||
char *serverDescription;
|
||||
char *serverBitrate;
|
||||
char *serverChannels;
|
||||
char *serverSamplerate;
|
||||
char *serverQuality;
|
||||
int serverPublic;
|
||||
int reencode;
|
||||
FORMAT_ENCDEC *encoderDecoders[MAX_FORMAT_ENCDEC];
|
||||
int numEncoderDecoders;
|
||||
int shuffle;
|
||||
int fileNameIsProgram;
|
||||
int streamOnce;
|
||||
unsigned int reconnectAttempts;
|
||||
int metadataRefreshInterval;
|
||||
} EZCONFIG;
|
||||
|
||||
EZCONFIG * getEZConfig(void);
|
||||
const char * getFormatEncoder(const char *format);
|
||||
const char * getFormatDecoder(const char *match);
|
||||
int parseConfig(const char *fileName);
|
||||
void freeConfig(EZCONFIG *);
|
||||
|
||||
#endif /* __CONFIGFILE_H__ */
|
426
src/ezstream.c
426
src/ezstream.c
@ -28,7 +28,7 @@
|
||||
#include <shout/shout.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "configfile.h"
|
||||
#include "cmdline.h"
|
||||
#include "log.h"
|
||||
#include "metadata.h"
|
||||
#include "playlist.h"
|
||||
@ -41,9 +41,6 @@
|
||||
#define STREAM_SERVERR 3
|
||||
#define STREAM_UPDMDATA 4
|
||||
|
||||
int metadataFromProgram;
|
||||
|
||||
EZCONFIG *pezConfig = NULL;
|
||||
playlist_t *playlist = NULL;
|
||||
int playlistMode = 0;
|
||||
unsigned int resource_errors = 0;
|
||||
@ -79,7 +76,7 @@ typedef struct tag_ID3Tag {
|
||||
int urlParse(const char *, char **, unsigned short *, char **);
|
||||
char * shellQuote(const char *);
|
||||
char * replaceString(const char *, const char *, const char *);
|
||||
char * buildCommandString(const char *, const char *, metadata_t *);
|
||||
char * buildReencodeCommand(const char *, const char *, metadata_t *);
|
||||
char * getMetadataString(const char *, metadata_t *);
|
||||
metadata_t * getMetadata(const char *);
|
||||
int setMetadata(shout_t *, metadata_t *, char **);
|
||||
@ -90,7 +87,7 @@ const char * getTimeString(long);
|
||||
int sendStream(shout_t *, FILE *, const char *, int, const char *,
|
||||
struct timeval *);
|
||||
int streamFile(shout_t *, const char *);
|
||||
int streamPlaylist(shout_t *, const char *);
|
||||
int streamPlaylist(shout_t *);
|
||||
int ez_shutdown(int);
|
||||
|
||||
#ifdef HAVE_SIGNALS
|
||||
@ -239,44 +236,47 @@ replaceString(const char *source, const char *from, const char *to)
|
||||
}
|
||||
|
||||
char *
|
||||
buildCommandString(const char *extension, const char *fileName,
|
||||
metadata_t *mdata)
|
||||
buildReencodeCommand(const char *extension, const char *fileName,
|
||||
metadata_t *mdata)
|
||||
{
|
||||
char *commandString = NULL;
|
||||
size_t commandStringLen = 0;
|
||||
char *encoder = NULL;
|
||||
char *decoder = NULL;
|
||||
char *newDecoder = NULL;
|
||||
char *newEncoder = NULL;
|
||||
char *localTitle = UTF8toCHAR(metadata_get_title(mdata),
|
||||
ICONV_REPLACE);
|
||||
char *localArtist = UTF8toCHAR(metadata_get_artist(mdata),
|
||||
ICONV_REPLACE);
|
||||
char *localMetaString = UTF8toCHAR(metadata_get_string(mdata),
|
||||
ICONV_REPLACE);
|
||||
cfg_decoder_t decoder;
|
||||
cfg_encoder_t encoder;
|
||||
char *dec_str, *enc_str;
|
||||
char *commandString;
|
||||
size_t commandStringLen;
|
||||
char *localTitle, *localArtist, *localMetaString;
|
||||
|
||||
decoder = xstrdup(getFormatDecoder(extension));
|
||||
if (strlen(decoder) == 0) {
|
||||
log_error("cannot decode: %s: unknown file extension %s",
|
||||
decoder = cfg_decoder_find(extension);
|
||||
if (!decoder) {
|
||||
log_error("cannot decode: %s: unsupported file extension %s",
|
||||
fileName, extension);
|
||||
xfree(localTitle);
|
||||
xfree(localArtist);
|
||||
xfree(localMetaString);
|
||||
xfree(decoder);
|
||||
return (NULL);
|
||||
}
|
||||
newDecoder = replaceString(decoder, TRACK_PLACEHOLDER, fileName);
|
||||
if (strstr(decoder, ARTIST_PLACEHOLDER) != NULL) {
|
||||
char *tmpStr = replaceString(newDecoder, ARTIST_PLACEHOLDER,
|
||||
localArtist);
|
||||
xfree(newDecoder);
|
||||
newDecoder = tmpStr;
|
||||
encoder = cfg_encoder_get(cfg_get_stream_encoder());
|
||||
if (!encoder) {
|
||||
log_error("cannot encode: %s: unknown encoder",
|
||||
cfg_get_stream_encoder());
|
||||
return (NULL);
|
||||
}
|
||||
if (strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
|
||||
char *tmpStr = replaceString(newDecoder, TITLE_PLACEHOLDER,
|
||||
|
||||
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,
|
||||
localArtist);
|
||||
xfree(dec_str);
|
||||
dec_str = tmpStr;
|
||||
}
|
||||
if (strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
|
||||
char *tmpStr = replaceString(dec_str, PLACEHOLDER_TITLE,
|
||||
localTitle);
|
||||
xfree(newDecoder);
|
||||
newDecoder = tmpStr;
|
||||
xfree(dec_str);
|
||||
dec_str = tmpStr;
|
||||
}
|
||||
/*
|
||||
* if meta
|
||||
@ -288,89 +288,77 @@ buildCommandString(const char *extension, const char *fileName,
|
||||
* else
|
||||
* replacemeta
|
||||
*/
|
||||
if (strstr(decoder, METADATA_PLACEHOLDER) != NULL) {
|
||||
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
|
||||
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
|
||||
char *tmpStr = replaceString(newDecoder,
|
||||
METADATA_PLACEHOLDER, mdataString);
|
||||
xfree(newDecoder);
|
||||
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);
|
||||
xfree(mdataString);
|
||||
newDecoder = tmpStr;
|
||||
dec_str = tmpStr;
|
||||
} else {
|
||||
if (!metadataFromProgram && strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
|
||||
char *tmpStr = replaceString(newDecoder,
|
||||
METADATA_PLACEHOLDER, "");
|
||||
xfree(newDecoder);
|
||||
newDecoder = tmpStr;
|
||||
if (!cfg_get_metadata_program() &&
|
||||
strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
|
||||
char *tmpStr = replaceString(dec_str,
|
||||
PLACEHOLDER_METADATA, "");
|
||||
xfree(dec_str);
|
||||
dec_str = tmpStr;
|
||||
} else {
|
||||
char *tmpStr = replaceString(newDecoder,
|
||||
METADATA_PLACEHOLDER, localMetaString);
|
||||
xfree(newDecoder);
|
||||
newDecoder = tmpStr;
|
||||
char *tmpStr = replaceString(dec_str,
|
||||
PLACEHOLDER_METADATA, localMetaString);
|
||||
xfree(dec_str);
|
||||
dec_str = tmpStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encoder = xstrdup(getFormatEncoder(pezConfig->format));
|
||||
if (strlen(encoder) == 0) {
|
||||
log_notice("passing through%s%s data from the decoder",
|
||||
(strcmp(pezConfig->format, THEORA_FORMAT) != 0) ? " (unsupported) " : " ",
|
||||
pezConfig->format);
|
||||
commandStringLen = strlen(newDecoder) + 1;
|
||||
commandString = xcalloc(commandStringLen, sizeof(char));
|
||||
strlcpy(commandString, newDecoder, commandStringLen);
|
||||
xfree(localTitle);
|
||||
xfree(localArtist);
|
||||
xfree(localMetaString);
|
||||
xfree(decoder);
|
||||
xfree(encoder);
|
||||
xfree(newDecoder);
|
||||
return (commandString);
|
||||
}
|
||||
|
||||
newEncoder = replaceString(encoder, ARTIST_PLACEHOLDER, localArtist);
|
||||
if (strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
|
||||
char *tmpStr = replaceString(newEncoder, TITLE_PLACEHOLDER,
|
||||
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,
|
||||
localTitle);
|
||||
xfree(newEncoder);
|
||||
newEncoder = tmpStr;
|
||||
xfree(enc_str);
|
||||
enc_str = tmpStr;
|
||||
}
|
||||
if (strstr(encoder, METADATA_PLACEHOLDER) != NULL) {
|
||||
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
|
||||
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
|
||||
char *tmpStr = replaceString(newEncoder,
|
||||
METADATA_PLACEHOLDER, mdataString);
|
||||
xfree(newEncoder);
|
||||
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);
|
||||
xfree(mdataString);
|
||||
newEncoder = tmpStr;
|
||||
enc_str = tmpStr;
|
||||
} else {
|
||||
if (!metadataFromProgram && strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
|
||||
char *tmpStr = replaceString(newEncoder,
|
||||
METADATA_PLACEHOLDER, "");
|
||||
xfree(newEncoder);
|
||||
newEncoder = tmpStr;
|
||||
if (!cfg_get_metadata_program() &&
|
||||
strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
|
||||
char *tmpStr = replaceString(enc_str,
|
||||
PLACEHOLDER_METADATA, "");
|
||||
xfree(enc_str);
|
||||
enc_str = tmpStr;
|
||||
} else {
|
||||
char *tmpStr = replaceString(newEncoder,
|
||||
METADATA_PLACEHOLDER, localMetaString);
|
||||
xfree(newEncoder);
|
||||
newEncoder = tmpStr;
|
||||
char *tmpStr = replaceString(enc_str,
|
||||
PLACEHOLDER_METADATA, localMetaString);
|
||||
xfree(enc_str);
|
||||
enc_str = tmpStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commandStringLen = strlen(newDecoder) + strlen(" | ") +
|
||||
strlen(newEncoder) + 1;
|
||||
commandStringLen = strlen(dec_str) + strlen(" | ") +
|
||||
strlen(enc_str) + 1;
|
||||
commandString = xcalloc(commandStringLen, sizeof(char));
|
||||
snprintf(commandString, commandStringLen, "%s | %s", newDecoder,
|
||||
newEncoder);
|
||||
snprintf(commandString, commandStringLen, "%s | %s", dec_str,
|
||||
enc_str);
|
||||
|
||||
xfree(localTitle);
|
||||
xfree(localArtist);
|
||||
xfree(localMetaString);
|
||||
xfree(decoder);
|
||||
xfree(encoder);
|
||||
xfree(newDecoder);
|
||||
xfree(newEncoder);
|
||||
xfree(dec_str);
|
||||
xfree(enc_str);
|
||||
|
||||
return (commandString);
|
||||
}
|
||||
@ -385,26 +373,26 @@ getMetadataString(const char *format, metadata_t *mdata)
|
||||
|
||||
str = xstrdup(format);
|
||||
|
||||
if (strstr(format, ARTIST_PLACEHOLDER) != NULL) {
|
||||
tmp = replaceString(str, ARTIST_PLACEHOLDER,
|
||||
if (strstr(format, PLACEHOLDER_ARTIST) != NULL) {
|
||||
tmp = replaceString(str, PLACEHOLDER_ARTIST,
|
||||
metadata_get_artist(mdata));
|
||||
xfree(str);
|
||||
str = tmp;
|
||||
}
|
||||
if (strstr(format, TITLE_PLACEHOLDER) != NULL) {
|
||||
tmp = replaceString(str, TITLE_PLACEHOLDER,
|
||||
if (strstr(format, PLACEHOLDER_TITLE) != NULL) {
|
||||
tmp = replaceString(str, PLACEHOLDER_TITLE,
|
||||
metadata_get_title(mdata));
|
||||
xfree(str);
|
||||
str = tmp;
|
||||
}
|
||||
if (strstr(format, STRING_PLACEHOLDER) != NULL) {
|
||||
tmp = replaceString(str, STRING_PLACEHOLDER,
|
||||
if (strstr(format, PLACEHOLDER_STRING) != NULL) {
|
||||
tmp = replaceString(str, PLACEHOLDER_STRING,
|
||||
metadata_get_string(mdata));
|
||||
xfree(str);
|
||||
str = tmp;
|
||||
}
|
||||
if (strstr(format, TRACK_PLACEHOLDER) != NULL) {
|
||||
tmp = replaceString(str, TRACK_PLACEHOLDER,
|
||||
if (strstr(format, PLACEHOLDER_TRACK) != NULL) {
|
||||
tmp = replaceString(str, PLACEHOLDER_TRACK,
|
||||
metadata_get_filename(mdata));
|
||||
xfree(str);
|
||||
str = tmp;
|
||||
@ -418,8 +406,9 @@ getMetadata(const char *fileName)
|
||||
{
|
||||
metadata_t *mdata;
|
||||
|
||||
if (metadataFromProgram) {
|
||||
if ((mdata = metadata_program(fileName, cfg_normalize_strings())) == NULL)
|
||||
if (cfg_get_metadata_program()) {
|
||||
if (NULL == (mdata = metadata_program(fileName,
|
||||
cfg_get_metadata_normalize_strings())))
|
||||
return (NULL);
|
||||
|
||||
if (!metadata_program_update(mdata, METADATA_ALL)) {
|
||||
@ -427,7 +416,8 @@ getMetadata(const char *fileName)
|
||||
return (NULL);
|
||||
}
|
||||
} else {
|
||||
if ((mdata = metadata_file(fileName, cfg_normalize_strings())) == NULL)
|
||||
if (NULL == (mdata = metadata_file(fileName,
|
||||
cfg_get_metadata_normalize_strings())))
|
||||
return (NULL);
|
||||
|
||||
if (!metadata_file_update(mdata)) {
|
||||
@ -447,7 +437,7 @@ setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
|
||||
const char *artist, *title;
|
||||
int ret = SHOUTERR_SUCCESS;
|
||||
|
||||
if (cfg_no_metadata_updates())
|
||||
if (cfg_get_metadata_no_updates())
|
||||
return (SHOUTERR_SUCCESS);
|
||||
|
||||
if (mdata == NULL)
|
||||
@ -473,7 +463,8 @@ setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((songInfo = getMetadataString(pezConfig->metadataFormat, mdata)) == NULL) {
|
||||
songInfo = getMetadataString(cfg_get_metadata_format_str(), mdata);
|
||||
if (songInfo == NULL) {
|
||||
if (artist[0] == '\0' && title[0] == '\0')
|
||||
songInfo = xstrdup(metadata_get_string(mdata));
|
||||
else
|
||||
@ -531,8 +522,8 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
|
||||
*songLen = 0;
|
||||
|
||||
if (strcmp(fileName, "stdin") == 0) {
|
||||
if (metadataFromProgram) {
|
||||
if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
|
||||
if (cfg_get_metadata_program()) {
|
||||
if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
|
||||
return (NULL);
|
||||
if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) {
|
||||
metadata_free(&mdata);
|
||||
@ -565,8 +556,8 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
|
||||
return (filep);
|
||||
}
|
||||
|
||||
if (metadataFromProgram) {
|
||||
if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
|
||||
if (cfg_get_metadata_program()) {
|
||||
if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
|
||||
return (NULL);
|
||||
} else {
|
||||
if ((mdata = getMetadata(fileName)) == NULL)
|
||||
@ -576,17 +567,18 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
|
||||
*songLen = metadata_get_length(mdata);
|
||||
|
||||
*popenFlag = 0;
|
||||
if (pezConfig->reencode) {
|
||||
if (cfg_get_stream_encoder()) {
|
||||
int stderr_fd = -1;
|
||||
|
||||
pCommandString = buildCommandString(extension, fileName, mdata);
|
||||
pCommandString = buildReencodeCommand(extension, fileName,
|
||||
mdata);
|
||||
if (mdata_p != NULL)
|
||||
*mdata_p = mdata;
|
||||
else
|
||||
metadata_free(&mdata);
|
||||
log_info("running command: %s", pCommandString);
|
||||
|
||||
if (cfg_quiet_stderr()) {
|
||||
if (cfg_get_program_quiet_stderr()) {
|
||||
int fd;
|
||||
|
||||
stderr_fd = dup(fileno(stderr));
|
||||
@ -616,7 +608,7 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
|
||||
}
|
||||
xfree(pCommandString);
|
||||
|
||||
if (cfg_quiet_stderr())
|
||||
if (cfg_get_program_quiet_stderr())
|
||||
dup2(stderr_fd, fileno(stderr));
|
||||
|
||||
if (stderr_fd > 2)
|
||||
@ -644,16 +636,17 @@ reconnectServer(shout_t *shout, int closeConn)
|
||||
unsigned int i;
|
||||
int close_conn = closeConn;
|
||||
|
||||
log_warning("%s: connection lost", pezConfig->URL);
|
||||
log_warning("%s: connection lost", cfg_get_server_hostname());
|
||||
|
||||
i = 0;
|
||||
while (++i) {
|
||||
if (pezConfig->reconnectAttempts > 0)
|
||||
if (cfg_get_server_reconnect_attempts() > 0)
|
||||
log_notice("reconnect: %s: attempt #%u/%u ...",
|
||||
pezConfig->URL, i, pezConfig->reconnectAttempts);
|
||||
cfg_get_server_hostname(), i,
|
||||
cfg_get_server_reconnect_attempts());
|
||||
else
|
||||
log_notice("reconnect: %s: attempt #%u ...",
|
||||
pezConfig->URL, i);
|
||||
cfg_get_server_hostname(), i);
|
||||
|
||||
if (close_conn == 0)
|
||||
close_conn = 1;
|
||||
@ -661,15 +654,15 @@ reconnectServer(shout_t *shout, int closeConn)
|
||||
shout_close(shout);
|
||||
if (shout_open(shout) == SHOUTERR_SUCCESS) {
|
||||
log_notice("reconnect: %s: success",
|
||||
pezConfig->URL);
|
||||
cfg_get_server_hostname());
|
||||
return (1);
|
||||
}
|
||||
|
||||
log_warning("reconnect failed: %s: %s",
|
||||
pezConfig->URL, shout_get_error(shout));
|
||||
cfg_get_server_hostname(), shout_get_error(shout));
|
||||
|
||||
if (pezConfig->reconnectAttempts > 0 &&
|
||||
i >= pezConfig->reconnectAttempts)
|
||||
if (cfg_get_server_reconnect_attempts() > 0 &&
|
||||
i >= cfg_get_server_reconnect_attempts())
|
||||
break;
|
||||
|
||||
if (quit)
|
||||
@ -742,7 +735,7 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
|
||||
break;
|
||||
if (rereadPlaylist_notify) {
|
||||
rereadPlaylist_notify = 0;
|
||||
if (!pezConfig->fileNameIsProgram)
|
||||
if (CFG_MEDIA_PLAYLIST == cfg_get_media_type())
|
||||
log_notice("HUP signal received: playlist re-read scheduled");
|
||||
}
|
||||
if (skipTrack) {
|
||||
@ -754,25 +747,24 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
|
||||
ez_gettimeofday((void *)¤tTime);
|
||||
|
||||
if (queryMetadata ||
|
||||
(pezConfig->metadataRefreshInterval != -1
|
||||
&& (currentTime.tv_sec - callTime.tv_sec
|
||||
>= pezConfig->metadataRefreshInterval)
|
||||
)
|
||||
) {
|
||||
(cfg_get_metadata_refresh_interval() &&
|
||||
(currentTime.tv_sec - callTime.tv_sec >=
|
||||
cfg_get_metadata_refresh_interval()))) {
|
||||
queryMetadata = 0;
|
||||
if (metadataFromProgram) {
|
||||
if (cfg_get_metadata_program()) {
|
||||
ret = STREAM_UPDMDATA;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
total += bytes_read;
|
||||
if (cfg_quiet_stderr() && cfg_verbosity()) {
|
||||
if (cfg_get_program_quiet_stderr() &&
|
||||
cfg_get_program_verbosity()) {
|
||||
double oldTime, newTime;
|
||||
|
||||
if (!isStdin && playlistMode) {
|
||||
if (pezConfig->fileNameIsProgram) {
|
||||
char *tmp = xstrdup(pezConfig->fileName);
|
||||
if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
|
||||
char *tmp = xstrdup(cfg_get_media_filename());
|
||||
printf(" [%s]", basename(tmp));
|
||||
xfree(tmp);
|
||||
} else
|
||||
@ -858,7 +850,7 @@ streamFile(shout_t *shout, const char *fileName)
|
||||
xfree(metaData);
|
||||
|
||||
/* MP3 streams are special, so set the metadata explicitly: */
|
||||
if (strcmp(pezConfig->format, MP3_FORMAT) == 0)
|
||||
if (CFG_STREAM_MP3 == cfg_get_stream_format())
|
||||
setMetadata(shout, mdata, NULL);
|
||||
|
||||
metadata_free(&mdata);
|
||||
@ -892,15 +884,15 @@ streamFile(shout_t *shout, const char *fileName)
|
||||
}
|
||||
if (ret == STREAM_UPDMDATA || queryMetadata) {
|
||||
queryMetadata = 0;
|
||||
if (cfg_no_metadata_updates())
|
||||
if (cfg_get_metadata_no_updates())
|
||||
continue;
|
||||
if (metadataFromProgram) {
|
||||
if (cfg_get_metadata_program()) {
|
||||
char *mdataStr = NULL;
|
||||
metadata_t *prog_mdata;
|
||||
|
||||
log_info("running metadata program: %s",
|
||||
pezConfig->metadataProgram);
|
||||
if ((prog_mdata = getMetadata(pezConfig->metadataProgram)) == NULL) {
|
||||
cfg_get_metadata_program());
|
||||
if ((prog_mdata = getMetadata(cfg_get_metadata_program())) == NULL) {
|
||||
retval = 0;
|
||||
ret = STREAM_DONE;
|
||||
continue;
|
||||
@ -935,20 +927,21 @@ streamFile(shout_t *shout, const char *fileName)
|
||||
}
|
||||
|
||||
int
|
||||
streamPlaylist(shout_t *shout, const char *fileName)
|
||||
streamPlaylist(shout_t *shout)
|
||||
{
|
||||
const char *song;
|
||||
char lastSong[PATH_MAX];
|
||||
|
||||
if (playlist == NULL) {
|
||||
if (pezConfig->fileNameIsProgram) {
|
||||
if ((playlist = playlist_program(fileName)) == NULL)
|
||||
if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
|
||||
if ((playlist = playlist_program(cfg_get_media_filename())) == NULL)
|
||||
return (0);
|
||||
} else {
|
||||
if ((playlist = playlist_read(fileName)) == NULL)
|
||||
if ((playlist = playlist_read(cfg_get_media_filename())) == NULL)
|
||||
return (0);
|
||||
if (playlist_get_num_items(playlist) == 0)
|
||||
log_notice("%s: playlist empty", fileName);
|
||||
log_notice("%s: playlist empty",
|
||||
cfg_get_media_filename());
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
@ -959,7 +952,8 @@ streamPlaylist(shout_t *shout, const char *fileName)
|
||||
playlist_rewind(playlist);
|
||||
}
|
||||
|
||||
if (!pezConfig->fileNameIsProgram && pezConfig->shuffle)
|
||||
if (CFG_MEDIA_PROGRAM != cfg_get_media_type() &&
|
||||
cfg_get_media_shuffle())
|
||||
playlist_shuffle(playlist);
|
||||
|
||||
while ((song = playlist_get_next(playlist)) != NULL) {
|
||||
@ -970,12 +964,12 @@ streamPlaylist(shout_t *shout, const char *fileName)
|
||||
break;
|
||||
if (rereadPlaylist) {
|
||||
rereadPlaylist = rereadPlaylist_notify = 0;
|
||||
if (pezConfig->fileNameIsProgram)
|
||||
if (CFG_MEDIA_PROGRAM == cfg_get_media_type())
|
||||
continue;
|
||||
log_notice("rereading playlist");
|
||||
if (!playlist_reread(&playlist))
|
||||
return (0);
|
||||
if (pezConfig->shuffle)
|
||||
if (cfg_get_media_shuffle())
|
||||
playlist_shuffle(playlist);
|
||||
else {
|
||||
playlist_goto_entry(playlist, lastSong);
|
||||
@ -992,9 +986,12 @@ int
|
||||
ez_shutdown(int exitval)
|
||||
{
|
||||
shout_shutdown();
|
||||
playlist_shutdown();
|
||||
freeConfig(pezConfig);
|
||||
|
||||
playlist_exit();
|
||||
cfg_encoder_exit();
|
||||
cfg_decoder_exit();
|
||||
log_exit();
|
||||
cfg_exit();
|
||||
|
||||
return (exitval);
|
||||
}
|
||||
@ -1003,12 +1000,6 @@ int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
int ret;
|
||||
|
||||
const char *configFile;
|
||||
const char *playlistFile;
|
||||
char *host = NULL;
|
||||
unsigned short port = 0;
|
||||
char *mount = NULL;
|
||||
shout_t *shout;
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
@ -1017,38 +1008,15 @@ main(int argc, char *argv[])
|
||||
unsigned int i;
|
||||
#endif
|
||||
ret = 1;
|
||||
if (0 > cfg_cmdline_parse(argc, argv, &ret))
|
||||
if (0 > cmdline_parse(argc, argv, &ret) ||
|
||||
0 > log_init() ||
|
||||
0 > cfg_decoder_init() ||
|
||||
0 > cfg_encoder_init() ||
|
||||
0 > playlist_init() ||
|
||||
0 > cfg_reload())
|
||||
return (ret);
|
||||
log_init();
|
||||
|
||||
playlist_init();
|
||||
shout_init();
|
||||
|
||||
pezConfig = getEZConfig();
|
||||
|
||||
playlistFile = cfg_shuffle_file();
|
||||
if (playlistFile) {
|
||||
playlist_t *pl;
|
||||
const char *entry;
|
||||
|
||||
if (0 == strcmp(playlistFile, "-"))
|
||||
pl = playlist_read(NULL);
|
||||
else
|
||||
pl = playlist_read(playlistFile);
|
||||
|
||||
if (pl == NULL)
|
||||
return (ez_shutdown(1));
|
||||
|
||||
playlist_shuffle(pl);
|
||||
while ((entry = playlist_get_next(pl)) != NULL)
|
||||
printf("%s\n", entry);
|
||||
|
||||
playlist_free(&pl);
|
||||
|
||||
return (ez_shutdown(0));
|
||||
}
|
||||
|
||||
configFile = cfg_config_file();
|
||||
{
|
||||
/*
|
||||
* Attempt to open configFile here for a more meaningful error
|
||||
@ -1058,23 +1026,25 @@ main(int argc, char *argv[])
|
||||
#ifdef HAVE_STAT
|
||||
struct stat st;
|
||||
|
||||
if (stat(configFile, &st) == -1) {
|
||||
log_error("%s: %s", configFile, strerror(errno));
|
||||
if (stat(cfg_get_program_config_file(), &st) == -1) {
|
||||
log_error("%s: %s", cfg_get_program_config_file(),
|
||||
strerror(errno));
|
||||
return (ez_shutdown(2));
|
||||
}
|
||||
if (cfg_verbosity() && (st.st_mode & (S_IRGRP | S_IROTH)))
|
||||
if (cfg_get_program_verbosity() && (st.st_mode & (S_IRGRP | S_IROTH)))
|
||||
log_warning("%s: group and/or world readable",
|
||||
configFile);
|
||||
cfg_get_program_config_file());
|
||||
if (st.st_mode & (S_IWGRP | S_IWOTH)) {
|
||||
log_error("%s: group and/or world writeable",
|
||||
configFile);
|
||||
cfg_get_program_config_file());
|
||||
return (ez_shutdown(2));
|
||||
}
|
||||
#else
|
||||
FILE *tmp;
|
||||
|
||||
if ((tmp = fopen(configFile, "r")) == NULL) {
|
||||
log_error("%s: %s", configFile, strerror(errno));
|
||||
if ((tmp = fopen(cfg_get_program_config_file(), "r")) == NULL) {
|
||||
log_error("%s: %s", cfg_get_program_config_file(),
|
||||
strerror(errno));
|
||||
usage();
|
||||
return (ez_shutdown(2));
|
||||
}
|
||||
@ -1082,39 +1052,35 @@ main(int argc, char *argv[])
|
||||
#endif /* HAVE_STAT */
|
||||
}
|
||||
|
||||
if (!parseConfig(configFile))
|
||||
if (0 > cfg_reload())
|
||||
return (ez_shutdown(2));
|
||||
|
||||
if (pezConfig->URL == NULL) {
|
||||
log_error("%s: missing <url>", configFile);
|
||||
if (!cfg_get_server_hostname() ||
|
||||
!cfg_get_server_port()){
|
||||
log_error("%s: missing server configuration",
|
||||
cfg_get_program_config_file());
|
||||
return (ez_shutdown(2));
|
||||
}
|
||||
if (!urlParse(pezConfig->URL, &host, &port, &mount)) {
|
||||
log_error("%s: <url>: must be of the form ``http://server:port/mountpoint''",
|
||||
configFile);
|
||||
if (!cfg_get_server_password()) {
|
||||
log_error("%s: <sourcepassword> missing",
|
||||
cfg_get_program_config_file());
|
||||
return (ez_shutdown(2));
|
||||
}
|
||||
if (pezConfig->password == NULL) {
|
||||
log_error("%s: <sourcepassword> missing", configFile);
|
||||
if (!cfg_get_media_filename()) {
|
||||
log_error("%s: <filename> missing",
|
||||
cfg_get_program_config_file());
|
||||
return (ez_shutdown(2));
|
||||
}
|
||||
if (pezConfig->fileName == NULL) {
|
||||
log_error("%s: <filename> missing", configFile);
|
||||
return (ez_shutdown(2));
|
||||
}
|
||||
if (pezConfig->format == NULL) {
|
||||
if (CFG_STREAM_INVALID == cfg_get_stream_format()) {
|
||||
log_error("%s: <format> missing or unsupported value",
|
||||
configFile);
|
||||
cfg_get_program_config_file());
|
||||
}
|
||||
|
||||
if ((shout = stream_setup(host, port, mount)) == NULL)
|
||||
shout = stream_setup(cfg_get_server_hostname(),
|
||||
cfg_get_server_port(), cfg_get_stream_mountpoint());
|
||||
if (shout == NULL)
|
||||
return (ez_shutdown(1));
|
||||
|
||||
if (pezConfig->metadataProgram != NULL)
|
||||
metadataFromProgram = 1;
|
||||
else
|
||||
metadataFromProgram = 0;
|
||||
|
||||
#ifdef HAVE_SIGNALS
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = sig_handler;
|
||||
@ -1141,42 +1107,48 @@ main(int argc, char *argv[])
|
||||
if (shout_open(shout) == SHOUTERR_SUCCESS) {
|
||||
int cont;
|
||||
|
||||
log_notice("connected: http://%s:%hu%s", host, port, mount);
|
||||
log_notice("connected: %s://%s:%u%s",
|
||||
cfg_get_server_protocol_str(), cfg_get_server_hostname(),
|
||||
cfg_get_server_port(), cfg_get_stream_mountpoint());
|
||||
|
||||
if (pezConfig->fileNameIsProgram ||
|
||||
strrcasecmp(pezConfig->fileName, ".m3u") == 0 ||
|
||||
strrcasecmp(pezConfig->fileName, ".txt") == 0)
|
||||
if (CFG_MEDIA_PROGRAM == cfg_get_media_type() ||
|
||||
CFG_MEDIA_PLAYLIST == cfg_get_media_type() ||
|
||||
(CFG_MEDIA_AUTODETECT == cfg_get_media_type() &&
|
||||
(strrcasecmp(cfg_get_media_filename(), ".m3u") == 0 ||
|
||||
strrcasecmp(cfg_get_media_filename(), ".txt") == 0)))
|
||||
playlistMode = 1;
|
||||
else
|
||||
playlistMode = 0;
|
||||
|
||||
do {
|
||||
if (playlistMode) {
|
||||
cont = streamPlaylist(shout, pezConfig->fileName);
|
||||
cont = streamPlaylist(shout);
|
||||
} else {
|
||||
cont = streamFile(shout, pezConfig->fileName);
|
||||
cont = streamFile(shout,
|
||||
cfg_get_media_filename());
|
||||
}
|
||||
if (quit)
|
||||
break;
|
||||
if (pezConfig->streamOnce)
|
||||
if (cfg_get_media_stream_once())
|
||||
break;
|
||||
} while (cont);
|
||||
|
||||
shout_close(shout);
|
||||
} else
|
||||
log_error("connection failed: http://%s:%hu%s: %s",
|
||||
host, port, mount, shout_get_error(shout));
|
||||
log_error("connection failed: %s://%s:%u%s: %s",
|
||||
cfg_get_server_protocol_str(), cfg_get_server_hostname(),
|
||||
cfg_get_server_port(), cfg_get_stream_mountpoint(),
|
||||
shout_get_error(shout));
|
||||
|
||||
if (quit) {
|
||||
if (cfg_quiet_stderr() && cfg_verbosity())
|
||||
if (cfg_get_program_quiet_stderr() &&
|
||||
cfg_get_program_verbosity())
|
||||
printf("\r");
|
||||
log_notice("INT or TERM signal received");
|
||||
}
|
||||
|
||||
log_info("exiting");
|
||||
|
||||
xfree(host);
|
||||
xfree(mount);
|
||||
playlist_free(&playlist);
|
||||
|
||||
return (ez_shutdown(0));
|
||||
|
12
src/log.c
12
src/log.c
@ -62,18 +62,18 @@ _vlog(enum log_levels lvl, const char *fmt, va_list ap)
|
||||
p = LOG_WARNING;
|
||||
break;
|
||||
case NOTICE:
|
||||
if (cfg_verbosity() < 1)
|
||||
if (cfg_get_program_verbosity() < 1)
|
||||
return;
|
||||
p = LOG_NOTICE;
|
||||
break;
|
||||
case INFO:
|
||||
if (cfg_verbosity() < 2)
|
||||
if (cfg_get_program_verbosity() < 2)
|
||||
return;
|
||||
p = LOG_INFO;
|
||||
break;
|
||||
case DEBUG:
|
||||
default:
|
||||
if (cfg_verbosity() < 3)
|
||||
if (cfg_get_program_verbosity() < 3)
|
||||
return;
|
||||
p = LOG_DEBUG;
|
||||
break;
|
||||
@ -84,11 +84,13 @@ _vlog(enum log_levels lvl, const char *fmt, va_list ap)
|
||||
va_end(ap2);
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
log_init(void)
|
||||
{
|
||||
openlog(cfg_progname(), LOG_PID|LOG_CONS|LOG_NDELAY|LOG_PERROR,
|
||||
openlog(cfg_get_program_name(),
|
||||
LOG_PID|LOG_CONS|LOG_NDELAY|LOG_PERROR,
|
||||
LOG_USER);
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -28,7 +28,7 @@ enum log_levels {
|
||||
DEBUG
|
||||
};
|
||||
|
||||
void log_init(void);
|
||||
int log_init(void);
|
||||
void log_exit(void);
|
||||
|
||||
void log_syserr(enum log_levels, int, const char *);
|
||||
|
@ -156,7 +156,7 @@ playlist_run_program(playlist_t *pl)
|
||||
return ((const char *)pl->prog_track);
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
playlist_init(void)
|
||||
{
|
||||
#ifdef HAVE_RANDOM
|
||||
@ -168,9 +168,10 @@ playlist_init(void)
|
||||
#else
|
||||
srand((unsigned int)time(NULL));
|
||||
#endif /* HAVE_RANDOM */
|
||||
return (0);
|
||||
}
|
||||
|
||||
void playlist_shutdown(void) {}
|
||||
void playlist_exit(void) {}
|
||||
|
||||
playlist_t *
|
||||
playlist_read(const char *filename)
|
||||
|
@ -23,12 +23,12 @@ typedef struct playlist playlist_t;
|
||||
* Initialize the playlist routines. Should be called before any of the other
|
||||
* playlist functions.
|
||||
*/
|
||||
void playlist_init(void);
|
||||
int playlist_init(void);
|
||||
|
||||
/*
|
||||
* Clean up for clean shutdowns. No-op at the moment.
|
||||
*/
|
||||
void playlist_shutdown(void);
|
||||
void playlist_exit(void);
|
||||
|
||||
/*
|
||||
* Read a playlist file (in .M3U format), and return a new playlist handler
|
||||
|
49
src/util.c
49
src/util.c
@ -36,7 +36,7 @@
|
||||
#endif
|
||||
#include <shout/shout.h>
|
||||
|
||||
#include "configfile.h"
|
||||
#include "cfg.h"
|
||||
#include "log.h"
|
||||
#include "util.h"
|
||||
#include "xalloc.h"
|
||||
@ -45,8 +45,6 @@
|
||||
# define BUFSIZ 1024
|
||||
#endif
|
||||
|
||||
extern EZCONFIG *pezConfig;
|
||||
|
||||
char * iconvert(const char *, const char *, const char *, int);
|
||||
|
||||
int
|
||||
@ -111,7 +109,7 @@ stream_setup(const char *host, unsigned short port, const char *mount)
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (shout_set_password(shout, pezConfig->password) != SHOUTERR_SUCCESS) {
|
||||
if (shout_set_password(shout, cfg_get_server_password()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_password: %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
@ -130,15 +128,15 @@ stream_setup(const char *host, unsigned short port, const char *mount)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (!strcmp(pezConfig->format, MP3_FORMAT) &&
|
||||
if (CFG_STREAM_MP3 == cfg_get_stream_format() &&
|
||||
shout_set_format(shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_format(MP3): %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if ((!strcmp(pezConfig->format, VORBIS_FORMAT) ||
|
||||
!strcmp(pezConfig->format, THEORA_FORMAT)) &&
|
||||
if ((CFG_STREAM_VORBIS == cfg_get_stream_format() ||
|
||||
CFG_STREAM_THEORA == cfg_get_stream_format()) &&
|
||||
shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_format(OGG): %s",
|
||||
shout_get_error(shout));
|
||||
@ -146,71 +144,70 @@ stream_setup(const char *host, unsigned short port, const char *mount)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (pezConfig->username &&
|
||||
shout_set_user(shout, pezConfig->username) != SHOUTERR_SUCCESS) {
|
||||
if (shout_set_user(shout, cfg_get_server_user()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_user: %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (pezConfig->serverName &&
|
||||
shout_set_name(shout, pezConfig->serverName) != SHOUTERR_SUCCESS) {
|
||||
if (cfg_get_stream_name() &&
|
||||
shout_set_name(shout, cfg_get_stream_name()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_name: %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (pezConfig->serverURL &&
|
||||
shout_set_url(shout, pezConfig->serverURL) != SHOUTERR_SUCCESS) {
|
||||
if (cfg_get_stream_url() &&
|
||||
shout_set_url(shout, cfg_get_stream_url()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_url: %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (pezConfig->serverGenre &&
|
||||
shout_set_genre(shout, pezConfig->serverGenre) != SHOUTERR_SUCCESS) {
|
||||
if (cfg_get_stream_genre() &&
|
||||
shout_set_genre(shout, cfg_get_stream_genre()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_genre: %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (pezConfig->serverDescription &&
|
||||
shout_set_description(shout, pezConfig->serverDescription) != SHOUTERR_SUCCESS) {
|
||||
if (cfg_get_stream_description() &&
|
||||
shout_set_description(shout, cfg_get_stream_description()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_description: %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (pezConfig->serverBitrate &&
|
||||
shout_set_audio_info(shout, SHOUT_AI_BITRATE, pezConfig->serverBitrate) != SHOUTERR_SUCCESS) {
|
||||
if (cfg_get_stream_bitrate() &&
|
||||
shout_set_audio_info(shout, SHOUT_AI_BITRATE, cfg_get_stream_bitrate()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_audio_info(AI_BITRATE): %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (pezConfig->serverChannels &&
|
||||
shout_set_audio_info(shout, SHOUT_AI_CHANNELS, pezConfig->serverChannels) != SHOUTERR_SUCCESS) {
|
||||
if (cfg_get_stream_channels() &&
|
||||
shout_set_audio_info(shout, SHOUT_AI_CHANNELS, cfg_get_stream_channels()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_audio_info(AI_CHANNELS): %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (pezConfig->serverSamplerate &&
|
||||
shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, pezConfig->serverSamplerate) != SHOUTERR_SUCCESS) {
|
||||
if (cfg_get_stream_samplerate() &&
|
||||
shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, cfg_get_stream_samplerate()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_audio_info(AI_SAMPLERATE): %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
if (pezConfig->serverQuality &&
|
||||
shout_set_audio_info(shout, SHOUT_AI_QUALITY, pezConfig->serverQuality) != SHOUTERR_SUCCESS) {
|
||||
if (cfg_get_stream_quality() &&
|
||||
shout_set_audio_info(shout, SHOUT_AI_QUALITY, cfg_get_stream_quality()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_audio_info(AI_QUALITY): %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (shout_set_public(shout, (unsigned int)pezConfig->serverPublic) != SHOUTERR_SUCCESS) {
|
||||
if (shout_set_public(shout, (unsigned int)cfg_get_stream_server_public()) != SHOUTERR_SUCCESS) {
|
||||
log_error("shout_set_public: %s",
|
||||
shout_get_error(shout));
|
||||
shout_free(shout);
|
||||
|
@ -17,6 +17,8 @@
|
||||
#ifndef __XALLOC_H__
|
||||
#define __XALLOC_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define xmalloc(s) xmalloc_c(s, __FILE__, __LINE__)
|
||||
#define xcalloc(n, s) xcalloc_c(n, s, __FILE__, __LINE__)
|
||||
#define xreallocarray(p, n, s) xreallocarray_c(p, n, s, __FILE__, __LINE__)
|
||||
|
Loading…
x
Reference in New Issue
Block a user