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
14
22namespace audio_tools {
23
24// String array with genres
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" };
26
28enum ParseStatus { TagNotFound, PartialTagAtTail, TagFoundPartial, TagFoundComplete, TagProcessed};
29
30
33struct ID3v1 {
34 char header[3]; // TAG
35 char title[30];
36 char artist[30];
37 char album[30];
38 char year[4];
39 char comment[30];
40 char zero_byte[1];
41 char track[1];
42 char genre;
43};
44
45
49 char header[4]; // TAG+
50 char title[60];
51 char artist[60];
52 char album[60];
53 char speed;
54 char genre[30];
55 char start[6];
56 char end[6];
57};
58
59
68 public:
69
70 MetaDataID3Base() = default;
71
72 void setCallback(void (*fn)(MetaDataType info, const char* str, int len)) {
73 callback = fn;
74 armed = fn!=nullptr;
75 }
76
77 protected:
78 void (*callback)(MetaDataType info, const char* title, int len);
79 bool armed = false;
80
82 int findTag(const char* tag, const char*str, size_t len){
83 if (tag==nullptr || str==nullptr || len<=0) return -1;
84 // The tags are usally in the first 500 bytes - we limit the search
85 if (len>1600){
86 len = 1600;
87 }
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){
92 return j;
93 }
94 }
95 return -1;
96 }
97
98};
99
100
109 public:
110
111 MetaDataID3V1() = default;
112
114 void begin() {
115 end();
116 status = TagNotFound;
117 use_bytes_of_next_write = 0;
118 memset(tag_str, 0, 5);
119 }
120
122 void end() {
123 if (tag!=nullptr){
124 delete tag;
125 tag = nullptr;
126 }
127 if (tag_ext!=nullptr){
128 delete tag_ext;
129 tag_ext = nullptr;
130 }
131 }
132
134 size_t write(const uint8_t* data, size_t len){
135 if (armed){
136 switch(status){
137 case TagNotFound:
138 processTagNotFound(data,len);
139 break;
140 case PartialTagAtTail:
141 processPartialTagAtTail(data,len);
142 break;
143 case TagFoundPartial:
144 processTagFoundPartial(data,len);
145 break;
146 default:
147 // do nothing
148 break;
149 }
150 }
151 return len;
152 }
153
154 protected:
155 int use_bytes_of_next_write = 0;
156 char tag_str[5] = "";
157 ID3v1 *tag = nullptr;
158 ID3v1Enhanced *tag_ext = nullptr;
159 ParseStatus status = TagNotFound;
160
161
163 void processTagNotFound(const uint8_t* data, size_t len) {
164 // find tags
165 int pos = findTag("TAG+",(const char*) data, len);
166 if (pos>0){
167 tag_ext = new ID3v1Enhanced();
168 if (tag_ext!=nullptr){
169 if (len-pos>=sizeof(ID3v1Enhanced)){
170 memcpy(tag,data+pos,sizeof(ID3v1Enhanced));
172 } else {
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;
176 }
177 }
178 } else {
179 pos = findTag("TAG", (const char*) data, len);
180 if (pos>0){
181 tag = new ID3v1();
182 if (tag!=nullptr){
183 if (len-pos>=sizeof(ID3v1)){
184 memcpy(tag,data+pos,sizeof(ID3v1));
186 } else {
187 use_bytes_of_next_write = min(sizeof(ID3v1), len-pos);
188 memcpy(tag,data+pos,use_bytes_of_next_write);
189 status = TagFoundPartial;
190
191 }
192 }
193 }
194 }
195
196 // we did not find a full tag we check if the end might start with a tag
197 if (pos==-1){
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'){
205 strcpy(tag_str,"T");
206 status = TagFoundPartial;
207 }
208 }
209 }
210
212 void processPartialTagAtTail(const uint8_t* data, size_t len) {
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){
217 tag_ext = new ID3v1Enhanced();
218 memcpy(tag,tag_str, 4);
219 memcpy(tag,data+len,sizeof(ID3v1Enhanced));
221 } else if (strncmp((char*)tag_str,"TAG",3)==0){
222 tag = new ID3v1();
223 memcpy(tag,tag_str, 3);
224 memcpy(tag,data+len,sizeof(ID3v1));
226 }
227 }
228
230 void processTagFoundPartial(const uint8_t* data, size_t len) {
231 if (tag!=nullptr){
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;
241 }
242 }
243
246 if (callback==nullptr) return;
247
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));
253 delete tag_ext;
254 tag_ext = nullptr;
255 status = TagProcessed;
256 }
257
258 if (tag!=nullptr){
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));
266 }
267 delete tag;
268 tag = nullptr;
269 status = TagProcessed;
270 }
271 }
272
273};
274
275// -------------------------------------------------------------------------------------------------------------------------------------
276
277#define UnsynchronisationFlag 0x40
278#define ExtendedHeaderFlag 0x20
279#define ExperimentalIndicatorFlag 0x10
280
281// Relevant v2 Tags
282static const char* id3_v2_tags[] = {"TALB", "TOPE", "TPE1", "TIT2", "TCON"};
283
284
285// ID3 verion 2 TAG Header (10 bytes) @ingroup metadata-id3
286struct ID3v2 {
287 uint8_t header[3]; // ID3
288 uint8_t version[2];
289 uint8_t flags;
290 uint8_t size[4];
291};
292
293// /// ID3 verion 2 Extended Header
294// struct ID3v2ExtendedHeader {
295// uint8_t size[4];
296// uint16_t flags;
297// uint32_t padding_size;
298// };
299
300
301// ID3 verion 2 Tag
303 uint8_t id[4];
304 uint8_t size[4];
305 uint16_t flags;
306};
307
308// ID3 verion 2 Tag
310 uint8_t id[4];
311 uint8_t size[4];
312 uint16_t flags;
313
314// encoding:
315// 00 – ISO-8859-1 (ASCII).
316// 01 – UCS-2 (UTF-16 encoded Unicode with BOM), in ID3v2.2 and ID3v2.3.
317// 02 – UTF-16BE encoded Unicode without BOM, in ID3v2.4.
318// 03 – UTF-8 encoded Unicode, in ID3v2.4.
319 uint8_t encoding; // encoding byte for strings
320};
321
322static const int ID3FrameSize = 11;
323
331 public:
332 MetaDataID3V2() = default;
333
335 void begin() {
336 status = TagNotFound;
337 use_bytes_of_next_write = 0;
338 actual_tag = nullptr;
339 tag_active = false;
340 tag_processed = false;
341 result.resize(result_size);
342 }
343
345 void end() {
346 status = TagNotFound;
347 use_bytes_of_next_write = 0;
348 actual_tag = nullptr;
349 tag_active = false;
350 tag_processed = false;
351 }
352
354 size_t write(const uint8_t* data, size_t len){
355 if (armed){
356 switch(status){
357 case TagNotFound:
358 processTagNotFound(data,len);
359 break;
360 case PartialTagAtTail:
361 processPartialTagAtTail(data,len);
362 break;
363 default:
364 // do nothing
365 break;
366 }
367 }
368 return len;
369 }
370
373 return tagv2;
374 }
375
378 return frame_header;
379 }
380
382 bool isProcessed() {
383 return tag_processed;
384 }
385
387 void resize(int size){
388 result_size = size;
389 if (result.size()==0) {
390 result.resize(result_size);
391 }
392 }
393
394 protected:
395 ID3v2 tagv2;
396 bool tag_active = false;
397 bool tag_processed = false;
398 ParseStatus status = TagNotFound;
399 const char* actual_tag;
400 ID3v2FrameString frame_header;
401 int use_bytes_of_next_write = 0;
402 int result_size = 256;
403 Vector<char> result{0};
404 uint64_t total_len = 0;
405 uint64_t end_len = 0;
406
407 // calculate the synch save size
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;
414 }
415
417 void processTagNotFound(const uint8_t* data, size_t len) {
418
419 // activate tag processing when we have an ID3 Tag
420 if (!tag_active && !tag_processed){
421 int pos = findTag("ID3", (const char*) data, len);
422 if (pos>=0){
423 // fill v2 tag header
424 tag_active = true;
425 // if we have enough data for the header we process it
426 if (len>=pos+sizeof(ID3v2)){
427 memcpy(&tagv2, data+pos, sizeof(ID3v2));
428 end_len = total_len + calcSize(tagv2.size);
429 }
430 }
431 }
432
433 // deactivate tag processing when we are out of range
434 if (end_len>0 && total_len>end_len){
435 tag_active = false;
436 tag_processed = false;
437 }
438
439
440 if (tag_active){
441 // process all tags in current buffer
442 const char* partial_tag = nullptr;
443 for (const char* tag : id3_v2_tags){
444 actual_tag = tag;
445 int tag_pos = findTag(tag, (const char*) data, len);
446
447 if (tag_pos>0 && tag_pos+sizeof(ID3v2Frame)<=len){
448 // setup tag header
449 memmove(&frame_header, data+tag_pos, sizeof(ID3v2FrameString));
450
451 // get tag content
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);
457 if (isAscii(checkLen)){
459 } else {
460 LOGW("TAG %s ignored", tag);
461 }
462 } else {
463 partial_tag = tag;
464 LOGI("%s partial tag", tag);
465 }
466 }
467 }
468
469 // save partial tag information so that we process the remainder with the next write
470 if (partial_tag!=nullptr){
471 int tag_pos = findTag(partial_tag, (const char*) data, len);
472 memmove(&frame_header, data+tag_pos, sizeof(ID3v2FrameString));
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;
477 }
478 }
479
480 total_len += len;
481
482 }
483
484 int isCharAscii(int ch) {return ch >= 0 && ch < 128; }
485
487 bool isAscii(int l){
488 // check on first 10 characters
489 int m = l < 5 ? l : 10;
490 for (int j=0; j<m; j++){
491 if (!isCharAscii(result[j])) {
492 return false;
493 }
494 }
495 return true;
496 }
497
499 void processPartialTagAtTail(const uint8_t* data, size_t len) {
500 int remainder = calcSize(frame_header.size) - use_bytes_of_next_write;
501 memcpy(result.data()+use_bytes_of_next_write, data, remainder);
503
504 status = TagNotFound;
505 processTagNotFound(data+use_bytes_of_next_write, len-use_bytes_of_next_write);
506 }
507
510 return frame_header.encoding == 0 || frame_header.encoding == 3;
511 }
512
513 int strpos(char* str, const char* target) {
514 char* res = strstr(str, target);
515 return (res == nullptr) ? -1 : res - str;
516 }
517
518
521 if (callback!=nullptr && actual_tag!=nullptr && encodingIsSupported()){
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) {
532 if (result[0]=='('){
533 // convert genre id to string
534 int end_pos = strpos((char*)result.data(), ")");
535 if (end_pos>0){
536 // we just use the first entry
537 result[end_pos]=0;
538 int idx = atoi(result.data()+1);
539 if (idx>=0 && idx< (int)sizeof(genres)){
540 strncpy((char*)result.data(), genres[idx], result.size());
541 }
542 }
543 }
544 callback(Genre, result.data(),strnlength(result.data(), result.size()));
545 }
546 }
547 }
548
549};
550
560 public:
561
562 MetaDataID3() = default;
563
564 ~MetaDataID3(){
565 end();
566 }
567
568 void setCallback(void (*fn)(MetaDataType info, const char* str, int len)) {
569 id3v1.setCallback(fn);
570 id3v2.setCallback(fn);
571 }
572
573 void setFilter(ID3TypeSelection sel) {
574 this->filter = sel;
575 }
576
577 void begin() {
578 TRACEI();
579 id3v1.begin();
580 id3v2.begin();
581 }
582
583 void end() {
584 TRACEI();
585 id3v1.end();
586 id3v2.end();
587 }
588
590 virtual size_t write(const uint8_t *data, size_t len){
591 TRACED();
592 if (filter & SELECT_ID3V2) id3v2.write(data, len);
593 if (!id3v2.isProcessed()) {
594 if (filter & SELECT_ID3V1) id3v1.write(data, len);
595 }
596 return len;
597 }
598
600 void resize(int size){
601 id3v2.resize(size);
602 }
603
604 protected:
605 MetaDataID3V1 id3v1;
606 MetaDataID3V2 id3v2;
607 int filter = SELECT_ID3;
608};
609
610} // namespace
Common Metadata methods.
Definition AbstractMetaData.h:34
ID3 Meta Data Common Functionality.
Definition MetaDataID3.h:67
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:82
Simple ID3 Meta Data Parser which supports ID3 V1 and V2 and implements the Stream interface....
Definition MetaDataID3.h:559
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:590
void resize(int size)
Defines the ID3V3 result buffer size (default is 256);.
Definition MetaDataID3.h:600
Simple ID3 Meta Data API which supports ID3 V1.
Definition MetaDataID3.h:108
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:230
void processnotifyAudioChange()
executes the callbacks
Definition MetaDataID3.h:245
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:212
void end()
Ends the processing and releases the memory.
Definition MetaDataID3.h:122
void begin()
(re)starts the processing
Definition MetaDataID3.h:114
void processTagNotFound(const uint8_t *data, size_t len)
try to find the metatdata tag in the provided data
Definition MetaDataID3.h:163
size_t write(const uint8_t *data, size_t len)
provide the (partial) data which might contain the meta data
Definition MetaDataID3.h:134
Simple ID3 Meta Data API which supports ID3 V2: We only support the "TALB", "TOPE",...
Definition MetaDataID3.h:330
ID3v2 header()
provides the ID3v2 header
Definition MetaDataID3.h:372
bool encodingIsSupported()
For the time beeing we support only ASCII and UTF8.
Definition MetaDataID3.h:509
bool isProcessed()
returns true if the tag has been provided
Definition MetaDataID3.h:382
void processnotifyAudioChange()
executes the callbacks
Definition MetaDataID3.h:520
bool isAscii(int l)
Make sure that the result is a valid ASCII string.
Definition MetaDataID3.h:487
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:499
void end()
Ends the processing and releases the memory.
Definition MetaDataID3.h:345
void begin()
(re)starts the processing
Definition MetaDataID3.h:335
void resize(int size)
Defines the result buffer size (default is 256);.
Definition MetaDataID3.h:387
ID3v2FrameString frameHeader()
provides the current frame header
Definition MetaDataID3.h:377
void processTagNotFound(const uint8_t *data, size_t len)
try to find the metatdata tag in the provided data
Definition MetaDataID3.h:417
size_t write(const uint8_t *data, size_t len)
provide the (partial) data which might contain the meta data
Definition MetaDataID3.h:354
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:28
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:48
Definition MetaDataID3.h:33
Definition MetaDataID3.h:302
Definition MetaDataID3.h:309
Definition MetaDataID3.h:286