From: Tsuda Kageyu Date: Fri, 3 Feb 2017 08:52:27 +0000 (+0900) Subject: Enable FileRef to detect file types by the actual content of a stream. X-Git-Tag: v1.12-beta-1~45^2~5 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=931bb042c396f3452b0a8a82b88b93e0fe16dbc9;p=taglib 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. --- diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index 9f298aaf..fb01e4a2 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index cfb19ff7..a7c870b4 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -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 &); diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 8f395265..c209a640 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index b674da79..7df5a797 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -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(); diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index dca69f6a..cad1ae50 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -28,6 +28,7 @@ ***************************************************************************/ #include +#include #include #include #include @@ -59,41 +60,14 @@ namespace typedef List ResolverList; ResolverList fileTypeResolvers; - // Templatized internal functions. T should be String or IOStream*. + // Detect the file type by user-defined resolvers. - template - FileName toFileName(T arg); - - template <> - FileName toFileName(IOStream *arg) - { - return arg->name(); - } - - template <> - FileName toFileName(FileName arg) - { - return arg; - } - - template - File *resolveFileType(T arg, bool readProperties, - AudioProperties::ReadStyle style); - - template <> - File *resolveFileType(IOStream *arg, bool readProperties, - AudioProperties::ReadStyle style) - { - return 0; - } - - template <> - File *resolveFileType(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 - 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); } diff --git a/taglib/fileref.h b/taglib/fileref.h index a12b1a9b..c36f54cb 100644 --- a/taglib/fileref.h +++ b/taglib/fileref.h @@ -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; }; diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index b6b72960..780ab1c3 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 65d85679..56755ec5 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -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 &); diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp index 3733fb40..f06ae068 100644 --- a/taglib/mp4/mp4file.cpp +++ b/taglib/mp4/mp4file.cpp @@ -26,6 +26,8 @@ #include #include #include +#include + #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()) diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index 3840bd02..ca2f70de 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -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); diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index daf24c8f..b8544bb8 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index 541724dc..43759ea5 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -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 &); diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 74bf779b..74f304f8 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index e9e97387..71410fe7 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -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 &); diff --git a/taglib/mpeg/mpegheader.cpp b/taglib/mpeg/mpegheader.cpp index 610b0320..5a5015d6 100644 --- a/taglib/mpeg/mpegheader.cpp +++ b/taglib/mpeg/mpegheader.cpp @@ -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(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. diff --git a/taglib/mpeg/mpegutils.h b/taglib/mpeg/mpegutils.h index 1cee918a..31b45a43 100644 --- a/taglib/mpeg/mpegutils.h +++ b/taglib/mpeg/mpegutils.h @@ -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); } diff --git a/taglib/ogg/flac/oggflacfile.cpp b/taglib/ogg/flac/oggflacfile.cpp index fe4d8830..e294d411 100644 --- a/taglib/ogg/flac/oggflacfile.cpp +++ b/taglib/ogg/flac/oggflacfile.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/ogg/flac/oggflacfile.h b/taglib/ogg/flac/oggflacfile.h index 05762f9b..5f4313da 100644 --- a/taglib/ogg/flac/oggflacfile.h +++ b/taglib/ogg/flac/oggflacfile.h @@ -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 &); diff --git a/taglib/ogg/opus/opusfile.cpp b/taglib/ogg/opus/opusfile.cpp index ff1bfe2d..85d995bc 100644 --- a/taglib/ogg/opus/opusfile.cpp +++ b/taglib/ogg/opus/opusfile.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/ogg/opus/opusfile.h b/taglib/ogg/opus/opusfile.h index b718f0d7..60f60c3f 100644 --- a/taglib/ogg/opus/opusfile.h +++ b/taglib/ogg/opus/opusfile.h @@ -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 &); diff --git a/taglib/ogg/speex/speexfile.cpp b/taglib/ogg/speex/speexfile.cpp index 7af71d50..d3368774 100644 --- a/taglib/ogg/speex/speexfile.cpp +++ b/taglib/ogg/speex/speexfile.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/ogg/speex/speexfile.h b/taglib/ogg/speex/speexfile.h index 58b001dd..eda347e0 100644 --- a/taglib/ogg/speex/speexfile.h +++ b/taglib/ogg/speex/speexfile.h @@ -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 &); diff --git a/taglib/ogg/vorbis/vorbisfile.cpp b/taglib/ogg/vorbis/vorbisfile.cpp index 2773bd3b..7f02fff5 100644 --- a/taglib/ogg/vorbis/vorbisfile.cpp +++ b/taglib/ogg/vorbis/vorbisfile.cpp @@ -28,10 +28,10 @@ #include #include #include +#include #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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/ogg/vorbis/vorbisfile.h b/taglib/ogg/vorbis/vorbisfile.h index 9e71dcbe..9f7cb7e4 100644 --- a/taglib/ogg/vorbis/vorbisfile.h +++ b/taglib/ogg/vorbis/vorbisfile.h @@ -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 &); diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index 1a29938c..72705a9a 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index a79d76b2..611b7338 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -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 &); diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index 79ff9167..5e92b29e 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -23,10 +23,11 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "tbytevector.h" -#include "tdebug.h" -#include "tstringlist.h" -#include "tpropertymap.h" +#include +#include +#include +#include +#include #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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index 80f17a85..47e8116d 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -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 &); diff --git a/taglib/tagutils.cpp b/taglib/tagutils.cpp index dc047040..e27fd8bd 100644 --- a/taglib/tagutils.cpp +++ b/taglib/tagutils.cpp @@ -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; +} diff --git a/taglib/tagutils.h b/taglib/tagutils.h index fb11d1e0..4014c673 100644 --- a/taglib/tagutils.h +++ b/taglib/tagutils.h @@ -30,9 +30,12 @@ #ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header +#include + 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); } } diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index fc123ba3..c9b62bd7 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index 4bcb722a..c5b05d9f 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -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 &); diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index ef92f4bd..03f5ee07 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -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 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index 7e0bd27a..14bc823b 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -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 &); diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index 1b899975..c0d171be 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -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 #include @@ -39,9 +39,10 @@ #include #include #include +#include +#include #include #include "utils.h" -#include 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(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(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(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); } }