1
0
mirror of https://gitlab.xiph.org/xiph/ezstream.git synced 2024-11-03 04:17:18 -05:00

More metadata featuritis, add <metadata_format/> and implement support for

'@a@', '@t@' and '@s@'.


git-svn-id: https://svn.xiph.org/trunk/ezstream@12707 0101bb08-14d6-0310-b084-bc0e0c8e3800
This commit is contained in:
moritz 2007-03-10 19:03:07 +00:00
parent 6f779c21f3
commit c5aaa28594
7 changed files with 392 additions and 39 deletions

4
NEWS
View File

@ -25,6 +25,10 @@ Changes in 0.4.0, (SVN trunk):
- [ADD] New runtime control via the SIGUSR2 signal, which triggers reading
of fresh metadata information from <metadata_progname> (metadata
is always read at song changes.)
- [ADD] New <metadata_format> configuration option, to customize metadata
strings when used with the new <metadata_progname> feature.
- [ADD] New '@a@' and '@t@' placeholders for separate artist and title
metadata in de-/encoder commands.

View File

@ -215,6 +215,17 @@ configuration file.
See the
.Sy SCRIPTING
section for details on how the metadata program must behave.
.It Sy \&<metadata_format\ /\&>
.Pq Optional.
Set the format of the string that should be used for the
.Sq @M@
placeholder when setting metadata with an external program or script via
\&<metadata_progname/\&>.
.Pp
See the
.Sy METADATA
section for details on how metadata is handled by
.Nm .
.It Sy \&<stream_once\ /\&>
Set to
.Sy 1
@ -339,16 +350,17 @@ Set the command to decode the specified media file format to raw data and send
it to standard output.
During runtime, the placeholder
.Sq Li @T@
is replaced with the fully qualified name of the media file, as specified in
the \&<filename/\&> element or a playlist file.
is replaced with the name of the media file, as it is specified in the
\&<filename/\&> element or contained in a playlist file.
It should always be enclosed in quotes, to prevent problems with filenames that
contain whitespaces.
.Pp
The metadata placeholder,
.Sq @M@ ,
is also available in the \&<decode/\&> element.
That way it can be used for combined de-/encoder programs that produce readily
streamable data.
Metadata placeholders can be used in the \&<decode/\&> element as well, for
combined de-/encoder programs that produce streamable data.
See the
.Sy METADATA
section for details on how metadata is handled by
.Nm .
.Pp
For example, to decode Ogg Vorbis files using the
.Cm oggdec
@ -358,15 +370,13 @@ utility:
.It Sy \&<encode\ /\&>
Set the command to encode raw data, received from standard input, to the
specified stream format.
During runtime, the placeholder
.Sq Li @M@
is replaced with the metadata
.Po
e.g.
.Dq Artist - Title
.Pc
for the current track.
It also should be enclosed in quotes at all times.
.Pp
Metadata placeholders can be used in the \&<encode/\&> element.
For details about using metadata in
.Nm ,
see below in the
.Sy METADATA
section.
.Pp
For example, to encode an Ogg Vorbis stream using the quality setting 1.5 with
the
@ -423,11 +433,89 @@ When called with the command line parameter
the program should return only the title information of the metadata.
.Pq Optional.
.El
.Sh METADATA
The main tool for handling metadata with
.Nm
is placeholders in decoder and encoder commands that are replaced with real
content during runtime.
The tricky part about is that one placeholders has to be handled differently
depending on where the metadata comes from.
This section will explain each possible scenario.
.Ss Metadata Placeholders
.Bl -tag -width -Ds
.It Sy @T@
Replaced with the media file name.
Required in \&<decode/\&> and is available in \&<metadata_format/\&>.
.It Sy @M@
Replaced with a metadata string.
See below for a detailed explanation.
Available in \&<decode/\&> and \&<encode/\&>.
.It Sy @a@
Replaced with the artist information.
Available in \&<decode/\&>, \&<encode/\&> and \&<metadata_format/\&>.
.It Sy @t@
Replaced with the title information.
Available in \&<decode/\&>, \&<encode/\&> and \&<metadata_format/\&>.
.It Sy @s@
Replaced with the string returned by \&<metadata_progname/\&> when called
without any command line parameters.
Available only in \&<metadata_format/\&>.
.El
.Ss The @M@ Placeholder
While all other placeholders are simply replaced with whatever data they are
associated with,
.Sq @M@
is context-sensitive.
The logic used by
.Nm
is the following:
.Bd -literal -offset indent
If ('@M@ is present')
If ('\&<metadata_progname/\&>' AND '\&<metadata_format/\&>')
Replace with format string result.
Else
If (NOT '\&<metadata_progname/\&>' AND '@t@ is present')
Replace with empty string.
else
Replace with generated metadata string.
Endif
Endif
Endif
.Ed
.Pp
The generated metadata string for
.Sq @M@
is of the format
.Dq Artist - Title ,
if both artist and title information is available.
If one of the two is missing, the available one is displayed without a leading
or trailing dash, e.g. just
.Dq Artist .
If neither artist nor title are available, the name of the media file, without
its file extension, is used.
.Ss Metadata Caveats
It is possible to generate strange results with odd combinations of
placeholders, external metadata programs and updates during runtime via
.Sy SIGUSR2 .
If things start to become just confusing, simplify.
.Pp
Metadata updates during runtime are done with a relatively broken feature of
libshout.
Additional metadata information that is already present in the stream sent via
.Nm
is usually destroyed and replaced with the new data.
It is not possible to properly discern between artist and title information,
which means that anything set with the
.Sy SIGUSR2
feature will continue to end up entirely in the
.Qq Title
field of a stream.
.Sh FILES
.Bl -tag -width "!!EXAMPLES_DIR!!" -compact
.It Pa !!EXAMPLES_DIR!!
Directory containing example configuration files for various uses of
.Nm .
.Nm ,
as well as example playlist and metadata scripts.
.El
.Sh AUTHORS
.Nm

