1
0
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:
Moritz Grimm 2017-09-20 12:37:56 +02:00
parent 399e66df2b
commit 6616831590
35 changed files with 942 additions and 795 deletions

View File

@ -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 \

View File

@ -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
View 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
View 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__ */

View File

@ -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));
}

View File

@ -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__ */

View File

@ -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);
}

View File

@ -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

Binary file not shown.

View File

@ -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
View 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

Binary file not shown.

8
tests/test-meta01.sh Executable file
View 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
View File

@ -0,0 +1,3 @@
#!/bin/sh
exit 1

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
View File

@ -0,0 +1,3 @@
#!/bin/sh
kill -9 $$

3
tests/test-meta05-empty.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
exit 0

Binary file not shown.

BIN
tests/test02-whitespace.ogg Normal file

Binary file not shown.

BIN
tests/test03-apostrophe.ogg Normal file

Binary file not shown.

BIN
tests/test04-backticks.ogg Normal file

Binary file not shown.

BIN
tests/test05-spawnshell.ogg Normal file

Binary file not shown.

BIN
tests/test06-shellvar.ogg Normal file

Binary file not shown.

BIN
tests/test07-japanese.ogg Normal file

Binary file not shown.

BIN
tests/test08-arabic.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/test12-artist.ogg Normal file

Binary file not shown.

Binary file not shown.

BIN
tests/test14-album.ogg Normal file

Binary file not shown.

BIN
tests/test15-title.ogg Normal file

Binary file not shown.

BIN
tests/test16-nometa.ogg Normal file

Binary file not shown.

BIN
tests/test18-emptymeta.ogg Normal file

Binary file not shown.

Binary file not shown.