diff --git a/data/formats/mp3/ID3.h b/data/formats/mp3/ID3.h new file mode 100644 index 0000000..60e93fb --- /dev/null +++ b/data/formats/mp3/ID3.h @@ -0,0 +1,206 @@ +#pragma once + +#include +#include +#include + +/** + * very basic ID3v2 reader + * https://id3.org/id3v2.3.0#ID3v2_overview + */ +template class ID3v2 { + + /** 4 byte size, used to correct endianness */ + struct Size { + uint8_t raw[4]; + operator uint32_t () const {return raw[3]<<0 | raw[2]<<8 | raw[1]<<16 | raw[0]<<24;} + }; + + /** ID3 header at the start of the file (if present) */ + struct Header { + char ID3[3]; // "ID3" if present + uint16_t version; + uint8_t flags; + uint8_t sizeBits[4]; // size of content, 4 bytes, the MSB of every byte is 0 -> 28 bits used + uint32_t size() const {return sizeBits[0]<<21 | sizeBits[1]<<14 | sizeBits[2]<<7 | sizeBits[3]<<0;} + } __attribute__((packed)); + + /** header for every frame within the ID3 data */ + struct Frame { + char name[4]; // tag name (e.g. TALB, APIC, ...) + Size size; // size of content + uint16_t flags; + bool operator == (const char* needle) const {return memcmp(name, needle, 4) == 0;} + bool isPadding() const {return name[0] == 0x00 && size == 0;} + } __attribute__((packed)); + + File& f; + +public: + + /** ctor, define whether to read contained images */ + ID3v2(File& f, bool includeImages) : f(f) { + read(includeImages); + } + + /** parsed data */ + struct Data { + std::string album; + std::string artist; + std::string title; + std::string year; + std::vector image; + } data; + +private: + + bool read(bool includeImages) { + + // start at beginning of file + seekTo(0); + + // 10 byte ID3 header, file starts with "ID3" ? + Header head; + read(head); + if (memcmp(head.ID3, "ID3", 3) != 0) {return false;} + std::cout << head.size() << std::endl; + + // read all tags + while(curPos() < head.size()) { + + Frame frm; + read(frm); + uint32_t startOfData = curPos(); + + std::cout << frm.name << ":" << frm.size << std::endl; + + if (frm.isPadding()) { + break; // only (empty) padding blocks will follow -> stop + } else if (frm == "TALB") { + data.album = readString(frm.size); + } else if (frm == "TPE1") { + data.artist = readString(frm.size); + } else if (frm == "TIT2") { + data.title = readString(frm.size); + } else if (frm == "TYER") { + data.year = readString(frm.size); + } else if (frm == "APIC" && includeImages) { + data.image = readImage(frm.size); + } + + // ensure we are positioned after the frame + seekTo(startOfData + frm.size); + + } + + return true; + + } + +private: + + /** read a string with the given length, also corrects encoding */ + std::string readString(uint32_t size) { + + // read no more than 120 chars + uint8_t buf[128]; + size = std::min(120u, size); + f.read(size, buf); + + + + } + + /** read X bytes into a vector */ + std::vector readVector(const uint32_t size) { + std::vector res; + res.resize(size); + read(size, res.data()); + return res; + } + + /** read an image (is preceeded by mime type and description) */ + std::vector readImage(const uint32_t size) { + std::vector tmp = readVector(size); + + return tmp; + } + + + +private: + +// class StringWrapper { + +// const uint8_t* src; +// const uint32_t len; + +// public: + +// StringWrapper(const uint8_t* src, const uint8_t len) : src(src), len(len) {} + +// StringWrapper(const std::vector& vec, uint32_t offset = 0) : src(vec.data()+offset), len(vec.size()-offset) {} + +// std::string get(uint32_t& outPos) const { + +// // encoding? +// if (src[0] == 0x00) { // ISO8859-15 +// return std::string((const char*) &buf[1]); + +// } else if (buf[0] == 0x01) { // 16(!) bit unicode, starts with {0x01 0xFF 0xF4} +// uint32_t pos = 0; +// for (uint32_t i = 3; i < size; i += 2) { +// buf[pos] = buf[i]; +// ++pos; +// } +// buf[pos] = 0; +// return std::string((const char*)buf); + +// } else { +// return "ENCODING?"; + +// } + +// } + +// /** determine the length of the string */ +// uint32_t len() const { + +// if (src[0] == 0x00) { // ISO8859-15 + + +// } else if (buf[0] == 0x01) { // 16(!) bit unicode, starts with {0x01 0xFF 0xF4} +// uint32_t pos = 0; +// for (uint32_t i = 3; i < size; i += 2) { +// buf[pos] = buf[i]; +// ++pos; +// } +// buf[pos] = 0; +// return std::string((const char*)buf); + +// } else { + +// } + +// }; + + /** current position within the file */ + uint32_t curPos() { + return f.curPos(); + } + + /** seek to a new position within the file */ + void seekTo(uint32_t pos) { + f.seekTo(pos); + } + + template void read(T& dst) { + read(sizeof(T), (uint8_t*) &dst); + } + + void read(uint32_t size, uint8_t* dst) { + f.read(size, dst); + } + + +};