arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
MetaDataID3.h
1
8#pragma once
9#include <string.h>
10#include <stdint.h>
11#include <ctype.h>
12#include "AbstractMetaData.h"
13
19#ifndef AUDIOTOOLS_ID3_TAG_ALLOW_NONASCII
20#define AUDIOTOOLS_ID3_TAG_ALLOW_NONASCII false
21#endif
22
30namespace audio_tools {
31
32// String array with genres
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" };
34
36enum ParseStatus { TagNotFound, PartialTagAtTail, TagFoundPartial, TagFoundComplete, TagProcessed};
37
38
41struct ID3v1 {
42 char header[3]; // TAG
43 char title[30];
44 char artist[30];
45 char album[30];
46 char year[4];
47 char comment[30];
48 char zero_byte[1];
49 char track[1];
50 char genre;
51};
52
53
57 char header[4]; // TAG+
58 char title[60];
59 char artist[60];
60 char album[60];
61 char speed;
62 char genre[30];
63 char start[6];
64 char end[6];
65};
66
67
76 public:
77
78 MetaDataID3Base() = default;
79
80 void setCallback(void (*fn)(MetaDataType info, const char* str, int len)) {
81 callback = fn;
82 armed = fn!=nullptr;
83 }
84
85 protected:
86 void (*callback)(MetaDataType info, const char* title, int len);
87 bool armed = false;
88
90 int findTag(const char* tag, const char*str, size_t len){
91 if (tag==nullptr || str==nullptr || len<=0) return -1;
92 // The tags are usally in the first 500 bytes - we limit the search
93 if (len>1600){
94 len = 1600;
95 }
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){
100 return j;
101 }
102 }
103 return -1;
104 }
105
106};
107
108
117 public:
118
119 MetaDataID3V1() = default;
120
122 void begin() {
123 end();
124 status = TagNotFound;
125 use_bytes_of_next_write = 0;
126 memset(tag_str, 0, 5);
127 }
128
130 void end() {
131 if (tag!=nullptr){
132 delete tag;
133 tag = nullptr;
134 }
135 if (tag_ext!=nullptr){
136 delete tag_ext;
137 tag_ext = nullptr;
138 }
139 }
140
142 size_t write(const uint8_t* data, size_t len){
143 if (armed){
144 switch(status){
145 case TagNotFound:
146 processTagNotFound(data,len);
147 break;
148 case PartialTagAtTail:
149 processPartialTagAtTail(data,len);
150 break;
151 case TagFoundPartial:
152 processTagFoundPartial(data,len);
153 break;
154 default:
155 // do nothing
156 break;
157 }
158 }
159 return len;
160 }
161
162 protected:
163 int use_bytes_of_next_write = 0;
164 char tag_str[5] = "";
165 ID3v1 *tag = nullptr;
166 ID3v1Enhanced *tag_ext = nullptr;
167 ParseStatus status = TagNotFound;
168
169
171 void processTagNotFound(const uint8_t* data, size_t len) {
172 // find tags
173 int pos = findTag("TAG+",(const char*) data, len);
174 if (pos>0){
175 tag_ext = new ID3v1Enhanced();
176 if (tag_ext!=nullptr){
177 if (len-pos>=sizeof(ID3v1Enhanced)){
178 memcpy(tag,data+pos,sizeof(ID3v1Enhanced));
180 } else {
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;
184 }
185 }
186 } else {
187 pos = findTag("TAG", (const char*) data, len);
188 if (pos>0){
189 tag = new ID3v1();
190 if (tag!=nullptr){
191 if (len-pos>=sizeof(ID3v1)){
192 memcpy(tag,data+pos,sizeof(ID3v1));
194 } else {
195 use_bytes_of_next_write = min(sizeof(ID3v1), len-pos);
196 memcpy(tag,data+pos,use_bytes_of_next_write);
197 status = TagFoundPartial;
198
199 }
200 }
201 }
202 }
203
204 // we did not find a full tag we check if the end might start with a tag
205 if (pos==-1){
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'){
213 strcpy(tag_str,"T");
214 status = TagFoundPartial;
215 }
216 }
217 }
218
220 void processPartialTagAtTail(const uint8_t* data, size_t len) {
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){
225 tag_ext = new ID3v1Enhanced();
226 memcpy(tag,tag_str, 4);
227 memcpy(tag,data+len,sizeof(ID3v1Enhanced));
229 } else if (strncmp((char*)tag_str,"TAG",3)==0){
230 tag = new ID3v1();
231 memcpy(tag,tag_str, 3);
232 memcpy(tag,data+len,sizeof(ID3v1));
234 }
235 }
236
238 void processTagFoundPartial(const uint8_t* data, size_t len) {
239 if (tag!=nullptr){
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;
249 }
250 }
251
254 if (callback==nullptr) return;
255
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));
261 delete tag_ext;
262 tag_ext = nullptr;
263 status = TagProcessed;
264 }
265
266 if (tag!=nullptr){
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));
274 }
275 delete tag;
276 tag = nullptr;
277 status = TagProcessed;
278 }
279 }
280
281};
282
283// -------------------------------------------------------------------------------------------------------------------------------------
284
285#define UnsynchronisationFlag 0x40
286#define ExtendedHeaderFlag 0x20
287#define ExperimentalIndicatorFlag 0x10
288
289// Relevant v2 Tags
290static const char* id3_v2_tags[] = {"TALB", "TOPE", "TPE1", "TIT2", "TCON"};
291
292
293// ID3 verion 2 TAG Header (10 bytes) @ingroup metadata-id3
294struct ID3v2 {
295 uint8_t header[3]; // ID3
296 uint8_t version[2];
297 uint8_t flags;
298 uint8_t size[4];
299};
300
301// /// ID3 verion 2 Extended Header
302// struct ID3v2ExtendedHeader {
303// uint8_t size[4];
304// uint16_t flags;
305// uint32_t padding_size;
306// };
307
308
309// ID3 verion 2 Tag
311 uint8_t id[4];
312 uint8_t size[4];
313 uint16_t flags;
314};
315
316// ID3 verion 2 Tag
318 uint8_t id[4];
319 uint8_t size[4];
320 uint16_t flags;
321
322// encoding:
323// 00 – ISO-8859-1 (ASCII).
324// 01 – UCS-2 (UTF-16 encoded Unicode with BOM), in ID3v2.2 and ID3v2.3.
325// 02 – UTF-16BE encoded Unicode without BOM, in ID3v2.4.
326// 03 – UTF-8 encoded Unicode, in ID3v2.4.
327 uint8_t encoding; // encoding byte for strings
328};
329
330static const int ID3FrameSize = 11;
331
339 public:
340 MetaDataID3V2() = default;
341
343 void begin() {
344 status = TagNotFound;
345 use_bytes_of_next_write = 0;
346 actual_tag = nullptr;
347 tag_active = false;
348 tag_processed = false;
349 result.resize(result_size);
350 }
351
353 void end() {
354 status = TagNotFound;
355 use_bytes_of_next_write = 0;
356 actual_tag = nullptr;
357 tag_active = false;
358 tag_processed = false;
359 }
360
362 size_t write(const uint8_t* data, size_t len){
363 if (armed){
364 switch(status){
365 case TagNotFound:
366 processTagNotFound(data,len);
367 break;
368 case PartialTagAtTail:
369 processPartialTagAtTail(data,len);
370 break;
371 default:
372 // do nothing
373 break;
374 }
375 }
376 return len;
377 }
378
381 return tagv2;
382 }
383
386 return frame_header;
387 }
388
390 bool isProcessed() {
391 return tag_processed;
392 }
393
395 void resize(int size){
396 result_size = size;
397 if (result.size()==0) {
398 result.resize(result_size);
399 }
400 }
401
402 protected:
403 ID3v2 tagv2;
404 bool tag_active = false;
405 bool tag_processed = false;
406 ParseStatus status = TagNotFound;
407 const char* actual_tag;
408 ID3v2FrameString frame_header;
409 int use_bytes_of_next_write = 0;
410 int result_size = 256;
411 Vector<char> result{0};
412 uint64_t total_len = 0;
413 uint64_t end_len = 0;
414
415 // calculate the synch save size
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;
422 }
423
425 void processTagNotFound(const uint8_t* data, size_t len) {
426
427 // activate tag processing when we have an ID3 Tag
428 if (!tag_active && !tag_processed){
429 int pos = findTag("ID3", (const char*) data, len);
430 if (pos>=0){
431 // fill v2 tag header
432 tag_active = true;
433 // if we have enough data for the header we process it
434 if (len>=pos+sizeof(ID3v2)){
435 memcpy(&tagv2, data+pos, sizeof(ID3v2));
436 end_len = total_len + calcSize(tagv2.size);
437 }
438 }
439 }
440
441 // deactivate tag processing when we are out of range
442 if (end_len>0 && total_len>end_len){
443 tag_active = false;
444 tag_processed = false;
445 }
446
447
448 if (tag_active){
449 // process all tags in current buffer
450 const char* partial_tag = nullptr;
451 for (const char* tag : id3_v2_tags){
452 actual_tag = tag;
453 int tag_pos = findTag(tag, (const char*) data, len);
454
455 if (tag_pos>0 && tag_pos+sizeof(ID3v2Frame)<=len){
456 // setup tag header
457 memmove(&frame_header, data+tag_pos, sizeof(ID3v2FrameString));
458
459 // get tag content
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){
467 } else {
468 LOGW("TAG %s ignored", tag);
469 }
470 } else {
471 partial_tag = tag;
472 LOGI("%s partial tag", tag);
473 }
474 }
475 }
476
477 // save partial tag information so that we process the remainder with the next write
478 if (partial_tag!=nullptr){
479 int tag_pos = findTag(partial_tag, (const char*) data, len);
480 memmove(&frame_header, data+tag_pos, sizeof(ID3v2FrameString));
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;
485 }
486 }
487
488 total_len += len;
489
490 }
491
492 int isCharAscii(int ch) {return ch >= 0 && ch < 128; }
493
495 bool isAscii(int l){
496 // check on first 10 characters
497 int m = l < 5 ? l : 10;
498 for (int j=0; j<m; j++){
499 if (!isCharAscii(result[j])) {
500 return false;
501 }
502 }
503 return true;
504 }
505
507 void processPartialTagAtTail(const uint8_t* data, size_t len) {
508 int remainder = calcSize(frame_header.size) - use_bytes_of_next_write;
509 memcpy(result.data()+use_bytes_of_next_write, data, remainder);
511
512 status = TagNotFound;
513 processTagNotFound(data+use_bytes_of_next_write, len-use_bytes_of_next_write);
514 }
515
518 return frame_header.encoding == 0 || frame_header.encoding == 3;
519 }
520
521 int strpos(char* str, const char* target) {
522 char* res = strstr(str, target);
523 return (res == nullptr) ? -1 : res - str;
524 }
525
526
529 if (callback!=nullptr && actual_tag!=nullptr && encodingIsSupported()){
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) {
540 if (result[0]=='('){
541 // convert genre id to string
542 int end_pos = strpos((char*)result.data(), ")");
543 if (end_pos>0){
544 // we just use the first entry
545 result[end_pos]=0;
546 int idx = atoi(result.data()+1);
547 if (idx>=0 && idx< (int)sizeof(genres)){
548 strncpy((char*)result.data(), genres[idx], result.size());
549 }
550 }
551 }
552 callback(Genre, result.data(),strnlength(result.data(), result.size()));
553 }
554 }
555 }
556
557};
558
568 public:
569
570 MetaDataID3() = default;
571
572 ~MetaDataID3(){
573 end();
574 }
575
576 void setCallback(void (*fn)(MetaDataType info, const char* str, int len)) {
577 id3v1.setCallback(fn);
578 id3v2.setCallback(fn);
579 }
580
581 void setFilter(ID3TypeSelection sel) {
582 this->filter = sel;
583 }
584
585 void begin() {
586 TRACEI();
587 id3v1.begin();
588 id3v2.begin();
589 }
590
591 void end() {
592 TRACEI();
593 id3v1.end();
594 id3v2.end();
595 }
596
598 virtual size_t write(const uint8_t *data, size_t len){
599 TRACED();
600 if (filter & SELECT_ID3V2) id3v2.write(data, len);
601 if (!id3v2.isProcessed()) {
602 if (filter & SELECT_ID3V1) id3v1.write(data, len);
603 }
604 return len;
605 }
606
608 void resize(int size){
609 id3v2.resize(size);
610 }
611
612 protected:
613 MetaDataID3V1 id3v1;
614 MetaDataID3V2 id3v2;
615 int filter = SELECT_ID3;
616};
617
618} // namespace
Common Metadata methods.
Definition AbstractMetaData.h:34
ID3 Meta Data Common Functionality.
Definition MetaDataID3.h:75
int findTag(const char *tag, const char *str, size_t len)
find the tag position in the string - if not found we return -1;
Definition MetaDataID3.h:90
Simple ID3 Meta Data Parser which supports ID3 V1 and V2 and implements the Stream interface....
Definition MetaDataID3.h:567
virtual size_t write(const uint8_t *data, size_t len)
Provide tha audio data to the API to parse for Meta Data.
Definition MetaDataID3.h:598
void resize(int size)
Defines the ID3V3 result buffer size (default is 256);.
Definition MetaDataID3.h:608
Simple ID3 Meta Data API which supports ID3 V1.
Definition MetaDataID3.h:116
void processTagFoundPartial(const uint8_t *data, size_t len)
We have the beginning of the metadata and need to process the remainder.
Definition MetaDataID3.h:238
void processnotifyAudioChange()
executes the callbacks
Definition MetaDataID3.h:253
void processPartialTagAtTail(const uint8_t *data, size_t len)
We had part of the start tag at the end of the last write, now we get the full data.
Definition MetaDataID3.h:220
void end()
Ends the processing and releases the memory.
Definition MetaDataID3.h:130
void begin()
(re)starts the processing
Definition MetaDataID3.h:122
void processTagNotFound(const uint8_t *data, size_t len)
try to find the metatdata tag in the provided data
Definition MetaDataID3.h:171
size_t write(const uint8_t *data, size_t len)
provide the (partial) data which might contain the meta data
Definition MetaDataID3.h:142
Simple ID3 Meta Data API which supports ID3 V2: We only support the "TALB", "TOPE",...
Definition MetaDataID3.h:338
ID3v2 header()
provides the ID3v2 header
Definition MetaDataID3.h:380
bool encodingIsSupported()
For the time beeing we support only ASCII and UTF8.
Definition MetaDataID3.h:517
bool isProcessed()
returns true if the tag has been provided
Definition MetaDataID3.h:390
void processnotifyAudioChange()
executes the callbacks
Definition MetaDataID3.h:528
bool isAscii(int l)
Make sure that the result is a valid ASCII string.
Definition MetaDataID3.h:495
void processPartialTagAtTail(const uint8_t *data, size_t len)
We have the beginning of the metadata and need to process the remainder.
Definition MetaDataID3.h:507
void end()
Ends the processing and releases the memory.
Definition MetaDataID3.h:353
void begin()
(re)starts the processing
Definition MetaDataID3.h:343
void resize(int size)
Defines the result buffer size (default is 256);.
Definition MetaDataID3.h:395
ID3v2FrameString frameHeader()
provides the current frame header
Definition MetaDataID3.h:385
void processTagNotFound(const uint8_t *data, size_t len)
try to find the metatdata tag in the provided data
Definition MetaDataID3.h:425
size_t write(const uint8_t *data, size_t len)
provide the (partial) data which might contain the meta data
Definition MetaDataID3.h:362
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
ParseStatus
current status of the parsing
Definition MetaDataID3.h:36
ID3TypeSelection
Enum to filter by type of metadata.
Definition AbstractMetaData.h:8
MetaDataType
Type of meta info.
Definition AbstractMetaData.h:11
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
static size_t strnlength(const char *s, size_t n)
unfortunatly strnlen or strnlen_s is not available in all implementations
Definition AbstractMetaData.h:22
Definition MetaDataID3.h:56
Definition MetaDataID3.h:41
Definition MetaDataID3.h:310
Definition MetaDataID3.h:317
Definition MetaDataID3.h:294