mirror of
https://github.com/taglib/taglib.git
synced 2025-05-25 12:10:26 -04:00
DSF: Support frame factory, ID3v2.3, allow trailing garbage
Additionally test coverage and formatting are improved.
This commit is contained in:
parent
39d1d68237
commit
eaf7ff8b94
@ -36,12 +36,18 @@ using namespace TagLib;
|
||||
class DSF::File::FilePrivate
|
||||
{
|
||||
public:
|
||||
FilePrivate() = default;
|
||||
FilePrivate(ID3v2::FrameFactory *frameFactory)
|
||||
: ID3v2FrameFactory(frameFactory ? frameFactory
|
||||
: ID3v2::FrameFactory::instance())
|
||||
{
|
||||
}
|
||||
|
||||
~FilePrivate() = default;
|
||||
|
||||
FilePrivate(const FilePrivate &) = delete;
|
||||
FilePrivate &operator=(const FilePrivate &) = delete;
|
||||
|
||||
const ID3v2::FrameFactory *ID3v2FrameFactory;
|
||||
long long fileSize = 0;
|
||||
long long metadataOffset = 0;
|
||||
std::unique_ptr<Properties> properties;
|
||||
@ -56,18 +62,20 @@ bool DSF::File::isSupported(IOStream *stream)
|
||||
}
|
||||
|
||||
DSF::File::File(FileName file, bool readProperties,
|
||||
AudioProperties::ReadStyle propertiesStyle) :
|
||||
AudioProperties::ReadStyle propertiesStyle,
|
||||
ID3v2::FrameFactory *frameFactory) :
|
||||
TagLib::File(file),
|
||||
d(std::make_unique<FilePrivate>())
|
||||
d(std::make_unique<FilePrivate>(frameFactory))
|
||||
{
|
||||
if(isOpen())
|
||||
read(propertiesStyle);
|
||||
}
|
||||
|
||||
DSF::File::File(IOStream *stream, bool readProperties,
|
||||
AudioProperties::ReadStyle propertiesStyle) :
|
||||
AudioProperties::ReadStyle propertiesStyle,
|
||||
ID3v2::FrameFactory *frameFactory) :
|
||||
TagLib::File(stream),
|
||||
d(std::make_unique<FilePrivate>())
|
||||
d(std::make_unique<FilePrivate>(frameFactory))
|
||||
{
|
||||
if(isOpen())
|
||||
read(propertiesStyle);
|
||||
@ -96,6 +104,11 @@ DSF::Properties *DSF::File::audioProperties() const
|
||||
}
|
||||
|
||||
bool DSF::File::save()
|
||||
{
|
||||
return save(ID3v2::v4);
|
||||
}
|
||||
|
||||
bool DSF::File::save(ID3v2::Version version)
|
||||
{
|
||||
if(readOnly()) {
|
||||
debug("DSF::File::save() - Cannot save to a read only file.");
|
||||
@ -123,7 +136,7 @@ bool DSF::File::save()
|
||||
truncate(newFileSize);
|
||||
}
|
||||
else {
|
||||
ByteVector tagData = d->tag->render();
|
||||
ByteVector tagData = d->tag->render(version);
|
||||
|
||||
long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize;
|
||||
long long newFileSize = newMetadataOffset + tagData.size();
|
||||
@ -164,19 +177,19 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle)
|
||||
return;
|
||||
}
|
||||
|
||||
long long chunkSize = readBlock(8).toLongLong(false);
|
||||
long long dsdHeaderSize = readBlock(8).toLongLong(false);
|
||||
|
||||
// Integrity check
|
||||
if(chunkSize != 28) {
|
||||
debug("DSF::File::read() -- File is corrupted, wrong chunk size");
|
||||
if(dsdHeaderSize != 28) {
|
||||
debug("DSF::File::read() -- File is corrupted, wrong DSD header size");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
d->fileSize = readBlock(8).toLongLong(false);
|
||||
|
||||
// File is malformed or corrupted
|
||||
if(d->fileSize != length()) {
|
||||
// File is malformed or corrupted, allow trailing garbage
|
||||
if(d->fileSize > length()) {
|
||||
debug("DSF::File::read() -- File is corrupted wrong length");
|
||||
setValid(false);
|
||||
return;
|
||||
@ -199,9 +212,14 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle)
|
||||
return;
|
||||
}
|
||||
|
||||
chunkSize = readBlock(8).toLongLong(false);
|
||||
long long fmtHeaderSize = readBlock(8).toLongLong(false);
|
||||
if(fmtHeaderSize != 52) {
|
||||
debug("DSF::File::read() -- File is corrupted, wrong FMT header size");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
d->properties = std::make_unique<Properties>(readBlock(chunkSize), propertiesStyle);
|
||||
d->properties = std::make_unique<Properties>(readBlock(fmtHeaderSize), propertiesStyle);
|
||||
|
||||
// Skip the data chunk
|
||||
|
||||
@ -209,5 +227,6 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle)
|
||||
if(d->metadataOffset == 0)
|
||||
d->tag = std::make_unique<ID3v2::Tag>();
|
||||
else
|
||||
d->tag = std::make_unique<ID3v2::Tag>(this, d->metadataOffset);
|
||||
d->tag = std::make_unique<ID3v2::Tag>(this, d->metadataOffset,
|
||||
d->ID3v2FrameFactory);
|
||||
}
|
||||
|
@ -45,10 +45,14 @@ namespace TagLib {
|
||||
* \note In the current implementation, both \a readProperties and
|
||||
* \a propertiesStyle are ignored. The audio properties are always
|
||||
* read.
|
||||
*
|
||||
* If this file contains and ID3v2 tag the frames will be created using
|
||||
* \a frameFactory (default if null).
|
||||
*/
|
||||
File(FileName file, bool readProperties = true,
|
||||
AudioProperties::ReadStyle propertiesStyle =
|
||||
AudioProperties::Average);
|
||||
AudioProperties::Average,
|
||||
ID3v2::FrameFactory *frameFactory = nullptr);
|
||||
|
||||
/*!
|
||||
* Constructs a DSD stream file from \a stream.
|
||||
@ -57,18 +61,25 @@ namespace TagLib {
|
||||
* \a propertiesStyle are ignored. The audio properties are always
|
||||
* read.
|
||||
*
|
||||
* If this file contains and ID3v2 tag the frames will be created using
|
||||
* \a frameFactory (default if null).
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
AudioProperties::ReadStyle propertiesStyle =
|
||||
AudioProperties::Average);
|
||||
AudioProperties::Average,
|
||||
ID3v2::FrameFactory *frameFactory = nullptr);
|
||||
|
||||
/*!
|
||||
* Destroys this instance of the File.
|
||||
*/
|
||||
~File() override;
|
||||
|
||||
/*!
|
||||
* Returns the ID3v2 Tag for this file.
|
||||
*/
|
||||
ID3v2::Tag *tag() const override;
|
||||
|
||||
/*!
|
||||
@ -91,19 +102,26 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Save the file.
|
||||
*
|
||||
*
|
||||
* This returns true if the save was successful.
|
||||
*/
|
||||
bool save() override;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the given \a stream can be opened as a DSF
|
||||
* file.
|
||||
*
|
||||
* \note This method is designed to do a quick check. The result may
|
||||
* not necessarily be correct.
|
||||
*/
|
||||
static bool isSupported(IOStream *stream);
|
||||
/*!
|
||||
* Save the file.
|
||||
*
|
||||
* \a version specifies the ID3v2 version to be used for writing tags.
|
||||
*/
|
||||
bool save(ID3v2::Version version);
|
||||
|
||||
/*!
|
||||
* Returns whether or not the given \a stream can be opened as a DSF
|
||||
* file.
|
||||
*
|
||||
* \note This method is designed to do a quick check. The result may
|
||||
* not necessarily be correct.
|
||||
*/
|
||||
static bool isSupported(IOStream *stream);
|
||||
|
||||
private:
|
||||
void read(AudioProperties::ReadStyle propertiesStyle);
|
||||
|
@ -117,17 +117,18 @@ int DSF::Properties::blockSizePerChannel() const
|
||||
|
||||
void DSF::Properties::read(const ByteVector &data)
|
||||
{
|
||||
d->formatVersion = data.toUInt(0U,false);
|
||||
d->formatID = data.toUInt(4U,false);
|
||||
d->channelType = data.toUInt(8U,false);
|
||||
d->channelNum = data.toUInt(12U,false);
|
||||
d->samplingFrequency = data.toUInt(16U,false);
|
||||
d->bitsPerSample = data.toUInt(20U,false);
|
||||
d->sampleCount = data.toLongLong(24U,false);
|
||||
d->blockSizePerChannel = data.toUInt(32U,false);
|
||||
d->formatVersion = data.toUInt(0U,false);
|
||||
d->formatID = data.toUInt(4U,false);
|
||||
d->channelType = data.toUInt(8U,false);
|
||||
d->channelNum = data.toUInt(12U,false);
|
||||
d->samplingFrequency = data.toUInt(16U,false);
|
||||
d->bitsPerSample = data.toUInt(20U,false);
|
||||
d->sampleCount = data.toLongLong(24U,false);
|
||||
d->blockSizePerChannel = data.toUInt(32U,false);
|
||||
|
||||
d->bitrate
|
||||
= static_cast<unsigned int>((d->samplingFrequency * d->bitsPerSample * d->channelNum) / 1000.0 + 0.5);
|
||||
d->length
|
||||
= d->samplingFrequency > 0 ? static_cast<unsigned int>(d->sampleCount * 1000.0 / d->samplingFrequency + 0.5) : 0;
|
||||
d->bitrate = static_cast<unsigned int>(
|
||||
(d->samplingFrequency * d->bitsPerSample * d->channelNum) / 1000.0 + 0.5);
|
||||
d->length = d->samplingFrequency > 0
|
||||
? static_cast<unsigned int>(d->sampleCount * 1000.0 / d->samplingFrequency + 0.5)
|
||||
: 0;
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <dsffile.h>
|
||||
#include <cstdio>
|
||||
|
||||
#include "tbytevectorlist.h"
|
||||
#include "tpropertymap.h"
|
||||
#include "tag.h"
|
||||
#include "dsffile.h"
|
||||
#include "plainfile.h"
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
@ -22,7 +25,6 @@ public:
|
||||
{
|
||||
DSF::File f(TEST_FILE_PATH_C("empty10ms.dsf"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length());
|
||||
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(10, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(5645, f.audioProperties()->bitrate());
|
||||
@ -41,15 +43,30 @@ public:
|
||||
ScopedFileCopy copy("empty10ms", ".dsf");
|
||||
string newname = copy.fileName();
|
||||
|
||||
DSF::File *f = new DSF::File(newname.c_str());
|
||||
CPPUNIT_ASSERT_EQUAL(String(""), f->tag()->artist());
|
||||
f->tag()->setArtist("The Artist");
|
||||
f->save();
|
||||
delete f;
|
||||
{
|
||||
DSF::File f(newname.c_str());
|
||||
CPPUNIT_ASSERT(f.properties().isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL(String(""), f.tag()->artist());
|
||||
f.tag()->setArtist("The Artist");
|
||||
PropertyMap properties = f.properties();
|
||||
properties["ALBUMARTIST"] = StringList("Album Artist");
|
||||
f.setProperties(properties);
|
||||
f.save();
|
||||
}
|
||||
|
||||
f = new DSF::File(newname.c_str());
|
||||
CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist());
|
||||
delete f;
|
||||
{
|
||||
DSF::File f(newname.c_str());
|
||||
CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.tag()->artist());
|
||||
CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.properties()["ARTIST"].front());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Album Artist"), f.properties()["ALBUMARTIST"].front());
|
||||
f.setProperties(PropertyMap());
|
||||
f.save();
|
||||
}
|
||||
|
||||
// Check if file without tags is same as original empty file
|
||||
const ByteVector dsfData = PlainFile(TEST_FILE_PATH_C("empty10ms.dsf")).readAll();
|
||||
const ByteVector fileData = PlainFile(newname.c_str()).readAll();
|
||||
CPPUNIT_ASSERT(dsfData == fileData);
|
||||
}
|
||||
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user