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.
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <tstring.h>
+#include <tagutils.h>
#include "asffile.h"
#include "asftag.h"
}
}
+////////////////////////////////////////////////////////////////////////////////
+// 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
////////////////////////////////////////////////////////////////////////////////
*/
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();
***************************************************************************/
#include <tfile.h>
+#include <tfilestream.h>
#include <tstring.h>
#include <tdebug.h>
#include <trefcounter.h>
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;
}
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;
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;
}
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;
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
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) :
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);
}
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;
};
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
#include <tdebug.h>
#include <tstring.h>
#include <tpropertymap.h>
+#include <tagutils.h>
+
#include "mp4atom.h"
#include "mp4tag.h"
#include "mp4file.h"
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())
*/
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);
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
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
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);
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
d->sampleRate = sampleRates[d->version][samplerateIndex];
if(d->sampleRate == 0) {
- debug("MPEG::Header::parse() -- Invalid sample rate.");
return;
}
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.
* \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);
}
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
+#include <tagutils.h>
#include <xiphcomment.h>
#include "oggflacfile.h"
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
+#include <tagutils.h>
#include "opusfile.h"
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
+#include <tagutils.h>
#include "speexfile.h"
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
+#include <tagutils.h>
#include "vorbisfile.h"
-
using namespace TagLib;
class Vorbis::File::FilePrivate
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
+#include <tagutils.h>
#include "aifffile.h"
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
* 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"
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
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;
+}
#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header
+#include <tbytevector.h>
+
namespace TagLib {
class File;
+ class IOStream;
namespace Utils {
long findID3v2(File *file);
long findAPE(File *file, long id3v1Location);
+
+ ByteVector readHeader(IOStream *stream, unsigned int length, bool skipID3v2);
}
}
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
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
////////////////////////////////////////////////////////////////////////////////
*/
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 &);
/***************************************************************************
- 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>
#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;
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);
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);
}
}