/*************************************************************************** copyright : (C) 2004 by Allan Sandfeld Jensen email : kde@carewolf.com ***************************************************************************/ /*************************************************************************** * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License version * * 2.1 as published by the Free Software Foundation. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * * USA * ***************************************************************************/ #include #include #include #include #include "apetag.h" using namespace TagLib; using namespace APE; class APE::Tag::TagPrivate { public: TagPrivate() : file(0), tagOffset(-1), tagLength(0) {} File *file; long tagOffset; long tagLength; FieldListMap fieldListMap; Map binaries; }; APE::Item::Item(const String& str) : readOnly(false), locator(false) { value.append(str); } APE::Item::Item(const StringList& values) : readOnly(false), locator(false) { value.append(values); } bool APE::Item::isEmpty() const { return value.isEmpty(); } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// APE::Tag::Tag() : TagLib::Tag() { d = new TagPrivate; } APE::Tag::Tag(File *file, long tagOffset) : TagLib::Tag() { d = new TagPrivate; d->file = file; d->tagOffset = tagOffset; read(); } APE::Tag::~Tag() { delete d; } using TagLib::uint; static ByteVector _render_APEItem(String key, Item item) { ByteVector data; uint flags = ((item.readOnly) ? 1 : 0) | ((item.locator) ? 2 : 0); ByteVector value; if (item.value.isEmpty()) return data; StringList::Iterator it = item.value.begin(); value.append(it->data(String::UTF8)); it++; while (it != item.value.end()) { value.append('\0'); value.append(it->data(String::UTF8)); it++; } data.append(ByteVector::fromUInt(value.size(), false)); data.append(ByteVector::fromUInt(flags, false)); data.append(key.data(String::UTF8)); data.append(ByteVector('\0')); data.append(value); return data; } static ByteVector _render_APEFrame(bool isHeader, uint dataSize, uint itemCount) { ByteVector header; TagLib::uint tagSize = 32 + dataSize; // bit 31: Has a header // bit 29: Is the header TagLib::uint flags = (1U << 31) | ((isHeader) ? (1U << 29) : 0); header.append(APE::Tag::fileIdentifier()); header.append(ByteVector::fromUInt(2, false)); header.append(ByteVector::fromUInt(tagSize, false)); header.append(ByteVector::fromUInt(itemCount, false)); header.append(ByteVector::fromUInt(flags, false)); header.append(ByteVector::fromLongLong(0, false)); return header; } ByteVector APE::Tag::render() const { ByteVector data; uint itemCount = 0; { Map::Iterator i = d->fieldListMap.begin(); while (i != d->fieldListMap.end()) { if (!i->second.value.isEmpty()) { data.append(_render_APEItem(i->first, i->second)); itemCount++; } i++; } } { Map::Iterator i = d->binaries.begin(); while (i != d->binaries.end()) { if (!i->second.isEmpty()) { data.append(i->second); itemCount++; } i++; } } ByteVector tag; tag.append(_render_APEFrame(true, data.size(), itemCount)); tag.append(data); tag.append(_render_APEFrame(false, data.size(), itemCount)); return tag; } ByteVector APE::Tag::fileIdentifier() { return ByteVector::fromCString("APETAGEX"); } String APE::Tag::title() const { if(d->fieldListMap["TITLE"].isEmpty()) return String::null; return d->fieldListMap["TITLE"].value.front(); } String APE::Tag::artist() const { if(d->fieldListMap["ARTIST"].isEmpty()) return String::null; return d->fieldListMap["ARTIST"].value.front(); } String APE::Tag::album() const { if(d->fieldListMap["ALBUM"].isEmpty()) return String::null; return d->fieldListMap["ALBUM"].value.front(); } String APE::Tag::comment() const { if(d->fieldListMap["COMMENT"].isEmpty()) return String::null; return d->fieldListMap["COMMENT"].value.front(); } String APE::Tag::genre() const { if(d->fieldListMap["GENRE"].isEmpty()) return String::null; return d->fieldListMap["GENRE"].value.front(); } TagLib::uint APE::Tag::year() const { if(d->fieldListMap["YEAR"].isEmpty()) return 0; return d->fieldListMap["YEAR"].value.front().toInt(); } TagLib::uint APE::Tag::track() const { if(d->fieldListMap["TRACK"].isEmpty()) return 0; return d->fieldListMap["TRACK"].value.front().toInt(); } void APE::Tag::setTitle(const String &s) { addField("TITLE", s, true); } void APE::Tag::setArtist(const String &s) { addField("ARTIST", s, true); } void APE::Tag::setAlbum(const String &s) { addField("ALBUM", s, true); } void APE::Tag::setComment(const String &s) { addField("COMMENT", s, true); } void APE::Tag::setGenre(const String &s) { addField("GENRE", s, true); } void APE::Tag::setYear(uint i) { if(i <=0 ) removeField("YEAR"); else addField("YEAR", String::number(i), true); } void APE::Tag::setTrack(uint i) { if(i <=0 ) removeField("TRACK"); else addField("TRACK", String::number(i), true); } const APE::FieldListMap& APE::Tag::fieldListMap() const { return d->fieldListMap; } void APE::Tag::removeField(const String &key) { Map::Iterator it = d->fieldListMap.find(key.upper()); if(it != d->fieldListMap.end()) d->fieldListMap.erase(it); } void APE::Tag::addField(const String &key, const String &value, bool replace) { if(replace) removeField(key); if(!value.isEmpty()) { Map::Iterator it = d->fieldListMap.find(key.upper()); if (it != d->fieldListMap.end()) d->fieldListMap[key].value.append(value); else addItem(key, Item(value)); } } void APE::Tag::addField(const String &key, const StringList &values) { removeField(key); if(values.isEmpty()) return; else { addItem(key, Item(values)); } } TagLib::uint APE::Tag::tagSize(const ByteVector &footer) { // The reported length (excl. header) uint length = footer.mid(12, 4).toUInt(false); // Flags (bit 31: tag contains a header) uint flags = footer.mid(20, 4).toUInt(false); return length + ((flags & (1U << 31)) ? 32 : 0); } //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// void APE::Tag::addItem(const String &key, const Item &item) { removeField(key); Map::Iterator it = d->fieldListMap.find(key.upper()); d->fieldListMap.insert(key, item); } void APE::Tag::read() { if(d->file && d->file->isValid()) { d->file->seek(d->tagOffset); // read the footer -- always 32 bytes ByteVector footer = d->file->readBlock(32); // parse footer and some initial sanity checking if(footer.size() == 32 && footer.mid(0, 8) == "APETAGEX") { uint length = footer.mid(12, 4).toUInt(false); uint count = footer.mid(16, 4).toUInt(false); d->tagLength = length; d->file->seek(d->tagOffset + 32 - length); ByteVector data = d->file->readBlock(length - 32); parse(data, count); } else debug("APE tag is not valid or could not be read at the specified offset."); } } static StringList _parse_APEString(ByteVector val) { StringList value; int pold = 0; int p = val.find('\0'); while (p != -1) { value.append(String(val.mid(pold, p), String::UTF8)); pold = p+1; p = val.find('\0', pold); }; value.append(String(val.mid(pold), String::UTF8)); return value; } void APE::Tag::parse(const ByteVector &data, uint count) { uint pos = 0; uint vallen, flags; String key, value; while(count > 0) { vallen = data.mid(pos+0,4).toUInt(false); flags = data.mid(pos+4,4).toUInt(false); key = String(data.mid(pos+8), String::UTF8); key = key.upper(); APE::Item item; if (flags < 4 ) { ByteVector val = data.mid(pos+8+key.size()+1, vallen); d->fieldListMap.insert(key, Item(_parse_APEString(val))); } else { d->binaries.insert(key,data.mid(pos, 8+key.size()+1+vallen)); } pos += 8 + key.size() + 1 + vallen; count--; } }