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){
90 int findTag(
const char* tag,
const char*str,
size_t len) {
…}
124 status = TagNotFound;
125 use_bytes_of_next_write = 0;
126 memset(tag_str, 0, 5);
135 if (tag_ext!=
nullptr){
142 size_t write(
const uint8_t* data,
size_t len){
148 case PartialTagAtTail:
151 case TagFoundPartial:
142 size_t write(
const uint8_t* data,
size_t len) {
…}
163 int use_bytes_of_next_write = 0;
164 char tag_str[5] =
"";
165 ID3v1 *tag =
nullptr;
173 int pos =
findTag(
"TAG+",(
const char*) data, len);
176 if (tag_ext!=
nullptr){
181 use_bytes_of_next_write = min(
sizeof(
ID3v1Enhanced), len-pos);
182 memcpy(tag_ext, data+pos, use_bytes_of_next_write);
183 status = TagFoundPartial;
187 pos =
findTag(
"TAG", (
const char*) data, len);
191 if (len-pos>=
sizeof(
ID3v1)){
192 memcpy(tag,data+pos,
sizeof(
ID3v1));
195 use_bytes_of_next_write = min(
sizeof(
ID3v1), len-pos);
196 memcpy(tag,data+pos,use_bytes_of_next_write);
197 status = TagFoundPartial;
206 if (data[len-3] ==
'T' && data[len-2] ==
'A' && data[len-1] ==
'G'){
207 strcpy(tag_str,
"TAG");
208 status = TagFoundPartial;
209 }
else if (data[len-2] ==
'T' && data[len-1] ==
'A' ){
210 strcpy(tag_str,
"TA");
211 status = TagFoundPartial;
212 }
else if (data[len-1] ==
'T'){
214 status = TagFoundPartial;
221 int tag_len = strlen(tag_str);
222 int missing = 5 - tag_len;
223 strncat((
char*)tag_str, (
char*)data, missing);
224 if (strncmp((
char*)tag_str,
"TAG+", 4)==0){
226 memcpy(tag,tag_str, 4);
229 }
else if (strncmp((
char*)tag_str,
"TAG",3)==0){
231 memcpy(tag,tag_str, 3);
232 memcpy(tag,data+len,
sizeof(
ID3v1));
240 int remainder =
sizeof(
ID3v1) - use_bytes_of_next_write;
241 memcpy(tag,data+use_bytes_of_next_write,remainder);
243 use_bytes_of_next_write = 0;
244 }
else if (tag_ext!=
nullptr){
245 int remainder =
sizeof(
ID3v1Enhanced) - use_bytes_of_next_write;
246 memcpy(tag_ext,data+use_bytes_of_next_write,remainder);
248 use_bytes_of_next_write = 0;
254 if (callback==
nullptr)
return;
256 if (tag_ext!=
nullptr){
257 callback(Title, tag_ext->title,
strnlength(tag_ext->title,60));
258 callback(Artist, tag_ext->artist,
strnlength(tag_ext->artist,60));
259 callback(Album, tag_ext->album,
strnlength(tag_ext->album,60));
260 callback(Genre, tag_ext->genre,
strnlength(tag_ext->genre,30));
263 status = TagProcessed;
267 callback(Title, tag->title,
strnlength(tag->title,30));
268 callback(Artist, tag->artist,
strnlength(tag->artist,30));
269 callback(Album, tag->album,
strnlength(tag->album,30));
270 uint16_t genre = tag->genre;
271 if (genre <
sizeof(genres)){
272 const char* genre_str = genres[genre];
273 callback(Genre, genre_str,strlen(genre_str));
277 status = TagProcessed;
285#define UnsynchronisationFlag 0x40
286#define ExtendedHeaderFlag 0x20
287#define ExperimentalIndicatorFlag 0x10
290static const char* id3_v2_tags[] = {
"TALB",
"TOPE",
"TPE1",
"TIT2",
"TCON"};
330static const int ID3FrameSize = 11;
344 status = TagNotFound;
345 use_bytes_of_next_write = 0;
346 actual_tag =
nullptr;
348 tag_processed =
false;
349 result.resize(result_size);
354 status = TagNotFound;
355 use_bytes_of_next_write = 0;
356 actual_tag =
nullptr;
358 tag_processed =
false;
362 size_t write(
const uint8_t* data,
size_t len){
368 case PartialTagAtTail:
362 size_t write(
const uint8_t* data,
size_t len) {
…}
391 return tag_processed;
397 if (result.size()==0) {
398 result.resize(result_size);
404 bool tag_active =
false;
405 bool tag_processed =
false;
407 const char* actual_tag;
409 int use_bytes_of_next_write = 0;
410 int result_size = 256;
412 uint64_t total_len = 0;
413 uint64_t end_len = 0;
416 uint32_t calcSize(uint8_t chars[4]) {
417 uint32_t byte0 = chars[0];
418 uint32_t byte1 = chars[1];
419 uint32_t byte2 = chars[2];
420 uint32_t byte3 = chars[3];
421 return byte0 << 21 | byte1 << 14 | byte2 << 7 | byte3;
428 if (!tag_active && !tag_processed){
429 int pos =
findTag(
"ID3", (
const char*) data, len);
434 if (len>=pos+
sizeof(
ID3v2)){
435 memcpy(&tagv2, data+pos,
sizeof(
ID3v2));
436 end_len = total_len + calcSize(tagv2.size);
442 if (end_len>0 && total_len>end_len){
444 tag_processed =
false;
450 const char* partial_tag =
nullptr;
451 for (
const char* tag : id3_v2_tags){
453 int tag_pos =
findTag(tag, (
const char*) data, len);
455 if (tag_pos>0 && tag_pos+
sizeof(
ID3v2Frame)<=len){
460 if(calcSize(frame_header.size) <= len){
461 int l = min(calcSize(frame_header.size)-1, (uint32_t) result.size());
462 memset(result.data(), 0, result.size());
463 strncpy((
char*)result.data(), (
char*) data+tag_pos+ID3FrameSize, l);
464 int checkLen = min(l, 10);
465 if (
isAscii(checkLen) || AUDIOTOOLS_ID3_TAG_ALLOW_NONASCII){
468 LOGW(
"TAG %s ignored", tag);
472 LOGI(
"%s partial tag", tag);
478 if (partial_tag!=
nullptr){
479 int tag_pos =
findTag(partial_tag, (
const char*) data, len);
481 int size = min(len - tag_pos, (
size_t) calcSize(frame_header.size)-1);
482 strncpy((
char*)result.data(), (
char*)data+tag_pos+ID3FrameSize, size);
483 use_bytes_of_next_write = size;
484 status = PartialTagAtTail;
492 int isCharAscii(
int ch) {
return ch >= 0 && ch < 128; }
497 int m = l < 5 ? l : 10;
498 for (
int j=0; j<m; j++){
499 if (!isCharAscii(result[j])) {
508 int remainder = calcSize(frame_header.size) - use_bytes_of_next_write;
509 memcpy(result.data()+use_bytes_of_next_write, data, remainder);
512 status = TagNotFound;
518 return frame_header.encoding == 0 || frame_header.encoding == 3;
521 int strpos(
char* str,
const char* target) {
522 char* res = strstr(str, target);
523 return (res ==
nullptr) ? -1 : res - str;
530 LOGI(
"callback %s",actual_tag);
531 if (memcmp(actual_tag,
"TALB",4)==0)
532 callback(Album, result.data(),
strnlength(result.data(), result.size()));
533 else if (memcmp(actual_tag,
"TPE1",4)==0)
534 callback(Artist, result.data(),
strnlength(result.data(), result.size()));
535 else if (memcmp(actual_tag,
"TOPE",4)==0)
536 callback(Artist, result.data(),
strnlength(result.data(), result.size()));
537 else if (memcmp(actual_tag,
"TIT2",4)==0)
538 callback(Title, result.data(),
strnlength(result.data(), result.size()));
539 else if (memcmp(actual_tag,
"TCON",4)==0) {
542 int end_pos = strpos((
char*)result.data(),
")");
546 int idx = atoi(result.data()+1);
547 if (idx>=0 && idx< (
int)
sizeof(genres)){
548 strncpy((
char*)result.data(), genres[idx], result.size());
552 callback(Genre, result.data(),
strnlength(result.data(), result.size()));
576 void setCallback(
void (*fn)(
MetaDataType info,
const char* str,
int len)) {
577 id3v1.setCallback(fn);
578 id3v2.setCallback(fn);
598 virtual size_t write(
const uint8_t *data,
size_t len){
600 if (filter & SELECT_ID3V2) id3v2.
write(data, len);
602 if (filter & SELECT_ID3V1) id3v1.
write(data, len);
598 virtual size_t write(
const uint8_t *data,
size_t len) {
…}
615 int filter = SELECT_ID3;