mirror of
https://github.com/taglib/taglib.git
synced 2026-06-07 14:59:24 -04:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
606edf8171 | ||
|
|
009c43952f | ||
|
|
9c1668f28b | ||
|
|
110cac8429 | ||
|
|
258ae751b5 | ||
|
|
df1d3e028e | ||
|
|
23c86cf27d | ||
|
|
f59c3b67aa | ||
|
|
294cb22241 | ||
|
|
b7ec0d26ab | ||
|
|
934ce51790 | ||
|
|
d112a68193 | ||
|
|
f624d6e2af | ||
|
|
d3e79ddc38 | ||
|
|
b3ae839a38 | ||
|
|
cce88b7005 | ||
|
|
311be56432 | ||
|
|
5685dd078f | ||
|
|
eee860f9c6 | ||
|
|
10fff6d797 | ||
|
|
7cc1ce1c36 | ||
|
|
272719e666 | ||
|
|
d21ff8be54 |
@@ -40,7 +40,7 @@ endif (WIN32)
|
||||
|
||||
SET(TAGLIB_LIB_MAJOR_VERSION "1")
|
||||
SET(TAGLIB_LIB_MINOR_VERSION "7")
|
||||
SET(TAGLIB_LIB_PATCH_VERSION "0")
|
||||
SET(TAGLIB_LIB_PATCH_VERSION "1")
|
||||
|
||||
SET(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}")
|
||||
|
||||
@@ -76,6 +76,7 @@ endif(NOT WIN32)
|
||||
|
||||
INSTALL( PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR})
|
||||
|
||||
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile)
|
||||
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
||||
file(COPY doc/taglib.png DESTINATION doc)
|
||||
ADD_CUSTOM_TARGET(docs doxygen)
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ WARN_LOGFILE =
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the input files
|
||||
#---------------------------------------------------------------------------
|
||||
INPUT = taglib
|
||||
INPUT = @CMAKE_SOURCE_DIR@/taglib
|
||||
FILE_PATTERNS = *.h \
|
||||
*.hh \
|
||||
*.H
|
||||
@@ -96,9 +96,9 @@ IGNORE_PREFIX =
|
||||
GENERATE_HTML = YES
|
||||
HTML_OUTPUT = html
|
||||
HTML_FILE_EXTENSION = .html
|
||||
HTML_HEADER = doc/api-header.html
|
||||
HTML_FOOTER = doc/api-footer.html
|
||||
HTML_STYLESHEET = doc/taglib-api.css
|
||||
HTML_HEADER = @CMAKE_SOURCE_DIR@/doc/api-header.html
|
||||
HTML_FOOTER = @CMAKE_SOURCE_DIR@/doc/api-footer.html
|
||||
HTML_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/taglib-api.css
|
||||
HTML_ALIGN_MEMBERS = YES
|
||||
GENERATE_HTMLHELP = NO
|
||||
CHM_FILE =
|
||||
|
||||
19
NEWS
19
NEWS
@@ -1,5 +1,20 @@
|
||||
TagLib 1.7
|
||||
==========
|
||||
TagLib 1.7.1 (Mar 17, 2012)
|
||||
===========================
|
||||
|
||||
* Improved parsing of corrupted WMA, RIFF and OGG files.
|
||||
* Fixed a memory leak in the WMA parser.
|
||||
* Fixed a memory leak in the FLAC parser.
|
||||
* Fixed a possible division by zero in the APE parser.
|
||||
* Added detection of TTA2 files.
|
||||
* Fixed saving of multiple identically named tags to Vorbis Comments.
|
||||
|
||||
TagLib 1.7 (Mar 11, 2011)
|
||||
=========================
|
||||
|
||||
1.7:
|
||||
|
||||
* Fixed memory leaks in the FLAC file format parser.
|
||||
* Fixed bitrate calculation for WAV files.
|
||||
|
||||
1.7 RC1:
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ void APE::Properties::analyzeCurrent()
|
||||
uint blocksPerFrame = header.mid(4, 4).toUInt(false);
|
||||
uint finalFrameBlocks = header.mid(8, 4).toUInt(false);
|
||||
uint totalBlocks = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0;
|
||||
d->length = totalBlocks / d->sampleRate;
|
||||
d->length = d->sampleRate > 0 ? totalBlocks / d->sampleRate : 0;
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,11 +141,19 @@ class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject
|
||||
{
|
||||
public:
|
||||
List<ASF::File::BaseObject *> objects;
|
||||
~HeaderExtensionObject();
|
||||
ByteVector guid();
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
ASF::File::HeaderExtensionObject::~HeaderExtensionObject()
|
||||
{
|
||||
for(unsigned int i = 0; i < objects.size(); i++) {
|
||||
delete objects[i];
|
||||
}
|
||||
}
|
||||
|
||||
void ASF::File::BaseObject::parse(ASF::File *file, unsigned int size)
|
||||
{
|
||||
data.clear();
|
||||
@@ -319,7 +327,16 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
|
||||
long long dataPos = 0;
|
||||
while(dataPos < dataSize) {
|
||||
ByteVector guid = file->readBlock(16);
|
||||
long long size = file->readQWORD();
|
||||
if(guid.size() != 16) {
|
||||
file->setValid(false);
|
||||
break;
|
||||
}
|
||||
bool ok;
|
||||
long long size = file->readQWORD(&ok);
|
||||
if(!ok) {
|
||||
file->setValid(false);
|
||||
break;
|
||||
}
|
||||
BaseObject *obj;
|
||||
if(guid == metadataGuid) {
|
||||
obj = new MetadataObject();
|
||||
@@ -389,19 +406,37 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
|
||||
ByteVector guid = readBlock(16);
|
||||
if(guid != headerGuid) {
|
||||
debug("ASF: Not an ASF file.");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
d->tag = new ASF::Tag();
|
||||
d->properties = new ASF::Properties();
|
||||
|
||||
d->size = readQWORD();
|
||||
int numObjects = readDWORD();
|
||||
bool ok;
|
||||
d->size = readQWORD(&ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
int numObjects = readDWORD(&ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
seek(2, Current);
|
||||
|
||||
for(int i = 0; i < numObjects; i++) {
|
||||
ByteVector guid = readBlock(16);
|
||||
long size = (long)readQWORD();
|
||||
if(guid.size() != 16) {
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
long size = (long)readQWORD(&ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
BaseObject *obj;
|
||||
if(guid == filePropertiesGuid) {
|
||||
obj = new FilePropertiesObject();
|
||||
@@ -429,7 +464,12 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
|
||||
bool ASF::File::save()
|
||||
{
|
||||
if(readOnly()) {
|
||||
debug("ASF: File is read-only.");
|
||||
debug("ASF::File::save() -- File is read only.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isValid()) {
|
||||
debug("ASF::File::save() -- Trying to save invalid file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -491,27 +531,47 @@ bool ASF::File::save()
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int ASF::File::readBYTE()
|
||||
int ASF::File::readBYTE(bool *ok)
|
||||
{
|
||||
ByteVector v = readBlock(1);
|
||||
if(v.size() != 1) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v[0];
|
||||
}
|
||||
|
||||
int ASF::File::readWORD()
|
||||
int ASF::File::readWORD(bool *ok)
|
||||
{
|
||||
ByteVector v = readBlock(2);
|
||||
if(v.size() != 2) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toUShort(false);
|
||||
}
|
||||
|
||||
unsigned int ASF::File::readDWORD()
|
||||
unsigned int ASF::File::readDWORD(bool *ok)
|
||||
{
|
||||
ByteVector v = readBlock(4);
|
||||
if(v.size() != 4) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toUInt(false);
|
||||
}
|
||||
|
||||
long long ASF::File::readQWORD()
|
||||
long long ASF::File::readQWORD(bool *ok)
|
||||
{
|
||||
ByteVector v = readBlock(8);
|
||||
if(v.size() != 8) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toLongLong(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,10 +88,10 @@ namespace TagLib {
|
||||
|
||||
private:
|
||||
|
||||
int readBYTE();
|
||||
int readWORD();
|
||||
unsigned int readDWORD();
|
||||
long long readQWORD();
|
||||
int readBYTE(bool *ok = 0);
|
||||
int readWORD(bool *ok = 0);
|
||||
unsigned int readDWORD(bool *ok = 0);
|
||||
long long readQWORD(bool *ok = 0);
|
||||
static ByteVector renderString(const String &str, bool includeLength = false);
|
||||
String readString(int len);
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
|
||||
@@ -65,13 +65,13 @@ public:
|
||||
hasID3v2(false),
|
||||
hasID3v1(false)
|
||||
{
|
||||
for(uint i = 0; i < blocks.size(); i++) {
|
||||
delete blocks[i];
|
||||
}
|
||||
}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
for(uint i = 0; i < blocks.size(); i++) {
|
||||
delete blocks[i];
|
||||
}
|
||||
delete properties;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ bool FLAC::File::save()
|
||||
|
||||
// Create new vorbis comments
|
||||
|
||||
Tag::duplicate(&d->tag, xiphComment(true), true);
|
||||
Tag::duplicate(&d->tag, xiphComment(true), false);
|
||||
|
||||
d->xiphCommentData = xiphComment()->render(false);
|
||||
|
||||
@@ -161,10 +161,12 @@ bool FLAC::File::save()
|
||||
MetadataBlock *block = d->blocks[i];
|
||||
if(block->code() == MetadataBlock::VorbisComment) {
|
||||
// Set the new Vorbis Comment block
|
||||
delete block;
|
||||
block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData);
|
||||
foundVorbisCommentBlock = true;
|
||||
}
|
||||
if(block->code() == MetadataBlock::Padding) {
|
||||
delete block;
|
||||
continue;
|
||||
}
|
||||
newBlocks.append(block);
|
||||
@@ -190,7 +192,7 @@ bool FLAC::File::save()
|
||||
// Adjust the padding block(s)
|
||||
|
||||
long originalLength = d->streamStart - d->flacStart;
|
||||
int paddingLength = originalLength - data.size() - 4;
|
||||
int paddingLength = originalLength - data.size() - 4;
|
||||
if (paddingLength < 0) {
|
||||
paddingLength = MinPaddingLength;
|
||||
}
|
||||
@@ -403,6 +405,7 @@ void FLAC::File::scan()
|
||||
}
|
||||
else {
|
||||
debug("FLAC::File::scan() -- invalid picture found, discarting");
|
||||
delete picture;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +415,9 @@ void FLAC::File::scan()
|
||||
if(block->code() != MetadataBlock::Padding) {
|
||||
d->blocks.append(block);
|
||||
}
|
||||
else {
|
||||
delete block;
|
||||
}
|
||||
|
||||
nextBlockOffset += length + 4;
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ void Ogg::XiphComment::parse(const ByteVector &data)
|
||||
|
||||
int pos = 0;
|
||||
|
||||
int vendorLength = data.mid(0, 4).toUInt(false);
|
||||
uint vendorLength = data.mid(0, 4).toUInt(false);
|
||||
pos += 4;
|
||||
|
||||
d->vendorID = String(data.mid(pos, vendorLength), String::UTF8);
|
||||
@@ -295,21 +295,31 @@ void Ogg::XiphComment::parse(const ByteVector &data)
|
||||
|
||||
// Next the number of fields in the comment vector.
|
||||
|
||||
int commentFields = data.mid(pos, 4).toUInt(false);
|
||||
uint commentFields = data.mid(pos, 4).toUInt(false);
|
||||
pos += 4;
|
||||
|
||||
for(int i = 0; i < commentFields; i++) {
|
||||
if(commentFields > (data.size() - 8) / 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(uint i = 0; i < commentFields; i++) {
|
||||
|
||||
// Each comment field is in the format "KEY=value" in a UTF8 string and has
|
||||
// 4 bytes before the text starts that gives the length.
|
||||
|
||||
int commentLength = data.mid(pos, 4).toUInt(false);
|
||||
uint commentLength = data.mid(pos, 4).toUInt(false);
|
||||
pos += 4;
|
||||
|
||||
String comment = String(data.mid(pos, commentLength), String::UTF8);
|
||||
pos += commentLength;
|
||||
if(pos > data.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
int commentSeparatorPosition = comment.find("=");
|
||||
if(commentSeparatorPosition == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
String key = comment.substr(0, commentSeparatorPosition);
|
||||
String value = comment.substr(commentSeparatorPosition + 1);
|
||||
|
||||
@@ -87,6 +87,11 @@ bool RIFF::AIFF::File::save()
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isValid()) {
|
||||
debug("RIFF::AIFF::File::save() -- Trying to save invalid file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
setChunkData(d->tagChunkID, d->tag->render());
|
||||
|
||||
return true;
|
||||
|
||||
@@ -148,6 +148,6 @@ void RIFF::AIFF::Properties::read(const ByteVector &data)
|
||||
d->sampleWidth = data.mid(6, 2).toShort();
|
||||
double sampleRate = ConvertFromIeeeExtended(reinterpret_cast<unsigned char *>(data.mid(8, 10).data()));
|
||||
d->sampleRate = sampleRate;
|
||||
d->bitrate = (sampleRate * d->sampleWidth * d->channels) / 1024.0;
|
||||
d->length = sampleFrames / d->sampleRate;
|
||||
d->bitrate = (sampleRate * d->sampleWidth * d->channels) / 1000.0;
|
||||
d->length = d->sampleRate > 0 ? sampleFrames / d->sampleRate : 0;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,14 @@
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
struct Chunk
|
||||
{
|
||||
ByteVector name;
|
||||
TagLib::uint offset;
|
||||
TagLib::uint size;
|
||||
char padding;
|
||||
};
|
||||
|
||||
class RIFF::File::FilePrivate
|
||||
{
|
||||
public:
|
||||
@@ -43,13 +51,10 @@ public:
|
||||
}
|
||||
Endianness endianness;
|
||||
ByteVector type;
|
||||
uint size;
|
||||
TagLib::uint size;
|
||||
ByteVector format;
|
||||
|
||||
std::vector<ByteVector> chunkNames;
|
||||
std::vector<uint> chunkOffsets;
|
||||
std::vector<uint> chunkSizes;
|
||||
std::vector<char> chunkPadding;
|
||||
std::vector<Chunk> chunks;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -81,22 +86,22 @@ TagLib::uint RIFF::File::riffSize() const
|
||||
|
||||
TagLib::uint RIFF::File::chunkCount() const
|
||||
{
|
||||
return d->chunkNames.size();
|
||||
return d->chunks.size();
|
||||
}
|
||||
|
||||
TagLib::uint RIFF::File::chunkDataSize(uint i) const
|
||||
{
|
||||
return d->chunkSizes[i];
|
||||
return d->chunks[i].size;
|
||||
}
|
||||
|
||||
TagLib::uint RIFF::File::chunkOffset(uint i) const
|
||||
{
|
||||
return d->chunkOffsets[i];
|
||||
return d->chunks[i].offset;
|
||||
}
|
||||
|
||||
TagLib::uint RIFF::File::chunkPadding(uint i) const
|
||||
{
|
||||
return d->chunkPadding[i];
|
||||
return d->chunks[i].padding;
|
||||
}
|
||||
|
||||
ByteVector RIFF::File::chunkName(uint i) const
|
||||
@@ -104,7 +109,7 @@ ByteVector RIFF::File::chunkName(uint i) const
|
||||
if(i >= chunkCount())
|
||||
return ByteVector::null;
|
||||
|
||||
return d->chunkNames[i];
|
||||
return d->chunks[i].name;
|
||||
}
|
||||
|
||||
ByteVector RIFF::File::chunkData(uint i)
|
||||
@@ -117,39 +122,39 @@ ByteVector RIFF::File::chunkData(uint i)
|
||||
long begin = 12 + 8;
|
||||
|
||||
for(uint it = 0; it < i; it++)
|
||||
begin += 8 + d->chunkSizes[it] + d->chunkPadding[it];
|
||||
begin += 8 + d->chunks[it].size + d->chunks[it].padding;
|
||||
|
||||
seek(begin);
|
||||
|
||||
return readBlock(d->chunkSizes[i]);
|
||||
return readBlock(d->chunks[i].size);
|
||||
}
|
||||
|
||||
void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
|
||||
{
|
||||
if(d->chunkNames.size() == 0) {
|
||||
if(d->chunks.size() == 0) {
|
||||
debug("RIFF::File::setChunkData - No valid chunks found.");
|
||||
return;
|
||||
}
|
||||
|
||||
for(uint i = 0; i < d->chunkNames.size(); i++) {
|
||||
if(d->chunkNames[i] == name) {
|
||||
for(uint i = 0; i < d->chunks.size(); i++) {
|
||||
if(d->chunks[i].name == name) {
|
||||
|
||||
// First we update the global size
|
||||
|
||||
d->size += ((data.size() + 1) & ~1) - (d->chunkSizes[i] + d->chunkPadding[i]);
|
||||
d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
|
||||
insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4);
|
||||
|
||||
// Now update the specific chunk
|
||||
|
||||
writeChunk(name, data, d->chunkOffsets[i] - 8, d->chunkSizes[i] + d->chunkPadding[i] + 8);
|
||||
writeChunk(name, data, d->chunks[i].offset - 8, d->chunks[i].size + d->chunks[i].padding + 8);
|
||||
|
||||
d->chunkSizes[i] = data.size();
|
||||
d->chunkPadding[i] = (data.size() & 0x01) ? 1 : 0;
|
||||
d->chunks[i].size = data.size();
|
||||
d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0;
|
||||
|
||||
// Now update the internal offsets
|
||||
|
||||
for(i++; i < d->chunkNames.size(); i++)
|
||||
d->chunkOffsets[i] = d->chunkOffsets[i-1] + 8 + d->chunkSizes[i-1] + d->chunkPadding[i-1];
|
||||
for(i++; i < d->chunks.size(); i++)
|
||||
d->chunks[i].offset = d->chunks[i-1].offset + 8 + d->chunks[i-1].size + d->chunks[i-1].padding;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -157,8 +162,8 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
|
||||
|
||||
// Couldn't find an existing chunk, so let's create a new one.
|
||||
|
||||
uint i = d->chunkNames.size() - 1;
|
||||
ulong offset = d->chunkOffsets[i] + d->chunkSizes[i];
|
||||
uint i = d->chunks.size() - 1;
|
||||
ulong offset = d->chunks[i].offset + d->chunks[i].size;
|
||||
|
||||
// First we update the global size
|
||||
|
||||
@@ -172,19 +177,36 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
|
||||
// And update our internal structure
|
||||
|
||||
if (offset & 1) {
|
||||
d->chunkPadding[i] = 1;
|
||||
d->chunks[i].padding = 1;
|
||||
offset++;
|
||||
}
|
||||
d->chunkNames.push_back(name);
|
||||
d->chunkSizes.push_back(data.size());
|
||||
d->chunkOffsets.push_back(offset + 8);
|
||||
d->chunkPadding.push_back((data.size() & 0x01) ? 1 : 0);
|
||||
|
||||
Chunk chunk;
|
||||
chunk.name = name;
|
||||
chunk.size = data.size();
|
||||
chunk.offset = offset + 8;
|
||||
chunk.padding = (data.size() & 0x01) ? 1 : 0;
|
||||
|
||||
d->chunks.push_back(chunk);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static bool isValidChunkID(const ByteVector &name)
|
||||
{
|
||||
if(name.size() != 4) {
|
||||
return false;
|
||||
}
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(name[i] < 32 || name[i] > 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RIFF::File::read()
|
||||
{
|
||||
bool bigEndian = (d->endianness == BigEndian);
|
||||
@@ -198,20 +220,27 @@ void RIFF::File::read()
|
||||
ByteVector chunkName = readBlock(4);
|
||||
uint chunkSize = readBlock(4).toUInt(bigEndian);
|
||||
|
||||
if(tell() + chunkSize > uint(length())) {
|
||||
// something wrong
|
||||
if(!isValidChunkID(chunkName)) {
|
||||
debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid ID");
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
|
||||
d->chunkNames.push_back(chunkName);
|
||||
d->chunkSizes.push_back(chunkSize);
|
||||
if(tell() + chunkSize > uint(length())) {
|
||||
debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid size (larger than the file size)");
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
|
||||
d->chunkOffsets.push_back(tell());
|
||||
Chunk chunk;
|
||||
chunk.name = chunkName;
|
||||
chunk.size = chunkSize;
|
||||
chunk.offset = tell();
|
||||
|
||||
seek(chunkSize, Current);
|
||||
seek(chunk.size, Current);
|
||||
|
||||
// check padding
|
||||
char paddingSize = 0;
|
||||
chunk.padding = 0;
|
||||
long uPosNotPadded = tell();
|
||||
if((uPosNotPadded & 0x01) != 0) {
|
||||
ByteVector iByte = readBlock(1);
|
||||
@@ -220,10 +249,10 @@ void RIFF::File::read()
|
||||
seek(uPosNotPadded, Beginning);
|
||||
}
|
||||
else {
|
||||
paddingSize = 1;
|
||||
chunk.padding = 1;
|
||||
}
|
||||
}
|
||||
d->chunkPadding.push_back(paddingSize);
|
||||
d->chunks.push_back(chunk);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,11 @@ bool RIFF::WAV::File::save()
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isValid()) {
|
||||
debug("RIFF::WAV::File::save() -- Trying to save invalid file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
setChunkData(d->tagChunkID, d->tag->render());
|
||||
|
||||
return true;
|
||||
|
||||
@@ -114,7 +114,7 @@ void RIFF::WAV::Properties::read(const ByteVector &data)
|
||||
d->sampleWidth = data.mid(14, 2).toShort(false);
|
||||
|
||||
uint byteRate = data.mid(8, 4).toUInt(false);
|
||||
d->bitrate = byteRate * 8 / 1024;
|
||||
d->bitrate = byteRate * 8 / 1000;
|
||||
|
||||
d->length = byteRate > 0 ? d->streamLength / byteRate : 0;
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ ByteVector ByteVector::mid(uint index, uint length) const
|
||||
|
||||
ConstIterator endIt;
|
||||
|
||||
if(length < 0xffffffff && length + index < size())
|
||||
if(length < size() - index)
|
||||
endIt = d->data.begin() + index + length;
|
||||
else
|
||||
endIt = d->data.end();
|
||||
|
||||
@@ -118,19 +118,26 @@ void TrueAudio::Properties::read()
|
||||
int pos = 3;
|
||||
|
||||
d->version = d->data[pos] - '0';
|
||||
pos += 1 + 2;
|
||||
pos += 1;
|
||||
|
||||
d->channels = d->data.mid(pos, 2).toShort(false);
|
||||
pos += 2;
|
||||
// According to http://en.true-audio.com/TTA_Lossless_Audio_Codec_-_Format_Description
|
||||
// TTA2 headers are in development, and have a different format
|
||||
if(1 == d->version) {
|
||||
// Skip the audio format
|
||||
pos += 2;
|
||||
|
||||
d->bitsPerSample = d->data.mid(pos, 2).toShort(false);
|
||||
pos += 2;
|
||||
d->channels = d->data.mid(pos, 2).toShort(false);
|
||||
pos += 2;
|
||||
|
||||
d->sampleRate = d->data.mid(pos, 4).toUInt(false);
|
||||
pos += 4;
|
||||
d->bitsPerSample = d->data.mid(pos, 2).toShort(false);
|
||||
pos += 2;
|
||||
|
||||
unsigned long samples = d->data.mid(pos, 4).toUInt(false);
|
||||
d->length = samples / d->sampleRate;
|
||||
d->sampleRate = d->data.mid(pos, 4).toUInt(false);
|
||||
pos += 4;
|
||||
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
|
||||
uint sampleFrames = d->data.mid(pos, 4).toUInt(false);
|
||||
d->length = d->sampleRate > 0 ? sampleFrames / d->sampleRate : 0;
|
||||
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
tests/data/zero-size-chunk.wav
Normal file
BIN
tests/data/zero-size-chunk.wav
Normal file
Binary file not shown.
@@ -23,7 +23,7 @@ public:
|
||||
string filename = copy.fileName();
|
||||
|
||||
RIFF::AIFF::File *f = new RIFF::AIFF::File(filename.c_str());
|
||||
CPPUNIT_ASSERT_EQUAL(689, f->audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(705, f->audioProperties()->bitrate());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <tstringlist.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <flacfile.h>
|
||||
#include <xiphcomment.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -20,6 +21,7 @@ class TestFLAC : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testReplacePicture);
|
||||
CPPUNIT_TEST(testRemoveAllPictures);
|
||||
CPPUNIT_TEST(testRepeatedSave);
|
||||
CPPUNIT_TEST(testSaveMultipleValues);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -186,6 +188,26 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), tag->title());
|
||||
}
|
||||
|
||||
void testSaveMultipleValues()
|
||||
{
|
||||
ScopedFileCopy copy("silence-44-s", ".flac", false);
|
||||
string newname = copy.fileName();
|
||||
|
||||
FLAC::File *f = new FLAC::File(newname.c_str());
|
||||
Ogg::XiphComment* c = f->xiphComment(true);
|
||||
c->addField("ARTIST", "artist 1", true);
|
||||
c->addField("ARTIST", "artist 2", false);
|
||||
f->save();
|
||||
delete f;
|
||||
|
||||
f = new FLAC::File(newname.c_str());
|
||||
c = f->xiphComment(true);
|
||||
Ogg::FieldListMap m = c->fieldListMap();
|
||||
CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), m["ARTIST"].size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("artist 1"), m["ARTIST"][0]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("artist 2"), m["ARTIST"][1]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC);
|
||||
|
||||
@@ -13,6 +13,7 @@ class TestWAV : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestWAV);
|
||||
CPPUNIT_TEST(testLength);
|
||||
CPPUNIT_TEST(testZeroSizeDataChunk);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -20,9 +21,16 @@ public:
|
||||
void testLength()
|
||||
{
|
||||
RIFF::WAV::File f("data/empty.wav");
|
||||
CPPUNIT_ASSERT_EQUAL(true, f.isValid());
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length());
|
||||
}
|
||||
|
||||
void testZeroSizeDataChunk()
|
||||
{
|
||||
RIFF::WAV::File f("data/zero-size-chunk.wav");
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.isValid());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestWAV);
|
||||
|
||||
Reference in New Issue
Block a user