#include <errno.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "log.h"
#include "misc.h"
#include "tag.h"
void tag_vorbis_comment_parse(const char *line, Tag *tags)
{
const char *val = strchr(line, '=');
const Gperf_match *tag = tag_match(line, val - line);
if (!tag)
return;
if (!val)
{
fprintf(stderr, "%s tag is malformed (= not found)\n", tag->name);
return;
}
if (*++val == '\0')
fprintf(stderr, "%s tag is empty\n", tag->name);
char *end;
float ftmp;
switch (tag->id)
{
case RG_TRACK_GAIN:
case RG_ALBUM_GAIN:
ftmp = strtof(val, &end);
if (*end && strcasecmp(end, "db") && strcasecmp(end, " db"))
{
fprintf(stderr, "%s tag has trailing garbage: %s\n",
tag->name, end);
break;
}
if (errno == ERANGE)
{
fprintf(stderr, "%s tag float %s, value: %s\n", tag->name,
ftmp == HUGE_VAL ? "overflow" : "underflow", val);
break;
}
tags[tag->id].fval = ftmp;
tags[tag->id].empty = false;
break;
case RG_TRACK_PEAK:
case RG_ALBUM_PEAK:
ftmp = strtof(val, &end);
if (*end)
{
fprintf(stderr, "%s tag has trailing garbage: %s\n",
tag->name, end);
break;
}
if (errno == ERANGE)
{
fprintf(stderr, "%s tag float %s, value: %s\n", tag->name,
ftmp == HUGE_VAL ? "overflow" : "underflow", val);
break;
}
tags[tag->id].fval = ftmp;
tags[tag->id].empty = false;
break;
default:
;
}
switch (tag_type[tag->id])
{
case STRING:
tags[tag->id].sval = xstrdup(val);
tags[tag->id].empty = false;
break;
case FLOAT:
;
}
}
Tag * tags_init(void)
{
Tag *ret = xmalloc(NUM_TAG * sizeof(Tag));
for (size_t i = 0; i < NUM_TAG; ++i)
ret[i].empty = true;
return ret;
}
void tags_free(const Tag *tags)
{
for (size_t i = 0; i < NUM_TAG; ++i)
{
if (!tags[i].empty && tag_type[i] == STRING)
free(tags[i].sval);
}
free((void *)tags);
}
/* Reverse gperf matching for printing */
static const char *const tag_key[NUM_TAG] =
{
"TITLE",
"ARTIST",
"ALBUMARTIST",
"ALBUM",
"DATE",
"TRACKNUMBER",
"TRACKTOTAL",
"DISCNUMBER",
"DISCTOTAL",
"DISCSUBTITLE",
"REPLAYGAIN_TRACK_GAIN",
"REPLAYGAIN_TRACK_PEAK",
"REPLAYGAIN_ALBUM_GAIN",
"REPLAYGAIN_ALBUM_PEAK",
};
void tags_fprint(FILE *stream, const Tag *tags)
{
for (size_t i = 0; i < NUM_TAG; ++i)
{
if (!tags[i].empty)
{
switch (tag_type[i])
{
case STRING:
fprintf(stream, "tag %s %s\n",tag_key[i], tags[i].sval);
break;
case FLOAT:
fprintf(stream, "tag %s %f\n", tag_key[i], tags[i].fval);
}
}
}
}
float tag_compute_replay_gain(const Tag *tags, const ReplayGain_type rgtype)
{
float gain, peak = 1.f;
if (rgtype == ALBUM_GAIN && !tags[RG_ALBUM_GAIN].empty)
{
gain = tags[RG_ALBUM_GAIN].fval;
if (tags[RG_ALBUM_PEAK].empty)
{
log_append(LOG_WARNING, "No RG_ALBUM_PEAK tag found, clipping "
"could happend");
}
else
peak = tags[RG_ALBUM_PEAK].fval;
}
else if (rgtype == TRACK_GAIN && !tags[RG_TRACK_GAIN].empty)
{
gain = tags[RG_TRACK_GAIN].fval;
if (tags[RG_TRACK_PEAK].empty)
{
log_append(LOG_WARNING, "No RG_TRACK_PEAK tag found, clipping "
"could happend");
}
else
peak = tags[RG_TRACK_PEAK].fval;
}
else
return 1.f;
return fminf(powf(10, gain / 20), 1 / peak);
}