1
0
Fork 0
forked from len0rd/rockbox

Generalized id3v2 parsing code. Added support for the composer frame and free-form genre and tracknum frames. (Patch #706111 by Thomas Paul Diffenbach)

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@3727 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Björn Stenberg 2003-06-04 15:09:35 +00:00
parent 7bc69aa084
commit 8498a48496
5 changed files with 187 additions and 107 deletions

View file

@ -285,34 +285,38 @@ static char* get_tag(struct mp3entry* id3,
return id3->artist;
case 'n': /* ID3 Track Number */
if (id3->tracknum)
{
if (id3->track_string)
return id3->track_string;
if (id3->tracknum) {
snprintf(buf, buf_size, "%d", id3->tracknum);
return buf;
}
else
{
return NULL;
}
return NULL;
case 'd': /* ID3 Album/Disc */
return id3->album;
case 'c': /* ID3 Composer */
return id3->composer;
case 'y': /* year */
if( id3->year_string )
return id3->year_string;
if (id3->year) {
snprintf(buf, buf_size, "%d", id3->year);
return buf;
}
else
return NULL;
break;
return NULL;
case 'g': /* genre */
if( id3->genre_string )
return id3->genre_string ;
if (id3->genre < sizeof(genres)/sizeof(char*))
return (char*)genres[id3->genre];
else
return NULL;
break;
return NULL;
case 'v': /* id3 version */
switch (id3->id3version) {

View file

@ -68,3 +68,4 @@ Jos
Stevie Oh
Jörg Hohensohn
Dave Jones
Thomas Paul Diffenbach

View file

@ -21,12 +21,13 @@ Tags
----
ID3 Info Tags:
%ia : ID3 Artist
%ic : ID3 Composer
%id : ID3 Album Name
%ig : ID3 Genre Name
%in : ID3 Track Number
%it : ID3 Track Title
%iv : ID3 Version (1.0, 1.1, 2.2, 2.3, 2.4 or empty if no id3 tag)
%iy : ID3 Year
%iv : ID3 Version (1.0, 1.1, 2.2, 2.3, 2.4 or empty if no id3 tag) (*)
Battery Info:

View file

@ -26,6 +26,10 @@ struct mp3entry {
char *title;
char *artist;
char *album;
char* genre_string ;
char* track_string ;
char* year_string ;
char* composer ;
int tracknum;
int version;
int layer;

View file

@ -24,11 +24,15 @@
*
*/
/* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include "file.h"
#include "debug.h"
#include "atoi.h"
@ -46,6 +50,99 @@
((b2 & 0xFF) << (1*8)) | \
((b3 & 0xFF) << (0*8)))
/*
HOW TO ADD ADDITIONAL ID3 VERSION 2 TAGS
Code and comments by Thomas Paul Diffenbach
To add another ID3v2 Tag, do the following:
1. add a char* named for the tag to struct mp3entry in id3.h,
(I (tpd) prefer to use char* rather than ints, even for what seems like
numerical values, for cases where a number won't do, e.g.,
YEAR: "circa 1765", "1790/1977" (composed/performed), "28 Feb 1969"
TRACK: "1/12", "1 of 12", GENRE: "Freeform genre name"
Text is more flexible, and as the main use of id3 data is to
display it, converting it to an int just means reconverting to
display it, at a runtime cost.)
2. If any special processing beyond copying the tag value from the Id3
block to the struct mp3entry is rrequired (such as converting to an
int), write a function to perform this special processing.
This function's prototype must match that of
typedef tagPostProcessFunc, that is it must be:
int func( struct mp3entry*, char* tag, int bufferpos )
the first argument is a pointer to the current mp3entry structure the
second argument is a pointer to the null terminated string value of the
tag found the third argument is the offset of the next free byte in the
mp3entry's buffer your function should return the corrected offset; if
you don't lengthen or shorten the tag string, you can return the third
argument unchanged.
Unless you have a good reason no to, make the function static.
TO JUST COPY THE TAG NO SPECIAL PROCESSING FUNCTION IS NEEDED.
3. add one or more entries to the tagList array, using the format:
char* ID3 Tag symbolic name -- see the ID3 specification for these,
sizeof() that name minus 1,
offsetof( struct mp3entry, variable_name_in_struct_mp3entry ),
pointer to your special processing function or NULL
if you need no special processing
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.
4. Alternately, use the TAG_LIST_ENTRY macro with
ID3 tag symbolic name,
variable in struct mp3entry,
special processing function address
5. Add code to wps-display.c function get_tag to assign a printf-like
format specifier for the tag */
/* Structure for ID3 Tag extraction information */
struct tag_resolver {
const char* tag;
int tag_length;
size_t offset;
int (*ppFunc)(struct mp3entry*, char* tag, int bufferpos);
};
/* parse numeric value from string */
static int parsenum( struct mp3entry* entry, char* tag, int bufferpos )
{
entry->tracknum = atoi( tag );
return bufferpos;
}
/* parse numeric genre from string */
static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos )
{
if( tag[ 1 ] == '(' && tag[ 2 ] != '(' ) {
entry->genre = atoi( tag + 2 );
entry->genre_string = 0;
return tag - entry->id3v2buf;
}
else {
entry->genre = 0xFF;
return bufferpos;
}
}
static struct tag_resolver taglist[] = {
{ "TPE1", 4, offsetof(struct mp3entry, artist), NULL },
{ "TP1", 3, offsetof(struct mp3entry, artist), NULL },
{ "TIT2", 4, offsetof(struct mp3entry, title), NULL },
{ "TT2", 3, offsetof(struct mp3entry, title), NULL },
{ "TALB", 4, offsetof(struct mp3entry, album), NULL },
{ "TRCK", 4, offsetof(struct mp3entry, track_string), &parsenum },
{ "TYER", 4, offsetof(struct mp3entry, year_string), &parsenum },
{ "TYR", 3, offsetof(struct mp3entry, year_string), &parsenum },
{ "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre },
{ "TCOM", 5, offsetof(struct mp3entry, composer), NULL }
};
#define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0])))
/* Checks to see if the passed in string is a 16-bit wide Unicode v2
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
@ -186,24 +283,6 @@ static bool setid3v1title(int fd, struct mp3entry *entry)
return true;
}
static int read_frame(int fd, unsigned char *buf, char **destptr, int framelen)
{
int bytesread;
bytesread = read(fd, buf, framelen);
if(bytesread < 0)
return bytesread * 10 - 1;
if(bytesread < framelen)
return -1;
*destptr = buf;
if(unicode_munge(destptr, &bytesread) < 0)
return -2;
(*destptr)[bytesread] = '\0';
return bytesread + 1;
}
/*
* Sets the title of an MP3 entry based on its ID3v2 tag.
@ -221,11 +300,11 @@ static void setid3v2title(int fd, struct mp3entry *entry)
char header[10];
unsigned char version;
char *buffer = entry->id3v2buf;
char *tmp = NULL;
int bytesread = 0;
int buffersize = sizeof(entry->id3v2buf);
int flags;
int skip;
int i;
/* Bail out if the tag is shorter than 10 bytes */
if(entry->id3v2len < 10)
@ -281,7 +360,7 @@ static void setid3v2title(int fd, struct mp3entry *entry)
* We must have at least minframesize bytes left for the
* remaining frames to be interesting
*/
while(size > minframesize) {
while(size > minframesize ) {
flags = 0;
/* Read frame header and check length */
@ -353,78 +432,56 @@ static void setid3v2title(int fd, struct mp3entry *entry)
DEBUGF("id3v2 frame: %.4s\n", header);
/* Check for certain frame headers */
if (!entry->artist &&
(!strncmp(header, "TPE1", strlen("TPE1")) ||
!strncmp(header, "TP1", strlen("TP1")))) {
bytesread = read_frame(fd, buffer + bufferpos,
&entry->artist, framelen);
if(bytesread < 0)
return;
/* Check for certain frame headers
bufferpos += bytesread;
size -= framelen;
'size' is the amount of frame bytes remaining. We decrement it by
the amount of bytes we read. If we fail to read as many bytes as
we expect, we assume that we can't read from this file, and bail
out.
For each frame. we will iterate over the list of supported tags,
and read the tag into entry's buffer. All tags will be kept as
strings, for cases where a number won't do, e.g., YEAR: "circa
1765", "1790/1977" (composed/performed), "28 Feb 1969" TRACK:
"1/12", "1 of 12", GENRE: "Freeform genre name" Text is more
flexible, and as the main use of id3 data is to display it,
converting it to an int just means reconverting to display it, at a
runtime cost.
For tags that the current code does convert to ints, a post
processing function will be called via a pointer to function. */
for (i=0; i<TAGLIST_SIZE; i++) {
struct tag_resolver* tr = &taglist[i];
char** ptag = (char**) (((char*)entry) + tr->offset);
char* tag;
if( !*ptag && !memcmp( header, tr->tag, tr->tag_length ) ) {
/* found a tag matching one in tagList, and not yet filled */
bytesread = read(fd, buffer + bufferpos, framelen);
if( bytesread != framelen )
return;
size -= bytesread;
*ptag = buffer + bufferpos;
unicode_munge( ptag, &bytesread );
tag = *ptag;
tag[bytesread + 1] = 0;
bufferpos += bytesread + 2;
if( tr->ppFunc )
bufferpos = tr->ppFunc(entry, tag, bufferpos);
break;
}
}
else if (!entry->title &&
(!strncmp(header, "TIT2", strlen("TIT2")) ||
!strncmp(header, "TT2", strlen("TT2")))) {
bytesread = read_frame(fd, buffer + bufferpos,
&entry->title, framelen);
if(bytesread < 0)
return;
bufferpos += bytesread;
size -= framelen;
}
else if( !entry->album &&
!strncmp(header, "TALB", strlen("TALB"))) {
bytesread = read_frame(fd, buffer + bufferpos,
&entry->album, framelen);
if(bytesread < 0)
return;
if( i == TAGLIST_SIZE ) {
/* no tag in tagList was found, or it was a repeat.
skip it using the total size */
bufferpos += bytesread;
size -= framelen;
}
else if (!entry->tracknum &&
!strncmp(header, "TRCK", strlen("TRCK"))) {
bytesread = read_frame(fd, buffer + bufferpos,
&tmp, framelen);
if(bytesread < 0)
return;
entry->tracknum = atoi(tmp);
size -= framelen;
}
else if (!entry->year &&
(!strncmp(header, "TYER", 4) ||
!strncmp(header, "TYR", 3))) {
bytesread = read_frame(fd, buffer + bufferpos,
&tmp, framelen);
if(bytesread < 0)
return;
entry->year = atoi(tmp);
size -= bytesread;
}
else if (!entry->genre &&
!strncmp(header, "TCON", 4)) {
char* ptr = buffer + bufferpos;
bytesread = read(fd, ptr, framelen);
if(bytesread < 0 || bytesread < framelen)
return;
if (ptr[1] == '(' && ptr[2] != '(')
entry->genre = atoi(ptr+2);
bufferpos += bytesread + 1;
size -= bytesread;
}
else {
/* Unknown frame, skip it using the total size in case
it was truncated */
size -= totframelen;
lseek(fd, totframelen, SEEK_CUR);
if( lseek(fd, totframelen, SEEK_CUR) == -1 )
return;
}
}
}
@ -448,10 +505,11 @@ static int getid3v2len(int fd)
offset = 0;
/* Now check what the ID3v2 size field says */
else if(read(fd, buf, 4) != 4)
offset = 0;
else
offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10;
if(read(fd, buf, 4) != 4)
offset = 0;
else
offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10;
DEBUGF("ID3V2 Length: 0x%x\n", offset);
return offset;
@ -588,6 +646,7 @@ int main(int argc, char **argv)
int i;
for(i=1; i<argc; i++) {
struct mp3entry mp3;
mp3.album = "Bogus";
if(mp3info(&mp3, argv[i])) {
printf("Failed to get %s\n", argv[i]);
return 0;
@ -597,6 +656,10 @@ int main(int argc, char **argv)
" Title: %s\n"
" Artist: %s\n"
" Album: %s\n"
" Genre: %s (%d) \n"
" Composer: %s\n"
" Year: %s (%d)\n"
" Track: %s (%d)\n"
" Length: %s / %d s\n"
" Bitrate: %d\n"
" Frequency: %d\n",
@ -604,6 +667,13 @@ int main(int argc, char **argv)
mp3.title?mp3.title:"<blank>",
mp3.artist?mp3.artist:"<blank>",
mp3.album?mp3.album:"<blank>",
mp3.genre_string?mp3.genre_string:"<blank>",
mp3.genre,
mp3.composer?mp3.composer:"<blank>",
mp3.year_string?mp3.year_string:"<blank>",
mp3.year,
mp3.track_string?mp3.track_string:"<blank>",
mp3.tracknum,
secs2str(mp3.length),
mp3.length/1000,
mp3.bitrate,