Enable FileRef to detect file types by the actual content of a stream.

FileRef doesn't work with ByteVectorStream as reported at #796, since ByteVectorStream is not associated with a file name and FileRef detects file types based on file extensions.
This commit makes FileRef to work with ByteVectorStream by enabling it to detect file types based on the actual content of a stream.
This commit is contained in:
Tsuda Kageyu 2017-02-03 17:52:27 +09:00
parent a5d9e49c49
commit 931bb042c3
35 changed files with 613 additions and 120 deletions

View File

@ -83,6 +83,18 @@ public:
Properties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool APE::File::isValidStream(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 ") >= 0);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -211,6 +211,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 isValidStream(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::isValidStream(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

@ -115,6 +115,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 isValidStream(IOStream *stream);
private:
void read();

View File

@ -28,6 +28,7 @@
***************************************************************************/
#include <tfile.h>
#include <tfilestream.h>
#include <tstring.h>
#include <tdebug.h>
#include <trefcounter.h>
@ -59,41 +60,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;
}
@ -101,18 +75,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).toString();
const String s = stream->name().toString();
#else
const String s(toFileName(arg));
const String s(stream->name());
#endif
String ext;
@ -127,49 +98,91 @@ 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, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
return new FLAC::File(stream, ID3v2::FrameFactory::instance(), 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);
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::isValidStream(stream))
file = new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
else if(Ogg::Vorbis::File::isValidStream(stream))
file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::FLAC::File::isValidStream(stream))
file = new Ogg::FLAC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(FLAC::File::isValidStream(stream))
file = new FLAC::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
else if(MPC::File::isValidStream(stream))
file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(WavPack::File::isValidStream(stream))
file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::Speex::File::isValidStream(stream))
file = new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::Opus::File::isValidStream(stream))
file = new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle);
else if(TrueAudio::File::isValidStream(stream))
file = new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle);
else if(MP4::File::isValidStream(stream))
file = new MP4::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ASF::File::isValidStream(stream))
file = new ASF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(RIFF::AIFF::File::isValidStream(stream))
file = new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(RIFF::WAV::File::isValidStream(stream))
file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
else if(APE::File::isValidStream(stream))
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
// isValidStream() only does a quick check, so double check the file here.
if(file) {
if(file->isValid())
return file;
else
delete file;
}
return 0;
}
@ -178,15 +191,18 @@ namespace
class FileRef::FileRefPrivate : public RefCounter
{
public:
FileRefPrivate(File *f) :
FileRefPrivate() :
RefCounter(),
file(f) {}
file(0),
stream(0) {}
~FileRefPrivate() {
delete file;
delete stream;
}
File *file;
File *file;
IOStream *stream;
};
////////////////////////////////////////////////////////////////////////////////
@ -194,24 +210,27 @@ public:
////////////////////////////////////////////////////////////////////////////////
FileRef::FileRef() :
d(new FileRefPrivate(0))
d(new FileRefPrivate())
{
}
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->file = file;
}
FileRef::FileRef(const FileRef &ref) :
@ -331,5 +350,53 @@ bool FileRef::operator!=(const FileRef &ref) const
File *FileRef::create(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle) // static
{
return createInternal(fileName, readAudioProperties, audioPropertiesStyle);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void FileRef::parse(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
// Try user-defined resolvers.
d->file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle);
if(d->file)
return;
// Try to resolve file types based on the file extension.
d->stream = new FileStream(fileName);
d->file = detectByExtension(d->stream, readAudioProperties, audioPropertiesStyle);
if(d->file)
return;
// At last, try to resolve file types based on the actual content.
d->file = detectByContent(d->stream, readAudioProperties, audioPropertiesStyle);
if(d->file)
return;
// Stream have to be closed here if failed to resolve file types.
delete d->stream;
d->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->file = detectByExtension(stream, readAudioProperties, audioPropertiesStyle);
if(d->file)
return;
// At last, try to resolve file types based on the actual content of the file.
d->file = detectByContent(stream, readAudioProperties, audioPropertiesStyle);
}

View File

