12 #include "AbstractMetaData.h"
25 static 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" };
28 enum ParseStatus { TagNotFound, PartialTagAtTail, TagFoundPartial, TagFoundComplete, TagProcessed};
72 void setCallback(
void (*fn)(
MetaDataType info,
const char* str,
int len)) {
78 void (*callback)(
MetaDataType info,
const char* title,
int len);
82 int findTag(
const char* tag,
const char*str,
size_t len){
83 if (str==
nullptr || len<=0)
return -1;
88 size_t tag_len = strlen(tag);
89 for (
size_t j=0;j<=len-tag_len-1;j++){
90 if (memcmp(str+j,tag, tag_len)==0){
115 status = TagNotFound;
116 use_bytes_of_next_write = 0;
117 memset(tag_str, 0, 5);
126 if (tag_ext!=
nullptr){
133 size_t write(
const uint8_t* data,
size_t len){
139 case PartialTagAtTail:
142 case TagFoundPartial:
154 int use_bytes_of_next_write = 0;
155 char tag_str[5] =
"";
156 ID3v1 *tag =
nullptr;
164 int pos =
findTag(
"TAG+",(
const char*) data, len);
167 if (tag_ext!=
nullptr){
172 use_bytes_of_next_write = min(
sizeof(
ID3v1Enhanced), len-pos);
173 memcpy(tag_ext, data+pos, use_bytes_of_next_write);
174 status = TagFoundPartial;
178 pos =
findTag(
"TAG", (
const char*) data, len);
182 if (len-pos>=
sizeof(
ID3v1)){
183 memcpy(tag,data+pos,
sizeof(
ID3v1));
186 use_bytes_of_next_write = min(
sizeof(
ID3v1), len-pos);
187 memcpy(tag,data+pos,use_bytes_of_next_write);
188 status = TagFoundPartial;
197 if (data[len-3] ==
'T' && data[len-2] ==
'A' && data[len-1] ==
'G'){
198 strcpy(tag_str,
"TAG");
199 status = TagFoundPartial;
200 }
else if (data[len-2] ==
'T' && data[len-1] ==
'A' ){
201 strcpy(tag_str,
"TA");
202 status = TagFoundPartial;
203 }
else if (data[len-1] ==
'T'){
205 status = TagFoundPartial;
212 int tag_len = strlen(tag_str);
213 int missing = 5 - tag_len;
214 strncat((
char*)tag_str, (
char*)data, missing);
215 if (strncmp((
char*)tag_str,
"TAG+", 4)==0){
217 memcpy(tag,tag_str, 4);
220 }
else if (strncmp((
char*)tag_str,
"TAG",3)==0){
222 memcpy(tag,tag_str, 3);
223 memcpy(tag,data+len,
sizeof(
ID3v1));
231 int remainder =
sizeof(
ID3v1) - use_bytes_of_next_write;
232 memcpy(tag,data+use_bytes_of_next_write,remainder);
234 use_bytes_of_next_write = 0;
235 }
else if (tag_ext!=
nullptr){
236 int remainder =
sizeof(
ID3v1Enhanced) - use_bytes_of_next_write;
237 memcpy(tag_ext,data+use_bytes_of_next_write,remainder);
239 use_bytes_of_next_write = 0;
245 if (callback==
nullptr)
return;
247 if (tag_ext!=
nullptr){
248 callback(Title, tag_ext->title,
strnlength(tag_ext->title,60));
249 callback(Artist, tag_ext->artist,
strnlength(tag_ext->artist,60));
250 callback(Album, tag_ext->album,
strnlength(tag_ext->album,60));
251 callback(Genre, tag_ext->genre,
strnlength(tag_ext->genre,30));
254 status = TagProcessed;
258 callback(Title, tag->title,
strnlength(tag->title,30));
259 callback(Artist, tag->artist,
strnlength(tag->artist,30));
260 callback(Album, tag->album,
strnlength(tag->album,30));
261 uint16_t genre = tag->genre;
262 if (genre <
sizeof(genres)){
263 const char* genre_str = genres[genre];
264 callback(Genre, genre_str,strlen(genre_str));
268 status = TagProcessed;
276 #define UnsynchronisationFlag 0x40
277 #define ExtendedHeaderFlag 0x20
278 #define ExperimentalIndicatorFlag 0x10
281 static const char* id3_v2_tags[] = {
"TALB",
"TOPE",
"TPE1",
"TIT2",
"TCON"};
321 static const int ID3FrameSize = 11;
335 status = TagNotFound;
336 use_bytes_of_next_write = 0;
337 actual_tag =
nullptr;
339 tag_processed =
false;
344 status = TagNotFound;
345 use_bytes_of_next_write = 0;
346 actual_tag =
nullptr;
348 tag_processed =
false;
352 size_t write(
const uint8_t* data,
size_t len){
358 case PartialTagAtTail:
381 return tag_processed;
386 bool tag_active =
false;
387 bool tag_processed =
false;
389 const char* actual_tag;
391 int use_bytes_of_next_write = 0;
393 uint64_t total_len = 0;
394 uint64_t end_len = 0;
397 uint32_t calcSize(uint8_t chars[4]) {
398 uint32_t byte0 = chars[0];
399 uint32_t byte1 = chars[1];
400 uint32_t byte2 = chars[2];
401 uint32_t byte3 = chars[3];
402 return byte0 << 21 | byte1 << 14 | byte2 << 7 | byte3;
409 if (!tag_active && !tag_processed){
410 int pos =
findTag(
"ID3", (
const char*) data, len);
415 if (len>=pos+
sizeof(
ID3v2)){
416 memcpy(&tagv2, data+pos,
sizeof(
ID3v2));
417 end_len = total_len + calcSize(tagv2.size);
423 if (end_len>0 && total_len>end_len){
425 tag_processed =
false;
431 const char* partial_tag =
nullptr;
432 for (
const char* tag : id3_v2_tags){
434 int tag_pos =
findTag(tag, (
const char*) data, len);
436 if (tag_pos>0 && tag_pos+
sizeof(
ID3v2Frame)<=len){
441 if(calcSize(frame_header.size) <= len){
442 int l = min(calcSize(frame_header.size)-1, (uint32_t) 256);
443 memset(result,0,256);
444 strncpy((
char*)result, (
char*) data+tag_pos+ID3FrameSize, l);
445 int checkLen = min(l, 10);
449 LOGW(
"TAG %s ignored", tag);
453 LOGI(
"%s partial tag", tag);
459 if (partial_tag!=
nullptr){
460 int tag_pos =
findTag(partial_tag, (
const char*) data, len);
462 int size = min(len - tag_pos, (
size_t) calcSize(frame_header.size)-1);
463 strncpy((
char*)result, (
char*)data+tag_pos+ID3FrameSize, size);
464 use_bytes_of_next_write = size;
465 status = PartialTagAtTail;
473 int isCharAscii(
int ch) {
return ch >= 0 && ch < 128; }
478 int m = l < 5 ? l : 10;
479 for (
int j=0; j<m; j++){
480 if (!isCharAscii(result[j])) {
489 int remainder = calcSize(frame_header.size) - use_bytes_of_next_write;
490 memcpy(result+use_bytes_of_next_write, data, remainder);
493 status = TagNotFound;
499 return frame_header.encoding == 0 || frame_header.encoding == 3;
502 int strpos(
char* str,
const char* target) {
503 char* res = strstr(str, target);
504 return (res ==
nullptr) ? -1 : res - str;
511 LOGI(
"callback %s",actual_tag);
512 if (memcmp(actual_tag,
"TALB",4)==0)
513 callback(Album, result,
strnlength(result, 256));
514 else if (memcmp(actual_tag,
"TPE1",4)==0)
515 callback(Artist, result,
strnlength(result, 256));
516 else if (memcmp(actual_tag,
"TOPE",4)==0)
517 callback(Artist, result,
strnlength(result, 256));
518 else if (memcmp(actual_tag,
"TIT2",4)==0)
519 callback(Title, result,
strnlength(result, 256));
520 else if (memcmp(actual_tag,
"TCON",4)==0) {
523 int end_pos = strpos((
char*)result,
")");
527 int idx = atoi(result+1);
528 if (idx>=0 && idx< (
int)
sizeof(genres)){
529 strncpy((
char*)result,genres[idx],256);
533 callback(Genre, result,
strnlength(result, 256));
557 void setCallback(
void (*fn)(
MetaDataType info,
const char* str,
int len)) {
558 id3v1.setCallback(fn);
559 id3v2.setCallback(fn);
579 virtual size_t write(
const uint8_t *data,
size_t len){
581 if (filter & SELECT_ID3V2) id3v2.
write(data, len);
583 if (filter & SELECT_ID3V1) id3v1.
write(data, len);
591 int filter = SELECT_ID3;