arduino-audio-tools
Loading...
Searching...
No Matches
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 bool begin() {
123 end();
124 status = TagNotFound;
125 use_bytes_of_next_write = 0;
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:
164 int use_bytes_of_next_write = 0;
165 char tag_str[5] = "";
166 ID3v1 *tag = nullptr;
167 ID3v1Enhanced *tag_ext = nullptr;
168 ParseStatus status = TagNotFound;
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);
183 memcpy(tag_ext, data+pos, use_bytes_of_next_write);
184 status = TagFoundPartial;
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);
197 memcpy(tag,data+pos,use_bytes_of_next_write);
198 status = TagFoundPartial;
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");
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'){
214 strcpy(tag_str,"T");
215 status = TagFoundPartial;
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){
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;
250 }
251 }
252
255 if (callback==nullptr) return;
256
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));
262 delete tag_ext;
263 tag_ext = nullptr;
264 status = TagProcessed;
265 }
266
267 if (tag!=nullptr){
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));
275 }
276 delete tag;
277 tag = nullptr;
278 status = TagProcessed;
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
295struct ID3v2 {
296 uint8_t header[3]; // ID3
297 uint8_t version[2];
298 uint8_t flags;
299 uint8_t size[4];
300};
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
312 uint8_t id[4];
313 uint8_t size[4];
314 uint16_t flags;
315};
316
317// ID3 verion 2 Tag
319 uint8_t id[4];
320 uint8_t size[4];
321 uint16_t flags;
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() {
345 status = TagNotFound;
346 use_bytes_of_next_write = 0;
347 actual_tag = nullptr;
348 tag_active = false;
349 tag_processed = false;
350 result.resize(result_size);
351 return true;
352 }
353
355 void end() {
356 status = TagNotFound;
357 use_bytes_of_next_write = 0;
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
388 return frame_header;
389 }
390
392 bool isProcessed() {
393 return tag_processed;
394 }
395
397 void resize(int size){
398 result_size = size;
399 if (result.size()==0) {
400 result.resize(result_size);
401 }
402 }
403
404 protected:
405 ID3v2 tagv2;
406 bool tag_active = false;
407 bool tag_processed = false;
408 ParseStatus status = TagNotFound;
409 const char* actual_tag;
410 ID3v2FrameString frame_header;
411 int use_bytes_of_next_write = 0;
412 int result_size = 256;
413 Vector<char> result{0};
414 uint64_t total_len = 0;
415 uint64_t end_len = 0;
416
417 // calculate the synch save size
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;
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));
438 end_len = total_len + calcSize(tagv2.size);
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
459 memmove(&frame_header, data+tag_pos, sizeof(ID3v2FrameString));
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);
467 if (isAscii(checkLen) || AUDIOTOOLS_ID3_TAG_ALLOW_NONASCII){
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);
482 memmove(&frame_header, data+tag_pos, sizeof(ID3v2FrameString));
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;
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
509 void processPartialTagAtTail(const uint8_t* data, size_t len) {
510 int remainder = calcSize(frame_header.size) - use_bytes_of_next_write;
511 memcpy(result.data()+use_bytes_of_next_write, data, remainder);
513
514 status = TagNotFound;
515 processTagNotFound(data+use_bytes_of_next_write, len-use_bytes_of_next_write);
516 }
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)
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) {
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 }
554 callback(Genre, result.data(),strnlength(result.data(), result.size()));
555 }
556 }
557 }
558
559};
560
570 public:
571
572 MetaDataID3() = default;
573
574 ~MetaDataID3(){
575 end();
576 }
577
578 void setCallback(void (*fn)(MetaDataType info, const char* str, int len)) {
579 id3v1.setCallback(fn);
580 id3v2.setCallback(fn);
581 }
582
583 void setFilter(ID3TypeSelection sel) {
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:
616 MetaDataID3V1 id3v1;
617 MetaDataID3V2 id3v2;
618 int filter = SELECT_ID3;
619};
620
621} // 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:569
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
void resize(int size)
Defines the ID3V3 result buffer size (default is 256);.
Definition MetaDataID3.h:611
Simple ID3 Meta Data API which supports ID3 V1.
Definition MetaDataID3.h:116
bool begin()
(re)starts the processing
Definition MetaDataID3.h:122
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
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
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
ID3v2 header()
provides the ID3v2 header
Definition MetaDataID3.h:382
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
bool begin()
(re)starts the processing
Definition MetaDataID3.h:344
void processnotifyAudioChange()
executes the callbacks
Definition MetaDataID3.h:530
bool isAscii(int l)
Make sure that the result is a valid ASCII string.
Definition MetaDataID3.h:497
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
void end()
Ends the processing and releases the memory.
Definition MetaDataID3.h:355
void resize(int size)
Defines the result buffer size (default is 256);.
Definition MetaDataID3.h:397
ID3v2FrameString frameHeader()
provides the current frame header
Definition MetaDataID3.h:387
void processTagNotFound(const uint8_t *data, size_t len)
try to find the metatdata tag in the provided data
Definition MetaDataID3.h:427
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
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:311
Definition MetaDataID3.h:318
Definition MetaDataID3.h:295