Merge branch 'master' into merge-master-to-taglib2

# Conflicts:
#	taglib/fileref.cpp
#	taglib/fileref.h
#	taglib/mpeg/mpegheader.cpp
#	taglib/tagutils.h
This commit is contained in:
Tsuda Kageyu 2017-06-12 17:23:44 +09:00
commit 3ae0d4aa90
37 changed files with 726 additions and 111 deletions

4
NEWS
View File

@ -2,13 +2,15 @@
* Added support for WinRT.
* Added support for classical music tags of iTunes 12.5.
* Enabled FileRef to detect file types based on the stream content.
* Dropped support for Windows 9x and NT 4.0 or older.
* Fixed reading MP4 atoms with zero length.
* Fixed reading FLAC files with zero-sized seektables.
* Fixed handling of lowercase field names in Vorbis Comments.
* Fixed handling of 'rate' atoms in MP4 files.
* Fixed handling of invalid UTF-8 sequences.
* Fixed possible file corruptions when saving Ogg files.
* Better handling of invalid UTF-8 sequences.
* Marked FileRef::create() deprecated.
* Several smaller bug fixes and performance improvements.
TagLib 1.11.1 (Oct 24, 2016)

View File

@ -76,6 +76,18 @@ public:
SCOPED_PTR<AudioProperties> properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool APE::File::isSupported(IOStream *stream)
{
// An APE file has an ID "MAC " somewhere. An ID3v2 tag may precede.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true);
return (buffer.find("MAC ") != ByteVector::npos());
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -198,6 +198,15 @@ namespace TagLib {
*/
bool hasID3v1Tag() const;
/*!
* Returns whether or not the given \a stream can be opened as an APE
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -27,6 +27,7 @@
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <tstring.h>
#include <tagutils.h>
#include "asffile.h"
#include "asftag.h"
@ -473,6 +474,18 @@ void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, unsigned in
}
}
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool ASF::File::isSupported(IOStream *stream)
{
// An ASF file has to start with the designated GUID.
const ByteVector id = Utils::readHeader(stream, 16, false);
return (id == headerGuid);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -103,6 +103,15 @@ namespace TagLib {
*/
virtual bool save();
/*!
* Returns whether or not the given \a stream can be opened as an ASF
* 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();

View File

@ -28,6 +28,7 @@
***************************************************************************/
#include <tfile.h>
#include <tfilestream.h>
#include <tstring.h>
#include <tdebug.h>
#include <tsmartptr.h>
@ -60,41 +61,14 @@ namespace
typedef List<const FileRef::FileTypeResolver *> ResolverList;
ResolverList fileTypeResolvers;
// Templatized internal functions. T should be String or IOStream*.
// Detect the file type by user-defined resolvers.
template <typename T>
FileName toFileName(T arg);
template <>
FileName toFileName<IOStream *>(IOStream *arg)
{
return arg->name();
}
template <>
FileName toFileName<FileName>(FileName arg)
{
return arg;
}
template <typename T>
File *resolveFileType(T arg, bool readProperties,
AudioProperties::ReadStyle style);
template <>
File *resolveFileType<IOStream *>(IOStream *arg, bool readProperties,
AudioProperties::ReadStyle style)
{
return 0;
}
template <>
File *resolveFileType<FileName>(FileName arg, bool readProperties,
AudioProperties::ReadStyle style)
File *detectByResolvers(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
ResolverList::ConstIterator it = fileTypeResolvers.begin();
for(; it != fileTypeResolvers.end(); ++it) {
File *file = (*it)->createFile(arg, readProperties, style);
File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle);
if(file)
return file;
}
@ -102,18 +76,15 @@ namespace
return 0;
}
template <typename T>
File* createInternal(T arg, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
File *file = resolveFileType(arg, readAudioProperties, audioPropertiesStyle);
if(file)
return file;
// Detect the file type based on the file extension.
File* detectByExtension(IOStream *stream, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
#ifdef _WIN32
const String s(toFileName(arg).wstr());
const String s(stream->name().wstr());
#else
const String s(toFileName(arg));
const String s(stream->name());
#endif
String ext;
@ -128,66 +99,192 @@ namespace
if(ext.isEmpty())
return 0;
// .oga can be any audio in the Ogg container. So leave it to content-based detection.
if(ext == "MP3")
return new MPEG::File(arg, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
return new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
if(ext == "OGG")
return new Ogg::Vorbis::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "OGA") {
/* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */
File *file = new Ogg::FLAC::File(arg, readAudioProperties, audioPropertiesStyle);
if(file->isValid())
return file;
delete file;
return new Ogg::Vorbis::File(arg, readAudioProperties, audioPropertiesStyle);
}
return new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "FLAC")
return new FLAC::File(arg, readAudioProperties, audioPropertiesStyle);
return new FLAC::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "MPC")
return new MPC::File(arg, readAudioProperties, audioPropertiesStyle);
return new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "WV")
return new WavPack::File(arg, readAudioProperties, audioPropertiesStyle);
return new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "SPX")
return new Ogg::Speex::File(arg, readAudioProperties, audioPropertiesStyle);
return new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "OPUS")
return new Ogg::Opus::File(arg, readAudioProperties, audioPropertiesStyle);
return new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "TTA")
return new TrueAudio::File(arg, readAudioProperties, audioPropertiesStyle);
return new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V")
return new MP4::File(arg, readAudioProperties, audioPropertiesStyle);
return new MP4::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "WMA" || ext == "ASF")
return new ASF::File(arg, readAudioProperties, audioPropertiesStyle);
return new ASF::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
return new RIFF::AIFF::File(arg, readAudioProperties, audioPropertiesStyle);
return new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "WAV")
return new RIFF::WAV::File(arg, readAudioProperties, audioPropertiesStyle);
return new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "APE")
return new APE::File(arg, readAudioProperties, audioPropertiesStyle);
return new APE::File(stream, readAudioProperties, audioPropertiesStyle);
// module, nst and wow are possible but uncommon extensions
if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
return new Mod::File(arg, readAudioProperties, audioPropertiesStyle);
return new Mod::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "S3M")
return new S3M::File(arg, readAudioProperties, audioPropertiesStyle);
return new S3M::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "IT")
return new IT::File(arg, readAudioProperties, audioPropertiesStyle);
return new IT::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "XM")
return new XM::File(arg, readAudioProperties, audioPropertiesStyle);
if (ext == "DSF")
return new DSF::File(arg, readAudioProperties, audioPropertiesStyle);
return new XM::File(stream, readAudioProperties, audioPropertiesStyle);
return 0;
}
// Detect the file type based on the actual content of the stream.
File *detectByContent(IOStream *stream, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
File *file = 0;
if(MPEG::File::isSupported(stream))
file = new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
else if(Ogg::Vorbis::File::isSupported(stream))
file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::FLAC::File::isSupported(stream))
file = new Ogg::FLAC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(FLAC::File::isSupported(stream))
file = new FLAC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(MPC::File::isSupported(stream))
file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(WavPack::File::isSupported(stream))
file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::Speex::File::isSupported(stream))
file = new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::Opus::File::isSupported(stream))
file = new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle);
else if(TrueAudio::File::isSupported(stream))
file = new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle);
else if(MP4::File::isSupported(stream))
file = new MP4::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ASF::File::isSupported(stream))
file = new ASF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(RIFF::AIFF::File::isSupported(stream))
file = new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(RIFF::WAV::File::isSupported(stream))
file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
else if(APE::File::isSupported(stream))
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
// isSupported() only does a quick check, so double check the file here.
if(file) {
if(file->isValid())
return file;
else
delete file;
}
return 0;
}
// Internal function that supports FileRef::create().
// This looks redundant, but necessary in order not to change the previous
// behavior of FileRef::create().
File* createInternal(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
File *file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle);
if(file)
return file;
#ifdef _WIN32
const String s(fileName.wstr());
#else
const String s(fileName);
#endif
String ext;
const size_t pos = s.rfind(".");
if(pos != String::npos())
ext = s.substr(pos + 1).upper();
if(ext.isEmpty())
return 0;
if(ext == "MP3")
return new MPEG::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "OGG")
return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "OGA") {
/* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */
File *file = new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle);
if(file->isValid())
return file;
delete file;
return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle);
}
if(ext == "FLAC")
return new FLAC::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "MPC")
return new MPC::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "WV")
return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "SPX")
return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "OPUS")
return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "TTA")
return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V")
return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "WMA" || ext == "ASF")
return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "WAV")
return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "APE")
return new APE::File(fileName, readAudioProperties, audioPropertiesStyle);
// module, nst and wow are possible but uncommon extensions
if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
return new Mod::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "S3M")
return new S3M::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "IT")
return new IT::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "XM")
return new XM::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "DSF")
return new DSF::File(fileName, readAudioProperties, audioPropertiesStyle);
return 0;
}
struct FileRefData
{
FileRefData() :
file(0),
stream(0) {}
~FileRefData() {
delete file;
delete stream;
}
File *file;
IOStream *stream;
};
}
class FileRef::FileRefPrivate
{
public:
FileRefPrivate() :
file() {}
data(new FileRefData()) {}
explicit FileRefPrivate(File *f) :
file(f) {}
SHARED_PTR<File> file;
SHARED_PTR<FileRefData> data;
};
////////////////////////////////////////////////////////////////////////////////
@ -201,18 +298,21 @@ FileRef::FileRef() :
FileRef::FileRef(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle) :
d(new FileRefPrivate(createInternal(fileName, readAudioProperties, audioPropertiesStyle)))
d(new FileRefPrivate())
{
parse(fileName, readAudioProperties, audioPropertiesStyle);
}
FileRef::FileRef(IOStream* stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) :
d(new FileRefPrivate(createInternal(stream, readAudioProperties, audioPropertiesStyle)))
d(new FileRefPrivate())
{
parse(stream, readAudioProperties, audioPropertiesStyle);
}
FileRef::FileRef(File *file) :
d(new FileRefPrivate(file))
d(new FileRefPrivate())
{
d->data->file = file;
}
FileRef::FileRef(const FileRef &ref) :
@ -231,7 +331,7 @@ Tag *FileRef::tag() const
debug("FileRef::tag() - Called without a valid file.");
return 0;
}
return d->file->tag();
return d->data->file->tag();
}
PropertyMap FileRef::properties() const
@ -241,7 +341,7 @@ PropertyMap FileRef::properties() const
return PropertyMap();
}
return d->file->properties();
return d->data->file->properties();
}
void FileRef::removeUnsupportedProperties(const StringList& properties)
@ -251,7 +351,7 @@ void FileRef::removeUnsupportedProperties(const StringList& properties)
return;
}
d->file->removeUnsupportedProperties(properties);
d->data->file->removeUnsupportedProperties(properties);
}
PropertyMap FileRef::setProperties(const PropertyMap &properties)
@ -261,7 +361,7 @@ PropertyMap FileRef::setProperties(const PropertyMap &properties)
return PropertyMap();
}
return d->file->setProperties(properties);
return d->data->file->setProperties(properties);
}
AudioProperties *FileRef::audioProperties() const
@ -270,12 +370,12 @@ AudioProperties *FileRef::audioProperties() const
debug("FileRef::audioProperties() - Called without a valid file.");
return 0;
}
return d->file->audioProperties();
return d->data->file->audioProperties();
}
File *FileRef::file() const
{
return d->file.get();
return d->data->file;
}
bool FileRef::save()
@ -284,7 +384,7 @@ bool FileRef::save()
debug("FileRef::save() - Called without a valid file.");
return false;
}
return d->file->save();
return d->data->file->save();
}
const FileRef::FileTypeResolver *FileRef::addFileTypeResolver(const FileRef::FileTypeResolver *resolver) // static
@ -332,7 +432,7 @@ StringList FileRef::defaultFileExtensions()
bool FileRef::isValid() const
{
return (d->file && d->file->isValid());
return (d->data->file && d->data->file->isValid());
}
bool FileRef::isNull() const
@ -355,10 +455,64 @@ void FileRef::swap(FileRef &ref)
bool FileRef::operator==(const FileRef &ref) const
{
return (ref.d->file == d->file);
return (ref.d->data == d->data);
}
bool FileRef::operator!=(const FileRef &ref) const
{
return (ref.d->file != d->file);
return (ref.d->data != d->data);
}
File *FileRef::create(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle) // static
{
return createInternal(fileName, readAudioProperties, audioPropertiesStyle);
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void FileRef::parse(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
// Try user-defined resolvers.
d->data->file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle);
if(d->data->file)
return;
// Try to resolve file types based on the file extension.
d->data->stream = new FileStream(fileName);
d->data->file = detectByExtension(d->data->stream, readAudioProperties, audioPropertiesStyle);
if(d->data->file)
return;
// At last, try to resolve file types based on the actual content.
d->data->file = detectByContent(d->data->stream, readAudioProperties, audioPropertiesStyle);
if(d->data->file)
return;
// Stream have to be closed here if failed to resolve file types.
delete d->data->stream;
d->data->stream = 0;
}
void FileRef::parse(IOStream *stream, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
// User-defined resolvers won't work with a stream.
// Try to resolve file types based on the file extension.
d->data->file = detectByExtension(stream, readAudioProperties, audioPropertiesStyle);
if(d->data->file)
return;
// At last, try to resolve file types based on the actual content of the file.
d->data->file = detectByContent(stream, readAudioProperties, audioPropertiesStyle);
}