View File

@ -39,6 +39,7 @@ static const char *blankString = "";
void freeConfig(EZCONFIG *);
unsigned int checkDecoderLine(const char *, const char *, long);
unsigned int checkEncoderLine(const char *, const char *, long);
unsigned int checkFormatLine(const char *, const char *, long);
EZCONFIG *
getEZConfig(void)
@ -198,6 +199,27 @@ parseConfig(const char *fileName)
xmlFree(ls_xmlContentPtr);
}
}
if (!xmlStrcmp(cur->name, BAD_CAST "metadata_format")) {
if (ezConfig.metadataFormat != NULL) {
printf("%s[%ld]: Error: Cannot have multiple <metadata_format> elements\n",
fileName, xmlGetLineNo(cur));
config_error++;
continue;
}
if (cur->xmlChildrenNode != NULL) {
unsigned int ret;
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
ezConfig.metadataFormat = xstrdup(ls_xmlContentPtr);
xmlFree(ls_xmlContentPtr);
if ((ret = checkFormatLine(ezConfig.metadataFormat,
fileName, xmlGetLineNo(cur)))
> 0) {
config_error += ret;
continue;
}
}
}
if (!xmlStrcmp(cur->name, BAD_CAST "playlist_program")) {
if (program_set) {
printf("%s[%ld]: Error: Cannot have multiple <playlist_program> elements\n",
@ -564,6 +586,8 @@ freeConfig(EZCONFIG *cfg)
xfree(cfg->fileName);
if (cfg->metadataProgram != NULL)
xfree(cfg->metadataProgram);
if (cfg->metadataFormat != NULL)
xfree(cfg->metadataFormat);
if (cfg->serverName != NULL)
xfree(cfg->serverName);
if (cfg->serverURL != NULL)
@ -604,15 +628,22 @@ checkDecoderLine(const char *str, const char *file, long line)
{
unsigned int errors;
char *p;
int have_track = 0;
errors = 0;
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
printf("%s[%ld]: Error: `%s' placeholder not allowed in decoder command\n",
file, line, STRING_PLACEHOLDER);
errors++;
}
if ((p = strstr(str, TRACK_PLACEHOLDER)) != NULL) {
p += strlen(TRACK_PLACEHOLDER);
if ((p = strstr(p, TRACK_PLACEHOLDER)) != NULL) {
printf("%s[%ld]: Error: Multiple `%s' placeholders in decoder command\n",
file, line, TRACK_PLACEHOLDER);
errors++;
}
} else
have_track = 1;
}
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
p += strlen(METADATA_PLACEHOLDER);
@ -639,6 +670,12 @@ checkDecoderLine(const char *str, const char *file, long line)
}
}
if (!have_track) {
printf("%s[%ld]: Error: The decoder command requires the '%s' track placeholder\n",
file, line, TRACK_PLACEHOLDER);
errors++;
}
return (errors);
}
@ -654,6 +691,11 @@ checkEncoderLine(const char *str, const char *file, long line)
file, line, TRACK_PLACEHOLDER);
errors++;
}
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
printf("%s[%ld]: Error: `%s' placeholder not allowed in encoder command\n",
file, line, STRING_PLACEHOLDER);
errors++;
}
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
p += strlen(METADATA_PLACEHOLDER);
if ((p = strstr(p, METADATA_PLACEHOLDER)) != NULL) {
@ -681,3 +723,51 @@ checkEncoderLine(const char *str, const char *file, long line)
return (errors);
}
unsigned int
checkFormatLine(const char *str, const char *file, long line)
{
unsigned int errors;
char *p;
errors = 0;
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
printf("%s[%ld]: Error: `%s' placeholder not allowed in <metadata_format>\n",
file, line, METADATA_PLACEHOLDER);
errors++;
}
if ((p = strstr(str, TRACK_PLACEHOLDER)) != NULL) {
p += strlen(TRACK_PLACEHOLDER);
if ((p = strstr(p, TRACK_PLACEHOLDER)) != NULL) {
printf("%s[%ld]: Error: Multiple `%s' placeholders in <metadata_format>\n",
file, line, TRACK_PLACEHOLDER);
errors++;
}
}
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
p += strlen(STRING_PLACEHOLDER);
if ((p = strstr(p, STRING_PLACEHOLDER)) != NULL) {
printf("%s[%ld]: Error: Multiple `%s' placeholders in <metadata_format>\n",
file, line, STRING_PLACEHOLDER);
errors++;
}
}
if ((p = strstr(str, ARTIST_PLACEHOLDER)) != NULL) {
p += strlen(ARTIST_PLACEHOLDER);
if ((p = strstr(p, ARTIST_PLACEHOLDER)) != NULL) {
printf("%s[%ld]: Error: Multiple `%s' placeholders in <metadata_format>\n",
file, line, ARTIST_PLACEHOLDER);
errors++;
}
}
if ((p = strstr(str, TITLE_PLACEHOLDER)) != NULL) {
p += strlen(TITLE_PLACEHOLDER);
if ((p = strstr(p, TITLE_PLACEHOLDER)) != NULL) {
printf("%s[%ld]: Error: Multiple `%s' placeholders in <metadata_format>\n",
file, line, TITLE_PLACEHOLDER);
errors++;
}
}
return (errors);
}