@ -274,8 +274,10 @@ namespace TagLib {
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

@ -95,6 +95,18 @@ public:
bool scanned;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool FLAC::File::isValidStream(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") >= 0);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -318,6 +318,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 isValidStream(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::Properties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool MP4::File::isValidStream(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

@ -120,6 +120,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 isValidStream(IOStream *stream);
private:
void read(bool readProperties);

View File

@ -75,6 +75,19 @@ public:
Properties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool MPC::File::isValidStream(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

@ -214,6 +214,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 isValidStream(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -76,6 +76,63 @@ public:
Properties *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::isValidStream(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.
const long originalPosition = stream->tell();
long bufferOffset = 0;
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 buffer = stream->readBlock(bufferSize());
AdapterFile file(stream);
for(unsigned int i = 0; i < buffer.size() - 1; ++i) {
if(isFrameSync(buffer, i)) {
const Header header(&file, bufferOffset + i, true);
if(header.isValid()) {
stream->seek(originalPosition);
return true;
}
}
}
stream->seek(originalPosition);
return false;
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -370,6 +370,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 isValidStream(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -197,10 +197,8 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength)
d->version = Version2;
else if(versionBits == 3)
d->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 offset, bool checkLength)
d->layer = 2;
else if(layerBits == 3)
d->layer = 1;
else {
debug("MPEG::Header::parse() -- Invalid MPEG layer bits.");
else
return;
}
d->protectionEnabled = (static_cast<unsigned char>(data[1] & 0x01) == 0);
@ -244,10 +240,8 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength)
d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex];
if(d->bitrate == 0) {
debug("MPEG::Header::parse() -- Invalid bit rate.");
if(d->bitrate == 0)
return;
}
// Set the sample rate
@ -264,7 +258,6 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength)
d->sampleRate = sampleRates[d->version][samplerateIndex];
if(d->sampleRate == 0) {
debug("MPEG::Header::parse() -- Invalid sample rate.");
return;
}
@ -311,20 +304,16 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength)
file->seek(offset + d->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.toUInt(0, true) & HeaderMask;
const unsigned int nextHeader = nextData.toUInt(0, true) & 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,18 @@ public:
int commentPacket;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::FLAC::File::isValidStream(IOStream *stream)
{
// An Ogg FLAC file has IDs "OggS" and "fLaC" somewhere.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
return (buffer.find("OggS") >= 0 && buffer.find("fLaC") >= 0);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -143,6 +143,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 isValidStream(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,18 @@ public:
Properties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::Opus::File::isValidStream(IOStream *stream)
{
// An Opus file has IDs "OggS" and "OpusHead" somewhere.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
return (buffer.find("OggS") >= 0 && buffer.find("OpusHead") >= 0);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -113,6 +113,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 isValidStream(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,18 @@ public:
Properties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::Speex::File::isValidStream(IOStream *stream)
{
// A Speex file has IDs "OggS" and "Speex " somewhere.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
return (buffer.find("OggS") >= 0 && buffer.find("Speex ") >= 0);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -113,6 +113,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 isValidStream(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 Vorbis::File::FilePrivate
@ -59,6 +59,18 @@ namespace TagLib {
static const char vorbisCommentHeaderID[] = { 0x03, 'v', 'o', 'r', 'b', 'i', 's', 0 };
}
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Vorbis::File::isValidStream(IOStream *stream)
{
// An Ogg Vorbis file has IDs "OggS" and "\x01vorbis" somewhere.
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
return (buffer.find("OggS") >= 0 && buffer.find("\x01vorbis") >= 0);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

View File

@ -121,6 +121,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 isValidStream(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::isValidStream(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

@ -126,6 +126,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 isValidStream(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::isValidStream(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

@ -175,6 +175,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 isValidStream(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -77,3 +77,25 @@ long Utils::findAPE(File *file, long id3v1Location)
return -1;
}
ByteVector TagLib::Utils::readHeader(IOStream *stream, unsigned int length, bool skipID3v2)
{
if(!stream || !stream->isOpen())
return ByteVector();
const long originalPosition = stream->tell();
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);
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,8 @@ namespace TagLib {
long findID3v2(File *file);
long findAPE(File *file, long id3v1Location);
ByteVector readHeader(IOStream *stream, unsigned int length, bool skipID3v2);
}
}

View File

@ -73,6 +73,18 @@ public:
Properties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool TrueAudio::File::isValidStream(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

@ -235,6 +235,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 isValidStream(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -71,6 +71,18 @@ public:
Properties *properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool WavPack::File::isValidStream(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

@ -200,6 +200,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 isValidStream(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);

View File

@ -1,27 +1,27 @@
/***************************************************************************
copyright : (C) 2007 by Lukas Lalinsky
email : lukas@oxygene.sk
***************************************************************************/
copyright : (C) 2007 by Lukas Lalinsky
email : lukas@oxygene.sk
***************************************************************************/
/***************************************************************************
* 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., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
* 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., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <string>
#include <stdio.h>
@ -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;
@ -129,6 +130,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 +142,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);
}
}