12#include "AbstractMetaData.h" 
   19#ifndef AUDIOTOOLS_ID3_TAG_ALLOW_NONASCII 
   20#define AUDIOTOOLS_ID3_TAG_ALLOW_NONASCII false 
   33static const char *genres[] = { 
"Classic Rock", 
"Country", 
"Dance", 
"Disco", 
"Funk", 
"Grunge", 
"Hip-Hop", 
"Jazz", 
"Metal", 
"New Age", 
"Oldies", 
"Other", 
"Pop", 
"R&B", 
"Rap", 
"Reggae", 
"Rock", 
"Techno", 
"Industrial", 
"Alternative", 
"Ska", 
"Death Metal", 
"Pranks", 
"Soundtrack", 
"Euro-Techno", 
"Ambient", 
"Trip-Hop", 
"Vocal", 
"Jazz+Funk", 
"Fusion", 
"Trance", 
"Classical", 
"Instrumental", 
"Acid", 
"House", 
"Game", 
"Sound Clip", 
"Gospel", 
"Noise", 
"Alternative Rock", 
"Bass", 
"Soul", 
"Punk", 
"Space", 
"Meditative", 
"Instrumental Pop", 
"Instrumental Rock", 
"Ethnic", 
"Gothic", 
"Darkwave", 
"Techno-Insdustiral", 
"Electronic", 
"Pop-Folk", 
"Eurodance", 
"Dream", 
"Southern Rock", 
"Comedy", 
"Cult", 
"Gangsta", 
"Top 40", 
"Christian Rap", 
"Pop/Funk", 
"Jungle", 
"Native US", 
"Cabaret", 
"New Wave", 
"Psychadelic", 
"Rave", 
"Showtunes", 
"Trailer", 
"Lo-Fi", 
"Tribal", 
"Acid Punk", 
"Acid Jazz", 
"Polka", 
"Retro", 
"Musical", 
"Rock & Roll", 
"Hard Rock", 
"Folk", 
"Folk-Rock", 
"National Folk", 
"Swing", 
"Fast Fusion", 
"Bebob", 
"Latin", 
"Revival", 
"Celtic", 
"Bluegrass", 
"Avantgarde", 
"Gothic Rock", 
"Progressive Rock", 
"Psychedelic Rock", 
"Symphonic Rock", 
"Slow Rock", 
"Big Band", 
"Chorus", 
"Easy Listening", 
"Acoustic",
"Humour", 
"Speech", 
"Chanson", 
"Opera", 
"Chamber Music", 
"Sonata", 
"Symphony", 
"Booty Bass", 
"Primus", 
"Porn Groove", 
"Satire", 
"Slow Jam", 
"Club", 
"Tango", 
"Samba", 
"Folklore", 
"Ballad", 
"Power Ballad", 
"Rhytmic Soul", 
"Freestyle", 
"Duet", 
"Punk Rock", 
"Drum Solo", 
"Acapella", 
"Euro-House", 
"Dance Hall", 
"Goa", 
"Drum & Bass", 
"Club-House", 
"Hardcore", 
"Terror", 
"Indie", 
"BritPop", 
"Negerpunk", 
"Polsk Punk", 
"Beat", 
"Christian Gangsta", 
"Heavy Metal", 
"Black Metal", 
"Crossover", 
"Contemporary C", 
"Christian Rock", 
"Merengue", 
"Salsa", 
"Thrash Metal", 
"Anime", 
"JPop", 
"SynthPop" };
 
   36enum ParseStatus { TagNotFound, PartialTagAtTail, TagFoundPartial, TagFoundComplete, TagProcessed};
 
   80    void setCallback(
void (*fn)(
MetaDataType info, 
const char* str, 
int len)) {
 
   86    void (*callback)(
MetaDataType info, 
const char* title, 
int len);
 
   90    int findTag(
const char* tag, 
const char*str, 
size_t len){
 
   91        if (tag==
nullptr || str==
nullptr || len<=0) 
return -1;
 
   96        size_t tag_len = strlen(tag);
 
   97        if (tag_len >= len) 
return -1;
 
   98        for (
size_t j=0;j<=len-tag_len-1;j++){
 
   99            if (memcmp(str+j,tag, tag_len)==0){
 
 
 
  124        status = TagNotFound;
 
  125        use_bytes_of_next_write = 0;
 
  126        memset(tag_str, 0, 5);
 
 
  136        if (tag_ext!=
nullptr){
 
 
  143    size_t write(
const uint8_t* data, 
size_t len){
 
  149                case PartialTagAtTail:
 
  152                case TagFoundPartial:
 
 
  164    int use_bytes_of_next_write = 0;
 
  165    char tag_str[5] = 
"";
 
  166    ID3v1 *tag = 
nullptr;
 
  174        int pos = 
findTag(
"TAG+",(
const char*) data, len);
 
  177            if (tag_ext!=
nullptr){
 
  182                    use_bytes_of_next_write = min(
sizeof(
ID3v1Enhanced), len-pos);
 
  183                    memcpy(tag_ext, data+pos, use_bytes_of_next_write);
 
  184                    status = TagFoundPartial;
 
  188            pos = 
findTag(
"TAG", (
const char*) data, len);
 
  192                    if (len-pos>=
sizeof(
ID3v1)){
 
  193                        memcpy(tag,data+pos,
sizeof(
ID3v1));
 
  196                        use_bytes_of_next_write = min(
sizeof(
ID3v1), len-pos);
 
  197                        memcpy(tag,data+pos,use_bytes_of_next_write);
 
  198                        status = TagFoundPartial;
 
  207            if (data[len-3] == 
'T' && data[len-2] == 
'A' && data[len-1] == 
'G'){
 
  208                strcpy(tag_str,
"TAG");
 
  209                status = TagFoundPartial;
 
  210            } 
else if (data[len-2] == 
'T' && data[len-1] == 
'A' ){
 
  211                strcpy(tag_str,
"TA");
 
  212                status = TagFoundPartial;                
 
  213            } 
else if (data[len-1] == 
'T'){            
 
  215                status = TagFoundPartial;
 
 
  222        int tag_len = strlen(tag_str);
 
  223        int missing = 5 - tag_len;
 
  224        strncat((
char*)tag_str, (
char*)data, missing);
 
  225        if (strncmp((
char*)tag_str, 
"TAG+", 4)==0){
 
  227            memcpy(tag,tag_str, 4);
 
  230        } 
else if (strncmp((
char*)tag_str,
"TAG",3)==0){
 
  232            memcpy(tag,tag_str, 3);
 
  233            memcpy(tag,data+len,
sizeof(
ID3v1));
 
 
  241            int remainder = 
sizeof(
ID3v1) - use_bytes_of_next_write;
 
  242            memcpy(tag,data+use_bytes_of_next_write,remainder);
 
  244            use_bytes_of_next_write = 0;   
 
  245        } 
else if (tag_ext!=
nullptr){
 
  246            int remainder = 
sizeof(
ID3v1Enhanced) - use_bytes_of_next_write;
 
  247            memcpy(tag_ext,data+use_bytes_of_next_write,remainder);
 
  249            use_bytes_of_next_write = 0;   
 
 
  255        if (callback==
nullptr) 
return;
 
  257        if (tag_ext!=
nullptr){
 
  258            callback(Title, tag_ext->title,
strnlength(tag_ext->title,60));
 
  259            callback(Artist, tag_ext->artist,
strnlength(tag_ext->artist,60));
 
  260            callback(Album, tag_ext->album,
strnlength(tag_ext->album,60));
 
  261            callback(Genre, tag_ext->genre,
strnlength(tag_ext->genre,30));
 
  264            status = TagProcessed;
 
  268            callback(Title, tag->title,
strnlength(tag->title,30));
 
  269            callback(Artist, tag->artist,
strnlength(tag->artist,30));
 
  270            callback(Album, tag->album,
strnlength(tag->album,30));        
 
  271            uint16_t genre = tag->genre;
 
  272            if (genre < 
sizeof(genres)){
 
  273                const char* genre_str = genres[genre];
 
  274                callback(Genre, genre_str,strlen(genre_str));
 
  278            status = TagProcessed;
 
 
 
  286#define UnsynchronisationFlag 0x40 
  287#define ExtendedHeaderFlag 0x20 
  288#define ExperimentalIndicatorFlag 0x10 
  291static const char* id3_v2_tags[] = {
"TALB", 
"TOPE", 
"TPE1", 
"TIT2", 
"TCON"};
 
  331static const int ID3FrameSize = 11;
 
  345        status = TagNotFound;
 
  346        use_bytes_of_next_write = 0;
 
  347        actual_tag = 
nullptr;
 
  349        tag_processed = 
false;
 
  350        result.resize(result_size);
 
 
  356        status = TagNotFound;
 
  357        use_bytes_of_next_write = 0;
 
  358        actual_tag = 
nullptr;
 
  360        tag_processed = 
false;
 
 
  364    size_t write(
const uint8_t* data, 
size_t len){
 
  370                case PartialTagAtTail:
 
 
  393        return tag_processed;
 
 
  399        if (result.size()==0) {
 
  400            result.resize(result_size);
 
 
  406    bool tag_active = 
false;
 
  407    bool tag_processed = 
false;
 
  409    const char* actual_tag;
 
  411    int use_bytes_of_next_write = 0;
 
  412    int result_size = 256;
 
  414    uint64_t total_len = 0;
 
  415    uint64_t end_len = 0;
 
  418    uint32_t calcSize(uint8_t chars[4]) {
 
  419        uint32_t byte0 = chars[0];
 
  420        uint32_t byte1 = chars[1];
 
  421        uint32_t byte2 = chars[2];
 
  422        uint32_t byte3 = chars[3];
 
  423        return byte0 << 21 | byte1 << 14 | byte2 << 7 | byte3;
 
  430        if (!tag_active && !tag_processed){
 
  431            int pos = 
findTag(
"ID3", (
const char*) data, len);
 
  436                if (len>=pos+
sizeof(
ID3v2)){
 
  437                    memcpy(&tagv2, data+pos, 
sizeof(
ID3v2));   
 
  438                    end_len = total_len + calcSize(tagv2.size);
 
  444        if (end_len>0 && total_len>end_len){
 
  446            tag_processed = 
false;
 
  452            const char* partial_tag = 
nullptr;
 
  453            for (
const char* tag : id3_v2_tags){
 
  455                int tag_pos = 
findTag(tag, (
const char*)  data, len);
 
  457                if (tag_pos>0 && tag_pos+
sizeof(
ID3v2Frame)<=len){
 
  462                    if(calcSize(frame_header.size) <= len){
 
  463                        int l = min(calcSize(frame_header.size)-1, (uint32_t) result.size());
 
  464                        memset(result.data(), 0, result.size());
 
  465                        strncpy((
char*)result.data(), (
char*) data+tag_pos+ID3FrameSize, l);
 
  466                        int checkLen = min(l, 10);
 
  467                        if (
isAscii(checkLen) || AUDIOTOOLS_ID3_TAG_ALLOW_NONASCII){
 
  470                            LOGW(
"TAG %s ignored", tag);
 
  474                        LOGI(
"%s partial tag", tag);
 
  480            if (partial_tag!=
nullptr){
 
  481                int tag_pos = 
findTag(partial_tag, (
const char*)  data, len);
 
  483                int size = min(len - tag_pos, (
size_t) calcSize(frame_header.size)-1); 
 
  484                strncpy((
char*)result.data(), (
char*)data+tag_pos+ID3FrameSize, size);
 
  485                use_bytes_of_next_write = size;
 
  486                status = PartialTagAtTail;
 
 
  494    int isCharAscii(
int ch) {
return ch >= 0 && ch < 128; }
 
  499        int m = l < 5 ? l : 10;
 
  500        for (
int j=0; j<m; j++){
 
  501            if (!isCharAscii(result[j])) {
 
 
  510        int remainder = calcSize(frame_header.size) - use_bytes_of_next_write;
 
  511        memcpy(result.data()+use_bytes_of_next_write, data, remainder);
 
  514        status = TagNotFound;
 
 
  520        return frame_header.encoding == 0 || frame_header.encoding == 3;
 
 
  523    int strpos(
char* str, 
const char* target) {
 
  524        char* res = strstr(str, target); 
 
  525        return (res == 
nullptr) ? -1 : res - str;
 
  532            LOGI(
"callback %s",actual_tag);
 
  533            if (memcmp(actual_tag,
"TALB",4)==0)
 
  534                callback(Album, result.data(), 
strnlength(result.data(), result.size()));
 
  535            else if (memcmp(actual_tag,
"TPE1",4)==0)
 
  536                callback(Artist, result.data(),
strnlength(result.data(), result.size()));
 
  537            else if (memcmp(actual_tag,
"TOPE",4)==0)
 
  538                callback(Artist, result.data(),
strnlength(result.data(), result.size()));
 
  539            else if (memcmp(actual_tag,
"TIT2",4)==0)
 
  540                callback(Title, result.data(),
strnlength(result.data(), result.size()));
 
  541            else if (memcmp(actual_tag,
"TCON",4)==0) {
 
  544                    int end_pos = strpos((
char*)result.data(), 
")");
 
  548                        int idx = atoi(result.data()+1);
 
  549                        if (idx>=0 && idx< (
int)
sizeof(genres)){
 
  550                            strncpy((
char*)result.data(), genres[idx], result.size());
 
  554                callback(Genre, result.data(),
strnlength(result.data(), result.size()));
 
 
 
  578    void setCallback(
void (*fn)(
MetaDataType info, 
const char* str, 
int len)) {
 
  579        id3v1.setCallback(fn);        
 
  580        id3v2.setCallback(fn);        
 
  589        if (!id3v1.
begin()) 
return false;
 
  590        if (!id3v2.
begin()) 
return false;
 
  601    virtual size_t write(
const uint8_t *data, 
size_t len){
 
  603        if (filter & SELECT_ID3V2) id3v2.
write(data, len);
 
  605            if (filter & SELECT_ID3V1) id3v1.
write(data, len);
 
 
  618    int filter = SELECT_ID3;