12 #include "AbstractMetaData.h"
29 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" };
32 enum ParseStatus { TagNotFound, PartialTagAtTail, TagFoundPartial, TagFoundComplete, TagProcessed};
76 void setCallback(
void (*fn)(
MetaDataType info,
const char* str,
int len)) {
82 void (*callback)(
MetaDataType info,
const char* title,
int len);
86 int findTag(
const char* tag,
const char*str,
size_t len){
87 if (str==
nullptr || len<=0)
return -1;
92 size_t tag_len = strlen(tag);
93 for (
size_t j=0;j<=len-tag_len-1;j++){
94 if (memcmp(str+j,tag, tag_len)==0){
119 status = TagNotFound;
120 use_bytes_of_next_write = 0;
121 memset(tag_str, 0, 5);
130 if (tag_ext!=
nullptr){
137 size_t write(
const uint8_t* data,
size_t len){
143 case PartialTagAtTail:
146 case TagFoundPartial:
158 int use_bytes_of_next_write = 0;
159 char tag_str[5] =
"";
160 ID3v1 *tag =
nullptr;
168 int pos =
findTag(
"TAG+",(
const char*) data, len);
171 if (tag_ext!=
nullptr){
176 use_bytes_of_next_write = min(
sizeof(
ID3v1Enhanced), len-pos);
177 memcpy(tag_ext, data+pos, use_bytes_of_next_write);
178 status = TagFoundPartial;
182 pos =
findTag(
"TAG", (
const char*) data, len);
186 if (len-pos>=
sizeof(
ID3v1)){
187 memcpy(tag,data+pos,
sizeof(
ID3v1));
190 use_bytes_of_next_write = min(
sizeof(
ID3v1), len-pos);
191 memcpy(tag,data+pos,use_bytes_of_next_write);
192 status = TagFoundPartial;
201 if (data[len-3] ==
'T' && data[len-2] ==
'A' && data[len-1] ==
'G'){
202 strcpy(tag_str,
"TAG");
203 status = TagFoundPartial;
204 }
else if (data[len-2] ==
'T' && data[len-1] ==
'A' ){
205 strcpy(tag_str,
"TA");
206 status = TagFoundPartial;
207 }
else if (data[len-1] ==
'T'){
209 status = TagFoundPartial;
216 int tag_len = strlen(tag_str);
217 int missing = 5 - tag_len;
218 strncat((
char*)tag_str, (
char*)data, missing);
219 if (strncmp((
char*)tag_str,
"TAG+", 4)==0){
221 memcpy(tag,tag_str, 4);
224 }
else if (strncmp((
char*)tag_str,
"TAG",3)==0){
226 memcpy(tag,tag_str, 3);
227 memcpy(tag,data+len,
sizeof(
ID3v1));
235 int remainder =
sizeof(
ID3v1) - use_bytes_of_next_write;
236 memcpy(tag,data+use_bytes_of_next_write,remainder);
238 use_bytes_of_next_write = 0;
239 }
else if (tag_ext!=
nullptr){
240 int remainder =
sizeof(
ID3v1Enhanced) - use_bytes_of_next_write;
241 memcpy(tag_ext,data+use_bytes_of_next_write,remainder);
243 use_bytes_of_next_write = 0;
249 if (callback==
nullptr)
return;
251 if (tag_ext!=
nullptr){
252 callback(Title, tag_ext->title,
strnlength(tag_ext->title,60));
253 callback(Artist, tag_ext->artist,
strnlength(tag_ext->artist,60));
254 callback(Album, tag_ext->album,
strnlength(tag_ext->album,60));
255 callback(Genre, tag_ext->genre,
strnlength(tag_ext->genre,30));
258 status = TagProcessed;
262 callback(Title, tag->title,
strnlength(tag->title,30));
263 callback(Artist, tag->artist,
strnlength(tag->artist,30));
264 callback(Album, tag->album,
strnlength(tag->album,30));
265 uint16_t genre = tag->genre;
266 if (genre <
sizeof(genres)){
267 const char* genre_str = genres[genre];
268 callback(Genre, genre_str,strlen(genre_str));
272 status = TagProcessed;
280 #define UnsynchronisationFlag 0x40
281 #define ExtendedHeaderFlag 0x20
282 #define ExperimentalIndicatorFlag 0x10
285 static const char* id3_v2_tags[] = {
"TALB",
"TOPE",
"TPE1",
"TIT2",
"TCON"};
325 static const int ID3FrameSize = 11;
339 status = TagNotFound;
340 use_bytes_of_next_write = 0;
341 actual_tag =
nullptr;
343 tag_processed =
false;
348 status = TagNotFound;
349 use_bytes_of_next_write = 0;
350 actual_tag =
nullptr;
352 tag_processed =
false;
356 size_t write(
const uint8_t* data,
size_t len){
362 case PartialTagAtTail:
385 return tag_processed;
390 bool tag_active =
false;
391 bool tag_processed =
false;
393 const char* actual_tag;
395 int use_bytes_of_next_write = 0;
397 uint64_t total_len = 0;
398 uint64_t end_len = 0;
401 uint32_t calcSize(uint8_t chars[4]) {
402 uint32_t byte0 = chars[0];
403 uint32_t byte1 = chars[1];
404 uint32_t byte2 = chars[2];
405 uint32_t byte3 = chars[3];
406 return byte0 << 21 | byte1 << 14 | byte2 << 7 | byte3;
413 if (!tag_active && !tag_processed){
414 int pos =
findTag(
"ID3", (
const char*) data, len);
419 if (len>=pos+
sizeof(
ID3v2)){
420 memcpy(&tagv2, data+pos,
sizeof(
ID3v2));
421 end_len = total_len + calcSize(tagv2.size);
427 if (end_len>0 && total_len>end_len){
429 tag_processed =
false;
435 const char* partial_tag =
nullptr;
436 for (
const char* tag : id3_v2_tags){
438 int tag_pos =
findTag(tag, (
const char*) data, len);
440 if (tag_pos>0 && tag_pos+
sizeof(
ID3v2Frame)<=len){
445 if(calcSize(frame_header.size) <= len){
446 int l = min(calcSize(frame_header.size)-1, (uint32_t) 256);
447 memset(result,0,256);
448 strncpy((
char*)result, (
char*) data+tag_pos+ID3FrameSize, l);
449 int checkLen = min(l, 10);
453 LOGW(
"TAG %s ignored", tag);
457 LOGI(
"%s partial tag", tag);
463 if (partial_tag!=
nullptr){
464 int tag_pos =
findTag(partial_tag, (
const char*) data, len);
466 int size = min(len - tag_pos, (
size_t) calcSize(frame_header.size)-1);
467 strncpy((
char*)result, (
char*)data+tag_pos+ID3FrameSize, size);
468 use_bytes_of_next_write = size;
469 status = PartialTagAtTail;
480 int m = l < 5 ? l : 10;
481 for (
int j=0; j<m; j++){
482 if (!isascii(result[j])) {
491 int remainder = calcSize(frame_header.size) - use_bytes_of_next_write;
492 memcpy(result+use_bytes_of_next_write, data, remainder);
495 status = TagNotFound;
501 return frame_header.encoding == 0 || frame_header.encoding == 3;
504 int strpos(
char* str,
const char* target) {
505 char* res = strstr(str, target);
506 return (res ==
nullptr) ? -1 : res - str;
513 LOGI(
"callback %s",actual_tag);
514 if (memcmp(actual_tag,
"TALB",4)==0)
515 callback(Album, result,
strnlength(result, 256));
516 else if (memcmp(actual_tag,
"TPE1",4)==0)
517 callback(Artist, result,
strnlength(result, 256));
518 else if (memcmp(actual_tag,
"TOPE",4)==0)
519 callback(Artist, result,
strnlength(result, 256));
520 else if (memcmp(actual_tag,
"TIT2",4)==0)
521 callback(Title, result,
strnlength(result, 256));
522 else if (memcmp(actual_tag,
"TCON",4)==0) {
525 int end_pos = strpos((
char*)result,
")");
529 int idx = atoi(result+1);
530 if (idx>=0 && idx< (
int)
sizeof(genres)){
531 strncpy((
char*)result,genres[idx],256);
535 callback(Genre, result,
strnlength(result, 256));
559 void setCallback(
void (*fn)(
MetaDataType info,
const char* str,
int len)) {
560 id3v1.setCallback(fn);
561 id3v2.setCallback(fn);
581 virtual size_t write(
const uint8_t *data,
size_t len){
583 if (filter & SELECT_ID3V2) id3v2.
write(data, len);
585 if (filter & SELECT_ID3V1) id3v1.
write(data, len);
593 int filter = SELECT_ID3;