arduino-audio-tools
Loading...
Searching...
No Matches
MetaDataID3.h
Go to the documentation of this file.
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
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 bool begin() {
123 end();
126 memset(tag_str, 0, 5);
127 return true;
128 }
129
131 void end() {
132 if (tag!=nullptr){
133 delete tag;
134 tag = nullptr;
135 }
136 if (tag_ext!=nullptr){
137 delete tag_ext;
138 tag_ext = nullptr;
139 }
140 }
141
143 size_t write(const uint8_t* data, size_t len){
144 if (armed){
145 switch(status){
146 case TagNotFound:
147 processTagNotFound(data,len);
148 break;
149 case PartialTagAtTail:
150 processPartialTagAtTail(data,len);
151 break;
152 case TagFoundPartial:
153 processTagFoundPartial(data,len);
154 break;
155 default:
156 // do nothing
157 break;
158 }
159 }
160 return len;
161 }
162
163 protected:
165 char tag_str[5] = "";
166 ID3v1 *tag = nullptr;
169
170
172 void processTagNotFound(const uint8_t* data, size_t len) {
173 // find tags
174 int pos = findTag("TAG+",(const char*) data, len);
175 if (pos>0){
176 tag_ext = new ID3v1Enhanced();
177 if (tag_ext!=nullptr){
178 if (len-pos>=sizeof(ID3v1Enhanced)){
179 memcpy(tag,data+pos,sizeof(ID3v1Enhanced));
181 } else {
182 use_bytes_of_next_write = min(sizeof(ID3v1Enhanced), len-pos);
185 }
186 }
187 } else {
188 pos = findTag("TAG", (const char*) data, len);
189 if (pos>0){
190 tag = new ID3v1();
191 if (tag!=nullptr){
192 if (len-pos>=sizeof(ID3v1)){
193 memcpy(tag,data+pos,sizeof(ID3v1));
195 } else {
196 use_bytes_of_next_write = min(sizeof(ID3v1), len-pos);
199
200 }
201 }
202 }
203 }
204
205 // we did not find a full tag we check if the end might start with a tag
206 if (pos==-1){
207 if (data[len-3] == 'T' && data[len-2] == 'A' && data[len-1] == 'G'){
208 strcpy(tag_str,"TAG");
210 } else if (data[len-2] == 'T' && data[len-1] == 'A' ){
211 strcpy(tag_str,"TA");
213 } else if (data[len-1] == 'T'){
214 strcpy(tag_str,"T");
216 }
217 }
218 }
219
221 void processPartialTagAtTail(const uint8_t* data, size_t len) {
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){
226 tag_ext = new ID3v1Enhanced();
227 memcpy(tag,tag_str, 4);
228 memcpy(tag,data+len,sizeof(ID3v1Enhanced));
230 } else if (strncmp((char*)tag_str,"TAG",3)==0){
231 tag = new ID3v1();
232 memcpy(tag,tag_str, 3);
233 memcpy(tag,data+len,sizeof(ID3v1));
235 }
236 }
237
239 void processTagFoundPartial(const uint8_t* data, size_t len) {
240 if (tag!=nullptr){
245 } else if (tag_ext!=nullptr){
250 }
251 }
252
255 if (callback==nullptr) return;
256
257 if (tag_ext!=nullptr){
262 delete tag_ext;
263 tag_ext = nullptr;
265 }
266
267 if (tag!=nullptr){
271 uint16_t genre = tag->genre;
272 if (genre < sizeof(genres)){
273 const char* genre_str = genres[genre];
275 }
276 delete tag;
277 tag = nullptr;
279 }
280 }
281
282};
283
284// -------------------------------------------------------------------------------------------------------------------------------------
285
286#define UnsynchronisationFlag 0x40
287#define ExtendedHeaderFlag 0x20
288#define ExperimentalIndicatorFlag 0x10
289
290// Relevant v2 Tags
291static const char* id3_v2_tags[] = {"TALB", "TOPE", "TPE1", "TIT2", "TCON"};
292
293
294// ID3 verion 2 TAG Header (10 bytes) @ingroup metadata-id3
301
302// /// ID3 verion 2 Extended Header
303// struct ID3v2ExtendedHeader {
304// uint8_t size[4];
305// uint16_t flags;
306// uint32_t padding_size;
307// };
308
309
310// ID3 verion 2 Tag
316
317// ID3 verion 2 Tag
319 uint8_t id[4];
322
323// encoding:
324// 00 – ISO-8859-1 (ASCII).
325// 01 – UCS-2 (UTF-16 encoded Unicode with BOM), in ID3v2.2 and ID3v2.3.
326// 02 – UTF-16BE encoded Unicode without BOM, in ID3v2.4.
327// 03 – UTF-8 encoded Unicode, in ID3v2.4.
328 uint8_t encoding; // encoding byte for strings
329};
330
331static const int ID3FrameSize = 11;
332
340 public:
341 MetaDataID3V2() = default;
342
344 bool begin() {
347 actual_tag = nullptr;
348 tag_active = false;
349 tag_processed = false;
351 return true;
352 }
353
355 void end() {
358 actual_tag = nullptr;
359 tag_active = false;
360 tag_processed = false;
361 }
362
364 size_t write(const uint8_t* data, size_t len){
365 if (armed){
366 switch(status){
367 case TagNotFound:
368 processTagNotFound(data,len);
369 break;
370 case PartialTagAtTail:
371 processPartialTagAtTail(data,len);
372 break;
373 default:
374 // do nothing
375 break;
376 }
377 }
378 return len;
379 }
380
383 return tagv2;
384 }
385
390
392 bool isProcessed() {
393 return tag_processed;
394 }
395
397 void resize(int size){
398 result_size = size;
399 if (result.size()==0) {
401 }
402 }
403
404 protected:
406 bool tag_active = false;
407 bool tag_processed = false;
409 const char* actual_tag;
412 int result_size = 256;
416
417 // calculate the synch save size
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;
424 }
425
427 void processTagNotFound(const uint8_t* data, size_t len) {
428
429 // activate tag processing when we have an ID3 Tag
430 if (!tag_active && !tag_processed){
431 int pos = findTag("ID3", (const char*) data, len);
432 if (pos>=0){
433 // fill v2 tag header
434 tag_active = true;
435 // if we have enough data for the header we process it
436 if (len>=pos+sizeof(ID3v2)){
437 memcpy(&tagv2, data+pos, sizeof(ID3v2));
439 }
440 }
441 }
442
443 // deactivate tag processing when we are out of range
444 if (end_len>0 && total_len>end_len){
445 tag_active = false;
446 tag_processed = false;
447 }
448
449
450 if (tag_active){
451 // process all tags in current buffer
452 const char* partial_tag = nullptr;
453 for (const char* tag : id3_v2_tags){
454 actual_tag = tag;
455 int tag_pos = findTag(tag, (const char*) data, len);
456
457 if (tag_pos>0 && tag_pos+sizeof(ID3v2Frame)<=len){
458 // setup tag header
460
461 // get tag content
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);
469 } else {
470 LOGW("TAG %s ignored", tag);
471 }
472 } else {
473 partial_tag = tag;
474 LOGI("%s partial tag", tag);
475 }
476 }
477 }
478
479 // save partial tag information so that we process the remainder with the next write
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);
487 }
488 }
489
490 total_len += len;
491
492 }
493
494 int isCharAscii(int ch) {return ch >= 0 && ch < 128; }
495
497 bool isAscii(int l){
498 // check on first 10 characters
499 int m = l < 5 ? l : 10;
500 for (int j=0; j<m; j++){
501 if (!isCharAscii(result[j])) {
502 return false;
503 }
504 }
505 return true;
506 }
507
517
520 return frame_header.encoding == 0 || frame_header.encoding == 3;
521 }
522
523 int strpos(char* str, const char* target) {
524 char* res = strstr(str, target);
525 return (res == nullptr) ? -1 : res - str;
526 }
527
528
531 if (callback!=nullptr && actual_tag!=nullptr && encodingIsSupported()){
532 LOGI("callback %s",actual_tag);
533 if (memcmp(actual_tag,"TALB",4)==0)
535 else if (memcmp(actual_tag,"TPE1",4)==0)
537 else if (memcmp(actual_tag,"TOPE",4)==0)
539 else if (memcmp(actual_tag,"TIT2",4)==0)
541 else if (memcmp(actual_tag,"TCON",4)==0) {
542 if (result[0]=='('){
543 // convert genre id to string
544 int end_pos = strpos((char*)result.data(), ")");
545 if (end_pos>0){
546 // we just use the first entry
547 result[end_pos]=0;
548 int idx = atoi(result.data()+1);
549 if (idx>=0 && idx< (int)sizeof(genres)){
550 strncpy((char*)result.data(), genres[idx], result.size());
551 }
552 }
553 }
555 }
556 }
557 }
558
559};
560
570 public:
571
572 MetaDataID3() = default;
573
575 end();
576 }
577
578 void setCallback(void (*fn)(MetaDataType info, const char* str, int len)) {
581 }
582
584 this->filter = sel;
585 }
586
587 bool begin() {
588 TRACEI();
589 if (!id3v1.begin()) return false;
590 if (!id3v2.begin()) return false;
591 return true;
592 }
593
594 void end() {
595 TRACEI();
596 id3v1.end();
597 id3v2.end();
598 }
599
601 virtual size_t write(const uint8_t *data, size_t len){
602 TRACED();
603 if (filter & SELECT_ID3V2) id3v2.write(data, len);
604 if (!id3v2.isProcessed()) {
605 if (filter & SELECT_ID3V1) id3v1.write(data, len);
606 }
607 return len;
608 }
609
611 void resize(int size){
612 id3v2.resize(size);
613 }
614
615 protected:
619};
620
621} // namespace
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define TRACEI()
Definition AudioLoggerIDF.h:32
#define TRACED()
Definition AudioLoggerIDF.h:31
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define AUDIOTOOLS_ID3_TAG_ALLOW_NONASCII
Parser for MP3 ID3 Meta Data: The goal is to implement a simple API which provides the title,...
Definition MetaDataID3.h:20
Common Metadata methods.
Definition AbstractMetaData.h:34
ID3 Meta Data Common Functionality.
Definition MetaDataID3.h:75
void setCallback(void(*fn)(MetaDataType info, const char *str, int len))
Definition MetaDataID3.h:80
void(* callback)(MetaDataType info, const char *title, int len)
Definition MetaDataID3.h:86
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
bool armed
Definition MetaDataID3.h:87
Simple ID3 Meta Data Parser which supports ID3 V1 and V2 and implements the Stream interface....
Definition MetaDataID3.h:569
void setFilter(ID3TypeSelection sel)
Definition MetaDataID3.h:583
void setCallback(void(*fn)(MetaDataType info, const char *str, int len))
Definition MetaDataID3.h:578
bool begin()
Definition MetaDataID3.h:587
MetaDataID3V1 id3v1
Definition MetaDataID3.h:616
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:601
~MetaDataID3()
Definition MetaDataID3.h:574
void end()
Definition MetaDataID3.h:594
void resize(int size)
Defines the ID3V3 result buffer size (default is 256);.
Definition MetaDataID3.h:611
MetaDataID3V2 id3v2
Definition MetaDataID3.h:617
int filter
Definition MetaDataID3.h:618
Simple ID3 Meta Data API which supports ID3 V1.
Definition MetaDataID3.h:116
ParseStatus status
Definition MetaDataID3.h:168
bool begin()
(re)starts the processing
Definition MetaDataID3.h:122
char tag_str[5]
Definition MetaDataID3.h:165
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:239
void processnotifyAudioChange()
executes the callbacks
Definition MetaDataID3.h:254
int use_bytes_of_next_write
Definition MetaDataID3.h:164
ID3v1 * tag
Definition MetaDataID3.h:166
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:221
void end()
Ends the processing and releases the memory.
Definition MetaDataID3.h:131
ID3v1Enhanced * tag_ext
Definition MetaDataID3.h:167
void processTagNotFound(const uint8_t *data, size_t len)
try to find the metatdata tag in the provided data
Definition MetaDataID3.h:172
size_t write(const uint8_t *data, size_t len)
provide the (partial) data which might contain the meta data
Definition MetaDataID3.h:143
Simple ID3 Meta Data API which supports ID3 V2: We only support the "TALB", "TOPE",...
Definition MetaDataID3.h:339
int result_size
Definition MetaDataID3.h:412
ID3v2 header()
provides the ID3v2 header
Definition MetaDataID3.h:382
uint32_t calcSize(uint8_t chars[4])
Definition MetaDataID3.h:418
ParseStatus status
Definition MetaDataID3.h:408
bool encodingIsSupported()
For the time beeing we support only ASCII and UTF8.
Definition MetaDataID3.h:519
bool isProcessed()
returns true if the tag has been provided
Definition MetaDataID3.h:392
uint64_t end_len
Definition MetaDataID3.h:415
bool tag_processed
Definition MetaDataID3.h:407
bool begin()
(re)starts the processing
Definition MetaDataID3.h:344
void processnotifyAudioChange()
executes the callbacks
Definition MetaDataID3.h:530
int use_bytes_of_next_write
Definition MetaDataID3.h:411
bool isAscii(int l)
Make sure that the result is a valid ASCII string.
Definition MetaDataID3.h:497
ID3v2FrameString frame_header
Definition MetaDataID3.h:410
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:509
ID3v2 tagv2
Definition MetaDataID3.h:405
void end()
Ends the processing and releases the memory.
Definition MetaDataID3.h:355
const char * actual_tag
Definition MetaDataID3.h:409
uint64_t total_len
Definition MetaDataID3.h:414
void resize(int size)
Defines the result buffer size (default is 256);.
Definition MetaDataID3.h:397
int isCharAscii(int ch)
Definition MetaDataID3.h:494
int strpos(char *str, const char *target)
Definition MetaDataID3.h:523
ID3v2FrameString frameHeader()
provides the current frame header
Definition MetaDataID3.h:387
bool tag_active
Definition MetaDataID3.h:406
void processTagNotFound(const uint8_t *data, size_t len)
try to find the metatdata tag in the provided data
Definition MetaDataID3.h:427
Vector< char > result
Definition MetaDataID3.h:413
size_t write(const uint8_t *data, size_t len)
provide the (partial) data which might contain the meta data
Definition MetaDataID3.h:364
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
bool resize(int newSize, T value)
Definition Vector.h:266
T * data()
Definition Vector.h:316
int size()
Definition Vector.h:178
ParseStatus
current status of the parsing
Definition MetaDataID3.h:36
@ TagProcessed
Definition MetaDataID3.h:36
@ PartialTagAtTail
Definition MetaDataID3.h:36
@ TagFoundPartial
Definition MetaDataID3.h:36
@ TagFoundComplete
Definition MetaDataID3.h:36
@ TagNotFound
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
@ SELECT_ID3
Definition AbstractMetaData.h:8
@ SELECT_ID3V1
Definition AbstractMetaData.h:8
@ SELECT_ID3V2
Definition AbstractMetaData.h:8
@ Album
Definition AbstractMetaData.h:11
@ Genre
Definition AbstractMetaData.h:11
@ Title
Definition AbstractMetaData.h:11
@ Artist
Definition AbstractMetaData.h:11
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
static const int ID3FrameSize
Definition MetaDataID3.h:331
static const char * genres[]
Definition MetaDataID3.h:33
static const char * id3_v2_tags[]
Definition MetaDataID3.h:291
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
size_t writeData(Print *p_out, T *data, int samples, int maxSamples=512)
Definition AudioTypes.h:512
Definition MetaDataID3.h:56
char end[6]
Definition MetaDataID3.h:64
char speed
Definition MetaDataID3.h:61
char start[6]
Definition MetaDataID3.h:63
char header[4]
Definition MetaDataID3.h:57
char album[60]
Definition MetaDataID3.h:60
char title[60]
Definition MetaDataID3.h:58
char genre[30]
Definition MetaDataID3.h:62
char artist[60]
Definition MetaDataID3.h:59
Definition MetaDataID3.h:41
char genre
Definition MetaDataID3.h:50
char album[30]
Definition MetaDataID3.h:45
char comment[30]
Definition MetaDataID3.h:47
char artist[30]
Definition MetaDataID3.h:44
char track[1]
Definition MetaDataID3.h:49
char header[3]
Definition MetaDataID3.h:42
char title[30]
Definition MetaDataID3.h:43
char year[4]
Definition MetaDataID3.h:46
char zero_byte[1]
Definition MetaDataID3.h:48
Definition MetaDataID3.h:311
uint16_t flags
Definition MetaDataID3.h:314
uint8_t size[4]
Definition MetaDataID3.h:313
Definition MetaDataID3.h:318
uint16_t flags
Definition MetaDataID3.h:321
uint8_t encoding
Definition MetaDataID3.h:328
uint8_t size[4]
Definition MetaDataID3.h:320
Definition MetaDataID3.h:295
uint8_t header[3]
Definition MetaDataID3.h:296
uint8_t flags
Definition MetaDataID3.h:298
uint8_t size[4]
Definition MetaDataID3.h:299
uint8_t version[2]
Definition MetaDataID3.h:297