12#include "AbstractMetaData.h"
25static 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" };
28enum 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 (tag==
nullptr || str==
nullptr || len<=0)
return -1;
88 size_t tag_len = strlen(tag);
89 if (tag_len >= len)
return -1;
90 for (
size_t j=0;j<=len-tag_len-1;j++){
91 if (memcmp(str+j,tag, tag_len)==0){
82 int findTag(
const char* tag,
const char*str,
size_t len) {
…}
116 status = TagNotFound;
117 use_bytes_of_next_write = 0;
118 memset(tag_str, 0, 5);
127 if (tag_ext!=
nullptr){
134 size_t write(
const uint8_t* data,
size_t len){
140 case PartialTagAtTail:
143 case TagFoundPartial:
134 size_t write(
const uint8_t* data,
size_t len) {
…}
155 int use_bytes_of_next_write = 0;
156 char tag_str[5] =
"";
157 ID3v1 *tag =
nullptr;
165 int pos =
findTag(
"TAG+",(
const char*) data, len);
168 if (tag_ext!=
nullptr){
173 use_bytes_of_next_write = min(
sizeof(
ID3v1Enhanced), len-pos);
174 memcpy(tag_ext, data+pos, use_bytes_of_next_write);
175 status = TagFoundPartial;
179 pos =
findTag(
"TAG", (
const char*) data, len);
183 if (len-pos>=
sizeof(
ID3v1)){
184 memcpy(tag,data+pos,
sizeof(
ID3v1));
187 use_bytes_of_next_write = min(
sizeof(
ID3v1), len-pos);
188 memcpy(tag,data+pos,use_bytes_of_next_write);
189 status = TagFoundPartial;
198 if (data[len-3] ==
'T' && data[len-2] ==
'A' && data[len-1] ==
'G'){
199 strcpy(tag_str,
"TAG");
200 status = TagFoundPartial;
201 }
else if (data[len-2] ==
'T' && data[len-1] ==
'A' ){
202 strcpy(tag_str,
"TA");
203 status = TagFoundPartial;
204 }
else if (data[len-1] ==
'T'){
206 status = TagFoundPartial;
213 int tag_len = strlen(tag_str);
214 int missing = 5 - tag_len;
215 strncat((
char*)tag_str, (
char*)data, missing);
216 if (strncmp((
char*)tag_str,
"TAG+", 4)==0){
218 memcpy(tag,tag_str, 4);
221 }
else if (strncmp((
char*)tag_str,
"TAG",3)==0){
223 memcpy(tag,tag_str, 3);
224 memcpy(tag,data+len,
sizeof(
ID3v1));
232 int remainder =
sizeof(
ID3v1) - use_bytes_of_next_write;
233 memcpy(tag,data+use_bytes_of_next_write,remainder);
235 use_bytes_of_next_write = 0;
236 }
else if (tag_ext!=
nullptr){
237 int remainder =
sizeof(
ID3v1Enhanced) - use_bytes_of_next_write;
238 memcpy(tag_ext,data+use_bytes_of_next_write,remainder);
240 use_bytes_of_next_write = 0;
246 if (callback==
nullptr)
return;
248 if (tag_ext!=
nullptr){
249 callback(Title, tag_ext->title,
strnlength(tag_ext->title,60));
250 callback(Artist, tag_ext->artist,
strnlength(tag_ext->artist,60));
251 callback(Album, tag_ext->album,
strnlength(tag_ext->album,60));
252 callback(Genre, tag_ext->genre,
strnlength(tag_ext->genre,30));
255 status = TagProcessed;
259 callback(Title, tag->title,
strnlength(tag->title,30));
260 callback(Artist, tag->artist,
strnlength(tag->artist,30));
261 callback(Album, tag->album,
strnlength(tag->album,30));
262 uint16_t genre = tag->genre;
263 if (genre <
sizeof(genres)){
264 const char* genre_str = genres[genre];
265 callback(Genre, genre_str,strlen(genre_str));
269 status = TagProcessed;
277#define UnsynchronisationFlag 0x40
278#define ExtendedHeaderFlag 0x20
279#define ExperimentalIndicatorFlag 0x10
282static const char* id3_v2_tags[] = {
"TALB",
"TOPE",
"TPE1",
"TIT2",
"TCON"};
322static const int ID3FrameSize = 11;
336 status = TagNotFound;
337 use_bytes_of_next_write = 0;
338 actual_tag =
nullptr;
340 tag_processed =
false;
341 result.resize(result_size);
346 status = TagNotFound;
347 use_bytes_of_next_write = 0;
348 actual_tag =
nullptr;
350 tag_processed =
false;
354 size_t write(
const uint8_t* data,
size_t len){
360 case PartialTagAtTail:
354 size_t write(
const uint8_t* data,
size_t len) {
…}
383 return tag_processed;
389 if (result.size()==0) {
390 result.resize(result_size);
396 bool tag_active =
false;
397 bool tag_processed =
false;
399 const char* actual_tag;
401 int use_bytes_of_next_write = 0;
402 int result_size = 256;
404 uint64_t total_len = 0;
405 uint64_t end_len = 0;
408 uint32_t calcSize(uint8_t chars[4]) {
409 uint32_t byte0 = chars[0];
410 uint32_t byte1 = chars[1];
411 uint32_t byte2 = chars[2];
412 uint32_t byte3 = chars[3];
413 return byte0 << 21 | byte1 << 14 | byte2 << 7 | byte3;
420 if (!tag_active && !tag_processed){
421 int pos =
findTag(
"ID3", (
const char*) data, len);
426 if (len>=pos+
sizeof(
ID3v2)){
427 memcpy(&tagv2, data+pos,
sizeof(
ID3v2));
428 end_len = total_len + calcSize(tagv2.size);
434 if (end_len>0 && total_len>end_len){
436 tag_processed =
false;
442 const char* partial_tag =
nullptr;
443 for (
const char* tag : id3_v2_tags){
445 int tag_pos =
findTag(tag, (
const char*) data, len);
447 if (tag_pos>0 && tag_pos+
sizeof(
ID3v2Frame)<=len){
452 if(calcSize(frame_header.size) <= len){
453 int l = min(calcSize(frame_header.size)-1, (uint32_t) result.size());
454 memset(result.data(), 0, result.size());
455 strncpy((
char*)result.data(), (
char*) data+tag_pos+ID3FrameSize, l);
456 int checkLen = min(l, 10);
460 LOGW(
"TAG %s ignored", tag);
464 LOGI(
"%s partial tag", tag);
470 if (partial_tag!=
nullptr){
471 int tag_pos =
findTag(partial_tag, (
const char*) data, len);
473 int size = min(len - tag_pos, (
size_t) calcSize(frame_header.size)-1);
474 strncpy((
char*)result.data(), (
char*)data+tag_pos+ID3FrameSize, size);
475 use_bytes_of_next_write = size;
476 status = PartialTagAtTail;
484 int isCharAscii(
int ch) {
return ch >= 0 && ch < 128; }
489 int m = l < 5 ? l : 10;
490 for (
int j=0; j<m; j++){
491 if (!isCharAscii(result[j])) {
500 int remainder = calcSize(frame_header.size) - use_bytes_of_next_write;
501 memcpy(result.data()+use_bytes_of_next_write, data, remainder);
504 status = TagNotFound;
510 return frame_header.encoding == 0 || frame_header.encoding == 3;
513 int strpos(
char* str,
const char* target) {
514 char* res = strstr(str, target);
515 return (res ==
nullptr) ? -1 : res - str;
522 LOGI(
"callback %s",actual_tag);
523 if (memcmp(actual_tag,
"TALB",4)==0)
524 callback(Album, result.data(),
strnlength(result.data(), result.size()));
525 else if (memcmp(actual_tag,
"TPE1",4)==0)
526 callback(Artist, result.data(),
strnlength(result.data(), result.size()));
527 else if (memcmp(actual_tag,
"TOPE",4)==0)
528 callback(Artist, result.data(),
strnlength(result.data(), result.size()));
529 else if (memcmp(actual_tag,
"TIT2",4)==0)
530 callback(Title, result.data(),
strnlength(result.data(), result.size()));
531 else if (memcmp(actual_tag,
"TCON",4)==0) {
534 int end_pos = strpos((
char*)result.data(),
")");
538 int idx = atoi(result.data()+1);
539 if (idx>=0 && idx< (
int)
sizeof(genres)){
540 strncpy((
char*)result.data(), genres[idx], result.size());
544 callback(Genre, result.data(),
strnlength(result.data(), result.size()));
568 void setCallback(
void (*fn)(
MetaDataType info,
const char* str,
int len)) {
569 id3v1.setCallback(fn);
570 id3v2.setCallback(fn);
590 virtual size_t write(
const uint8_t *data,
size_t len){
592 if (filter & SELECT_ID3V2) id3v2.
write(data, len);
594 if (filter & SELECT_ID3V1) id3v1.
write(data, len);
590 virtual size_t write(
const uint8_t *data,
size_t len) {
…}
607 int filter = SELECT_ID3;