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 bool resize(size_t size){
398 result_size = size;
399 if (result.size()==0) {
400 return result.resize(result_size);
401 }
402 return true;
403 }
404
405 protected:
407 bool tag_active = false;
408 bool tag_processed = false;
410 const char* actual_tag;
413 int result_size = 256;
417
418 // calculate the synch save size
420 uint32_t byte0 = chars[0];
421 uint32_t byte1 = chars[1];
422 uint32_t byte2 = chars[2];
423 uint32_t byte3 = chars[3];
424 return byte0 << 21 | byte1 << 14 | byte2 << 7 | byte3;
425 }
426
428 void processTagNotFound(const uint8_t* data, size_t len) {
429
430 // activate tag processing when we have an ID3 Tag
431 if (!tag_active && !tag_processed){
432 int pos = findTag("ID3", (const char*) data, len);
433 if (pos>=0){
434 // fill v2 tag header
435 tag_active = true;
436 // if we have enough data for the header we process it
437 if (len>=pos+sizeof(ID3v2)){
438 memcpy(&tagv2, data+pos, sizeof(ID3v2));
440 }
441 }
442 }
443
444 // deactivate tag processing when we are out of range
445 if (end_len>0 && total_len>end_len){
446 tag_active = false;
447 tag_processed = false;
448 }
449
450
451 if (tag_active){
452 // process all tags in current buffer
453 const char* partial_tag = nullptr;
454 for (const char* tag : id3_v2_tags){
455 actual_tag = tag;
456 int tag_pos = findTag(tag, (const char*) data, len);
457
458 if (tag_pos>0 && tag_pos+sizeof(ID3v2Frame)<=len){
459 // setup tag header
461
462 // get tag content
463 if(calcSize(frame_header.size) <= len){
464 int l = min(calcSize(frame_header.size)-1, (uint32_t) result.size());
465 memset(result.data(), 0, result.size());
466 strncpy((char*)result.data(), (char*) data+tag_pos+ID3FrameSize, l);
467 int checkLen = min(l, 10);
470 } else {
471 LOGW("TAG %s ignored", tag);
472 }
473 } else {
474 partial_tag = tag;
475 LOGI("%s partial tag", tag);
476 }
477 }
478 }
479
480 // save partial tag information so that we process the remainder with the next write
481 if (partial_tag!=nullptr){
482 int tag_pos = findTag(partial_tag, (const char*) data, len);
484 int size = min(len - tag_pos, (size_t) calcSize(frame_header.size)-1);
485 strncpy((char*)result.data(), (char*)data+tag_pos+ID3FrameSize, size);
488 }
489 }
490
491 total_len += len;
492
493 }
494
495 int isCharAscii(int ch) {return ch >= 0 && ch < 128; }
496
498 bool isAscii(int l){
499 // check on first 10 characters
500 int m = l < 5 ? l : 10;
501 for (int j=0; j<m; j++){
502 if (!isCharAscii(result[j])) {
503 return false;
504 }
505 }
506 return true;
507 }
508
518
521 return frame_header.encoding == 0 || frame_header.encoding == 3;
522 }
523
524 int strpos(char* str, const char* target) {
525 char* res = strstr(str, target);
526 return (res == nullptr) ? -1 : res - str;
527 }
528
529
532 if (callback!=nullptr && actual_tag!=nullptr && encodingIsSupported()){
533 LOGI("callback %s",actual_tag);
534 if (memcmp(actual_tag,"TALB",4)==0)
536 else if (memcmp(actual_tag,"TPE1",4)==0)
538 else if (memcmp(actual_tag,"TOPE",4)==0)
540 else if (memcmp(actual_tag,"TIT2",4)==0)
542 else if (memcmp(actual_tag,"TCON",4)==0) {
543 if (result[0]=='('){
544 // convert genre id to string
545 int end_pos = strpos((char*)result.data(), ")");
546 if (end_pos>0){
547 // we just use the first entry
548 result[end_pos]=0;
549 int idx = atoi(result.data()+1);
550 if (idx>=0 && idx< (int)sizeof(genres)){
551 strncpy((char*)result.data(), genres[idx], result.size());
552 }
553 }
554 }
556 }
557 }
558 }
559
560};
561
571 public:
572
573 MetaDataID3() = default;
574
576 end();
577 }
578
579 void setCallback(void (*fn)(MetaDataType info, const char* str, int len)) {
582 }
583
585 this->filter = sel;
586 }
587
588 bool begin() {
589 TRACEI();
590 if (!id3v1.begin()) return false;
591 if (!id3v2.begin()) return false;
592 return true;
593 }
594
595 void end() {
596 TRACEI();
597 id3v1.end();
598 id3v2.end();
599 }
600
602 virtual size_t write(const uint8_t *data, size_t len){
603 TRACED();
604 if (filter & SELECT_ID3V2) id3v2.write(data, len);
605 if (!id3v2.isProcessed()) {
606 if (filter & SELECT_ID3V1) id3v1.write(data, len);
607 }
608 return len;
609 }
610
612 bool resize(size_t size){
613 return id3v2.resize(size);
614 }
615
616 protected:
620};
621
622} // 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:570
void setFilter(ID3TypeSelection sel)
Definition MetaDataID3.h:584
void setCallback(void(*fn)(MetaDataType info, const char *str, int len))
Definition MetaDataID3.h:579
bool begin()
Definition MetaDataID3.h:588
MetaDataID3V1 id3v1
Definition MetaDataID3.h:617
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:602
~MetaDataID3()
Definition MetaDataID3.h:575
void end()
Definition MetaDataID3.h:595
bool resize(size_t size)
Defines the ID3V3 result buffer size (default is 256);.
Definition MetaDataID3.h:612
MetaDataID3V2 id3v2
Definition MetaDataID3.h:618
int filter
Definition MetaDataID3.h:619
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:413
ID3v2 header()
provides the ID3v2 header
Definition MetaDataID3.h:382
uint32_t calcSize(uint8_t chars[4])
Definition MetaDataID3.h:419
ParseStatus status
Definition MetaDataID3.h:409
bool encodingIsSupported()
For the time beeing we support only ASCII and UTF8.
Definition MetaDataID3.h:520
bool isProcessed()
returns true if the tag has been provided
Definition MetaDataID3.h:392
uint64_t end_len
Definition MetaDataID3.h:416
bool tag_processed
Definition MetaDataID3.h:408
bool begin()
(re)starts the processing
Definition MetaDataID3.h:344
void processnotifyAudioChange()
executes the callbacks
Definition MetaDataID3.h:531
int use_bytes_of_next_write
Definition MetaDataID3.h:412
bool isAscii(int l)
Make sure that the result is a valid ASCII string.
Definition MetaDataID3.h:498
ID3v2FrameString frame_header
Definition MetaDataID3.h:411
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:510
ID3v2 tagv2
Definition MetaDataID3.h:406
void end()
Ends the processing and releases the memory.
Definition MetaDataID3.h:355
const char * actual_tag
Definition MetaDataID3.h:410
uint64_t total_len
Definition MetaDataID3.h:415
int isCharAscii(int ch)
Definition MetaDataID3.h:495
int strpos(char *str, const char *target)
Definition MetaDataID3.h:524
ID3v2FrameString frameHeader()
provides the current frame header
Definition MetaDataID3.h:387
bool resize(size_t size)
Defines the result buffer size (default is 256);.
Definition MetaDataID3.h:397
bool tag_active
Definition MetaDataID3.h:407
void processTagNotFound(const uint8_t *data, size_t len)
try to find the metatdata tag in the provided data
Definition MetaDataID3.h:428
Vector< char > result
Definition MetaDataID3.h:414
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(size_t 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:508
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