mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-12-10 21:55:10 -05:00
Added support for ID3V2 ReplayGain tags (as written by Foobar). Generalized the replaygain tag parsing a bit, to cut down the code size (APE tags should use this as well, but as it requires larger changes, it will have to wait for another commit). Also fixed a bug in the ID3V2 parser; ISO-8859-1 strings could confuse the main parsing loop (causing bufferpos to come out of sync).
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7243 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
e44372ef18
commit
988ea2cffc
7 changed files with 236 additions and 97 deletions
|
|
@ -118,6 +118,7 @@ enum codec_status codec_start(struct codec_api* api)
|
|||
frequency_divider = 441;
|
||||
|
||||
ci->configure(DSP_SET_FREQUENCY, (int *)ci->id3->frequency);
|
||||
codec_set_replaygain(ci->id3);
|
||||
|
||||
ci->request_buffer(&size, ci->id3->first_frame_offset);
|
||||
ci->advance_buffer(size);
|
||||
|
|
|
|||
|
|
@ -528,7 +528,7 @@ static bool get_apetag_info (struct mp3entry *entry, int fd)
|
|||
|
||||
if (rem_space > 1 &&
|
||||
get_apetag_item (&temp_apetag, "replaygain_track_gain", temp_buffer, rem_space)) {
|
||||
entry->track_gain = get_replaygain (entry->track_gain_str = temp_buffer);
|
||||
entry->track_gain = get_replaygain (entry->track_gain_string = temp_buffer);
|
||||
str_space = strlen (temp_buffer) + 1;
|
||||
temp_buffer += str_space;
|
||||
rem_space -= str_space;
|
||||
|
|
@ -536,7 +536,7 @@ static bool get_apetag_info (struct mp3entry *entry, int fd)
|
|||
|
||||
if (rem_space > 1 &&
|
||||
get_apetag_item (&temp_apetag, "replaygain_album_gain", temp_buffer, rem_space)) {
|
||||
entry->album_gain = get_replaygain (entry->album_gain_str = temp_buffer);
|
||||
entry->album_gain = get_replaygain (entry->album_gain_string = temp_buffer);
|
||||
str_space = strlen (temp_buffer) + 1;
|
||||
temp_buffer += str_space;
|
||||
rem_space -= str_space;
|
||||
|
|
@ -910,35 +910,11 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
|
|||
} else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) {
|
||||
name_length = 11;
|
||||
p = &(entry->track_string);
|
||||
} else if ((strncasecmp(temp, "RG_RADIO=", 9) == 0)
|
||||
&& !entry->track_gain) {
|
||||
entry->track_gain = get_replaygain(&temp[9]);
|
||||
name_length = 8;
|
||||
p = &(entry->track_gain_str);
|
||||
} else if (strncasecmp(temp, "REPLAYGAIN_TRACK_GAIN=", 22) == 0) {
|
||||
entry->track_gain = get_replaygain(&temp[22]);
|
||||
name_length = 21;
|
||||
p = &(entry->track_gain_str);
|
||||
} else if ((strncasecmp(temp, "RG_AUDIOPHILE=", 14) == 0)
|
||||
&& !entry->album_gain) {
|
||||
entry->album_gain = get_replaygain(&temp[14]);
|
||||
name_length = 13;
|
||||
p = &(entry->album_gain_str);
|
||||
} else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_GAIN=", 22) == 0) {
|
||||
entry->album_gain = get_replaygain(&temp[22]);
|
||||
name_length = 21;
|
||||
p = &(entry->album_gain_str);
|
||||
} else if ((strncasecmp(temp, "RG_PEAK=", 8) == 0)
|
||||
&& !entry->track_peak) {
|
||||
entry->track_peak = get_replaypeak(&temp[8]);
|
||||
p = NULL;
|
||||
} else if (strncasecmp(temp, "REPLAYGAIN_TRACK_PEAK=", 22) == 0) {
|
||||
entry->track_peak = get_replaypeak(&temp[22]);
|
||||
p = NULL;
|
||||
} else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_PEAK=", 22) == 0) {
|
||||
entry->album_peak = get_replaypeak(&temp[22]);
|
||||
p = NULL;
|
||||
} else {
|
||||
int value_length = parse_replaygain(temp, NULL, entry, buffer,
|
||||
buffer_remaining);
|
||||
buffer_remaining -= value_length;
|
||||
buffer += value_length;
|
||||
p = NULL;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1388,15 +1388,15 @@ bool browse_id3(void)
|
|||
#if CONFIG_HWCODEC == MASNONE
|
||||
case 11:
|
||||
lcd_puts(0, 0, str(LANG_ID3_TRACK_GAIN));
|
||||
lcd_puts(0, 1, id3->track_gain_str
|
||||
? id3->track_gain_str
|
||||
lcd_puts(0, 1, id3->track_gain_string
|
||||
? id3->track_gain_string
|
||||
: (char*) str(LANG_ID3_NO_GAIN));
|
||||
break;
|
||||
|
||||
case 12:
|
||||
lcd_puts(0, 0, str(LANG_ID3_ALBUM_GAIN));
|
||||
lcd_puts(0, 1, id3->album_gain_str
|
||||
? id3->album_gain_str
|
||||
lcd_puts(0, 1, id3->album_gain_string
|
||||
? id3->album_gain_string
|
||||
: (char*) str(LANG_ID3_NO_GAIN));
|
||||
break;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ struct mp3entry {
|
|||
/* replaygain support */
|
||||
|
||||
#if CONFIG_HWCODEC == MASNONE
|
||||
char* track_gain_str;
|
||||
char* album_gain_str;
|
||||
char* track_gain_string;
|
||||
char* album_gain_string;
|
||||
long track_gain; /* 7.24 signed fixed point. 0 for no gain. */
|
||||
long album_gain;
|
||||
long track_peak; /* 7.24 signed fixed point. 0 for no peak. */
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@
|
|||
#ifndef _REPLAYGAIN_H
|
||||
#define _REPLAYGAIN_H
|
||||
|
||||
#include "id3.h"
|
||||
|
||||
long get_replaygain(const char* str);
|
||||
long get_replaypeak(const char* str);
|
||||
long parse_replaygain(const char* key, const char* value,
|
||||
struct mp3entry* entry, char* buffer, int length);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
#include "id3.h"
|
||||
#include "mp3data.h"
|
||||
#include "system.h"
|
||||
#include "replaygain.h"
|
||||
|
||||
#define UNSYNC(b0,b1,b2,b3) (((long)(b0 & 0x7F) << (3*7)) | \
|
||||
((long)(b1 & 0x7F) << (2*7)) | \
|
||||
|
|
@ -163,6 +164,10 @@ char* id3_get_codec(const struct mp3entry* id3)
|
|||
Many ID3 symbolic names come in more than one form. You can add both
|
||||
forms, each referencing the same variable in struct mp3entry.
|
||||
If both forms are present, the last found will be used.
|
||||
Note that the offset can be zero, in which case no entry will be set
|
||||
in the mp3entry struct; the frame is still read into the buffer and
|
||||
the special processing function is called (several times, if there
|
||||
are several frames with the same name).
|
||||
|
||||
4. Alternately, use the TAG_LIST_ENTRY macro with
|
||||
ID3 tag symbolic name,
|
||||
|
|
@ -305,6 +310,34 @@ static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos )
|
|||
}
|
||||
}
|
||||
|
||||
#if CONFIG_HWCODEC == MASNONE
|
||||
/* parse user defined text, looking for replaygain information. */
|
||||
static int parseuser( struct mp3entry* entry, char* tag, int bufferpos )
|
||||
{
|
||||
char* value = NULL;
|
||||
int desc_len = strlen(tag);
|
||||
int value_len = 0;
|
||||
|
||||
if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) {
|
||||
/* At least part of the value was read, so we can safely try to
|
||||
* parse it
|
||||
*/
|
||||
|
||||
value = tag + desc_len + 1;
|
||||
value_len = parse_replaygain(tag, value, entry, tag,
|
||||
bufferpos - (tag - entry->id3v2buf));
|
||||
}
|
||||
|
||||
if (value_len) {
|
||||
bufferpos = tag - entry->id3v2buf + value_len;
|
||||
} else {
|
||||
bufferpos = tag - entry->id3v2buf;
|
||||
}
|
||||
|
||||
return bufferpos;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct tag_resolver taglist[] = {
|
||||
{ "TPE1", 4, offsetof(struct mp3entry, artist), NULL },
|
||||
{ "TP1", 3, offsetof(struct mp3entry, artist), NULL },
|
||||
|
|
@ -319,6 +352,9 @@ static const struct tag_resolver taglist[] = {
|
|||
{ "TCOM", 4, offsetof(struct mp3entry, composer), NULL },
|
||||
{ "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre },
|
||||
{ "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre },
|
||||
#if CONFIG_HWCODEC == MASNONE
|
||||
{ "TXXX", 4, 0, &parseuser },
|
||||
#endif
|
||||
};
|
||||
|
||||
#define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0])))
|
||||
|
|
@ -327,12 +363,12 @@ static const struct tag_resolver taglist[] = {
|
|||
string. If it is, we attempt to convert it to a 8-bit ASCII string
|
||||
(for valid 8-bit ASCII characters). If it's not unicode, we leave
|
||||
it alone. At some point we should fully support unicode strings */
|
||||
static int unicode_munge(char** string, int *len) {
|
||||
static int unicode_munge(char* string, int *len) {
|
||||
long tmp;
|
||||
bool le = false;
|
||||
int i;
|
||||
char *str = *string;
|
||||
char *outstr = *string;
|
||||
char *str = string;
|
||||
char *outstr = string;
|
||||
bool bom = false;
|
||||
int outlen;
|
||||
|
||||
|
|
@ -343,8 +379,14 @@ static int unicode_munge(char** string, int *len) {
|
|||
|
||||
/* Type 0x00 is ordinary ISO 8859-1 */
|
||||
if(str[0] == 0x00) {
|
||||
(*len)--;
|
||||
(*string)++; /* Skip the encoding type byte */
|
||||
int i = --(*len);
|
||||
|
||||
/* We must move the string to the left */
|
||||
while (i--) {
|
||||
string[0] = string[1];
|
||||
string++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -352,6 +394,11 @@ static int unicode_munge(char** string, int *len) {
|
|||
if(str[0] == 0x01 || str[0] == 0x02) {
|
||||
(*len)--;
|
||||
str++;
|
||||
i = 0;
|
||||
|
||||
/* Handle frames with more than one string (needed for TXXX frames).
|
||||
*/
|
||||
do {
|
||||
tmp = BYTES2INT(0, 0, str[0], str[1]);
|
||||
|
||||
/* Now check if there is a BOM (zero-width non-breaking space, 0xfeff)
|
||||
|
|
@ -377,8 +424,6 @@ static int unicode_munge(char** string, int *len) {
|
|||
le = true;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
outlen = *len / 2;
|
||||
|
||||
do {
|
||||
|
|
@ -396,9 +441,12 @@ static int unicode_munge(char** string, int *len) {
|
|||
str += 2;
|
||||
} while((str[0] || str[1]) && (i < outlen));
|
||||
|
||||
*len = i;
|
||||
str += 2;
|
||||
outstr[i++] = 0; /* Terminate the string */
|
||||
} while(i < outlen);
|
||||
|
||||
*len = i - 1;
|
||||
|
||||
outstr[i] = 0; /* Terminate the string */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -686,29 +734,33 @@ static void setid3v2title(int fd, struct mp3entry *entry)
|
|||
|
||||
for (i=0; i<TAGLIST_SIZE; i++) {
|
||||
const struct tag_resolver* tr = &taglist[i];
|
||||
char** ptag = (char**) (((char*)entry) + tr->offset);
|
||||
char** ptag = tr->offset ? (char**) (((char*)entry) + tr->offset)
|
||||
: NULL;
|
||||
char* tag;
|
||||
|
||||
if( !*ptag && !memcmp( header, tr->tag, tr->tag_length ) ) {
|
||||
if( (!ptag || !*ptag) && !memcmp( header, tr->tag, tr->tag_length ) ) {
|
||||
|
||||
/* found a tag matching one in tagList, and not yet filled */
|
||||
tag = buffer + bufferpos;
|
||||
|
||||
if(global_unsynch && version <= ID3_VER_2_3)
|
||||
bytesread = read_unsynched(fd, buffer + bufferpos,
|
||||
framelen);
|
||||
bytesread = read_unsynched(fd, tag, framelen);
|
||||
else
|
||||
bytesread = read(fd, buffer + bufferpos, framelen);
|
||||
bytesread = read(fd, tag, framelen);
|
||||
|
||||
if( bytesread != framelen )
|
||||
return;
|
||||
|
||||
size -= bytesread;
|
||||
*ptag = buffer + bufferpos;
|
||||
|
||||
if(unsynch || (global_unsynch && version >= ID3_VER_2_4))
|
||||
bytesread = unsynchronize_frame(*ptag, bytesread);
|
||||
bytesread = unsynchronize_frame(tag, bytesread);
|
||||
|
||||
unicode_munge( tag, &bytesread );
|
||||
|
||||
if (ptag)
|
||||
*ptag = tag;
|
||||
|
||||
unicode_munge( ptag, &bytesread );
|
||||
tag = *ptag;
|
||||
/* remove trailing spaces */
|
||||
while ( bytesread > 0 && isspace(tag[bytesread-1]))
|
||||
bytesread--;
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@
|
|||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <system.h>
|
||||
#include "id3.h"
|
||||
#include "debug.h"
|
||||
|
||||
/* The fixed point math routines (with the exception of fp_atof) are based
|
||||
|
|
@ -326,3 +329,106 @@ long get_replaypeak(const char* str)
|
|||
|
||||
return peak;
|
||||
}
|
||||
|
||||
/* Compare two strings, ignoring case, up to the end nil or another end of
|
||||
* string character. E.g., if eos is '=', "a=" would equal "a". Returns
|
||||
* true for a match, false otherwise.
|
||||
* TODO: This should be placed somewhere else, as it could be useful in
|
||||
* other places too.
|
||||
*/
|
||||
static bool str_equal(const char* s1, const char* s2, char eos)
|
||||
{
|
||||
char c1 = 0;
|
||||
char c2 = 0;
|
||||
|
||||
while (*s1 && *s2 && (*s1 != eos) && (*s2 != eos))
|
||||
{
|
||||
if ((c1 = toupper(*s1)) != (c2 = toupper(*s2)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
|
||||
if (c1 == eos)
|
||||
{
|
||||
c1 = '\0';
|
||||
}
|
||||
|
||||
if (c2 == eos)
|
||||
{
|
||||
c2 = '\0';
|
||||
}
|
||||
|
||||
return c1 == c2;
|
||||
}
|
||||
|
||||
/* Check for a ReplayGain tag conforming to the "VorbisGain standard". If
|
||||
* found, set the mp3entry accordingly. If value is NULL, key is expected
|
||||
* to be on the "key=value" format, and the comparion/extraction is done
|
||||
* accordingly. buffer is where to store the text contents of the gain tags;
|
||||
* up to length bytes (including end nil) can be written.
|
||||
* Returns number of bytes written to the tag text buffer, or zero if
|
||||
* no ReplayGain tag was found (or nothing was copied to the buffer for
|
||||
* other reasons).
|
||||
*/
|
||||
long parse_replaygain(const char* key, const char* value,
|
||||
struct mp3entry* entry, char* buffer, int length)
|
||||
{
|
||||
const char* val = value;
|
||||
char **p = NULL;
|
||||
char eos = '\0';
|
||||
|
||||
if (!val)
|
||||
{
|
||||
if (!(val = strchr(key, '=')))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
val++;
|
||||
eos = '=';
|
||||
}
|
||||
|
||||
if (str_equal(key, "replaygain_track_gain", eos)
|
||||
|| (str_equal(key, "rg_radio", eos) && !entry->track_gain))
|
||||
{
|
||||
entry->track_gain = get_replaygain(val);
|
||||
p = &(entry->track_gain_string);
|
||||
}
|
||||
else if (str_equal(key, "replaygain_album_gain", eos)
|
||||
|| (str_equal(key, "rg_audiophile", eos) && !entry->album_gain))
|
||||
{
|
||||
entry->album_gain = get_replaygain(val);
|
||||
p = &(entry->album_gain_string);
|
||||
}
|
||||
else if (str_equal(key, "replaygain_track_peak", eos)
|
||||
|| (str_equal(key, "rg_peak", eos) && !entry->track_peak))
|
||||
{
|
||||
entry->track_peak = get_replaypeak(val);
|
||||
}
|
||||
else if (str_equal(key, "replaygain_album_peak", eos))
|
||||
{
|
||||
entry->album_peak = get_replaypeak(val);
|
||||
}
|
||||
|
||||
if (p)
|
||||
{
|
||||
int len = strlen(val);
|
||||
|
||||
len = MIN(len, length - 1);
|
||||
|
||||
/* A few characters just isn't interesting... */
|
||||
if (len > 1)
|
||||
{
|
||||
strncpy(buffer, val, len);
|
||||
buffer[len] = 0;
|
||||
*p = buffer;
|
||||
return len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue