281 lines
7.8 KiB
C++
281 lines
7.8 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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<uint8_t*>(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<uint8_t*>(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; j<ehsz-4; j++) id3.getByte(); // Throw it away
|
|
}
|
|
|
|
do {
|
|
unsigned char frameid[4];
|
|
int framesize;
|
|
bool compressed;
|
|
|
|
frameid[0] = id3.getByte();
|
|
frameid[1] = id3.getByte();
|
|
frameid[2] = id3.getByte();
|
|
if (rev==2) frameid[3] = 0;
|
|
else frameid[3] = id3.getByte();
|
|
|
|
if (frameid[0]==0 && frameid[1]==0 && frameid[2]==0 && frameid[3]==0) {
|
|
// We're in padding
|
|
while (!id3.eof()) {
|
|
id3.getByte();
|
|
}
|
|
} else {
|
|
if (rev==2) {
|
|
framesize = (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
|
|
compressed = false;
|
|
} else {
|
|
framesize = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
|
|
id3.getByte(); // skip 1st flag
|
|
compressed = id3.getByte()&0x80;
|
|
}
|
|
if (compressed) {
|
|
int decompsize = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
|
|
// TODO - add libz decompression, for now ignore this one...
|
|
(void)decompsize;
|
|
for (int j=0; j<framesize; j++)
|
|
id3.getByte();
|
|
}
|
|
|
|
// Read the value and send to callback
|
|
char value[64];
|
|
uint32_t i;
|
|
bool isUnicode = (id3.getByte()==1) ? true : false;
|
|
for (i=0; i<(uint32_t)framesize-1; i++) {
|
|
if (i<sizeof(value)-1) value[i] = id3.getByte();
|
|
else (void)id3.getByte();
|
|
}
|
|
value[i<sizeof(value)-1?i:sizeof(value)-1] = 0; // Terminate the string...
|
|
if ( (frameid[0]=='T' && frameid[1]=='A' && frameid[2]=='L' && frameid[3] == 'B' ) ||
|
|
(frameid[0]=='T' && frameid[1]=='A' && frameid[2]=='L' && rev==2) ) {
|
|
cb.md("Album", isUnicode, value);
|
|
} else if ( (frameid[0]=='T' && frameid[1]=='I' && frameid[2]=='T' && frameid[3] == '2') ||
|
|
(frameid[0]=='T' && frameid[1]=='T' && frameid[2]=='2' && rev==2) ) {
|
|
cb.md("Title", isUnicode, value);
|
|
} else if ( (frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='E' && frameid[3] == '1') ||
|
|
(frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='1' && rev==2) ) {
|
|
cb.md("Performer", isUnicode, value);
|
|
} else if ( (frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && frameid[3] == 'R') ||
|
|
(frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && rev==2) ) {
|
|
cb.md("Year", isUnicode, value);
|
|
} else if ( (frameid[0]=='T' && frameid[1]=='R' && frameid[2]=='C' && frameid[3] == 'K') ||
|
|
(frameid[0]=='T' && frameid[1]=='R' && frameid[2]=='K' && rev==2) ) {
|
|
cb.md("track", isUnicode, value);
|
|
} else if ( (frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='O' && frameid[3] == 'S') ||
|
|
(frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='A' && rev==2) ) {
|
|
cb.md("Set", isUnicode, value);
|
|
} else if ( (frameid[0]=='P' && frameid[1]=='O' && frameid[2]=='P' && frameid[3] == 'M') ||
|
|
(frameid[0]=='P' && frameid[1]=='O' && frameid[2]=='P' && rev==2) ) {
|
|
cb.md("Popularimeter", isUnicode, value);
|
|
} else if ( (frameid[0]=='T' && frameid[1]=='C' && frameid[2]=='M' && frameid[3] == 'P') ) {
|
|
cb.md("Compilation", isUnicode, value);
|
|
}
|
|
}
|
|
} while (!id3.eof());
|
|
|
|
// use callback function to signal end of tags and beginning of content.
|
|
cb.md("eof", false, "id3");
|
|
|
|
// All ID3 processing done, return to main caller
|
|
return src->read(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();
|
|
}
|