/* AudioFileSourceID3 ID3 filter that extracts any ID3 fields and sends to CB function Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "AudioFileSourceID3.h" // Handle unsync operation in ID3 with custom class class AudioFileSourceUnsync : public AudioFileSource { public: AudioFileSourceUnsync(AudioFileSource *src, int len, bool unsync); virtual ~AudioFileSourceUnsync() override; virtual uint32_t read(void *data, uint32_t len) override; int getByte(); bool eof(); private: AudioFileSource *src; int remaining; bool unsync; int savedByte; }; AudioFileSourceUnsync::AudioFileSourceUnsync(AudioFileSource *src, int len, bool unsync) { this->src = src; this->remaining = len; this->unsync = unsync; this->savedByte = -1; } AudioFileSourceUnsync::~AudioFileSourceUnsync() { } uint32_t AudioFileSourceUnsync::read(void *data, uint32_t len) { uint32_t bytes = 0; uint8_t *ptr = reinterpret_cast(data); // This is only used during ID3 parsing, so no need to optimize here... while (len--) { int b = getByte(); if (b >= 0) { *(ptr++) = (uint8_t)b; bytes++; } } return bytes; } int AudioFileSourceUnsync::getByte() { // If we're not unsync, just read. if (!unsync) { uint8_t c; if (!remaining) return -1; remaining--; if (1 != src->read(&c, 1)) return -1; return c; } // If we've saved a pre-read character, return it immediately if (savedByte >= 0) { int s = savedByte; savedByte = -1; return s; } if (remaining <= 0) { return -1; } else if (remaining == 1) { remaining--; uint8_t c; if (1 != src->read(&c, 1)) return -1; else return c; } else { uint8_t c; remaining--; if (1 != src->read(&c, 1)) return -1; if (c != 0xff) { return c; } // Saw 0xff, check next byte. If 0 then eat it, OTW return the 0xff uint8_t d; remaining--; if (1 != src->read(&d, 1)) return c; if (d != 0x00) { savedByte = d; } return c; } } bool AudioFileSourceUnsync::eof() { if (remaining<=0) return true; else return false; } AudioFileSourceID3::AudioFileSourceID3(AudioFileSource *src) { this->src = src; this->checked = false; } AudioFileSourceID3::~AudioFileSourceID3() { } uint32_t AudioFileSourceID3::read(void *data, uint32_t len) { int rev = 0; if (checked) { return src->read(data, len); } checked = true; // <10 bytes initial read, not enough space to check header if (len<10) return src->read(data, len); uint8_t *buff = reinterpret_cast(data); int ret = src->read(data, 10); if (ret<10) return ret; if ((buff[0]!='I') || (buff[1]!='D') || (buff[2]!='3') || (buff[3]>0x04) || (buff[3]<0x02) || (buff[4]!=0)) { cb.md("eof", false, "id3"); return 10 + src->read(buff+10, len-10); } rev = buff[3]; bool unsync = false; bool exthdr = false; switch(rev) { case 2: unsync = (buff[5] & 0x80); exthdr = false; break; case 3: case 4: unsync = (buff[5] & 0x80); exthdr = (buff[5] & 0x40); break; }; int id3Size = buff[6]; id3Size = id3Size << 7; id3Size |= buff[7]; id3Size = id3Size << 7; id3Size |= buff[8]; id3Size = id3Size << 7; id3Size |= buff[9]; // Every read from now may be unsync'd AudioFileSourceUnsync id3(src, id3Size, unsync); if (exthdr) { int ehsz = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte()); for (int j=0; jread(data, len); } bool AudioFileSourceID3::seek(int32_t pos, int dir) { return src->seek(pos, dir); } bool AudioFileSourceID3::close() { return src->close(); } bool AudioFileSourceID3::isOpen() { return src->isOpen(); } uint32_t AudioFileSourceID3::getSize() { return src->getSize(); } uint32_t AudioFileSourceID3::getPos() { return src->getPos(); }