View File

@ -33,6 +33,7 @@
#define METADATA_PLACEHOLDER "@M@"
#define ARTIST_PLACEHOLDER "@a@"
#define TITLE_PLACEHOLDER "@t@"
#define STRING_PLACEHOLDER "@s@"
typedef struct tag_FORMAT_ENCDEC {
char *format;
@ -47,6 +48,7 @@ typedef struct tag_EZCONFIG {
char *format;
char *fileName;
char *metadataProgram;
char *metadataFormat;
char *serverName;
char *serverURL;
char *serverGenre;

View File

@ -112,6 +112,7 @@ typedef struct tag_ID3Tag {
int urlParse(const char *, char **, int *, char **);
void replaceString(const char *, char *, size_t, const char *, const char *);
char * buildCommandString(const char *, const char *, metadata_t *);
char * getMetadataString(const char *, metadata_t *);
metadata_t * getMetadata(const char *);
int setMetadata(shout_t *, metadata_t *, char **);
FILE * openResource(shout_t *, const char *, int *, char **, int *);
@ -247,14 +248,61 @@ buildCommandString(const char *extension, const char *fileName,
newDecoder = xcalloc(1, newDecoderLen);
replaceString(decoder, newDecoder, newDecoderLen, TRACK_PLACEHOLDER,
fileName);
if (strstr(decoder, ARTIST_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newDecoder) + strlen(metadata_get_artist(mdata)) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newDecoder, tmpStr, tmpLen, ARTIST_PLACEHOLDER,
metadata_get_artist(mdata));
xfree(newDecoder);
newDecoder = tmpStr;
}
if (strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newDecoder) + strlen(metadata_get_title(mdata)) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newDecoder, tmpStr, tmpLen, TITLE_PLACEHOLDER,
metadata_get_title(mdata));
xfree(newDecoder);
newDecoder = tmpStr;
}
/*
* if meta
* if (prog && format)
* metatoformat
* else
* if (!prog && title)
* emptymeta
* else
* replacemeta
*/
if (strstr(decoder, METADATA_PLACEHOLDER) != NULL) {
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
size_t tmpLen = strlen(newDecoder) + strlen(mdataString) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newDecoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER, mdataString);
xfree(newDecoder);
xfree(mdataString);
newDecoder = tmpStr;
} else {
if (!metadataFromProgram && strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newDecoder) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newDecoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER, "");
xfree(newDecoder);
newDecoder = tmpStr;
} else {
size_t tmpLen = strlen(newDecoder) + strlen(metadata_get_string(mdata)) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newDecoder, tmpStr, tmpLen, METADATA_PLACEHOLDER,
replaceString(newDecoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER,
metadata_get_string(mdata));
xfree(newDecoder);
newDecoder = tmpStr;
}
}
}
encoder = xstrdup(getFormatEncoder(pezConfig->format));
if (strlen(encoder) == 0) {
@ -272,10 +320,47 @@ buildCommandString(const char *extension, const char *fileName,
return (commandString);
}
newEncoderLen = strlen(encoder) + strlen(metadata_get_string(mdata)) + 1;
newEncoderLen = strlen(encoder) + strlen(metadata_get_artist(mdata)) + 1;
newEncoder = xcalloc(1, newEncoderLen);
replaceString(encoder, newEncoder, newEncoderLen, METADATA_PLACEHOLDER,
replaceString(encoder, newEncoder, newEncoderLen, ARTIST_PLACEHOLDER,
metadata_get_artist(mdata));
if (strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newEncoder) + strlen(metadata_get_title(mdata)) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newEncoder, tmpStr, tmpLen, TITLE_PLACEHOLDER,
metadata_get_title(mdata));
xfree(newEncoder);
newEncoder = tmpStr;
}
if (strstr(encoder, METADATA_PLACEHOLDER) != NULL) {
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
size_t tmpLen = strlen(newEncoder) + strlen(mdataString) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newEncoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER, mdataString);
xfree(newEncoder);
xfree(mdataString);
newEncoder = tmpStr;
} else {
if (!metadataFromProgram && strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
size_t tmpLen = strlen(newEncoder) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newEncoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER, "");
xfree(newEncoder);
newEncoder = tmpStr;
} else {
size_t tmpLen = strlen(newEncoder) + strlen(metadata_get_string(mdata)) + 1;
char *tmpStr = xcalloc(1, tmpLen);
replaceString(newEncoder, tmpStr, tmpLen,
METADATA_PLACEHOLDER,
metadata_get_string(mdata));
xfree(newEncoder);
newEncoder = tmpStr;
}
}
}
commandStringLen = strlen(newDecoder) + strlen(" | ") +
strlen(newEncoder) + 1;
@ -291,6 +376,59 @@ buildCommandString(const char *extension, const char *fileName,
return (commandString);
}
char *
getMetadataString(const char *format, metadata_t *mdata)
{
char *tmp, *str;
size_t siz;
if (mdata == NULL) {
printf("%s: getMetadataString(): Internal error: NULL metadata_t\n",
__progname);
abort();
}
if (format == NULL)
return (NULL);
str = xstrdup(format);
if (strstr(format, ARTIST_PLACEHOLDER) != NULL) {
siz = strlen(str) + strlen(metadata_get_artist(mdata)) + 1;
tmp = xcalloc(1, siz);
replaceString(str, tmp, siz, ARTIST_PLACEHOLDER,
metadata_get_artist(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, TITLE_PLACEHOLDER) != NULL) {
siz = strlen(str) + strlen(metadata_get_title(mdata)) + 1;
tmp = xcalloc(1, siz);
replaceString(str, tmp, siz, TITLE_PLACEHOLDER,
metadata_get_title(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, STRING_PLACEHOLDER) != NULL) {
siz = strlen(str) + strlen(metadata_get_string(mdata)) + 1;
tmp = xcalloc(1, siz);
replaceString(str, tmp, siz, STRING_PLACEHOLDER,
metadata_get_string(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, TRACK_PLACEHOLDER) != NULL) {
siz = strlen(str) + strlen(metadata_get_filename(mdata)) + 1;
tmp = xcalloc(1, siz);
replaceString(str, tmp, siz, TRACK_PLACEHOLDER,
metadata_get_filename(mdata));
xfree(str);
str = tmp;
}
return (str);
}
metadata_t *
getMetadata(const char *fileName)
{
@ -339,10 +477,13 @@ setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
exit(1);
}
if (metadata_get_artist(mdata) == NULL && metadata_get_title(mdata) == NULL)
if ((songInfo = getMetadataString(pezConfig->metadataFormat, mdata)) == NULL) {
if (strlen(metadata_get_artist(mdata)) == 0 &&
strlen(metadata_get_title(mdata)) == 0)
songInfo = xstrdup(metadata_get_string(mdata));
else
songInfo = metadata_assemble_string(mdata);
}
if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
/* Assume SHOUTERR_MALLOC */

View File

@ -46,6 +46,8 @@
extern char *__progname;
extern int vFlag;
static const char *blankString = "";
struct metadata {
char *filename;
char *string;
@ -563,6 +565,9 @@ metadata_get_artist(metadata_t *md)
abort();
}
if (md->artist == NULL)
return (blankString);
else
return (md->artist);
}
@ -575,9 +580,28 @@ metadata_get_title(metadata_t *md)
abort();
}
if (md->title == NULL)
return (blankString);
else
return (md->title);
}
const char *
metadata_get_filename(metadata_t *md)
{
if (md == NULL) {
printf("%s: metadata_get_filename(): Internal error: Bad arguments\n",
__progname);
abort();
}
if (md->filename == NULL)
/* Should never happen: */
return (blankString);
else
return (md->filename);
}
char *
metadata_assemble_string(metadata_t *md)
{

View File

@ -75,23 +75,27 @@ 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 usually returns the filename without the extension.
* This function never returns NULL.
* 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, or NULL if the file did not
* contain any artist information.
* Returns a pointer to the artist string, which may be empty.
*/
const char * metadata_get_artist(metadata_t *);
/*
* Returns a pointer to the title string, or NULL if the file did not
* contain any title information.
* 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 *);
/*
* 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