View File

@ -304,7 +304,25 @@ namespace TagLib {
*/
bool operator!=(const FileRef &ref) const;
/*!
* A simple implementation of file type guessing. If \a readAudioProperties
* is true then the audio properties will be read using
* \a audioPropertiesStyle. If \a readAudioProperties is false then
* \a audioPropertiesStyle will be ignored.
*
* \note You generally shouldn't use this method, but instead the constructor
* directly.
*
* \deprecated
*/
static File *create(FileName fileName,
bool readAudioProperties = true,
AudioProperties::ReadStyle audioPropertiesStyle = AudioProperties::Average);
private:
void parse(FileName fileName, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle);
void parse(IOStream *stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle);
class FileRefPrivate;
FileRefPrivate *d;
};

View File

@ -98,6 +98,18 @@ public:
bool scanned;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool FLAC::File::isSupported(IOStream *stream)
{
// A FLAC file has an ID "fLaC" somewhere. An ID3v2 tag may precede.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true);
return (buffer.find("fLaC") != ByteVector::npos());
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -280,6 +280,15 @@ namespace TagLib {
*/
bool hasID3v2Tag() const;
/*!
* Returns whether or not the given \a stream can be opened as a FLAC
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -26,6 +26,8 @@
#include <tdebug.h>
#include <tstring.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "mp4atom.h"
#include "mp4tag.h"
#include "mp4file.h"
@ -69,6 +71,22 @@ public:
MP4::AudioProperties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool MP4::File::isSupported(IOStream *stream)
{
// An MP4 file has to have an "ftyp" box first.
const ByteVector id = Utils::readHeader(stream, 8, false);
return id.containsAt("ftyp", 4);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate())

View File

@ -104,6 +104,15 @@ namespace TagLib {
*/
bool hasMP4Tag() const;
/*!
* Returns whether or not the given \a stream can be opened as an ASF
* 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(bool readProperties);

View File

@ -75,6 +75,19 @@ public:
AudioProperties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool MPC::File::isSupported(IOStream *stream)
{
// A newer MPC file has to start with "MPCK" or "MP+", but older files don't
// have keys to do a quick check.
const ByteVector id = Utils::readHeader(stream, 4, false);
return (id == "MPCK" || id.startsWith("MP+"));
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -205,6 +205,15 @@ namespace TagLib {
*/
bool hasAPETag() const;
/*!
* Returns whether or not the given \a stream can be opened as an MPC
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -29,6 +29,8 @@
#include "id3v2tag.h"
#include "id3v2frame.h"
#include "tbytevectorlist.h"
namespace TagLib {
namespace ID3v2 {

View File

@ -76,6 +76,55 @@ public:
AudioProperties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
namespace
{
// Dummy file class to make a stream work with MPEG::Header.
class AdapterFile : public TagLib::File
{
public:
AdapterFile(IOStream *stream) : File(stream) {}
Tag *tag() const { return 0; }
AudioProperties *audioProperties() const { return 0; }
bool save() { return false; }
};
}
bool MPEG::File::isSupported(IOStream *stream)
{
if(!stream || !stream->isOpen())
return false;
// An MPEG file has MPEG frame headers. An ID3v2 tag may precede.
// MPEG frame headers are really confusing with irrelevant binary data.
// So we check if a frame header is really valid.
long long headerOffset;
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true, &headerOffset);
const long long originalPosition = stream->tell();
AdapterFile file(stream);
for(unsigned int i = 0; i < buffer.size() - 1; ++i) {
if(isFrameSync(buffer, i)) {
const Header header(&file, headerOffset + i, true);
if(header.isValid()) {
stream->seek(originalPosition);
return true;
}
}
}
stream->seek(originalPosition);
return false;
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -321,6 +321,15 @@ namespace TagLib {
*/
bool hasAPETag() const;
/*!
* Returns whether or not the given \a stream can be opened as an MPEG
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -197,10 +197,8 @@ void MPEG::Header::parse(File *file, long long offset, bool checkLength)
d->data->version = Version2;
else if(versionBits == 3)
d->data->version = Version1;
else {
debug("MPEG::Header::parse() -- Invalid MPEG version bits.");
else
return;
}
// Set the MPEG layer
@ -212,10 +210,8 @@ void MPEG::Header::parse(File *file, long long offset, bool checkLength)
d->data->layer = 2;
else if(layerBits == 3)
d->data->layer = 1;
else {
debug("MPEG::Header::parse() -- Invalid MPEG layer bits.");
else
return;
}
d->data->protectionEnabled = (static_cast<unsigned char>(data[1] & 0x01) == 0);
@ -244,10 +240,8 @@ void MPEG::Header::parse(File *file, long long offset, bool checkLength)
d->data->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex];
if(d->data->bitrate == 0) {
debug("MPEG::Header::parse() -- Invalid bit rate.");
if(d->data->bitrate == 0)
return;
}
// Set the sample rate
@ -264,7 +258,6 @@ void MPEG::Header::parse(File *file, long long offset, bool checkLength)
d->data->sampleRate = sampleRates[d->data->version][samplerateIndex];
if(d->data->sampleRate == 0) {
debug("MPEG::Header::parse() -- Invalid sample rate.");
return;
}
@ -311,20 +304,16 @@ void MPEG::Header::parse(File *file, long long offset, bool checkLength)
file->seek(offset + d->data->frameLength);
const ByteVector nextData = file->readBlock(4);
if(nextData.size() < 4) {
debug("MPEG::Header::parse() -- Could not read the next frame header.");
if(nextData.size() < 4)
return;
}
const unsigned int HeaderMask = 0xfffe0c00;
const unsigned int header = data.toUInt32BE(0) & HeaderMask;
const unsigned int nextHeader = nextData.toUInt32BE(0) & HeaderMask;
if(header != nextHeader) {
debug("MPEG::Header::parse() -- The next frame was not consistent with this frame.");
if(header != nextHeader)
return;
}
}
// Now that we're done parsing, set this to be a valid frame.

View File

@ -45,12 +45,12 @@ namespace TagLib
* \note This does not check the length of the vector, since this is an
* internal utility function.
*/
inline bool isFrameSync(const ByteVector &bytes)
inline bool isFrameSync(const ByteVector &bytes, unsigned int offset = 0)
{
// 0xFF in the second byte is possible in theory, but it's very unlikely.
const unsigned char b1 = bytes[0];
const unsigned char b2 = bytes[1];
const unsigned char b1 = bytes[offset + 0];
const unsigned char b2 = bytes[offset + 1];
return (b1 == 0xFF && b2 != 0xFF && (b2 & 0xE0) == 0xE0);
}

View File

@ -27,6 +27,7 @@
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include <xiphcomment.h>
#include "oggflacfile.h"
@ -65,6 +66,19 @@ public:
int commentPacket;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::FLAC::File::isSupported(IOStream *stream)
{
// An Ogg FLAC file has IDs "OggS" and "fLaC" somewhere.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
return (buffer.find("OggS") != ByteVector::npos()
&& buffer.find("fLaC") != ByteVector::npos());
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -129,6 +129,14 @@ namespace TagLib {
*/
bool hasXiphComment() const;
/*!
* Check if the given \a stream can be opened as an Ogg FLAC file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -30,6 +30,7 @@
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "opusfile.h"
@ -53,6 +54,19 @@ public:
AudioProperties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::Opus::File::isSupported(IOStream *stream)
{
// An Opus file has IDs "OggS" and "OpusHead" somewhere.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
return (buffer.find("OggS") != ByteVector::npos()
&& buffer.find("OpusHead") != ByteVector::npos());
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -101,6 +101,15 @@ namespace TagLib {
*/
virtual bool save();
/*!
* Returns whether or not the given \a stream can be opened as an Opus
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -30,6 +30,7 @@
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "speexfile.h"
@ -53,6 +54,19 @@ public:
AudioProperties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::Speex::File::isSupported(IOStream *stream)
{
// A Speex file has IDs "OggS" and "Speex " somewhere.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
return (buffer.find("OggS") != ByteVector::npos()
&& buffer.find("Speex ") != ByteVector::npos());
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -101,6 +101,15 @@ namespace TagLib {
*/
virtual bool save();
/*!
* Returns whether or not the given \a stream can be opened as a Speex
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -28,10 +28,10 @@
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "vorbisfile.h"
using namespace TagLib;
class Ogg::Vorbis::File::FilePrivate
@ -59,6 +59,19 @@ namespace TagLib {
static const char vorbisCommentHeaderID[] = { 0x03, 'v', 'o', 'r', 'b', 'i', 's', 0 };
}
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::Vorbis::File::isSupported(IOStream *stream)
{
// An Ogg Vorbis file has IDs "OggS" and "\x01vorbis" somewhere.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
return (buffer.find("OggS") != ByteVector::npos()
&& buffer.find("\x01vorbis") != ByteVector::npos());
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -99,6 +99,14 @@ namespace TagLib {
*/
virtual bool save();
/*!
* Check if the given \a stream can be opened as an Ogg Vorbis file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -28,6 +28,7 @@
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "aifffile.h"
@ -53,6 +54,18 @@ public:
bool hasID3v2;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool RIFF::AIFF::File::isSupported(IOStream *stream)
{
// An AIFF file has to start with "FORM????AIFF" or "FORM????AIFC".
const ByteVector id = Utils::readHeader(stream, 12, false);
return (id.startsWith("FORM") && (id.containsAt("AIFF", 8) || id.containsAt("AIFC", 8)));
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -112,6 +112,14 @@ namespace TagLib {
*/
bool hasID3v2Tag() const;
/*!
* Check if the given \a stream can be opened as an AIFF file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -23,10 +23,11 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "tbytevector.h"
#include "tdebug.h"
#include "tstringlist.h"
#include "tpropertymap.h"
#include <tbytevector.h>
#include <tdebug.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "wavfile.h"
#include "id3v2tag.h"
@ -60,6 +61,18 @@ public:
bool hasInfo;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool RIFF::WAV::File::isSupported(IOStream *stream)
{
// A WAV file has to start with "RIFF????WAVE".
const ByteVector id = Utils::readHeader(stream, 12, false);
return (id.startsWith("RIFF") && id.containsAt("WAVE", 8));
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -165,6 +165,15 @@ namespace TagLib {
*/
bool hasInfoTag() const;
/*!
* Returns whether or not the given \a stream can be opened as a WAV
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -77,3 +77,29 @@ long long Utils::findAPE(File *file, long long id3v1Location)
return -1;
}
ByteVector TagLib::Utils::readHeader(IOStream *stream, size_t length,
bool skipID3v2, long long *headerOffset)
{
if(!stream || !stream->isOpen())
return ByteVector();
const long long originalPosition = stream->tell();
long long bufferOffset = 0;
if(skipID3v2) {
stream->seek(0);
const ByteVector data = stream->readBlock(ID3v2::Header::size());
if(data.startsWith(ID3v2::Header::fileIdentifier()))
bufferOffset = ID3v2::Header(data).completeTagSize();
}
stream->seek(bufferOffset);
const ByteVector header = stream->readBlock(length);
stream->seek(originalPosition);
if(headerOffset)
*headerOffset = bufferOffset;
return header;
}

View File

@ -30,9 +30,12 @@
#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header
#include <tbytevector.h>
namespace TagLib {
class File;
class IOStream;
namespace Utils {
@ -41,6 +44,9 @@ namespace TagLib {
long long findID3v2(File *file);
long long findAPE(File *file, long long id3v1Location);
ByteVector readHeader(IOStream *stream, size_t length, bool skipID3v2,
long long *headerOffset = 0);
}
}

View File

@ -75,6 +75,18 @@ public:
AudioProperties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool TrueAudio::File::isSupported(IOStream *stream)
{
// A TrueAudio file has to start with "TTA". An ID3v2 tag may precede.
const ByteVector id = Utils::readHeader(stream, 3, true);
return (id == "TTA");
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -226,6 +226,15 @@ namespace TagLib {
*/
bool hasID3v2Tag() const;
/*!
* Returns whether or not the given \a stream can be opened as a TrueAudio
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -71,6 +71,18 @@ public:
AudioProperties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool WavPack::File::isSupported(IOStream *stream)
{
// A WavPack file has to start with "wvpk".
const ByteVector id = Utils::readHeader(stream, 4, false);
return (id == "wvpk");
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -191,6 +191,14 @@ namespace TagLib {
*/
bool hasAPETag() const;
/*!
* Check if the given \a stream can be opened as a WavPack file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -39,9 +39,10 @@
#include <wavfile.h>
#include <apefile.h>
#include <aifffile.h>
#include <tfilestream.h>
#include <tbytevectorstream.h>
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"
#include <tfilestream.h>
using namespace std;
using namespace TagLib;
@ -79,6 +80,7 @@ class TestFileRef : public CppUnit::TestFixture
CPPUNIT_TEST(testAIFF_1);
CPPUNIT_TEST(testAIFF_2);
CPPUNIT_TEST(testUnsupported);
CPPUNIT_TEST(testCreate);
CPPUNIT_TEST(testFileResolver);
CPPUNIT_TEST_SUITE_END();
@ -129,6 +131,7 @@ public:
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7);
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080);
}
{
FileStream fs(newname.c_str());
FileRef f(&fs);
@ -140,6 +143,64 @@ public:
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm"));
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7);
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080);
f.tag()->setArtist("test artist");
f.tag()->setTitle("test title");
f.tag()->setGenre("Test!");
f.tag()->setAlbum("albummmm");
f.tag()->setTrack(5);
f.tag()->setYear(2020);
f.save();
}
ByteVector fileContent;
{
FileStream fs(newname.c_str());
FileRef f(&fs);
CPPUNIT_ASSERT(dynamic_cast<T*>(f.file()));
CPPUNIT_ASSERT(!f.isNull());
CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("test artist"));
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title"));
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!"));
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm"));
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5);
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020);
fs.seek(0);
fileContent = fs.readBlock(fs.length());
}
{
ByteVectorStream bs(fileContent);
FileRef f(&bs);
CPPUNIT_ASSERT(dynamic_cast<T*>(f.file()));
CPPUNIT_ASSERT(!f.isNull());
CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("test artist"));
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title"));
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!"));
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm"));
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5);
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020);
f.tag()->setArtist("ttest artist");
f.tag()->setTitle("ytest title");
f.tag()->setGenre("uTest!");
f.tag()->setAlbum("ialbummmm");
f.tag()->setTrack(7);
f.tag()->setYear(2080);
f.save();
fileContent = *bs.data();
}
{
ByteVectorStream bs(fileContent);
FileRef f(&bs);
CPPUNIT_ASSERT(dynamic_cast<T*>(f.file()));
CPPUNIT_ASSERT(!f.isNull());
CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("ttest artist"));
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title"));
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!"));
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm"));
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7);
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080);
}
}
@ -237,6 +298,19 @@ public:
CPPUNIT_ASSERT(f2.isNull());
}
void testCreate()
{
// This is depricated. But worth it to test.
File *f = FileRef::create(TEST_FILE_PATH_C("empty_vorbis.oga"));
CPPUNIT_ASSERT(dynamic_cast<Ogg::Vorbis::File*>(f));
delete f;
f = FileRef::create(TEST_FILE_PATH_C("xing.mp3"));
CPPUNIT_ASSERT(dynamic_cast<MPEG::File*>(f));
delete f;
}
void testFileResolver()
{
{