/*
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();
}