2#include "AbstractMetaData.h"
3#include "AudioTools/Communication/HTTP/AbstractURLStream.h"
4#include "AudioTools/CoreAudio/AudioBasic/StrView.h"
5#include "AudioToolsConfig.h"
7#ifndef AUDIOTOOLS_METADATA_ICY_ASCII_ONLY
8#define AUDIOTOOLS_METADATA_ICY_ASCII_ONLY true
29 enum Status { ProcessData, ProcessMetaData, SetupSize };
41 this->mp3_blocksize = value;
52 int bufferLen = 1024) {
53 dataBuffer =
new uint8_t[bufferLen];
62 LOGI(
"mp3_blocksize: %d", mp3_blocksize);
67 virtual void end()
override { clear(); }
71 virtual size_t write(
const uint8_t* data,
size_t len)
override {
72 if (callback !=
nullptr) {
73 for (
size_t j = 0; j < len; j++) {
81 virtual Status
status() {
return currentStatus; }
84 virtual bool isData() {
return currentStatus == ProcessData; }
87 virtual bool hasMetaData() {
return this->mp3_blocksize > 0; }
90 virtual int metaInt() {
return mp3_blocksize; }
96 currentStatus = ProcessData;
101 if (totalData >= mp3_blocksize) {
104 nextStatus = SetupSize;
109 currentStatus = SetupSize;
113 LOGI(
"metaDataLen: %d", metaDataLen);
114 if (metaDataLen > 0) {
115 if (metaDataLen > 200) {
116 LOGI(
"Unexpected metaDataLen -> processed as data");
117 nextStatus = ProcessData;
119 LOGI(
"Metadata found");
121 nextStatus = ProcessMetaData;
125 nextStatus = ProcessData;
129 case ProcessMetaData:
130 currentStatus = ProcessMetaData;
131 metaData[metaDataPos++] = ch;
132 if (metaDataPos >= metaDataLen) {
134 LOGI(
"Metadata ended")
135 nextStatus = ProcessData;
145 Status nextStatus = ProcessData;
146 Status currentStatus = ProcessData;
147 void (*callback)(
MetaDataType info,
const char* str,
int len) =
nullptr;
150 int mp3_blocksize = 0;
151 int metaDataMaxLen = 0;
156 uint8_t* dataBuffer =
nullptr;
157 void (*dataCallback)(
const uint8_t* str,
int len) =
nullptr;
160 bool is_ascii = AUDIOTOOLS_METADATA_ICY_ASCII_ONLY;
162 virtual void clear() {
163 nextStatus = ProcessData;
176 virtual bool isPrintable(
const char* str,
int l) {
178 for (
int j = 0; j < l; j++) {
181 if (ch < 0x80 || ch > 0xbf) {
182 LOGD(
"Invalid UTF-8 continuation byte: 0x%02X at pos %d", ch, j);
189 if (ch !=
'\n' && ch !=
'\r' && ch !=
'\t' && ch != 0 && (ch < 32 || ch == 127)) {
190 LOGD(
"Non-printable ASCII character: 0x%02X at pos %d", ch, j);
193 }
else if (!is_ascii) {
194 if (ch >= 0xc2 && ch <= 0xdf)
196 else if (ch >= 0xe0 && ch <= 0xef)
198 else if (ch >= 0xf0 && ch <= 0xf4)
201 LOGD(
"Invalid UTF-8 lead byte: 0x%02X at pos %d", ch, j);
205 LOGD(
"Non-ASCII character not allowed: 0x%02X at pos %d", ch, j);
211 LOGD(
"Incomplete UTF-8 sequence at end of string");
219 metaData.resize(meta_size + 1);
220 metaDataMaxLen = meta_size;
221 memset(metaData.data(), 0, meta_size + 1);
230 if (isPrintable(metaData, len)) {
231 LOGI(
"%s", metaData);
232 StrView meta(metaData, len + 1, len);
233 int start = meta.
indexOf(
"StreamTitle=");
238 if (start >= 0 &&
end > start) {
240 if (callback !=
nullptr) {
241 callback(Title, (
const char*)metaData + start + 1,
end - start);
249 LOGW(
"Unexpected Data: corrupted metadata block rejected (len=%d)", len);
251 if (callback !=
nullptr) {
252 callback(Corrupted,
nullptr, len);
260 if (dataBuffer !=
nullptr) {
261 dataBuffer[dataPos++] = ch;
263 if (dataPos >= dataLen) {
264 dataCallback(dataBuffer, dataLen);
287 LOGI(
"icy-metaint: %s", iceMetaintStr);
289 LOGE(
"icy-metaint not defined");
292 int iceMetaint = value.
toInt();
301 if (callback ==
nullptr) {
302 LOGW(
"callback not defined")
304 if (p_url ==
nullptr) {
305 LOGW(
"http not defined")
308 if (callback !=
nullptr && p_url !=
nullptr) {
317 callback(Description, descr.
c_str(), descr.
length());