mirror of
https://gitlab.xiph.org/xiph/ezstream.git
synced 2024-11-03 04:17:18 -05:00
Switch to a new (unit-tested) metadata handling API
This commit is contained in:
parent
399e66df2b
commit
6616831590
@ -11,7 +11,7 @@ noinst_HEADERS = \
|
||||
cmdline.h \
|
||||
ezstream.h \
|
||||
log.h \
|
||||
metadata.h \
|
||||
mdata.h \
|
||||
playlist.h \
|
||||
stream.h \
|
||||
util.h \
|
||||
@ -23,7 +23,7 @@ libezstream_la_SOURCES = \
|
||||
cfg_xmlfile.c \
|
||||
cmdline.c \
|
||||
log.c \
|
||||
metadata.c \
|
||||
mdata.c \
|
||||
playlist.c \
|
||||
stream.c \
|
||||
util.c \
|
||||
|
158
src/ezstream.c
158
src/ezstream.c
@ -26,7 +26,7 @@
|
||||
#include "cfg.h"
|
||||
#include "cmdline.h"
|
||||
#include "log.h"
|
||||
#include "metadata.h"
|
||||
#include "mdata.h"
|
||||
#include "playlist.h"
|
||||
#include "stream.h"
|
||||
#include "util.h"
|
||||
@ -53,9 +53,9 @@ volatile sig_atomic_t queryMetadata;
|
||||
volatile sig_atomic_t quit;
|
||||
|
||||
void sig_handler(int);
|
||||
char * _build_reencode_cmd(const char *, const char *, metadata_t);
|
||||
metadata_t getMetadata(const char *);
|
||||
FILE * openResource(stream_t, const char *, int *, metadata_t *,
|
||||
char * _build_reencode_cmd(const char *, const char *, mdata_t);
|
||||
mdata_t getMetadata(const char *);
|
||||
FILE * openResource(stream_t, const char *, int *, mdata_t *,
|
||||
int *, long *);
|
||||
int reconnect(stream_t);
|
||||
const char * getTimeString(long);
|
||||
@ -89,12 +89,12 @@ sig_handler(int sig)
|
||||
}
|
||||
|
||||
char *
|
||||
_build_reencode_cmd(const char *extension, const char *fileName,
|
||||
metadata_t mdata)
|
||||
_build_reencode_cmd(const char *extension, const char *filename,
|
||||
mdata_t md)
|
||||
{
|
||||
cfg_decoder_t decoder;
|
||||
cfg_encoder_t encoder;
|
||||
char *artist, *album, *title, *songinfo, *tmp;
|
||||
char *artist, *album, *title, *songinfo;
|
||||
char *filename_quoted;
|
||||
char *custom_songinfo;
|
||||
struct util_dict dicts[6];
|
||||
@ -106,7 +106,7 @@ _build_reencode_cmd(const char *extension, const char *fileName,
|
||||
decoder = cfg_decoder_find(extension);
|
||||
if (!decoder) {
|
||||
log_error("cannot decode: %s: unsupported file extension %s",
|
||||
fileName, extension);
|
||||
filename, extension);
|
||||
return (NULL);
|
||||
}
|
||||
encoder = cfg_encoder_get(cfg_get_stream_encoder());
|
||||
@ -116,23 +116,12 @@ _build_reencode_cmd(const char *extension, const char *fileName,
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
tmp = util_utf82char(metadata_get_artist(mdata));
|
||||
artist = util_shellquote(tmp);
|
||||
xfree(tmp);
|
||||
artist = util_shellquote(util_utf82char(mdata_get_artist(md)));
|
||||
album = util_shellquote(util_utf82char(mdata_get_album(md)));
|
||||
title = util_shellquote(util_utf82char(mdata_get_title(md)));
|
||||
songinfo = util_shellquote(util_utf82char(mdata_get_songinfo(md)));
|
||||
|
||||
tmp = util_utf82char(metadata_get_album(mdata));
|
||||
album = util_shellquote(tmp);
|
||||
xfree(tmp);
|
||||
|
||||
tmp = util_utf82char(metadata_get_title(mdata));
|
||||
title = util_shellquote(tmp);
|
||||
xfree(tmp);
|
||||
|
||||
tmp = util_utf82char(metadata_get_string(mdata));
|
||||
songinfo = util_shellquote(tmp);
|
||||
xfree(tmp);
|
||||
|
||||
filename_quoted = util_shellquote(fileName);
|
||||
filename_quoted = util_shellquote(filename);
|
||||
|
||||
/*
|
||||
* if (prog && format)
|
||||
@ -145,12 +134,12 @@ _build_reencode_cmd(const char *extension, const char *fileName,
|
||||
*/
|
||||
if (cfg_get_metadata_program() &&
|
||||
cfg_get_metadata_format_str()) {
|
||||
char *utf8, *unquoted;
|
||||
char buf[BUFSIZ];
|
||||
char *unquoted;
|
||||
|
||||
utf8 = metadata_format_string(mdata,
|
||||
mdata_strformat(md, buf, sizeof(buf),
|
||||
cfg_get_metadata_format_str());
|
||||
unquoted = util_utf82char(utf8);
|
||||
xfree(utf8);
|
||||
unquoted = util_utf82char(buf);
|
||||
custom_songinfo = util_shellquote(unquoted);
|
||||
xfree(unquoted);
|
||||
} else {
|
||||
@ -202,62 +191,50 @@ _build_reencode_cmd(const char *extension, const char *fileName,
|
||||
return (cmd_str);
|
||||
}
|
||||
|
||||
metadata_t
|
||||
getMetadata(const char *fileName)
|
||||
mdata_t
|
||||
getMetadata(const char *filename)
|
||||
{
|
||||
metadata_t mdata;
|
||||
mdata_t md = mdata_create();
|
||||
|
||||
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)) {
|
||||
metadata_free(&mdata);
|
||||
return (NULL);
|
||||
}
|
||||
if (0 > mdata_run_program(md, filename))
|
||||
mdata_destroy(&md);
|
||||
} else {
|
||||
if (NULL == (mdata = metadata_file(fileName,
|
||||
cfg_get_metadata_normalize_strings())))
|
||||
return (NULL);
|
||||
|
||||
if (!metadata_file_update(mdata)) {
|
||||
metadata_free(&mdata);
|
||||
return (NULL);
|
||||
}
|
||||
if (0 > mdata_parse_file(md, filename))
|
||||
mdata_destroy(&md);
|
||||
}
|
||||
|
||||
return (mdata);
|
||||
return (md);
|
||||
}
|
||||
|
||||
FILE *
|
||||
openResource(stream_t stream, const char *fileName, int *popenFlag,
|
||||
metadata_t *mdata_p, int *isStdin, long *songLen)
|
||||
openResource(stream_t stream, const char *filename, int *popenFlag,
|
||||
mdata_t *md_p, int *isStdin, long *songLen)
|
||||
{
|
||||
FILE *filep = NULL;
|
||||
char extension[25];
|
||||
char *p = NULL;
|
||||
char *pCommandString = NULL;
|
||||
metadata_t mdata;
|
||||
mdata_t md;
|
||||
|
||||
if (mdata_p != NULL)
|
||||
*mdata_p = NULL;
|
||||
if (md_p != NULL)
|
||||
*md_p = NULL;
|
||||
if (songLen != NULL)
|
||||
*songLen = 0;
|
||||
|
||||
if ((isStdin && *isStdin) ||
|
||||
strcasecmp(fileName, "stdin") == 0) {
|
||||
strcasecmp(filename, "stdin") == 0) {
|
||||
if (cfg_get_metadata_program()) {
|
||||
if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
|
||||
if ((md = getMetadata(cfg_get_metadata_program())) == NULL)
|
||||
return (NULL);
|
||||
if (0 > stream_set_metadata(stream, mdata, NULL)) {
|
||||
metadata_free(&mdata);
|
||||
if (0 > stream_set_metadata(stream, md, NULL)) {
|
||||
mdata_destroy(&md);
|
||||
return (NULL);
|
||||
}
|
||||
if (mdata_p != NULL)
|
||||
*mdata_p = mdata;
|
||||
if (md_p != NULL)
|
||||
*md_p = md;
|
||||
else
|
||||
metadata_free(&mdata);
|
||||
mdata_destroy(&md);
|
||||
}
|
||||
|
||||
if (isStdin != NULL)
|
||||
@ -270,37 +247,37 @@ openResource(stream_t stream, const char *fileName, int *popenFlag,
|
||||
*isStdin = 0;
|
||||
|
||||
extension[0] = '\0';
|
||||
p = strrchr(fileName, '.');
|
||||
p = strrchr(filename, '.');
|
||||
if (p != NULL)
|
||||
strlcpy(extension, p, sizeof(extension));
|
||||
for (p = extension; *p != '\0'; p++)
|
||||
*p = (char)tolower((int)*p);
|
||||
|
||||
if (strlen(extension) == 0) {
|
||||
log_error("%s: cannot determine file type", fileName);
|
||||
log_error("%s: cannot determine file type", filename);
|
||||
return (filep);
|
||||
}
|
||||
|
||||
if (cfg_get_metadata_program()) {
|
||||
if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
|
||||
if ((md = getMetadata(cfg_get_metadata_program())) == NULL)
|
||||
return (NULL);
|
||||
} else {
|
||||
if ((mdata = getMetadata(fileName)) == NULL)
|
||||
if ((md = getMetadata(filename)) == NULL)
|
||||
return (NULL);
|
||||
}
|
||||
if (songLen != NULL)
|
||||
*songLen = metadata_get_length(mdata);
|
||||
*songLen = mdata_get_length(md);
|
||||
|
||||
*popenFlag = 0;
|
||||
if (cfg_get_stream_encoder()) {
|
||||
int stderr_fd = -1;
|
||||
|
||||
pCommandString = _build_reencode_cmd(extension, fileName,
|
||||
mdata);
|
||||
if (mdata_p != NULL)
|
||||
*mdata_p = mdata;
|
||||
pCommandString = _build_reencode_cmd(extension, filename,
|
||||
md);
|
||||
if (md_p != NULL)
|
||||
*md_p = md;
|
||||
else
|
||||
metadata_free(&mdata);
|
||||
mdata_destroy(&md);
|
||||
log_info("running command: %s", pCommandString);
|
||||
|
||||
if (cfg_get_program_quiet_stderr()) {
|
||||
@ -346,13 +323,13 @@ openResource(stream_t stream, const char *fileName, int *popenFlag,
|
||||
return (filep);
|
||||
}
|
||||
|
||||
if (mdata_p != NULL)
|
||||
*mdata_p = mdata;
|
||||
if (md_p != NULL)
|
||||
*md_p = md;
|
||||
else
|
||||
metadata_free(&mdata);
|
||||
mdata_destroy(&md);
|
||||
|
||||
if ((filep = fopen(fileName, "rb")) == NULL) {
|
||||
log_error("%s: %s", fileName, strerror(errno));
|
||||
if ((filep = fopen(filename, "rb")) == NULL) {
|
||||
log_error("%s: %s", filename, strerror(errno));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
@ -544,11 +521,12 @@ streamFile(stream_t stream, const char *fileName)
|
||||
int isStdin = cfg_get_media_type() == CFG_MEDIA_STDIN;
|
||||
int ret, retval = 0;
|
||||
long songLen;
|
||||
metadata_t mdata;
|
||||
mdata_t md = NULL;
|
||||
struct timespec startTime;
|
||||
|
||||
if ((filepstream = openResource(stream, fileName, &popenFlag, &mdata, &isStdin, &songLen))
|
||||
if ((filepstream = openResource(stream, fileName, &popenFlag, &md, &isStdin, &songLen))
|
||||
== NULL) {
|
||||
mdata_destroy(&md);
|
||||
if (++resource_errors > 100) {
|
||||
log_error("too many errors; giving up");
|
||||
return (0);
|
||||
@ -558,22 +536,22 @@ streamFile(stream_t stream, const char *fileName)
|
||||
}
|
||||
resource_errors = 0;
|
||||
|
||||
if (mdata != NULL) {
|
||||
char *tmp, *metaData;
|
||||
if (md != NULL) {
|
||||
const char *tmp;
|
||||
char *metaData;
|
||||
|
||||
tmp = metadata_assemble_string(mdata);
|
||||
if ((metaData = util_utf82char(tmp)) == NULL)
|
||||
metaData = xstrdup("(unknown title)");
|
||||
xfree(tmp);
|
||||
tmp = mdata_get_songinfo(md) ?
|
||||
mdata_get_songinfo(md) : mdata_get_name(md);
|
||||
metaData = util_utf82char(tmp);
|
||||
log_notice("streaming: %s (%s)", metaData,
|
||||
isStdin ? "stdin" : fileName);
|
||||
xfree(metaData);
|
||||
|
||||
/* MP3 streams are special, so set the metadata explicitly: */
|
||||
if (CFG_STREAM_MP3 == cfg_get_stream_format())
|
||||
stream_set_metadata(stream, mdata, NULL);
|
||||
stream_set_metadata(stream, md, NULL);
|
||||
|
||||
metadata_free(&mdata);
|
||||
mdata_destroy(&md);
|
||||
} else if (isStdin)
|
||||
log_notice("streaming: standard input");
|
||||
|
||||
@ -608,22 +586,22 @@ streamFile(stream_t stream, const char *fileName)
|
||||
continue;
|
||||
if (cfg_get_metadata_program()) {
|
||||
char *mdataStr = NULL;
|
||||
metadata_t prog_mdata;
|
||||
mdata_t prog_md;
|
||||
|
||||
log_info("running metadata program: %s",
|
||||
cfg_get_metadata_program());
|
||||
if ((prog_mdata = getMetadata(cfg_get_metadata_program())) == NULL) {
|
||||
if ((prog_md = getMetadata(cfg_get_metadata_program())) == NULL) {
|
||||
retval = 0;
|
||||
ret = STREAM_DONE;
|
||||
continue;
|
||||
}
|
||||
if (0 > stream_set_metadata(stream, prog_mdata, &mdataStr)) {
|
||||
if (0 > stream_set_metadata(stream, prog_md, &mdataStr)) {
|
||||
retval = 0;
|
||||
ret = STREAM_DONE;
|
||||
metadata_free(&prog_mdata);
|
||||
mdata_destroy(&prog_md);
|
||||
continue;
|
||||
}
|
||||
metadata_free(&prog_mdata);
|
||||
mdata_destroy(&prog_md);
|
||||
log_info("new metadata: %s", mdataStr);
|
||||
xfree(mdataStr);
|
||||
}
|
||||
|
502
src/mdata.c
Normal file
502
src/mdata.c
Normal file
@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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 <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <errno.h>
|
||||
#if defined(HAVE_LIBGEN_H) && !defined(__linux__)
|
||||
# include <libgen.h>
|
||||
#endif /* HAVE_LIBGEN_H && !__linux__ */
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <taglib/tag_c.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "log.h"
|
||||
#include "mdata.h"
|
||||
#include "util.h"
|
||||
#include "xalloc.h"
|
||||
|
||||
struct mdata {
|
||||
char *filename;
|
||||
char *name;
|
||||
char *artist;
|
||||
char *album;
|
||||
char *title;
|
||||
char *songinfo;
|
||||
int length;
|
||||
int normalize_strings;
|
||||
int run_program;
|
||||
};
|
||||
|
||||
enum mdata_request {
|
||||
MDATA_ARTIST,
|
||||
MDATA_ALBUM,
|
||||
MDATA_TITLE,
|
||||
MDATA_SONGINFO
|
||||
};
|
||||
|
||||
static void _mdata_clear(struct mdata *);
|
||||
static char * _mdata_get_name_from_filename(const char *);
|
||||
static void _mdata_generate_songinfo(struct mdata *);
|
||||
static void _mdata_normalize_string(char **);
|
||||
static void _mdata_normalize_strings(struct mdata *);
|
||||
static char * _mdata_run(const char *, enum mdata_request);
|
||||
|
||||
static void
|
||||
_mdata_clear(struct mdata *md)
|
||||
{
|
||||
int normalize_strings;
|
||||
|
||||
normalize_strings = md->normalize_strings;
|
||||
xfree(md->filename);
|
||||
xfree(md->name);
|
||||
xfree(md->artist);
|
||||
xfree(md->album);
|
||||
xfree(md->title);
|
||||
xfree(md->songinfo);
|
||||
memset(md, 0, sizeof(*md));
|
||||
md->length = -1;
|
||||
md->normalize_strings = normalize_strings;
|
||||
}
|
||||
|
||||
static char *
|
||||
_mdata_get_name_from_filename(const char *filename)
|
||||
{
|
||||
char *tmp;
|
||||
char *p1, *p2, *name;
|
||||
|
||||
/*
|
||||
* Make a copy of filename in case basename() is broken and attempts
|
||||
* to modify its argument.
|
||||
*/
|
||||
tmp = xstrdup(filename);
|
||||
if ((p1 = basename(tmp)) == NULL) {
|
||||
/*
|
||||
* Some implementations limit the input to PATH_MAX; bail out
|
||||
* if that is exceeded and an error is returned.
|
||||
*/
|
||||
log_alert("%s: %s", tmp, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((p2 = strrchr(p1, '.')) != NULL)
|
||||
*p2 = '\0';
|
||||
|
||||
if (strlen(p1) == 0)
|
||||
name = xstrdup("[unknown]");
|
||||
else
|
||||
name = util_char2utf8(p1);
|
||||
|
||||
xfree(tmp);
|
||||
|
||||
return (name);
|
||||
}
|
||||
|
||||
void
|
||||
_mdata_generate_songinfo(struct mdata *md)
|
||||
{
|
||||
char *str;
|
||||
size_t str_size;
|
||||
|
||||
str_size = 0;
|
||||
if (md->artist)
|
||||
str_size += strlen(md->artist);
|
||||
if (md->title) {
|
||||
if (str_size)
|
||||
str_size += strlen(" - ");
|
||||
str_size += strlen(md->title);
|
||||
}
|
||||
if (md->album) {
|
||||
if (str_size)
|
||||
str_size += strlen(" - ");
|
||||
str_size += strlen(md->album);
|
||||
}
|
||||
if (!str_size)
|
||||
return;
|
||||
str_size++;
|
||||
str = xcalloc(str_size, sizeof(*str));
|
||||
|
||||
if (md->artist)
|
||||
strlcpy(str, md->artist, str_size);
|
||||
if (md->title) {
|
||||
if (strlen(str))
|
||||
strlcat(str, " - ", str_size);
|
||||
strlcat(str, md->title, str_size);
|
||||
}
|
||||
if (md->album) {
|
||||
if (strlen(str))
|
||||
strlcat(str, " - ", str_size);
|
||||
strlcat(str, md->album, str_size);
|
||||
}
|
||||
|
||||
md->songinfo = str;
|
||||
}
|
||||
|
||||
static void
|
||||
_mdata_normalize_string(char **s)
|
||||
{
|
||||
char *str, *cp, *tmpstr, *tp;
|
||||
int is_space;
|
||||
|
||||
str = *s;
|
||||
if (NULL == str)
|
||||
return;
|
||||
|
||||
tmpstr = xcalloc(strlen(str) + 1, sizeof(char));
|
||||
|
||||
tp = tmpstr;
|
||||
is_space = 1;
|
||||
for (cp = str; *cp != '\0'; cp++) {
|
||||
if (*cp == ' ') {
|
||||
if (!is_space)
|
||||
*tp++ = ' ';
|
||||
is_space = 1;
|
||||
} else {
|
||||
*tp++ = *cp;
|
||||
is_space = 0;
|
||||
}
|
||||
}
|
||||
if (strlen(tmpstr) > 0 && tmpstr[strlen(tmpstr) - 1] == ' ')
|
||||
tmpstr[strlen(tmpstr) - 1] = '\0';
|
||||
|
||||
xfree(str);
|
||||
*s = xreallocarray(tmpstr, strlen(tmpstr) + 1, sizeof(*tmpstr));
|
||||
}
|
||||
|
||||
static void
|
||||
_mdata_normalize_strings(struct mdata *md)
|
||||
{
|
||||
_mdata_normalize_string(&md->artist);
|
||||
_mdata_normalize_string(&md->album);
|
||||
_mdata_normalize_string(&md->title);
|
||||
_mdata_normalize_string(&md->songinfo);
|
||||
}
|
||||
|
||||
static char *
|
||||
_mdata_run(const char *program, enum mdata_request md_req)
|
||||
{
|
||||
char cmd[PATH_MAX + sizeof(" artist")];
|
||||
char buf[BUFSIZ];
|
||||
FILE *fp;
|
||||
int ret;
|
||||
|
||||
switch (md_req) {
|
||||
case MDATA_ARTIST:
|
||||
snprintf(cmd, sizeof(cmd), "%s artist", program);
|
||||
break;
|
||||
case MDATA_ALBUM:
|
||||
snprintf(cmd, sizeof(cmd), "%s album", program);
|
||||
break;
|
||||
case MDATA_TITLE:
|
||||
snprintf(cmd, sizeof(cmd), "%s title", program);
|
||||
break;
|
||||
case MDATA_SONGINFO:
|
||||
default:
|
||||
snprintf(cmd, sizeof(cmd), "%s", program);
|
||||
break;
|
||||
}
|
||||
|
||||
fflush(NULL);
|
||||
log_debug("running metadata command: %s", cmd);
|
||||
errno = ENOMEM;
|
||||
if ((fp = popen(cmd, "r")) == NULL) {
|
||||
log_error("%s: execution error: %s", cmd,
|
||||
strerror(errno));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
if (NULL == fgets(buf, (int)sizeof(buf), fp) &&
|
||||
ferror(fp)) {
|
||||
log_alert("%s: output read error: %s", program,
|
||||
strerror(errno));
|
||||
pclose(fp);
|
||||
exit(1);
|
||||
}
|
||||
ret = pclose(fp);
|
||||
if (0 > ret) {
|
||||
log_error("%s: %s", program, strerror(errno));
|
||||
return (NULL);
|
||||
} else if (WIFSIGNALED(ret)) {
|
||||
log_error("%s: exited with signal %d", program,
|
||||
WTERMSIG(ret));
|
||||
return (NULL);
|
||||
} else if (0 != WEXITSTATUS(ret)) {
|
||||
log_error("%s: exited with error code %d", program,
|
||||
WEXITSTATUS(ret));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (strlen(buf) == sizeof(buf) - 1)
|
||||
log_warning("metadata output truncated: %s", cmd);
|
||||
|
||||
buf[strcspn(buf, "\n")] = '\0';
|
||||
buf[strcspn(buf, "\r")] = '\0';
|
||||
|
||||
return (xstrdup(buf));
|
||||
}
|
||||
|
||||
struct mdata *
|
||||
mdata_create(void)
|
||||
{
|
||||
struct mdata *md;
|
||||
|
||||
md = xcalloc(1UL, sizeof(*md));
|
||||
md->length = -1;
|
||||
|
||||
return (md);
|
||||
}
|
||||
|
||||
void
|
||||
mdata_destroy(struct mdata **md_p)
|
||||
{
|
||||
struct mdata *md = *md_p;
|
||||
|
||||
_mdata_clear(md);
|
||||
xfree(md);
|
||||
*md_p = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
mdata_set_normalize_strings(struct mdata *md, int normalize_strings)
|
||||
{
|
||||
md->normalize_strings = normalize_strings ? 1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
mdata_parse_file(struct mdata *md, const char *filename)
|
||||
{
|
||||
TagLib_File *tf;
|
||||
TagLib_Tag *tt;
|
||||
const TagLib_AudioProperties *ta;
|
||||
char *str;
|
||||
|
||||
if (0 > access(filename, R_OK)) {
|
||||
log_error("%s: %s", filename, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
|
||||
//taglib_set_string_management_enabled(0);
|
||||
#ifdef HAVE_ICONV
|
||||
taglib_set_strings_unicode(1);
|
||||
#else
|
||||
taglib_set_strings_unicode(0);
|
||||
#endif /* HAVE_ICONV */
|
||||
|
||||
_mdata_clear(md);
|
||||
md->filename = xstrdup(filename);
|
||||
md->name = _mdata_get_name_from_filename(filename);
|
||||
|
||||
if ((tf = taglib_file_new(md->filename)) == NULL) {
|
||||
log_info("%s: unable to extract metadata",
|
||||
md->filename);
|
||||
md->songinfo = xstrdup(md->name);
|
||||
return (0);
|
||||
}
|
||||
|
||||
tt = taglib_file_tag(tf);
|
||||
|
||||
str = taglib_tag_artist(tt);
|
||||
if (0 < strlen(str))
|
||||
md->artist = xstrdup(str);
|
||||
str = taglib_tag_album(tt);
|
||||
if (0 < strlen(str))
|
||||
md->album = xstrdup(str);
|
||||
str = taglib_tag_title(tt);
|
||||
if (0 < strlen(str))
|
||||
md->title = xstrdup(str);
|
||||
|
||||
taglib_tag_free_strings();
|
||||
|
||||
ta = taglib_file_audioproperties(tf);
|
||||
md->length = taglib_audioproperties_length(ta);
|
||||
|
||||
taglib_file_free(tf);
|
||||
|
||||
if (md->normalize_strings)
|
||||
_mdata_normalize_strings(md);
|
||||
_mdata_generate_songinfo(md);
|
||||
|
||||
md->run_program = 0;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
mdata_run_program(struct mdata *md, const char *program)
|
||||
{
|
||||
struct stat st;
|
||||
char *artist, *album, *title, *songinfo;
|
||||
|
||||
if (stat(program, &st) == -1) {
|
||||
log_error("%s: %s", program, strerror(errno));
|
||||
return (-1);
|
||||
}
|
||||
if (st.st_mode & S_IWOTH) {
|
||||
log_error("%s: world writeable", program);
|
||||
return (-1);
|
||||
}
|
||||
if (!(st.st_mode & (S_IEXEC | S_IXGRP | S_IXOTH))) {
|
||||
log_error("%s: not an executable program", program);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
artist = album = title = songinfo = NULL;
|
||||
if (NULL == (artist = _mdata_run(program, MDATA_ARTIST)) ||
|
||||
NULL == (album = _mdata_run(program, MDATA_ALBUM)) ||
|
||||
NULL == (title = _mdata_run(program, MDATA_TITLE)) ||
|
||||
NULL == (songinfo = _mdata_run(program, MDATA_SONGINFO)))
|
||||
goto error;
|
||||
|
||||
_mdata_clear(md);
|
||||
md->filename = xstrdup(program);
|
||||
md->name = xstrdup("[unknown]");
|
||||
|
||||
if (0 == strlen(artist))
|
||||
xfree(artist);
|
||||
else
|
||||
md->artist = artist;
|
||||
|
||||
if (0 == strlen(album))
|
||||
xfree(album);
|
||||
else
|
||||
md->album = album;
|
||||
|
||||
if (0 == strlen(title))
|
||||
xfree(title);
|
||||
else
|
||||
md->title = title;
|
||||
|
||||
if (0 == strlen(songinfo))
|
||||
xfree(songinfo);
|
||||
else
|
||||
md->songinfo = songinfo;
|
||||
|
||||
if (md->normalize_strings)
|
||||
_mdata_normalize_strings(md);
|
||||
|
||||
md->run_program = 1;
|
||||
|
||||
return (0);
|
||||
|
||||
error:
|
||||
xfree(artist);
|
||||
xfree(album);
|
||||
xfree(title);
|
||||
xfree(songinfo);
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
mdata_refresh(struct mdata *md)
|
||||
{
|
||||
char *filename = xstrdup(md->filename);
|
||||
int ret;
|
||||
|
||||
if (md->run_program)
|
||||
ret = mdata_run_program(md, filename);
|
||||
else
|
||||
ret = mdata_parse_file(md, filename);
|
||||
xfree(filename);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
const char *
|
||||
mdata_get_filename(struct mdata *md)
|
||||
{
|
||||
return (md->filename);
|
||||
}
|
||||
|
||||
const char *
|
||||
mdata_get_name(struct mdata *md)
|
||||
{
|
||||
return (md->name);
|
||||
}
|
||||
|
||||
const char *
|
||||
mdata_get_artist(struct mdata *md)
|
||||
{
|
||||
return (md->artist);
|
||||
}
|
||||
|
||||
const char *
|
||||
mdata_get_album(struct mdata *md)
|
||||
{
|
||||
return (md->album);
|
||||
}
|
||||
|
||||
const char *
|
||||
mdata_get_title(struct mdata *md)
|
||||
{
|
||||
return (md->title);
|
||||
}
|
||||
|
||||
const char *
|
||||
mdata_get_songinfo(struct mdata *md)
|
||||
{
|
||||
return (md->songinfo);
|
||||
}
|
||||
|
||||
int
|
||||
mdata_get_length(struct mdata *md)
|
||||
{
|
||||
return (md->length);
|
||||
}
|
||||
|
||||
int
|
||||
mdata_strformat(struct mdata *md, char *buf, size_t bufsize, const char *format)
|
||||
{
|
||||
struct util_dict dicts[6];
|
||||
char *str;
|
||||
int ret;
|
||||
|
||||
if (format == NULL)
|
||||
return (-1);
|
||||
|
||||
memset(dicts, 0, sizeof(dicts));
|
||||
dicts[0].from = PLACEHOLDER_ARTIST;
|
||||
dicts[0].to = mdata_get_artist(md);
|
||||
dicts[1].from = PLACEHOLDER_ALBUM;
|
||||
dicts[1].to = mdata_get_album(md);
|
||||
dicts[2].from = PLACEHOLDER_TITLE;
|
||||
dicts[2].to = mdata_get_title(md);
|
||||
dicts[3].from = PLACEHOLDER_TRACK;
|
||||
dicts[3].to = mdata_get_filename(md);
|
||||
dicts[4].from = PLACEHOLDER_STRING;
|
||||
dicts[4].to = mdata_get_songinfo(md);
|
||||
|
||||
str = util_expand_words(format, dicts);
|
||||
ret = (int)strlen(str);
|
||||
|
||||
strlcpy(buf, str, bufsize);
|
||||
xfree(str);
|
||||
|
||||
return (ret);
|
||||
}
|
49
src/mdata.h
Normal file
49
src/mdata.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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 __MDATA_H__
|
||||
#define __MDATA_H__
|
||||
|
||||
typedef struct mdata * mdata_t;
|
||||
|
||||
mdata_t mdata_create(void);
|
||||
void mdata_destroy(mdata_t *);
|
||||
|
||||
void mdata_set_normalize_strings(mdata_t, int);
|
||||
|
||||
int mdata_parse_file(mdata_t, const char *);
|
||||
int mdata_run_program(mdata_t, const char *);
|
||||
|
||||
int mdata_refresh(mdata_t);
|
||||
|
||||
const char *
|
||||
mdata_get_filename(mdata_t);
|
||||
const char *
|
||||
mdata_get_name(mdata_t);
|
||||
const char *
|
||||
mdata_get_artist(mdata_t);
|
||||
const char *
|
||||
mdata_get_album(mdata_t);
|
||||
const char *
|
||||
mdata_get_title(mdata_t);
|
||||
const char *
|
||||
mdata_get_songinfo(mdata_t);
|
||||
|
||||
int mdata_get_length(mdata_t);
|
||||
|
||||
int mdata_strformat(mdata_t, char *, size_t, const char *);
|
||||
|
||||
#endif /* __MDATA_H__ */
|
539
src/metadata.c
539
src/metadata.c
@ -1,539 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2007, 2009 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
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#if defined(HAVE_LIBGEN_H) && !defined(__linux__)
|
||||
# include <libgen.h>
|
||||
#endif /* HAVE_LIBGEN_H && !__linux__ */
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <taglib/tag_c.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "log.h"
|
||||
#include "metadata.h"
|
||||
#include "util.h"
|
||||
#include "xalloc.h"
|
||||
|
||||
/* Usually defined in <sys/stat.h>. */
|
||||
#ifndef S_IEXEC
|
||||
# define S_IEXEC S_IXUSR
|
||||
#endif /* !S_IEXEC */
|
||||
|
||||
static const char *blankString = "";
|
||||
|
||||
struct metadata {
|
||||
char *filename;
|
||||
char *string;
|
||||
char *artist;
|
||||
char *title;
|
||||
char *album;
|
||||
int songLen;
|
||||
int normalize;
|
||||
int program;
|
||||
};
|
||||
|
||||
static struct metadata *
|
||||
metadata_create(const char *);
|
||||
static void metadata_get(struct metadata *, FILE **);
|
||||
static void metadata_clean_md(struct metadata *);
|
||||
static char * metadata_get_name(const char *);
|
||||
static void metadata_process_md(struct metadata *);
|
||||
static void metadata_normalize_string(char **);
|
||||
|
||||
static struct metadata *
|
||||
metadata_create(const char *filename)
|
||||
{
|
||||
metadata_t md;
|
||||
|
||||
md = xcalloc(1UL, sizeof(*md));
|
||||
md->filename = xstrdup(filename);
|
||||
md->songLen = -1;
|
||||
|
||||
return (md);
|
||||
}
|
||||
|
||||
static void
|
||||
metadata_get(struct metadata *md, FILE **filep)
|
||||
{
|
||||
TagLib_File *tf;
|
||||
TagLib_Tag *tt;
|
||||
const TagLib_AudioProperties *ta;
|
||||
char *str;
|
||||
|
||||
if (filep != NULL)
|
||||
fclose(*filep);
|
||||
|
||||
metadata_clean_md(md);
|
||||
taglib_set_string_management_enabled(0);
|
||||
#ifdef HAVE_ICONV
|
||||
taglib_set_strings_unicode(1);
|
||||
#else
|
||||
taglib_set_strings_unicode(0);
|
||||
#endif /* HAVE_ICONV */
|
||||
|
||||
if (md->string != NULL) {
|
||||
xfree(md->string);
|
||||
md->string = NULL;
|
||||
}
|
||||
|
||||
if ((tf = taglib_file_new(md->filename)) == NULL) {
|
||||
md->string = metadata_get_name(md->filename);
|
||||
return;
|
||||
}
|
||||
|
||||
tt = taglib_file_tag(tf);
|
||||
ta = taglib_file_audioproperties(tf);
|
||||
|
||||
str = taglib_tag_artist(tt);
|
||||
if (str != NULL) {
|
||||
if (strlen(str) > 0)
|
||||
md->artist = xstrdup(str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
str = taglib_tag_title(tt);
|
||||
if (str != NULL) {
|
||||
if (strlen(str) > 0)
|
||||
md->title = xstrdup(str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
str = taglib_tag_album(tt);
|
||||
if (str != NULL) {
|
||||
if (strlen(str) > 0)
|
||||
md->album = xstrdup(str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
md->songLen = taglib_audioproperties_length(ta);
|
||||
|
||||
taglib_file_free(tf);
|
||||
}
|
||||
|
||||
static void
|
||||
metadata_clean_md(struct metadata *md)
|
||||
{
|
||||
if (md->string != NULL) {
|
||||
xfree(md->string);
|
||||
md->string = NULL;
|
||||
}
|
||||
if (md->artist != NULL) {
|
||||
xfree(md->artist);
|
||||
md->artist = NULL;
|
||||
}
|
||||
if (md->title != NULL) {
|
||||
xfree(md->title);
|
||||
md->title = NULL;
|
||||
}
|
||||
if (md->album != NULL) {
|
||||
xfree(md->title);
|
||||
md->album = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char *
|
||||
metadata_get_name(const char *file)
|
||||
{
|
||||
char *filename = xstrdup(file);
|
||||
char *p1, *p2, *name;
|
||||
|
||||
if ((p1 = basename(filename)) == NULL) {
|
||||
log_alert("basename: unexpected failure with input: %s",
|
||||
filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((p2 = strrchr(p1, '.')) != NULL)
|
||||
*p2 = '\0';
|
||||
|
||||
if (strlen(p1) == 0)
|
||||
name = xstrdup("[unknown]");
|
||||
else
|
||||
name = util_char2utf8(p1);
|
||||
|
||||
xfree(filename);
|
||||
return (name);
|
||||
}
|
||||
|
||||
static void
|
||||
metadata_process_md(struct metadata *md)
|
||||
{
|
||||
if (md->string == NULL)
|
||||
md->string = metadata_assemble_string(md);
|
||||
|
||||
if (md->normalize) {
|
||||
metadata_normalize_string(&md->string);
|
||||
metadata_normalize_string(&md->artist);
|
||||
metadata_normalize_string(&md->title);
|
||||
metadata_normalize_string(&md->album);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
metadata_normalize_string(char **s)
|
||||
{
|
||||
char *str, *cp, *tmpstr, *tp;
|
||||
int is_space;
|
||||
|
||||
if (s == NULL || (str = *s) == NULL || strlen(str) == 0)
|
||||
return;
|
||||
|
||||
tmpstr = xcalloc(strlen(str) + 1, sizeof(char));
|
||||
|
||||
tp = tmpstr;
|
||||
is_space = 1;
|
||||
for (cp = str; *cp != '\0'; cp++) {
|
||||
if (*cp == ' ') {
|
||||
if (!is_space && strlen(tmpstr) > 0 &&
|
||||
tmpstr[strlen(tmpstr) - 1] != ' ')
|
||||
*tp++ = ' ';
|
||||
is_space = 1;
|
||||
} else {
|
||||
*tp++ = *cp;
|
||||
is_space = 0;
|
||||
}
|
||||
}
|
||||
if (strlen(tmpstr) > 0 && tmpstr[strlen(tmpstr) - 1] == ' ')
|
||||
tmpstr[strlen(tmpstr) - 1] = '\0';
|
||||
|
||||
xfree(str);
|
||||
*s = xreallocarray(tmpstr, strlen(tmpstr) + 1, sizeof(char));
|
||||
}
|
||||
|
||||
struct metadata *
|
||||
metadata_file(const char *filename, int normalize)
|
||||
{
|
||||
struct metadata *md;
|
||||
|
||||
md = metadata_create(filename);
|
||||
if (!metadata_file_update(md)) {
|
||||
metadata_free(&md);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
md->normalize = normalize;
|
||||
|
||||
return (md);
|
||||
}
|
||||
|
||||
struct metadata *
|
||||
metadata_program(const char *program, int normalize)
|
||||
{
|
||||
struct metadata *md;
|
||||
struct stat st;
|
||||
|
||||
if (stat(program, &st) == -1) {
|
||||
log_error("%s: %s", program, strerror(errno));
|
||||
return (NULL);
|
||||
}
|
||||
if (st.st_mode & S_IWOTH) {
|
||||
log_error("%s: world writeable", program);
|
||||
return (NULL);
|
||||
}
|
||||
if (!(st.st_mode & (S_IEXEC | S_IXGRP | S_IXOTH))) {
|
||||
log_error("%s: not an executable program", program);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
md = metadata_create(program);
|
||||
md->program = 1;
|
||||
md->string = xstrdup("");
|
||||
md->normalize = normalize;
|
||||
|
||||
return (md);
|
||||
}
|
||||
|
||||
void
|
||||
metadata_free(struct metadata **md_p)
|
||||
{
|
||||
struct metadata *md;
|
||||
|
||||
if (md_p == NULL || (md = *md_p) == NULL)
|
||||
return;
|
||||
|
||||
if (md->filename != NULL) {
|
||||
xfree(md->filename);
|
||||
md->filename = NULL;
|
||||
}
|
||||
metadata_clean_md(md);
|
||||
xfree(*md_p);
|
||||
*md_p = NULL;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
metadata_file_update(struct metadata *md)
|
||||
{
|
||||
FILE *filep;
|
||||
|
||||
assert(!md->program);
|
||||
|
||||
if ((filep = fopen(md->filename, "rb")) == NULL) {
|
||||
log_error("%s: %s", md->filename, strerror(errno));
|
||||
return (0);
|
||||
}
|
||||
|
||||
metadata_get(md, &filep);
|
||||
metadata_process_md(md);
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
int
|
||||
metadata_program_update(struct metadata *md, enum metadata_request md_req)
|
||||
{
|
||||
FILE *filep;
|
||||
char buf[METADATA_MAX + 1];
|
||||
char command[PATH_MAX + sizeof(" artist")];
|
||||
|
||||
assert(md->program);
|
||||
|
||||
switch (md_req) {
|
||||
case METADATA_ALL:
|
||||
metadata_clean_md(md);
|
||||
if (!metadata_program_update(md, METADATA_STRING) ||
|
||||
!metadata_program_update(md, METADATA_ARTIST) ||
|
||||
!metadata_program_update(md, METADATA_TITLE) ||
|
||||
!metadata_program_update(md, METADATA_ALBUM))
|
||||
return (0);
|
||||
else
|
||||
return (1);
|
||||
case METADATA_STRING:
|
||||
strlcpy(command, md->filename, sizeof(command));
|
||||
if (md->string != NULL) {
|
||||
xfree(md->string);
|
||||
md->string = NULL;
|
||||
}
|
||||
break;
|
||||
case METADATA_ARTIST:
|
||||
snprintf(command, sizeof(command), "%s artist", md->filename);
|
||||
if (md->artist != NULL) {
|
||||
xfree(md->artist);
|
||||
md->artist = NULL;
|
||||
}
|
||||
break;
|
||||
case METADATA_TITLE:
|
||||
snprintf(command, sizeof(command), "%s title", md->filename);
|
||||
if (md->title != NULL) {
|
||||
xfree(md->title);
|
||||
md->title = NULL;
|
||||
}
|
||||
break;
|
||||
case METADATA_ALBUM:
|
||||
snprintf(command, sizeof(command), "%s album", md->filename);
|
||||
if (md->album != NULL) {
|
||||
xfree(md->album);
|
||||
md->album = NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log_alert("metadata_program_update: unknown md_req");
|
||||
abort();
|
||||
}
|
||||
|
||||
fflush(NULL);
|
||||
errno = 0;
|
||||
log_debug("running command: %s", command);
|
||||
if ((filep = popen(command, "r")) == NULL) {
|
||||
/* popen() does not set errno reliably ... */
|
||||
if (errno)
|
||||
log_error("execution error: %s: %s", command,
|
||||
strerror(errno));
|
||||
else
|
||||
log_error("execution error: %s", command);
|
||||
return (0);
|
||||
}
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
if (fgets(buf, (int)sizeof(buf), filep) == NULL &&
|
||||
ferror(filep)) {
|
||||
log_alert("%s: output read error: %s", md->filename,
|
||||
strerror(errno));
|
||||
pclose(filep);
|
||||
exit(1);
|
||||
}
|
||||
pclose(filep);
|
||||
|
||||
if (strlen(buf) == sizeof(buf) - 1)
|
||||
log_warning("metadata output truncated: %s", command);
|
||||
|
||||
buf[strcspn(buf, "\n")] = '\0';
|
||||
buf[strcspn(buf, "\r")] = '\0';
|
||||
|
||||
switch (md_req) {
|
||||
case METADATA_STRING:
|
||||
if (strlen(buf) == 0) {
|
||||
log_warning("metadata output empty: %s",
|
||||
md->filename);
|
||||
md->string = xstrdup("");
|
||||
} else
|
||||
md->string = xstrdup(buf);
|
||||
break;
|
||||
case METADATA_ARTIST:
|
||||
if (strlen(buf) > 0)
|
||||
md->artist = xstrdup(buf);
|
||||
break;
|
||||
case METADATA_TITLE:
|
||||
if (strlen(buf) > 0)
|
||||
md->title = xstrdup(buf);
|
||||
break;
|
||||
case METADATA_ALBUM:
|
||||
if (strlen(buf) > 0)
|
||||
md->album = xstrdup(buf);
|
||||
break;
|
||||
case METADATA_ALL:
|
||||
default:
|
||||
log_alert("metadata_program_update: METADATA_ALL in code unreachable by METADATA_ALL");
|
||||
abort();
|
||||
}
|
||||
|
||||
if (md->normalize) {
|
||||
metadata_normalize_string(&md->string);
|
||||
metadata_normalize_string(&md->artist);
|
||||
metadata_normalize_string(&md->title);
|
||||
metadata_normalize_string(&md->album);
|
||||
}
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
const char *
|
||||
metadata_get_string(struct metadata *md)
|
||||
{
|
||||
assert(md->string);
|
||||
return (md->string);
|
||||
}
|
||||
|
||||
const char *
|
||||
metadata_get_artist(struct metadata *md)
|
||||
{
|
||||
if (md->artist == NULL)
|
||||
return (blankString);
|
||||
else
|
||||
return (md->artist);
|
||||
}
|
||||
|
||||
const char *
|
||||
metadata_get_album(struct metadata *md)
|
||||
{
|
||||
if (md->album == NULL)
|
||||
return (blankString);
|
||||
else
|
||||
return (md->album);
|
||||
}
|
||||
|
||||
const char *
|
||||
metadata_get_title(struct metadata *md)
|
||||
{
|
||||
if (md->title == NULL)
|
||||
return (blankString);
|
||||
else
|
||||
return (md->title);
|
||||
}
|
||||
|
||||
const char *
|
||||
metadata_get_filename(struct metadata *md)
|
||||
{
|
||||
if (md->filename == NULL)
|
||||
/* Should never happen: */
|
||||
return (blankString);
|
||||
else
|
||||
return (md->filename);
|
||||
}
|
||||
|
||||
int
|
||||
metadata_get_length(struct metadata *md)
|
||||
{
|
||||
return (md->songLen);
|
||||
}
|
||||
|
||||
char *
|
||||
metadata_assemble_string(struct metadata *md)
|
||||
{
|
||||
size_t len;
|
||||
char *str;
|
||||
|
||||
if (md->artist == NULL && md->title == NULL && md->album && md->program == 0)
|
||||
return (metadata_get_name(md->filename));
|
||||
|
||||
len = 0;
|
||||
if (md->artist != NULL)
|
||||
len += strlen(md->artist);
|
||||
if (md->title != NULL) {
|
||||
if (len > 0)
|
||||
len += strlen(" - ");
|
||||
len += strlen(md->title);
|
||||
}
|
||||
if (md->album != NULL) {
|
||||
if (len > 0)
|
||||
len += strlen(" - ");
|
||||
len += strlen(md->album);
|
||||
}
|
||||
len++;
|
||||
str = xcalloc(len, sizeof(char));
|
||||
|
||||
if (md->artist != NULL)
|
||||
strlcpy(str, md->artist, len);
|
||||
if (md->title != NULL) {
|
||||
if (md->artist != NULL)
|
||||
strlcat(str, " - ", len);
|
||||
strlcat(str, md->title, len);
|
||||
}
|
||||
if (md->album != NULL) {
|
||||
if (md->artist != NULL || md->title != NULL)
|
||||
strlcat(str, " - ", len);
|
||||
strlcat(str, md->album, len);
|
||||
}
|
||||
|
||||
return (str);
|
||||
}
|
||||
|
||||
char *
|
||||
metadata_format_string(struct metadata *md, const char *format)
|
||||
{
|
||||
struct util_dict dicts[6];
|
||||
|
||||
if (format == NULL)
|
||||
return (NULL);
|
||||
|
||||
memset(dicts, 0, sizeof(dicts));
|
||||
dicts[0].from = PLACEHOLDER_ARTIST;
|
||||
dicts[0].to = metadata_get_artist(md);
|
||||
dicts[1].from = PLACEHOLDER_ALBUM;
|
||||
dicts[1].to = metadata_get_album(md);
|
||||
dicts[2].from = PLACEHOLDER_TITLE;
|
||||
dicts[2].to = metadata_get_title(md);
|
||||
dicts[3].from = PLACEHOLDER_TRACK;
|
||||
dicts[3].to = metadata_get_filename(md);
|
||||
dicts[4].from = PLACEHOLDER_STRING;
|
||||
dicts[4].to = metadata_get_string(md);
|
||||
|
||||
return (util_expand_words(format, dicts));
|
||||
}
|
125
src/metadata.h
125
src/metadata.h
@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2007 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 __METADATA_H__
|
||||
#define __METADATA_H__
|
||||
|
||||
#define METADATA_MAX 1023
|
||||
|
||||
enum metadata_request {
|
||||
METADATA_ALL = 0,
|
||||
METADATA_STRING,
|
||||
METADATA_ARTIST,
|
||||
METADATA_TITLE,
|
||||
METADATA_ALBUM
|
||||
};
|
||||
|
||||
typedef struct metadata * metadata_t;
|
||||
|
||||
/*
|
||||
* Read the metadata of a media file and return a new metadata handle on
|
||||
* success, or NULL on failure. The returned handle is "branded" for reading
|
||||
* metadata from media files.
|
||||
*/
|
||||
metadata_t metadata_file(const char * /* filename */,
|
||||
int /* normalize strings */);
|
||||
|
||||
/*
|
||||
* Create a metadata handle that is "branded" for acquiring metadata from an
|
||||
* external program. The handle is returned on success, or NULL on failure.
|
||||
* The program is NOT YET being queried, use metadata_program_update() for
|
||||
* that. Also, the program (or script) needs to follow these rules:
|
||||
*
|
||||
* - Print one line to standard output and exit.
|
||||
* - Accept no command line parameter and return a complete metadata string
|
||||
* (for metadata_get_string()). The program *should* always return
|
||||
* something in this case (e.g. something based on the filename in case no
|
||||
* metadata is available.)
|
||||
* - Accept the command line parameter "artist" and return only the artist
|
||||
* metadata, or an empty string if no artist information is available.
|
||||
* - Accept the command line parameter "title" and return only the song title
|
||||
* metadata, or an empty string if no artist information is available.
|
||||
* - Return at most METADATA_MAX characters, or the result will be truncated.
|
||||
*/
|
||||
metadata_t metadata_program(const char * /* program name */,
|
||||
int /* normalize strings */);
|
||||
|
||||
/*
|
||||
* Free all memory used by a metadata handle that has been created with
|
||||
* metadata_file() or metadata_program().
|
||||
*/
|
||||
void metadata_free(metadata_t *);
|
||||
|
||||
/*
|
||||
* Update/read the metadata for the given handle. Returns 1 on success, and 0
|
||||
* on failure.
|
||||
*/
|
||||
int metadata_file_update(metadata_t);
|
||||
|
||||
/*
|
||||
* Update/read the specified metadata for the given program-handle. Returns 1
|
||||
* on success, and 0 on failure.
|
||||
*/
|
||||
int metadata_program_update(metadata_t, enum metadata_request);
|
||||
|
||||
/*
|
||||
* Returns a pointer to a metadata string ``artist - title'', or just
|
||||
* ``artist'' or ``title'' if one of the two is not available. If neither
|
||||
* are present, it returns the filename without the extension. An empty string
|
||||
* is returned for metadata_program() handles that didn't supply any generic
|
||||
* information.
|
||||
*/
|
||||
const char * metadata_get_string(metadata_t);
|
||||
|
||||
/*
|
||||
* Returns a pointer to the artist string, which may be empty.
|
||||
*/
|
||||
const char * metadata_get_artist(metadata_t);
|
||||
|
||||
/*
|
||||
* Returns a pointer to the album string, which may be empty.
|
||||
*/
|
||||
const char * metadata_get_album(metadata_t);
|
||||
|
||||
/*
|
||||
* Returns a pointer to the title string, which may be empty.
|
||||
*/
|
||||
const char * metadata_get_title(metadata_t);
|
||||
|
||||
/*
|
||||
* Returns a pointer to the filename used in the metadata handle.
|
||||
*/
|
||||
const char * metadata_get_filename(metadata_t);
|
||||
|
||||
/*
|
||||
* Returns the length of the song, in seconds, or -1 if the information is not
|
||||
* available.
|
||||
*/
|
||||
int metadata_get_length(metadata_t);
|
||||
|
||||
/*
|
||||
* Allocates and returns a meaningful string based on a metadata handle's
|
||||
* content. The result is what metadata_get_string() defaults to if an external
|
||||
* program is not used.
|
||||
*/
|
||||
char * metadata_assemble_string(metadata_t);
|
||||
|
||||
/*
|
||||
* Allocates and returns a metadata string based on a template/format string.
|
||||
*/
|
||||
char * metadata_format_string(metadata_t, const char * /* format */);
|
||||
|
||||
#endif /* __METADATA_H__ */
|
85
src/stream.c
85
src/stream.c
@ -21,6 +21,7 @@
|
||||
#include <sys/queue.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
@ -29,7 +30,7 @@
|
||||
|
||||
#include "cfg.h"
|
||||
#include "log.h"
|
||||
#include "metadata.h"
|
||||
#include "mdata.h"
|
||||
#include "stream.h"
|
||||
#include "util.h"
|
||||
#include "xalloc.h"
|
||||
@ -331,12 +332,10 @@ stream_setup(struct stream *s)
|
||||
}
|
||||
|
||||
int
|
||||
stream_set_metadata(struct stream *s, metadata_t md, char **md_str)
|
||||
stream_set_metadata(struct stream *s, mdata_t md, char **md_str)
|
||||
{
|
||||
shout_metadata_t *shout_md = NULL;
|
||||
char *songInfo;
|
||||
const char *artist, *title;
|
||||
int ret = SHOUTERR_SUCCESS;
|
||||
int ret;
|
||||
|
||||
if (cfg_get_metadata_no_updates())
|
||||
return (0);
|
||||
@ -349,9 +348,6 @@ stream_set_metadata(struct stream *s, metadata_t md, char **md_str)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
artist = metadata_get_artist(md);
|
||||
title = metadata_get_title(md);
|
||||
|
||||
/*
|
||||
* We can do this, because we know how libshout works. This adds
|
||||
* "charset=UTF-8" to the HTTP metadata update request and has the
|
||||
@ -364,33 +360,49 @@ stream_set_metadata(struct stream *s, metadata_t md, char **md_str)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
songInfo = metadata_format_string(md, cfg_get_metadata_format_str());
|
||||
if (songInfo == NULL) {
|
||||
if (artist[0] == '\0' && title[0] == '\0')
|
||||
songInfo = xstrdup(metadata_get_string(md));
|
||||
else
|
||||
songInfo = metadata_assemble_string(md);
|
||||
if (artist[0] != '\0' && title[0] != '\0') {
|
||||
if (shout_metadata_add(shout_md, "artist", artist) != SHOUTERR_SUCCESS) {
|
||||
log_syserr(ALERT, ENOMEM,
|
||||
"shout_metadata_add");
|
||||
exit(1);
|
||||
}
|
||||
if (shout_metadata_add(shout_md, "title", title) != SHOUTERR_SUCCESS) {
|
||||
log_syserr(ALERT, ENOMEM,
|
||||
"shout_metadata_add");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
if (shout_metadata_add(shout_md, "song", songInfo) != SHOUTERR_SUCCESS) {
|
||||
log_syserr(ALERT, ENOMEM,
|
||||
"shout_metadata_add");
|
||||
exit(1);
|
||||
}
|
||||
if (cfg_get_metadata_format_str()) {
|
||||
char buf[BUFSIZ];
|
||||
|
||||
mdata_strformat(md, buf, sizeof(buf),
|
||||
cfg_get_metadata_format_str());
|
||||
if (SHOUTERR_SUCCESS !=
|
||||
shout_metadata_add(shout_md, "song", buf)) {
|
||||
log_syserr(ALERT, ENOMEM, "shout_metadata_add");
|
||||
exit(1);
|
||||
}
|
||||
log_info("stream metadata: formatted: %s", buf);
|
||||
} else {
|
||||
if (mdata_get_artist(md) && mdata_get_title(md)) {
|
||||
if (SHOUTERR_SUCCESS != shout_metadata_add(shout_md,
|
||||
"artist", mdata_get_artist(md)) ||
|
||||
SHOUTERR_SUCCESS != shout_metadata_add(shout_md,
|
||||
"title", mdata_get_title(md))) {
|
||||
log_syserr(ALERT, ENOMEM,
|
||||
"shout_metadata_add");
|
||||
exit(1);
|
||||
}
|
||||
log_info("stream metadata: artist=\"%s\" title=\"%s\"",
|
||||
mdata_get_artist(md),
|
||||
mdata_get_title(md));
|
||||
} else if (mdata_get_songinfo(md)) {
|
||||
if (SHOUTERR_SUCCESS != shout_metadata_add(shout_md,
|
||||
"song", mdata_get_songinfo(md))) {
|
||||
log_syserr(ALERT, ENOMEM,
|
||||
"shout_metadata_add");
|
||||
exit(1);
|
||||
}
|
||||
log_info("stream metadata: songinfo: %s",
|
||||
mdata_get_songinfo(md));
|
||||
} else {
|
||||
if (SHOUTERR_SUCCESS != shout_metadata_add(shout_md,
|
||||
"song", mdata_get_name(md))) {
|
||||
log_syserr(ALERT, ENOMEM,
|
||||
"shout_metadata_add");
|
||||
exit(1);
|
||||
}
|
||||
log_info("stream metadata: name: %s",
|
||||
mdata_get_name(md));
|
||||
}
|
||||
} else if (shout_metadata_add(shout_md, "song", songInfo) != SHOUTERR_SUCCESS) {
|
||||
log_syserr(ALERT, ENOMEM, "shout_metadata_add");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((ret = shout_set_metadata(s->shout, shout_md)) != SHOUTERR_SUCCESS)
|
||||
@ -400,10 +412,11 @@ stream_set_metadata(struct stream *s, metadata_t md, char **md_str)
|
||||
|
||||
if (ret == SHOUTERR_SUCCESS) {
|
||||
if (md_str != NULL && *md_str == NULL)
|
||||
*md_str = xstrdup(songInfo);
|
||||
*md_str = mdata_get_songinfo(md) ?
|
||||
xstrdup(mdata_get_songinfo(md)) :
|
||||
xstrdup(mdata_get_name(md));
|
||||
}
|
||||
|
||||
xfree(songInfo);
|
||||
return (ret == SHOUTERR_SUCCESS ? 0 : -1);
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
#include <shout/shout.h>
|
||||
|
||||
#include "metadata.h"
|
||||
#include "mdata.h"
|
||||
|
||||
#define STREAM_DEFAULT "default"
|
||||
|
||||
@ -31,7 +31,7 @@ void stream_exit(void);
|
||||
stream_t
|
||||
stream_get(const char *);
|
||||
int stream_setup(stream_t);
|
||||
int stream_set_metadata(stream_t, metadata_t, char **);
|
||||
int stream_set_metadata(stream_t, mdata_t, char **);
|
||||
int stream_get_connected(stream_t);
|
||||
|
||||
int stream_connect(stream_t);
|
||||
|
BIN
tests/.test17ogg
Normal file
BIN
tests/.test17ogg
Normal file
Binary file not shown.
@ -5,6 +5,7 @@ TESTS = \
|
||||
check_cfg_xmlfile \
|
||||
check_cmdline \
|
||||
check_log \
|
||||
check_mdata \
|
||||
check_playlist \
|
||||
check_stream \
|
||||
check_xalloc
|
||||
@ -30,6 +31,11 @@ check_log_SOURCES = \
|
||||
check_log_DEPENDENCIES = $(top_builddir)/src/libezstream.la
|
||||
check_log_LDADD = $(check_log_DEPENDENCIES) @CHECK_LIBS@
|
||||
|
||||
check_mdata_SOURCES = \
|
||||
check_mdata.c
|
||||
check_mdata_DEPENDENCIES = $(top_builddir)/src/libezstream.la
|
||||
check_mdata_LDADD = $(check_mdata_DEPENDENCIES) @CHECK_LIBS@
|
||||
|
||||
check_playlist_SOURCES = \
|
||||
check_playlist.c
|
||||
check_playlist_DEPENDENCIES = $(top_builddir)/src/libezstream.la
|
||||
@ -65,6 +71,31 @@ EXTRA_DIST = \
|
||||
play-bad3.sh \
|
||||
playlist-bad.txt \
|
||||
playlist-bad2.txt \
|
||||
playlist.txt
|
||||
playlist.txt \
|
||||
null.raw \
|
||||
test01-artist+album+title.ogg \
|
||||
test02-whitespace.ogg \
|
||||
test03-apostrophe.ogg \
|
||||
test04-backticks.ogg \
|
||||
test05-spawnshell.ogg \
|
||||
test06-shellvar.ogg \
|
||||
test07-japanese.ogg \
|
||||
test08-arabic.ogg \
|
||||
test09-apostrophe+spawnshell.ogg \
|
||||
test10-apostrophe+backticks.ogg \
|
||||
test11-artist+album.ogg \
|
||||
test12-artist.ogg \
|
||||
test13-album+title.ogg \
|
||||
test14-album.ogg \
|
||||
test15-title.ogg \
|
||||
test16-nometa.ogg \
|
||||
.test17ogg \
|
||||
test18-emptymeta.ogg \
|
||||
test19-onlywhitespace.ogg \
|
||||
test-meta01.sh \
|
||||
test-meta02-error.sh \
|
||||
test-meta03-huge.sh \
|
||||
test-meta04-kill.sh \
|
||||
test-meta05-empty.sh
|
||||
|
||||
CLEANFILES = *~ *.core core *.gcno *.gcda
|
||||
|
216
tests/check_mdata.c
Normal file
216
tests/check_mdata.c
Normal file
@ -0,0 +1,216 @@
|
||||
#include <check.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "mdata.h"
|
||||
|
||||
Suite * mdata_suite(void);
|
||||
void setup_checked(void);
|
||||
void teardown_checked(void);
|
||||
|
||||
mdata_t md;
|
||||
|
||||
START_TEST(test_mdata_md)
|
||||
{
|
||||
ck_assert_ptr_eq(mdata_get_filename(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_name(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_artist(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_album(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_title(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_songinfo(md), NULL);
|
||||
ck_assert_int_lt(mdata_get_length(md), 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_mdata_parse_file)
|
||||
{
|
||||
ck_assert_int_ne(mdata_parse_file(md, SRCDIR "/nonexistent"), 0);
|
||||
ck_assert_ptr_eq(mdata_get_filename(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_name(md), NULL);
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test01-artist+album+title.ogg"), 0);
|
||||
ck_assert_str_eq(mdata_get_filename(md), SRCDIR "/test01-artist+album+title.ogg");
|
||||
ck_assert_str_eq(mdata_get_name(md), "test01-artist+album+title");
|
||||
ck_assert_str_eq(mdata_get_artist(md), "test artist");
|
||||
ck_assert_str_eq(mdata_get_album(md), "test album");
|
||||
ck_assert_str_eq(mdata_get_title(md), "test title");
|
||||
ck_assert_str_eq(mdata_get_songinfo(md), "test artist - test title - test album");
|
||||
ck_assert_int_eq(mdata_get_length(md), 1);
|
||||
|
||||
mdata_set_normalize_strings(md, 1);
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test02-whitespace.ogg"), 0);
|
||||
ck_assert_str_eq(mdata_get_artist(md), "test artist");
|
||||
ck_assert_str_eq(mdata_get_album(md), "test album");
|
||||
ck_assert_str_eq(mdata_get_title(md), "test title");
|
||||
mdata_set_normalize_strings(md, 0);
|
||||
ck_assert_int_eq(mdata_refresh(md), 0);
|
||||
ck_assert_str_eq(mdata_get_artist(md), " test artist ");
|
||||
ck_assert_str_eq(mdata_get_album(md), " test album ");
|
||||
ck_assert_str_eq(mdata_get_title(md), " test title ");
|
||||
|
||||
mdata_set_normalize_strings(md, 1);
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test11-artist+album.ogg"), 0);
|
||||
ck_assert_str_eq(mdata_get_artist(md), "test artist");
|
||||
ck_assert_str_eq(mdata_get_album(md), "test album");
|
||||
ck_assert_ptr_eq(mdata_get_title(md), NULL);
|
||||
ck_assert_str_eq(mdata_get_songinfo(md), "test artist - test album");
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test13-album+title.ogg"), 0);
|
||||
ck_assert_ptr_eq(mdata_get_artist(md), NULL);
|
||||
ck_assert_str_eq(mdata_get_album(md), "test album");
|
||||
ck_assert_str_eq(mdata_get_title(md), "test title");
|
||||
ck_assert_str_eq(mdata_get_songinfo(md), "test title - test album");
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test12-artist.ogg"), 0);
|
||||
ck_assert_str_eq(mdata_get_artist(md), "test artist");
|
||||
ck_assert_ptr_eq(mdata_get_album(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_title(md), NULL);
|
||||
ck_assert_str_eq(mdata_get_songinfo(md), "test artist");
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test14-album.ogg"), 0);
|
||||
ck_assert_ptr_eq(mdata_get_artist(md), NULL);
|
||||
ck_assert_str_eq(mdata_get_album(md), "test album");
|
||||
ck_assert_ptr_eq(mdata_get_title(md), NULL);
|
||||
ck_assert_str_eq(mdata_get_songinfo(md), "test album");
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test15-title.ogg"), 0);
|
||||
ck_assert_ptr_eq(mdata_get_artist(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_album(md), NULL);
|
||||
ck_assert_str_eq(mdata_get_title(md), "test title");
|
||||
ck_assert_str_eq(mdata_get_songinfo(md), "test title");
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test16-nometa.ogg"), 0);
|
||||
ck_assert_ptr_eq(mdata_get_artist(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_album(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_title(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_songinfo(md), NULL);
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/.test17ogg"), 0);
|
||||
ck_assert_str_eq(mdata_get_name(md), "[unknown]");
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test18-emptymeta.ogg"), 0);
|
||||
ck_assert_ptr_eq(mdata_get_artist(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_album(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_title(md), NULL);
|
||||
ck_assert_ptr_eq(mdata_get_songinfo(md), NULL);
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test19-onlywhitespace.ogg"), 0);
|
||||
ck_assert_str_eq(mdata_get_artist(md), "");
|
||||
ck_assert_str_eq(mdata_get_album(md), "");
|
||||
ck_assert_str_eq(mdata_get_title(md), "");
|
||||
ck_assert_ptr_eq(mdata_get_songinfo(md), NULL);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_mdata_run_program)
|
||||
{
|
||||
ck_assert_int_ne(mdata_run_program(md, SRCDIR "/nonexistent"), 0);
|
||||
ck_assert_ptr_eq(mdata_get_filename(md), NULL);
|
||||
|
||||
ck_assert_int_ne(mdata_run_program(md, SRCDIR "/test01-artist+album+title.ogg"), 0);
|
||||
ck_assert_ptr_eq(mdata_get_filename(md), NULL);
|
||||
|
||||
mdata_set_normalize_strings(md, 1);
|
||||
ck_assert_int_eq(mdata_run_program(md, SRCDIR "/test-meta01.sh"), 0);
|
||||
ck_assert_str_eq(mdata_get_filename(md), SRCDIR "/test-meta01.sh");
|
||||
ck_assert_str_eq(mdata_get_name(md), "[unknown]");
|
||||
ck_assert_str_eq(mdata_get_artist(md), "artist");
|
||||
ck_assert_str_eq(mdata_get_album(md), "album");
|
||||
ck_assert_str_eq(mdata_get_title(md), "title");
|
||||
ck_assert_str_eq(mdata_get_songinfo(md), "songinfo");
|
||||
mdata_set_normalize_strings(md, 0);
|
||||
ck_assert_int_eq(mdata_refresh(md), 0);
|
||||
ck_assert_str_eq(mdata_get_artist(md), " artist ");
|
||||
ck_assert_str_eq(mdata_get_album(md), " album ");
|
||||
ck_assert_str_eq(mdata_get_title(md), " title ");
|
||||
ck_assert_str_eq(mdata_get_songinfo(md), " songinfo ");
|
||||
|
||||
mdata_set_normalize_strings(md, 1);
|
||||
|
||||
ck_assert_int_ne(mdata_run_program(md, SRCDIR "/test-meta02-error.sh"), 0);
|
||||
ck_assert_str_eq(mdata_get_filename(md), SRCDIR "/test-meta01.sh");
|
||||
|
||||
ck_assert_int_eq(mdata_run_program(md, SRCDIR "/test-meta03-huge.sh"), 0);
|
||||
|
||||
ck_assert_int_ne(mdata_run_program(md, SRCDIR "/test-meta04-kill.sh"), 0);
|
||||
|
||||
ck_assert_int_eq(mdata_run_program(md, SRCDIR "/test-meta05-empty.sh"), 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_mdata_strformat)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
int ret;
|
||||
|
||||
ck_assert_int_eq(mdata_parse_file(md, SRCDIR "/test01-artist+album+title.ogg"), 0);
|
||||
|
||||
ck_assert_int_lt(mdata_strformat(md, buf, sizeof(buf), NULL), 0);
|
||||
|
||||
ret = mdata_strformat(md, buf, sizeof(buf), "@a@/@b@/@t@/@T@/@s@");
|
||||
ck_assert_int_eq(ret, strlen(buf));
|
||||
ck_assert_str_eq(buf,
|
||||
"test artist/test album/test title"
|
||||
"/" SRCDIR "/test01-artist+album+title.ogg"
|
||||
"/test artist - test title - test album");
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *
|
||||
mdata_suite(void)
|
||||
{
|
||||
Suite *s;
|
||||
TCase *tc_mdata;
|
||||
|
||||
s = suite_create("MData");
|
||||
|
||||
tc_mdata = tcase_create("MData");
|
||||
tcase_add_checked_fixture(tc_mdata, setup_checked, teardown_checked);
|
||||
tcase_add_test(tc_mdata, test_mdata_md);
|
||||
tcase_add_test(tc_mdata, test_mdata_parse_file);
|
||||
tcase_add_test(tc_mdata, test_mdata_run_program);
|
||||
tcase_add_test(tc_mdata, test_mdata_strformat);
|
||||
suite_add_tcase(s, tc_mdata);
|
||||
|
||||
return (s);
|
||||
}
|
||||
|
||||
void
|
||||
setup_checked(void)
|
||||
{
|
||||
if (0 < log_init())
|
||||
ck_abort_msg("setup_checked failed");
|
||||
|
||||
md = mdata_create();
|
||||
ck_assert_ptr_ne(md, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
teardown_checked(void)
|
||||
{
|
||||
mdata_destroy(&md);
|
||||
ck_assert_ptr_eq(md, NULL);
|
||||
|
||||
log_exit();
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
unsigned int num_failed;
|
||||
Suite *s;
|
||||
SRunner *sr;
|
||||
|
||||
s = mdata_suite();
|
||||
sr = srunner_create(s);
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
num_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
|
||||
if (num_failed)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
BIN
tests/null.raw
Normal file
BIN
tests/null.raw
Normal file
Binary file not shown.
8
tests/test-meta01.sh
Executable file
8
tests/test-meta01.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
test -z "${1}" && echo " songinfo "
|
||||
test x"${1}" = "xartist" && echo " artist "
|
||||
test x"${1}" = "xtitle" && echo " title "
|
||||
test x"${1}" = "xalbum" && echo " album "
|
||||
|
||||
exit 0
|
3
tests/test-meta02-error.sh
Executable file
3
tests/test-meta02-error.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exit 1
|
5
tests/test-meta03-huge.sh
Executable file
5
tests/test-meta03-huge.sh
Executable file
File diff suppressed because one or more lines are too long
3
tests/test-meta04-kill.sh
Executable file
3
tests/test-meta04-kill.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
kill -9 $$
|
3
tests/test-meta05-empty.sh
Executable file
3
tests/test-meta05-empty.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exit 0
|
BIN
tests/test01-artist+album+title.ogg
Normal file
BIN
tests/test01-artist+album+title.ogg
Normal file
Binary file not shown.
BIN
tests/test02-whitespace.ogg
Normal file
BIN
tests/test02-whitespace.ogg
Normal file
Binary file not shown.
BIN
tests/test03-apostrophe.ogg
Normal file
BIN
tests/test03-apostrophe.ogg
Normal file
Binary file not shown.
BIN
tests/test04-backticks.ogg
Normal file
BIN
tests/test04-backticks.ogg
Normal file
Binary file not shown.
BIN
tests/test05-spawnshell.ogg
Normal file
BIN
tests/test05-spawnshell.ogg
Normal file
Binary file not shown.
BIN
tests/test06-shellvar.ogg
Normal file
BIN
tests/test06-shellvar.ogg
Normal file
Binary file not shown.
BIN
tests/test07-japanese.ogg
Normal file
BIN
tests/test07-japanese.ogg
Normal file
Binary file not shown.
BIN
tests/test08-arabic.ogg
Normal file
BIN
tests/test08-arabic.ogg
Normal file
Binary file not shown.
BIN
tests/test09-apostrophe+spawnshell.ogg
Normal file
BIN
tests/test09-apostrophe+spawnshell.ogg
Normal file
Binary file not shown.
BIN
tests/test10-apostrophe+backticks.ogg
Normal file
BIN
tests/test10-apostrophe+backticks.ogg
Normal file
Binary file not shown.
BIN
tests/test11-artist+album.ogg
Normal file
BIN
tests/test11-artist+album.ogg
Normal file
Binary file not shown.
BIN
tests/test12-artist.ogg
Normal file
BIN
tests/test12-artist.ogg
Normal file
Binary file not shown.
BIN
tests/test13-album+title.ogg
Normal file
BIN
tests/test13-album+title.ogg
Normal file
Binary file not shown.
BIN
tests/test14-album.ogg
Normal file
BIN
tests/test14-album.ogg
Normal file
Binary file not shown.
BIN
tests/test15-title.ogg
Normal file
BIN
tests/test15-title.ogg
Normal file
Binary file not shown.
BIN
tests/test16-nometa.ogg
Normal file
BIN
tests/test16-nometa.ogg
Normal file
Binary file not shown.
BIN
tests/test18-emptymeta.ogg
Normal file
BIN
tests/test18-emptymeta.ogg
Normal file
Binary file not shown.
BIN
tests/test19-onlywhitespace.ogg
Normal file
BIN
tests/test19-onlywhitespace.ogg
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user