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;