From cb7a48f84aeb30251caae909901555dffa4e9fcb Mon Sep 17 00:00:00 2001 From: Mohamed Ali Chebbi Date: Mon, 23 Jan 2023 17:26:52 +0100 Subject: [PATCH] Rework Asf and Riff formats ; add test/data for video support --- include/exiv2/asfvideo.hpp | 80 +- include/exiv2/riffvideo.hpp | 266 ++-- src/asfvideo.cpp | 369 +++-- src/helper_functions.cpp | 41 +- src/helper_functions.hpp | 29 +- src/riffvideo.cpp | 1287 ++++++----------- .../Free_Test_Data_500KB_WAV.wav.out | 10 +- test/data/test_reference_files/flame.avi.out | 11 +- 8 files changed, 795 insertions(+), 1298 deletions(-) diff --git a/include/exiv2/asfvideo.hpp b/include/exiv2/asfvideo.hpp index e41c6602..bf1f222c 100644 --- a/include/exiv2/asfvideo.hpp +++ b/include/exiv2/asfvideo.hpp @@ -1,31 +1,7 @@ -// ***************************************************************** -*- C++ -*- -/* - * Copyright (C) 2004-2021 Exiv2 authors - * This program is part of the Exiv2 distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. - */ -/*! - @file asfvideo.hpp - @brief An Image subclass to support ASF video files - @author Abhinav Badola for GSoC 2012 - mail.abu.to@gmail.com - @date 08-Aug-12, AB: created - */ -#ifndef ASFVIDEO_HPP -#define ASFVIDEO_HPP +// SPDX-License-Identifier: GPL-2.0-or-later +// Spec : Advanced Systems Format (ASF) Specification : Revision 01.20.05 : +// https://exse.eyewated.com/fls/54b3ed95bbfb1a92.pdf +#pragma once // ***************************************************************************** #include "exiv2lib_export.h" @@ -33,10 +9,13 @@ // included header files #include "image.hpp" +// ***************************************************************************** +// namespace extensions namespace Exiv2 { // ***************************************************************************** // class definitions + /*! @brief Class to access ASF video files. */ @@ -56,11 +35,14 @@ class EXIV2API AsfVideo : public Image { method to get a temporary reference. */ explicit AsfVideo(BasicIo::UniquePtr io); + //@} + //! @name NOT Implemented + //@{ //! Copy constructor - AsfVideo(const AsfVideo& rhs) = delete; + AsfVideo(const AsfVideo&) = delete; //! Assignment operator - AsfVideo& operator=(const AsfVideo& rhs) = delete; + AsfVideo& operator=(const AsfVideo&) = delete; //@} //! @name Manipulators @@ -71,35 +53,19 @@ class EXIV2API AsfVideo : public Image { //! @name Accessors //@{ - std::string mimeType() const override; + [[nodiscard]] std::string mimeType() const override; //@} private: - static constexpr size_t GUID_SIZE = 37; static constexpr size_t CODEC_TYPE_VIDEO = 1; static constexpr size_t CODEC_TYPE_AUDIO = 2; - static constexpr size_t BYTE = 1; - static constexpr size_t WCHAR = 2; - static constexpr size_t WORD = 2; - static constexpr size_t DWORD = 4; - static constexpr size_t QWORD = 8; - static constexpr size_t GUID = 16; - - class AsfObject { - byte IdBuf_[GUID + 1]; + class HeaderReader { + DataBuf IdBuf_; uint64_t size_; uint64_t remaining_size_; public: - explicit AsfObject(BasicIo::UniquePtr& io) { - DataBuf SizeBuf(QWORD + 1); - - io->read(IdBuf_, GUID); - io->read(SizeBuf.data(), QWORD); - - size_ = Exiv2::getULongLong(SizeBuf.data(), littleEndian); - remaining_size_ = size_ - GUID - QWORD; - } + explicit HeaderReader(BasicIo::UniquePtr& io); [[nodiscard]] uint64_t getSize() const { return size_; @@ -109,7 +75,7 @@ class EXIV2API AsfVideo : public Image { return remaining_size_; } - [[nodiscard]] byte* getId() { + [[nodiscard]] DataBuf& getId() { return IdBuf_; } }; @@ -158,6 +124,8 @@ class EXIV2API AsfVideo : public Image { */ void extendedContentDescription(); + void DegradableJPEGMedia(); + /*! @brief Calculates Aspect Ratio of a video, and stores it in the respective XMP container. @@ -168,12 +136,6 @@ class EXIV2API AsfVideo : public Image { //! Variable to store height and width of a video frame. uint64_t height_, width_; - [[nodiscard]] uint64_t readQWORDTag(); - [[nodiscard]] uint32_t readDWORDTag(); - [[nodiscard]] uint16_t readWORDTag(); - [[nodiscard]] std::string readStringWCHAR(uint16_t length); - [[nodiscard]] std::string readString(uint16_t length); - }; // Class AsfVideo // ***************************************************************************** @@ -191,6 +153,4 @@ EXIV2API Image::UniquePtr newAsfInstance(BasicIo::UniquePtr io, bool create); //! Check if the file iIo is a Windows Asf Video. EXIV2API bool isAsfType(BasicIo& iIo, bool advance); -} // namespace Exiv2 - -#endif // #ifndef ASFVIDEO_HPP_ \ No newline at end of file +} // namespace Exiv2 \ No newline at end of file diff --git a/include/exiv2/riffvideo.hpp b/include/exiv2/riffvideo.hpp index f724be53..5c2889dc 100644 --- a/include/exiv2/riffvideo.hpp +++ b/include/exiv2/riffvideo.hpp @@ -1,34 +1,10 @@ -// ***************************************************************** -*- C++ -*- -/* - * Copyright (C) 2004-2021 Exiv2 authors - * This program is part of the Exiv2 distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * asize_t with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. - */ -#ifndef RIFFVIDEO_HPP -#define RIFFVIDEO_HPP - -// ***************************************************************************** -#include "exiv2lib_export.h" +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once -// included header files #include "exif.hpp" +#include "exiv2lib_export.h" #include "image.hpp" -// ***************************************************************************** -// namespace extensions namespace Exiv2 { // ***************************************************************************** @@ -53,11 +29,14 @@ class EXIV2API RiffVideo : public Image { method to get a temporary reference. */ explicit RiffVideo(BasicIo::UniquePtr io); + //@} + //! @name NOT Implemented + //@{ //! Copy constructor - RiffVideo(const RiffVideo& rhs) = delete; + RiffVideo(const RiffVideo&) = delete; //! Assignment operator - RiffVideo& operator=(const RiffVideo& rhs) = delete; + RiffVideo& operator=(const RiffVideo&) = delete; //@} @@ -71,147 +50,140 @@ class EXIV2API RiffVideo : public Image { //! @name Accessors //@{ [[nodiscard]] std::string mimeType() const override; - [[nodiscard]] static const char* printAudioEncoding(uint64_t i); //@} protected: + class HeaderReader { + std::string id_; + uint64_t size_; + + public: + explicit HeaderReader(BasicIo::UniquePtr& io); + + [[nodiscard]] uint64_t getSize() const { + return size_; + } + + [[nodiscard]] std::string& getId() { + return id_; + } + }; + + void readList(HeaderReader& header_); + + void readChunk(HeaderReader& header_); + + void decodeBlocks(); + + private: + bool equal(const std::string& str1, const std::string& str2); + /*! - @brief Check for a valid tag and decode the block at the current IO - position. Calls tagDecoder() or skips to next tag, if required. - */ - void decodeBlock(); - /*! - @brief Interpret tag information, and call the respective function - to save it in the respective XMP container. Decodes a Tag - Information and saves it in the respective XMP container, if - the block size is small. - @param buf Data buffer which cotains tag ID. - @param size Size of the data block used to store Tag Information. - */ - void tagDecoder(Exiv2::DataBuf& buf, size_t size); - /*! - @brief Interpret Junk tag information, and save it - in the respective XMP container. - @param size Size of the data block used to store Tag Information. - */ - void junkHandler(size_t size); - /*! - @brief Interpret Stream tag information, and save it - in the respective XMP container. - @param size Size of the data block used to store Tag Information. - */ - void streamHandler(size_t size); + @brief Interpret MainAVIHeader (avih) structure, and save it in the respective XMP container. + @param size Size of the data block used to store Tag Information. + */ + void readAviHeader(); + /*! - @brief Interpret Stream Format tag information, and save it - in the respective XMP container. - @param size Size of the data block used to store Tag Information. - */ - void streamFormatHandler(size_t size); + @brief Interpret stream header list element (strh), and save it in the respective XMP container. + */ + void readStreamHeader(); + /*! - @brief Interpret Riff Header tag information, and save it - in the respective XMP container. - @param size Size of the data block used to store Tag Information. - */ - void aviHeaderTagsHandler(size_t size); + @brief Interpret stream header list element (strf), and save it in the respective XMP container. + @param size Size of the data block used to store Tag Information. + */ + void readStreamFormat(uint64_t size_); + /*! - @brief Interpret Riff List tag information, and save it - in the respective XMP container. - @param size Size of the data block used to store Tag Information. - */ - void listHandler(size_t size); + @brief Interpret Additional header data (strd), and save it in the respective XMP container. + @param size Size of the data block used to store Tag Information. + */ + void readStreamData(uint64_t size_); + /*! - @brief Interpret Riff Stream Data tag information, and save it - in the respective XMP container. - @param size Size of the data block used to store Tag Information. - */ - void streamDataTagHandler(size_t size); + @brief Interpret stream header list element (strn) , and save it in the respective XMP container. + @param size Size of the data block used to store Tag Information. + */ + void StreamName(uint64_t size_); /*! - @brief Interpret INFO tag information, and save it - in the respective XMP container. - */ - void infoTagsHandler(); + @brief Interpret INFO List Chunk, and save it in the respective XMP container. + @param size Size of the data block used to store Tag Information. + */ + void readInfoListChunk(uint64_t size_); + /*! - @brief Interpret Nikon Tags related to Video information, and - save it in the respective XMP container. - */ - void nikonTagsHandler(); + @brief Interpret Riff Stream Data tag information, and save it in the respective XMP container. + The Movi - Lists contain Video, Audio, Subtitle and (secondary) index data. Those can be grouped into rec - Lists. + @param size Size of the data block used to store Tag Information. + */ + void readMoviList(uint64_t size_); /*! - @brief Interpret OpenDML tag information, and save it - in the respective XMP container. - */ - void odmlTagsHandler(); - //! @brief Skips Particular Blocks of Metadata List. - void skipListData(); + @brief Interpret Video Properties Header chunk, and save it in the respective XMP container. + The video properties header identifies video signal properties associated with a digital video stream in an AVI file + @param size Size of the data block used to store Tag Information. + */ + void readVPRPChunk(uint64_t size_); /*! - @brief Interprets DateTimeOriginal tag or stream name tag - information, and save it in the respective XMP container. - @param size Size of the data block used to store Tag Information. - @param i parameter used to overload function - */ - void dateTimeOriginal(size_t size, int i = 0); + @brief Interpret Riff INdex Chunk, and save it in the respective XMP container. + @param size Size of the data block used to store Tag Information. + */ + void readIndexChunk(uint64_t size_); /*! - @brief Calculates Sample Rate of a particular stream. - @param buf Data buffer with the dividend. - @param divisor The Divisor required to calculate sample rate. - @return Return the sample rate of the stream. - */ - [[nodiscard]] static double returnSampleRate(Exiv2::DataBuf& buf, size_t divisor = 1); + @brief Interpret Riff Stream Chunk, and save it in the respective XMP container. + @param size Size of the data block used to store Tag Information. + */ + void readDataChunk(uint64_t size_); /*! - @brief Calculates Aspect Ratio of a video, and stores it in the - respective XMP container. - @param width Width of the video. - @param height Height of the video. - */ - void fillAspectRatio(size_t width = 1, size_t height = 1); + @brief Interpret Junk Chunk and save it in the respective XMP container. + @param size Size of the data block used to store Tag Information. + */ + void readJunk(uint64_t size_); + + std::string getStreamType(uint32_t stream); /*! - @brief Calculates Duration of a video, and stores it in the - respective XMP container. - @param frame_rate Frame rate of the video. - @param frame_count Total number of frames present in the video. - */ + @brief Calculates Duration of a video, and stores it in the respective XMP container. + @param frame_rate Frame rate of the video. + @param frame_count Total number of frames present in the video. + */ void fillDuration(double frame_rate, size_t frame_count); - [[nodiscard]] static bool equalsRiffTag(Exiv2::DataBuf& buf, const char* str); - - static void copyTagValue(DataBuf& buf_dest, DataBuf& buf_src, size_t index = RIFF_TAG_SIZE); + /*! + @brief Calculates Aspect Ratio of a video, and stores it in the respective XMP container. + @param width Width of the video. + @param height Height of the video. + */ + void fillAspectRatio(size_t width, size_t height); - private: - static constexpr size_t RIFF_TAG_SIZE = 0x4; - static constexpr auto RIFF_CHUNK_HEADER_ICCP = "ICCP"; - static constexpr auto RIFF_CHUNK_HEADER_EXIF = "EXIF"; - static constexpr auto RIFF_CHUNK_HEADER_XMP = "XMP "; + static constexpr auto CHUNK_HEADER_ICCP = "ICCP"; + static constexpr auto CHUNK_HEADER_EXIF = "EXIF"; + static constexpr auto CHUNK_HEADER_XMP = "XMP "; /* Chunk header names */ - static constexpr auto RIFF_CHUNK_ID_MOVI = "MOVI"; - static constexpr auto RIFF_CHUNK_ID_DATA = "DATA"; - static constexpr auto RIFF_CHUNK_ID_HDRL = "HDRL"; - static constexpr auto RIFF_CHUNK_ID_STRL = "STRL"; - static constexpr auto RIFF_CHUNK_ID_LIST = "LIST"; - static constexpr auto RIFF_CHUNK_ID_JUNK = "JUNK"; - static constexpr auto RIFF_CHUNK_ID_AVIH = "AVIH"; - static constexpr auto RIFF_CHUNK_ID_STRH = "STRH"; - static constexpr auto RIFF_CHUNK_ID_STRF = "STRF"; - static constexpr auto RIFF_CHUNK_ID_FMT = "FMT "; - static constexpr auto RIFF_CHUNK_ID_STRN = "STRN"; - static constexpr auto RIFF_CHUNK_ID_STRD = "STRD"; - static constexpr auto RIFF_CHUNK_ID_IDIT = "IDIT"; - static constexpr auto RIFF_CHUNK_ID_INFO = "INFO"; - static constexpr auto RIFF_CHUNK_ID_NCDT = "NCDT"; - static constexpr auto RIFF_CHUNK_ID_ODML = "ODML"; - - //! Variable to check the end of metadata traversing. - bool continueTraversing_; - //! Variable which stores current stream being processsed. + static constexpr auto CHUNK_ID_MOVI = "MOVI"; + static constexpr auto CHUNK_ID_DATA = "DATA"; + static constexpr auto CHUNK_ID_HDRL = "HDRL"; + static constexpr auto CHUNK_ID_STRL = "STRL"; + static constexpr auto CHUNK_ID_LIST = "LIST"; + static constexpr auto CHUNK_ID_JUNK = "JUNK"; + static constexpr auto CHUNK_ID_AVIH = "AVIH"; + static constexpr auto CHUNK_ID_STRH = "STRH"; + static constexpr auto CHUNK_ID_STRF = "STRF"; + static constexpr auto CHUNK_ID_FMT = "FMT "; + static constexpr auto CHUNK_ID_STRN = "STRN"; + static constexpr auto CHUNK_ID_STRD = "STRD"; + static constexpr auto CHUNK_ID_IDIT = "IDIT"; + static constexpr auto CHUNK_ID_INFO = "INFO"; + static constexpr auto CHUNK_ID_NCDT = "NCDT"; + static constexpr auto CHUNK_ID_ODML = "ODML"; + static constexpr auto CHUNK_ID_VPRP = "VPRP"; + static constexpr auto CHUNK_ID_IDX1 = "IDX1"; + int streamType_; }; // Class RiffVideo -// ***************************************************************************** -// template, inline and free functions - -// These could be static private functions on Image subclasses but then -// ImageFactory needs to be made a friend. -/*! +/* @brief Create a new RiffVideo instance and return an auto-pointer to it. Caller owns the returned object and the auto-pointer ensures that it will be deleted. @@ -222,5 +194,3 @@ EXIV2API Image::UniquePtr newRiffInstance(BasicIo::UniquePtr io, bool create); EXIV2API bool isRiffType(BasicIo& iIo, bool advance); } // namespace Exiv2 - -#endif // RIFFVIDEO_HPP diff --git a/src/asfvideo.cpp b/src/asfvideo.cpp index cd0b6d10..22505aceb 100644 --- a/src/asfvideo.cpp +++ b/src/asfvideo.cpp @@ -1,63 +1,26 @@ -// ***************************************************************** -*- C++ -*- -/* - * Copyright (C) 2004-2021 Exiv2 authors - * This program is part of the Exiv2 distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. - */ -/* - File: asfvideo.cpp - Author(s): Abhinav Badola for GSoC 2012 (AB) - History: 08-Aug-12, AB: created - Credits: See header file - Spec: https://exse.eyewated.com/fls/54b3ed95bbfb1a92.pdf - */ -// ***************************************************************************** +// SPDX-License-Identifier: GPL-2.0-or-later // included header files -#include -#include "config.h" - #include "asfvideo.hpp" +#include #include "basicio.hpp" -#include "convert.hpp" +#include "config.h" #include "error.hpp" #include "futils.hpp" #include "helper_functions.hpp" -#include "tags.hpp" -#include "tags_int.hpp" -#include "types.hpp" - -// + standard includes -#include -#include -#include - // ***************************************************************************** // class member definitions namespace Exiv2::Internal { /*! - TagVocabulary Look-up list for ASF Type Video Files - Associates the GUID of a TagVocabulary with its TagVocabulary Name(i.e. Human Readable Form) + Look-up list for ASF Type Video Files + Associates the GUID with its Name(i.e. Human Readable Form) Tags have been diferentiated into Various Categories. - The categories have been listed above the TagVocabulary Groups + The categories have been listed above Groups see : - https://fr.wikipedia.org/wiki/Advanced_Systems_Format - https://exse.eyewated.com/fls/54b3ed95bbfb1a92.pdf */ -constexpr const TagVocabulary GUIDReferenceTags[] = { +const std::map GUIDReferenceTags = { /// Top-level ASF object GUIDS {"75B22630-668E-11CF-A6D9-00AA0062CE6C", "Header"}, {"75B22636-668E-11CF-A6D9-00AA0062CE6C", "Data"}, @@ -149,52 +112,55 @@ constexpr const TagVocabulary GUIDReferenceTags[] = { {"6698B84E-0AFA-4330-AEB2-1C0A98D7A44D", "Payload_Extension_System_Encryption_Sample_ID"}, {"00E1AF06-7BEC-11D1-A582-00C04FC29CFB", "Payload_Extension_System_Degradable_JPEG"}}; -/*! - @brief Function used to check equality of two Tags (ignores case). - @param str1 char* Pointer to First TagVocabulary - @param str2 char* Pointer to Second TagVocabulary - @return Returns true if both are equal. - */ -bool compareTag(const char* str1, const char* str2) { - if (strlen(str1) != strlen(str2)) - return false; - - for (uint64_t i = 0; i < strlen(str1); ++i) - if (tolower(str1[i]) != tolower(str2[i])) - return false; - - return true; -} - /*! @brief Function used to calculate GUID, Tags comprises of 16 bytes. The Buffer contains the TagVocabulary in Binary Form. The information is then parsed into a character array GUID. + https://fr.wikipedia.org/wiki/Globally_unique_identifier */ -void getGUID(byte buf[], char GUID[]) { - int i; - for (i = 0; i < 4; ++i) { - GUID[(3 - i) * 2] = Util::returnHEX(buf[i] / 0x10); - GUID[(3 - i) * 2 + 1] = Util::returnHEX(buf[i] % 0x10); - } - for (i = 4; i < 6; ++i) { - GUID[(9 - i) * 2 + 1] = Util::returnHEX(buf[i] / 0x10); - GUID[(9 - i) * 2 + 2] = Util::returnHEX(buf[i] % 0x10); - } - for (i = 6; i < 8; ++i) { - GUID[(14 - i) * 2] = Util::returnHEX(buf[i] / 0x10); - GUID[(14 - i) * 2 + 1] = Util::returnHEX(buf[i] % 0x10); - } - for (i = 8; i < 10; ++i) { - GUID[i * 2 + 3] = Util::returnHEX(buf[i] / 0x10); - GUID[i * 2 + 4] = Util::returnHEX(buf[i] % 0x10); - } - for (i = 10; i < 16; ++i) { - GUID[i * 2 + 4] = Util::returnHEX(buf[i] / 0x10); - GUID[i * 2 + 5] = Util::returnHEX(buf[i] % 0x10); +std::string getGUID(DataBuf& buf) { + std::string GUID(36, '-'); + if (buf.size() >= 16) { + GUID[0] = returnHex(buf.data()[3] / 0x10); + GUID[1] = returnHex(buf.data()[3] % 0x10); + GUID[2] = returnHex(buf.data()[2] / 0x10); + GUID[3] = returnHex(buf.data()[2] % 0x10); + GUID[4] = returnHex(buf.data()[1] / 0x10); + GUID[5] = returnHex(buf.data()[1] % 0x10); + GUID[6] = returnHex(buf.data()[0] / 0x10); + GUID[7] = returnHex(buf.data()[0] % 0x10); + + GUID[9] = returnHex(buf.data()[5] / 0x10); + GUID[10] = returnHex(buf.data()[5] % 0x10); + GUID[11] = returnHex(buf.data()[4] / 0x10); + GUID[12] = returnHex(buf.data()[4] % 0x10); + + GUID[14] = returnHex(buf.data()[7] / 0x10); + GUID[15] = returnHex(buf.data()[7] % 0x10); + GUID[16] = returnHex(buf.data()[6] / 0x10); + GUID[17] = returnHex(buf.data()[6] % 0x10); + + GUID[19] = returnHex(buf.data()[8] / 0x10); + GUID[20] = returnHex(buf.data()[8] % 0x10); + GUID[21] = returnHex(buf.data()[9] / 0x10); + GUID[22] = returnHex(buf.data()[9] % 0x10); + + GUID[24] = returnHex(buf.data()[10] / 0x10); + GUID[25] = returnHex(buf.data()[10] % 0x10); + GUID[26] = returnHex(buf.data()[11] / 0x10); + GUID[27] = returnHex(buf.data()[11] % 0x10); + GUID[28] = returnHex(buf.data()[12] / 0x10); + GUID[29] = returnHex(buf.data()[12] % 0x10); + GUID[30] = returnHex(buf.data()[13] / 0x10); + GUID[31] = returnHex(buf.data()[13] % 0x10); + GUID[32] = returnHex(buf.data()[14] / 0x10); + GUID[33] = returnHex(buf.data()[14] % 0x10); + GUID[34] = returnHex(buf.data()[15] / 0x10); + GUID[35] = returnHex(buf.data()[15] % 0x10); } - GUID[36] = '\0'; - GUID[8] = GUID[13] = GUID[18] = GUID[23] = '-'; + + // Example of output 399595EC-8667-4E2D-8FDB-98814CE76C1E + return GUID; } /*! @@ -203,7 +169,7 @@ void getGUID(byte buf[], char GUID[]) { @param buf Exiv2 byte buffer @return Returns true if the buffer data is equivalent to Header GUID. */ -bool isASFType(byte buf[]) { +bool isASFType(const byte buf[]) { return buf[0] == 0x30 && buf[1] == 0x26 && buf[2] == 0xb2 && buf[3] == 0x75 && buf[4] == 0x8e && buf[5] == 0x66 && buf[6] == 0xcf && buf[7] == 0x11 && buf[8] == 0xa6 && buf[9] == 0xd9 && buf[10] == 0x00 && buf[11] == 0xaa && buf[12] == 0x00 && buf[13] == 0x62 && buf[14] == 0xce && buf[15] == 0x6c; @@ -249,57 +215,66 @@ void AsfVideo::readMetadata() { aspectRatio(); } // AsfVideo::readMetadata -void AsfVideo::decodeBlock() { - AsfObject obj(io_); +AsfVideo::HeaderReader::HeaderReader(BasicIo::UniquePtr& io) : IdBuf_(GUID) { + if (io->size() >= io->tell() + QWORD + QWORD) { + IdBuf_ = io->read(GUID); + + size_ = readQWORDTag(io); + if (size_ >= GUID + QWORD) + remaining_size_ = size_ - GUID - QWORD; + } +} - char guid[GUID_SIZE] = ""; - getGUID(obj.getId(), guid); +void AsfVideo::decodeBlock() { + HeaderReader header(io_); + std::string guid = getGUID(header.getId()); - auto tv = find(GUIDReferenceTags, guid); - if (tv) { - if (compareTag(exvGettext(tv->label_), "Header")) { + auto tv = GUIDReferenceTags.find(guid); + if (tv != GUIDReferenceTags.end()) { + if (tv->second == "Header") { DataBuf nbHeadersBuf(DWORD + 1); io_->read(nbHeadersBuf.data(), DWORD); uint32_t nb_headers = Exiv2::getULong(nbHeadersBuf.data(), littleEndian); - DataBuf reserved(BYTE + 1); - io_->read(reserved.data(), BYTE); - io_->read(reserved.data(), BYTE); + io_->seekOrThrow(io_->tell() + BYTE * 2, BasicIo::beg, + ErrorCode::kerFailedToReadImageData); // skip two reserved tags for (uint32_t i = 0; i < nb_headers; i++) { - AsfObject obj(io_); - char guid[GUID_SIZE] = ""; - getGUID(obj.getId(), guid); - auto tag = find(GUIDReferenceTags, guid); - if (tag) { - if (compareTag(exvGettext(tag->label_), "File_Properties")) + HeaderReader others(io_); + auto guid = getGUID(others.getId()); + auto tag = GUIDReferenceTags.find(guid); + if (tag != GUIDReferenceTags.end()) { + if (tag->second == "File_Properties") fileProperties(); - else if (compareTag(exvGettext(tag->label_), "Stream_Properties")) + else if (tag->second == "Stream_Properties") streamProperties(); - else if (compareTag(exvGettext(tag->label_), "Header_Extension")) + else if (tag->second == "Header_Extension") headerExtension(); - else if (compareTag(exvGettext(tag->label_), "Codec_List")) + else if (tag->second == "Codec_List") codecList(); - else if (compareTag(exvGettext(tag->label_), "Extended_Content_Description")) + else if (tag->second == "Extended_Content_Description") extendedContentDescription(); - else if (compareTag(exvGettext(tag->label_), "Content_Description")) + else if (tag->second == "Content_Description") contentDescription(); - else if (compareTag(exvGettext(tag->label_), "Extended_Stream_Properties")) + else if (tag->second == "Extended_Stream_Properties") extendedStreamProperties(); - else - io_->seek(io_->tell() + obj.getRemainingSize(), BasicIo::beg); + else if (tag->second == "Degradable_JPEG_Media") { + DegradableJPEGMedia(); + } else + io_->seekOrThrow(io_->tell() + others.getRemainingSize(), BasicIo::beg, + ErrorCode::kerFailedToReadImageData); } else - io_->seek(io_->tell() + obj.getRemainingSize(), BasicIo::beg); + io_->seekOrThrow(io_->tell() + others.getRemainingSize(), BasicIo::beg, ErrorCode::kerFailedToReadImageData); } } else - io_->seek(io_->tell() + obj.getRemainingSize(), BasicIo::beg); + io_->seekOrThrow(io_->tell() + header.getRemainingSize(), BasicIo::beg, ErrorCode::kerFailedToReadImageData); } else - io_->seek(io_->tell() + obj.getRemainingSize(), BasicIo::beg); + io_->seekOrThrow(io_->tell() + header.getRemainingSize(), BasicIo::beg, ErrorCode::kerFailedToReadImageData); } // AsfVideo::decodeBlock void AsfVideo::extendedStreamProperties() { - xmpData()["Xmp.video.StartTimecode"] = readQWORDTag(); // Start Time - xmpData()["Xmp.video.EndTimecode"] = readWORDTag(); // End Time + xmpData()["Xmp.video.StartTimecode"] = readQWORDTag(io_); // Start Time + xmpData()["Xmp.video.EndTimecode"] = readWORDTag(io_); // End Time io_->seek(io_->tell() + DWORD, BasicIo::beg); // ignore Data Bitrate io_->seek(io_->tell() + DWORD, BasicIo::beg); // ignore Buffer Size @@ -312,13 +287,13 @@ void AsfVideo::extendedStreamProperties() { io_->seek(io_->tell() + WORD, BasicIo::beg); // ignore Flags Stream Number io_->seek(io_->tell() + WORD, BasicIo::beg); // ignore Stream Language ID Index - xmpData()["Xmp.video.FrameRate"] = readWORDTag(); // Average Time Per Frame - uint16_t stream_name_count = readWORDTag(); - uint16_t payload_ext_sys_count = readWORDTag(); + xmpData()["Xmp.video.FrameRate"] = readWORDTag(io_); // Average Time Per Frame + uint16_t stream_name_count = readWORDTag(io_); + uint16_t payload_ext_sys_count = readWORDTag(io_); for (uint16_t i = 0; i < stream_name_count; i++) { io_->seek(io_->tell() + WORD, BasicIo::beg); // ignore Language ID Index - uint16_t stream_length = readWORDTag(); + uint16_t stream_length = readWORDTag(io_); if (stream_length) io_->seek(io_->tell() + stream_length, BasicIo::beg); // ignore Stream name } @@ -326,38 +301,51 @@ void AsfVideo::extendedStreamProperties() { for (uint16_t i = 0; i < payload_ext_sys_count; i++) { io_->seek(io_->tell() + GUID, BasicIo::beg); // ignore Extension System ID io_->seek(io_->tell() + WORD, BasicIo::beg); // ignore Extension Data Size - uint16_t ext_sys_info_length = readWORDTag(); + uint16_t ext_sys_info_length = readWORDTag(io_); if (ext_sys_info_length) io_->seek(io_->tell() + ext_sys_info_length, BasicIo::beg); // ignore Extension System Info } } // AsfVideo::extendedStreamProperties +void AsfVideo::DegradableJPEGMedia() { + uint32_t width = readDWORDTag(io_); + width_ = width; + xmpData_["Xmp.video.Width"] = width; + + uint32_t height = readDWORDTag(io_); + height_ = height; + xmpData_["Xmp.video.Height"] = height; + + io_->seek(io_->tell() + WORD * 3 /*3 Reserved*/, BasicIo::beg); + + uint32_t interchange_data_length = readWORDTag(io_); + io_->seek(io_->tell() + interchange_data_length /*Interchange data*/, BasicIo::beg); +} void AsfVideo::streamProperties() { - byte streamTypedBuf[GUID]; - io_->read(streamTypedBuf,GUID); - char stream_type[GUID_SIZE] = ""; - getGUID(streamTypedBuf, stream_type); + DataBuf streamTypedBuf = io_->read(GUID); + + auto stream_type = getGUID(streamTypedBuf); enum streamTypeInfo { Audio = 1, Video = 2 }; int stream = 0; - auto tag_stream_type = find(GUIDReferenceTags, stream_type); - if (tag_stream_type) { - if (compareTag(exvGettext(tag_stream_type->label_), "Audio_Media")) + auto tag_stream_type = GUIDReferenceTags.find(stream_type); + if (tag_stream_type != GUIDReferenceTags.end()) { + if (tag_stream_type->second == "Audio_Media") stream = Audio; - else if (compareTag(exvGettext(tag_stream_type->label_), "Video_Media")) + else if (tag_stream_type->second == "Video_Media") stream = Video; io_->seek(io_->tell() + GUID, BasicIo::beg); // ignore Error Correction Type - uint64_t time_offset = readQWORDTag(); + uint64_t time_offset = readQWORDTag(io_); if (stream == Video) xmpData()["Xmp.video.TimeOffset"] = time_offset; else if (stream == Audio) xmpData()["Xmp.audio.TimeOffset"] = time_offset; - auto specific_data_length = readDWORDTag(); - auto correction_data_length = readDWORDTag(); + auto specific_data_length = readDWORDTag(io_); + auto correction_data_length = readDWORDTag(io_); io_->seek(io_->tell() + WORD /*Flags*/ + DWORD /*Reserved*/ + specific_data_length + correction_data_length, BasicIo::beg); @@ -367,62 +355,62 @@ void AsfVideo::streamProperties() { void AsfVideo::codecList() { io_->seek(io_->tell() + GUID /*reserved*/, BasicIo::beg); - auto entries_count = readDWORDTag(); + auto entries_count = readDWORDTag(io_); for (uint32_t i = 0; i < entries_count; i++) { - uint16_t codec_type = readWORDTag() * 2; + uint16_t codec_type = readWORDTag(io_) * 2; std::string codec = (codec_type == 1) ? "Xmp.video" : "Xmp.audio"; - uint16_t codec_name_length = readWORDTag() * 2; + uint16_t codec_name_length = readWORDTag(io_) * 2; if (codec_name_length) - xmpData()[codec + std::string(".CodecName")] = readStringWCHAR(codec_name_length); + xmpData()[codec + std::string(".CodecName")] = readStringWcharTag(io_, codec_name_length); - uint16_t codec_desc_length = readWORDTag(); + uint16_t codec_desc_length = readWORDTag(io_); if (codec_desc_length) - xmpData()[codec + std::string(".CodecDescription")] = readStringWCHAR(codec_desc_length); + xmpData()[codec + std::string(".CodecDescription")] = readStringWcharTag(io_, codec_desc_length); - uint16_t codec_info_length = readWORDTag(); + uint16_t codec_info_length = readWORDTag(io_); if (codec_info_length) - xmpData()[codec + std::string(".CodecInfo")] = readString(codec_info_length); + xmpData()[codec + std::string(".CodecInfo")] = readStringTag(io_, codec_info_length); } } // AsfVideo::codecList void AsfVideo::headerExtension() { io_->seek(io_->tell() + GUID /*reserved1*/ + WORD /*Reserved2*/, BasicIo::beg); - auto header_ext_data_length = readDWORDTag(); + auto header_ext_data_length = readDWORDTag(io_); io_->seek(io_->tell() + header_ext_data_length, BasicIo::beg); } // AsfVideo::headerExtension void AsfVideo::extendedContentDescription() { - uint16_t content_descriptor_count = readWORDTag(); + uint16_t content_descriptor_count = readWORDTag(io_); std::string value; for (uint16_t i = 0; i < content_descriptor_count; i++) { - uint16_t descriptor_name_length = readWORDTag(); + uint16_t descriptor_name_length = readWORDTag(io_); if (descriptor_name_length) - value += readStringWCHAR(descriptor_name_length); // Descriptor Name + value += readStringWcharTag(io_, descriptor_name_length); // Descriptor Name - uint16_t descriptor_value_data_type = readWORDTag(); - uint16_t descriptor_value_length = readWORDTag(); + uint16_t descriptor_value_data_type = readWORDTag(io_); + uint16_t descriptor_value_length = readWORDTag(io_); if (descriptor_value_length) { // Descriptor Value switch (descriptor_value_data_type) { case 0 /*Unicode string */: - value += std::string(": ") + readStringWCHAR(descriptor_value_length); + value += std::string(": ") + readStringWcharTag(io_, descriptor_value_length); break; case 1 /*BYTE array */: - value += std::string(": ") + readString(descriptor_value_length); + value += std::string(": ") + readStringTag(io_, descriptor_value_length); break; case 2 /*BOOL*/: - value += std::string(": ") + std::to_string(readWORDTag()); + value += std::string(": ") + std::to_string(readWORDTag(io_)); break; case 3 /*DWORD */: - value += std::string(": ") + std::to_string(readDWORDTag()); + value += std::string(": ") + std::to_string(readDWORDTag(io_)); break; case 4 /*QWORD */: - value += std::string(": ") + std::to_string(readQWORDTag()); + value += std::string(": ") + std::to_string(readQWORDTag(io_)); break; case 5 /*WORD*/: - value += std::string(": ") + std::to_string(readWORDTag()); + value += std::string(": ") + std::to_string(readWORDTag(io_)); ; break; } @@ -434,82 +422,48 @@ void AsfVideo::extendedContentDescription() { } // AsfVideo::extendedContentDescription void AsfVideo::contentDescription() { - uint16_t title_length = readWORDTag(); - uint16_t author_length = readWORDTag(); - uint16_t copyright_length = readWORDTag(); - uint16_t desc_length = readWORDTag(); - uint16_t rating_length = readWORDTag(); + uint16_t title_length = readWORDTag(io_); + uint16_t author_length = readWORDTag(io_); + uint16_t copyright_length = readWORDTag(io_); + uint16_t desc_length = readWORDTag(io_); + uint16_t rating_length = readWORDTag(io_); if (title_length) - xmpData()["Xmp.video.Title"] = readStringWCHAR(title_length); + xmpData()["Xmp.video.Title"] = readStringWcharTag(io_, title_length); if (author_length) - xmpData()["Xmp.video.Author"] = readStringWCHAR(author_length); + xmpData()["Xmp.video.Author"] = readStringWcharTag(io_, author_length); if (copyright_length) - xmpData()["Xmp.video.Copyright"] = readStringWCHAR(copyright_length); + xmpData()["Xmp.video.Copyright"] = readStringWcharTag(io_, copyright_length); if (desc_length) - xmpData()["Xmp.video.Description"] = readStringWCHAR(desc_length); + xmpData()["Xmp.video.Description"] = readStringWcharTag(io_, desc_length); if (rating_length) - xmpData()["Xmp.video.Rating"] = readStringWCHAR(rating_length); + xmpData()["Xmp.video.Rating"] = readStringWcharTag(io_, rating_length); } // AsfVideo::extendedContentDescription -uint64_t AsfVideo::readQWORDTag() { - DataBuf FieldBuf(QWORD); - io_->read(FieldBuf.data(), QWORD); - uint64_t field = Util::getUint64_t(FieldBuf); - return field; -} - -uint32_t AsfVideo::readDWORDTag() { - DataBuf FieldBuf(DWORD); - io_->read(FieldBuf.data(), DWORD); - uint32_t field = Exiv2::getULong(FieldBuf.data(), littleEndian); - return field; -} - -uint16_t AsfVideo::readWORDTag() { - DataBuf FieldBuf(WORD); - io_->read(FieldBuf.data(), WORD); - uint16_t field = Exiv2::getUShort(FieldBuf.data(), littleEndian); - return field; -} - -std::string AsfVideo::readStringWCHAR(uint16_t length) { - DataBuf FieldBuf(length); - io_->read(FieldBuf.data(), length); - return Util::toString16(FieldBuf); -} - -std::string AsfVideo::readString(uint16_t length) { - DataBuf FieldBuf(length); - io_->read(FieldBuf.data(), length); - return Exiv2::toString(FieldBuf.data()); -} - void AsfVideo::fileProperties() { - byte FileIddBuf[GUID]; - io_->read(FileIddBuf, GUID); - char fileId[GUID_SIZE] = ""; - getGUID(FileIddBuf, fileId); - xmpData()["Xmp.video.FileID"] = fileId; - xmpData()["Xmp.video.FileLength"] = readQWORDTag(); - xmpData()["Xmp.video.CreationDate"] = readQWORDTag(); - xmpData()["Xmp.video.DataPackets"] = readQWORDTag(); - xmpData()["Xmp.video.Duration"] = readQWORDTag(); - xmpData()["Xmp.video.SendDuration"] = readQWORDTag(); - xmpData()["Xmp.video.Preroll"] = readQWORDTag(); + DataBuf FileIddBuf = io_->read(GUID); + xmpData()["Xmp.video.FileID"] = getGUID(FileIddBuf); + xmpData()["Xmp.video.FileLength"] = readQWORDTag(io_); + xmpData()["Xmp.video.CreationDate"] = readQWORDTag(io_); + xmpData()["Xmp.video.DataPackets"] = readQWORDTag(io_); + xmpData()["Xmp.video.Duration"] = readQWORDTag(io_); + xmpData()["Xmp.video.SendDuration"] = readQWORDTag(io_); + xmpData()["Xmp.video.Preroll"] = readQWORDTag(io_); io_->seek(io_->tell() + DWORD + DWORD + DWORD, BasicIo::beg); - xmpData()["Xmp.video.MaxBitRate"] = readDWORDTag(); + xmpData()["Xmp.video.MaxBitRate"] = readDWORDTag(io_); } // AsfVideo::fileProperties void AsfVideo::aspectRatio() { // TODO - Make a better unified method to handle all cases of Aspect Ratio + if (!height_) + return; double aspectRatio = static_cast(width_) / height_; aspectRatio = floor(aspectRatio * 10) / 10; xmpData()["Xmp.video.AspectRatio"] = aspectRatio; @@ -553,9 +507,8 @@ Image::UniquePtr newAsfInstance(BasicIo::UniquePtr io, bool /*create*/) { } bool isAsfType(BasicIo& iIo, bool advance) { - const int32_t len = 16; - byte buf[len]; - iIo.read(buf, len); + byte buf[GUID]; + iIo.read(buf, GUID); if (iIo.error() || iIo.eof()) { return false; diff --git a/src/helper_functions.cpp b/src/helper_functions.cpp index 2873cdc8..b378828f 100644 --- a/src/helper_functions.cpp +++ b/src/helper_functions.cpp @@ -4,6 +4,7 @@ #include #include +#include "enforce.hpp" std::string string_from_unterminated(const char* data, size_t data_length) { if (data_length == 0) { @@ -13,8 +14,8 @@ std::string string_from_unterminated(const char* data, size_t data_length) { return {data, StringLength}; } -namespace Util { -char returnHEX(int n) { +namespace Exiv2 { +char returnHex(int n) { if (n >= 0 && n <= 9) return static_cast(n + 48); return static_cast(n + 55); @@ -36,12 +37,34 @@ std::string toString16(Exiv2::DataBuf& buf) { return os.str(); } -uint64_t getUint64_t(Exiv2::DataBuf& buf) { - uint64_t temp = 0; +uint64_t readQWORDTag(BasicIo::UniquePtr& io) { + enforce(QWORD <= io->size() - io->tell(), Exiv2::ErrorCode::kerCorruptedMetadata); + DataBuf FieldBuf = io->read(QWORD); + return FieldBuf.read_uint64(0, littleEndian); +} - for (int i = 0; i < 8; ++i) { - temp = temp + static_cast(buf.data()[i] * (pow(static_cast(256), i))); - } - return temp; +uint32_t readDWORDTag(BasicIo::UniquePtr& io) { + enforce(DWORD <= io->size() - io->tell(), Exiv2::ErrorCode::kerCorruptedMetadata); + DataBuf FieldBuf = io->read(DWORD); + return FieldBuf.read_uint32(0, littleEndian); } -} // namespace Util + +uint16_t readWORDTag(BasicIo::UniquePtr& io) { + enforce(WORD <= io->size() - io->tell(), Exiv2::ErrorCode::kerCorruptedMetadata); + DataBuf FieldBuf = io->read(WORD); + return FieldBuf.read_uint16(0, littleEndian); +} + +std::string readStringWcharTag(BasicIo::UniquePtr& io, uint16_t length) { + enforce(length <= io->size() - io->tell(), Exiv2::ErrorCode::kerCorruptedMetadata); + DataBuf FieldBuf = io->read(length); + return toString16(FieldBuf); +} + +std::string readStringTag(BasicIo::UniquePtr& io, uint16_t length) { + enforce(length <= io->size() - io->tell(), Exiv2::ErrorCode::kerCorruptedMetadata); + DataBuf FieldBuf = io->read(length); + return Exiv2::toString(FieldBuf.data()); +} + +} // namespace Exiv2 diff --git a/src/helper_functions.hpp b/src/helper_functions.hpp index 34562ee9..0d530e71 100644 --- a/src/helper_functions.hpp +++ b/src/helper_functions.hpp @@ -5,6 +5,7 @@ #include #include "types.hpp" +#include "basicio.hpp" /*! @brief Convert a (potentially not null terminated) array into a std::string. @@ -22,14 +23,14 @@ */ std::string string_from_unterminated(const char* data, size_t data_length); -namespace Util { +namespace Exiv2 { /*! @brief Function used to convert a decimal number to its Hexadecimal equivalent, then parsed into a character @param n Integer which is to be parsed as Hexadecimal character @return Return a Hexadecimal number, in character */ -char returnHEX(int n); +char returnHex(int n); /*! @brief Function used to read data from data buffer, reads 16-bit character @@ -37,9 +38,27 @@ char returnHEX(int n); @param buf Exiv2 data buffer, which stores the information @return Returns std::string object . */ + + +static constexpr size_t BYTE = 0x1; +static constexpr size_t WCHAR = 0x2; +static constexpr size_t WORD = 0X2; +static constexpr size_t DWORD = 0x4; +static constexpr size_t QWORD = 0x8; +static constexpr size_t GUID = 0x10; + std::string toString16(Exiv2::DataBuf& buf); -//! Function used to convert buffer data into 64-bit Integer, information stored in littleEndian format -uint64_t getUint64_t(Exiv2::DataBuf& buf); -} // namespace Util + +[[nodiscard]] uint64_t readQWORDTag(Exiv2::BasicIo::UniquePtr& io) ; + +[[nodiscard]] uint32_t readDWORDTag(Exiv2::BasicIo::UniquePtr& io) ; + +[[nodiscard]] uint16_t readWORDTag(Exiv2::BasicIo::UniquePtr& io) ; + +[[nodiscard]] std::string readStringWcharTag(Exiv2::BasicIo::UniquePtr& io, uint16_t length) ; + +[[nodiscard]] std::string readStringTag(Exiv2::BasicIo::UniquePtr& io, uint16_t length=DWORD) ; + +} // namespace Exiv2 #endif // HELPER_FUNCTIONS_HPP diff --git a/src/riffvideo.cpp b/src/riffvideo.cpp index 15de358b..1f7b3bab 100644 --- a/src/riffvideo.cpp +++ b/src/riffvideo.cpp @@ -1,156 +1,105 @@ // ***************************************************************** -*- C++ -*- -/* - * Copyright (C) 2004-2021 Exiv2 authors - * This program is part of the Exiv2 distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * asize_t with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. +/* Spec: + * https://learn.microsoft.com/fr-fr/windows/win32/directshow/avi-riff-file-reference + * https://cdn.hackaday.io/files/274271173436768/avi.pdf + * http://abcavi.kibi.ru/docs/odml1.pdf */ // ***************************************************************************** -// included header files -#include "config.h" -#include "basicio.hpp" +// included header files +#include "riffvideo.hpp" #include "error.hpp" #include "futils.hpp" -#include "image_int.hpp" -#include "riffvideo.hpp" -#include "tags.hpp" -#include "tags_int.hpp" -#include "tiffimage_int.hpp" -#include "types.hpp" -// + standard includes -#include +#include "helper_functions.hpp" -// ***************************************************************************** -// class member definitions namespace Exiv2::Internal { -/*! - @brief Dummy TIFF header structure. - */ -class DummyTiffHeader : public TiffHeaderBase { - public: - //! @name Creators - //@{ - //! Default constructor - DummyTiffHeader(ByteOrder byteOrder); - //! Destructor - ~DummyTiffHeader() override = default; - //@} - - //! @name Manipulators - //@{ - //! Dummy read function. Does nothing and returns true. - bool read(const byte* pData, size_t size) override; - //@} - -}; // class TiffHeader - -DummyTiffHeader::DummyTiffHeader(ByteOrder byteOrder) : TiffHeaderBase(42, 0, byteOrder, 0) { -} - -bool DummyTiffHeader::read(const byte* /*pData*/, size_t /*size*/) { - return true; -} - -constexpr TagVocabulary infoTags[] = {{"AGES", "Xmp.video.Rated"}, - {"CMNT", "Xmp.video.Comment"}, - {"CODE", "Xmp.video.EncodedBy"}, - {"COMM", "Xmp.video.Comment"}, - {"DIRC", "Xmp.video.Director"}, - {"DISP", "Xmp.audio.SchemeTitle"}, - {"DTIM", "Xmp.video.DateTimeOriginal"}, - {"GENR", "Xmp.video.Genre"}, - {"IARL", "Xmp.video.ArchivalLocation"}, - {"IART", "Xmp.video.Artist"}, - {"IAS1", "Xmp.video.Edit1"}, - {"IAS2", "Xmp.video.Edit2"}, - {"IAS3", "Xmp.video.Edit3"}, - {"IAS4", "Xmp.video.Edit4"}, - {"IAS5", "Xmp.video.Edit5"}, - {"IAS6", "Xmp.video.Edit6"}, - {"IAS7", "Xmp.video.Edit7"}, - {"IAS8", "Xmp.video.Edit8"}, - {"IAS9", "Xmp.video.Edit9"}, - {"IBSU", "Xmp.video.BaseURL"}, - {"ICAS", "Xmp.audio.DefaultStream"}, - {"ICDS", "Xmp.video.CostumeDesigner"}, - {"ICMS", "Xmp.video.Commissioned"}, - {"ICMT", "Xmp.video.Comment"}, - {"ICNM", "Xmp.video.Cinematographer"}, - {"ICNT", "Xmp.video.Country"}, - {"ICOP", "Xmp.video.Copyright"}, - {"ICRD", "Xmp.video.DateTimeDigitized"}, - {"ICRP", "Xmp.video.Cropped"}, - {"IDIM", "Xmp.video.Dimensions"}, - {"IDPI", "Xmp.video.DotsPerInch"}, - {"IDST", "Xmp.video.DistributedBy"}, - {"IEDT", "Xmp.video.EditedBy"}, - {"IENC", "Xmp.video.EncodedBy"}, - {"IENG", "Xmp.video.Engineer"}, - {"IGNR", "Xmp.video.Genre"}, - {"IKEY", "Xmp.video.PerformerKeywords"}, - {"ILGT", "Xmp.video.Lightness"}, - {"ILGU", "Xmp.video.LogoURL"}, - {"ILIU", "Xmp.video.LogoIconURL"}, - {"ILNG", "Xmp.video.Language"}, - {"IMBI", "Xmp.video.InfoBannerImage"}, - {"IMBU", "Xmp.video.InfoBannerURL"}, - {"IMED", "Xmp.video.Medium"}, - {"IMIT", "Xmp.video.InfoText"}, - {"IMIU", "Xmp.video.InfoURL"}, - {"IMUS", "Xmp.video.MusicBy"}, - {"INAM", "Xmp.video.Title"}, - {"IPDS", "Xmp.video.ProductionDesigner"}, - {"IPLT", "Xmp.video.NumOfColors"}, - {"IPRD", "Xmp.video.Product"}, - {"IPRO", "Xmp.video.ProducedBy"}, - {"IRIP", "Xmp.video.RippedBy"}, - {"IRTD", "Xmp.video.Rating"}, - {"ISBJ", "Xmp.video.Subject"}, - {"ISFT", "Xmp.video.Software"}, - {"ISGN", "Xmp.video.SecondaryGenre"}, - {"ISHP", "Xmp.video.Sharpness"}, - {"ISRC", "Xmp.video.Source"}, - {"ISRF", "Xmp.video.SourceForm"}, - {"ISTD", "Xmp.video.ProductionStudio"}, - {"ISTR", "Xmp.video.Starring"}, - {"ITCH", "Xmp.video.Technician"}, - {"IWMU", "Xmp.video.WatermarkURL"}, - {"IWRI", "Xmp.video.WrittenBy"}, - {"LANG", "Xmp.video.Language"}, - {"LOCA", "Xmp.video.LocationInfo"}, - {"PRT1", "Xmp.video.Part"}, - {"PRT2", "Xmp.video.NumOfParts"}, - {"RATE", "Xmp.video.Rate"}, - {"STAR", "Xmp.video.Starring"}, - {"STAT", "Xmp.video.Statistics"}, - {"TAPE", "Xmp.video.TapeName"}, - {"TCDO", "Xmp.video.EndTimecode"}, - {"TCOD", "Xmp.video.StartTimecode"}, - {"TITL", "Xmp.video.Title"}, - {"TLEN", "Xmp.video.Length"}, - {"TORG", "Xmp.video.Organization"}, - {"TRCK", "Xmp.video.TrackNumber"}, - {"TURL", "Xmp.video.URL"}, - {"TVER", "Xmp.video.SoftwareVersion"}, - {"VMAJ", "Xmp.video.VegasVersionMajor"}, - {"VMIN", "Xmp.video.VegasVersionMinor"}, - {"YEAR", "Xmp.video.Year"}}; - -constexpr TagDetails audioEncodingValues[] = { +const std::map infoTags = {{"AGES", "Xmp.video.Rated"}, + {"CMNT", "Xmp.video.Comment"}, + {"CODE", "Xmp.video.EncodedBy"}, + {"COMM", "Xmp.video.Comment"}, + {"DIRC", "Xmp.video.Director"}, + {"DISP", "Xmp.audio.SchemeTitle"}, + {"DTIM", "Xmp.video.DateTimeOriginal"}, + {"GENR", "Xmp.video.Genre"}, + {"IARL", "Xmp.video.ArchivalLocation"}, + {"IART", "Xmp.video.Artist"}, + {"IAS1", "Xmp.video.Edit1"}, + {"IAS2", "Xmp.video.Edit2"}, + {"IAS3", "Xmp.video.Edit3"}, + {"IAS4", "Xmp.video.Edit4"}, + {"IAS5", "Xmp.video.Edit5"}, + {"IAS6", "Xmp.video.Edit6"}, + {"IAS7", "Xmp.video.Edit7"}, + {"IAS8", "Xmp.video.Edit8"}, + {"IAS9", "Xmp.video.Edit9"}, + {"IBSU", "Xmp.video.BaseURL"}, + {"ICAS", "Xmp.audio.DefaultStream"}, + {"ICDS", "Xmp.video.CostumeDesigner"}, + {"ICMS", "Xmp.video.Commissioned"}, + {"ICMT", "Xmp.video.Comment"}, + {"ICNM", "Xmp.video.Cinematographer"}, + {"ICNT", "Xmp.video.Country"}, + {"ICOP", "Xmp.video.Copyright"}, + {"ICRD", "Xmp.video.DateTimeDigitized"}, + {"ICRP", "Xmp.video.Cropped"}, + {"IDIM", "Xmp.video.Dimensions"}, + {"IDPI", "Xmp.video.DotsPerInch"}, + {"IDST", "Xmp.video.DistributedBy"}, + {"IEDT", "Xmp.video.EditedBy"}, + {"IENC", "Xmp.video.EncodedBy"}, + {"IENG", "Xmp.video.Engineer"}, + {"IGNR", "Xmp.video.Genre"}, + {"IKEY", "Xmp.video.PerformerKeywords"}, + {"ILGT", "Xmp.video.Lightness"}, + {"ILGU", "Xmp.video.LogoURL"}, + {"ILIU", "Xmp.video.LogoIconURL"}, + {"ILNG", "Xmp.video.Language"}, + {"IMBI", "Xmp.video.InfoBannerImage"}, + {"IMBU", "Xmp.video.InfoBannerURL"}, + {"IMED", "Xmp.video.Medium"}, + {"IMIT", "Xmp.video.InfoText"}, + {"IMIU", "Xmp.video.InfoURL"}, + {"IMUS", "Xmp.video.MusicBy"}, + {"INAM", "Xmp.video.Title"}, + {"IPDS", "Xmp.video.ProductionDesigner"}, + {"IPLT", "Xmp.video.NumOfColors"}, + {"IPRD", "Xmp.video.Product"}, + {"IPRO", "Xmp.video.ProducedBy"}, + {"IRIP", "Xmp.video.RippedBy"}, + {"IRTD", "Xmp.video.Rating"}, + {"ISBJ", "Xmp.video.Subject"}, + {"ISFT", "Xmp.video.Software"}, + {"ISGN", "Xmp.video.SecondaryGenre"}, + {"ISHP", "Xmp.video.Sharpness"}, + {"ISRC", "Xmp.video.Source"}, + {"ISRF", "Xmp.video.SourceForm"}, + {"ISTD", "Xmp.video.ProductionStudio"}, + {"ISTR", "Xmp.video.Starring"}, + {"ITCH", "Xmp.video.Technician"}, + {"IWMU", "Xmp.video.WatermarkURL"}, + {"IWRI", "Xmp.video.WrittenBy"}, + {"LANG", "Xmp.video.Language"}, + {"LOCA", "Xmp.video.LocationInfo"}, + {"PRT1", "Xmp.video.Part"}, + {"PRT2", "Xmp.video.NumOfParts"}, + {"RATE", "Xmp.video.Rate"}, + {"STAR", "Xmp.video.Starring"}, + {"STAT", "Xmp.video.Statistics"}, + {"TAPE", "Xmp.video.TapeName"}, + {"TCDO", "Xmp.video.EndTimecode"}, + {"TCOD", "Xmp.video.StartTimecode"}, + {"TITL", "Xmp.video.Title"}, + {"TLEN", "Xmp.video.Length"}, + {"TORG", "Xmp.video.Organization"}, + {"TRCK", "Xmp.video.TrackNumber"}, + {"TURL", "Xmp.video.URL"}, + {"TVER", "Xmp.video.SoftwareVersion"}, + {"VMAJ", "Xmp.video.VegasVersionMajor"}, + {"VMIN", "Xmp.video.VegasVersionMinor"}, + {"YEAR", "Xmp.video.Year"}}; + +const std::map audioEncodingValues = { {0x1, "Microsoft PCM"}, {0x2, "Microsoft ADPCM"}, {0x3, "Microsoft IEEE float"}, @@ -395,36 +344,13 @@ constexpr TagDetails audioEncodingValues[] = { {0xfffe, "Extensible"}, {0xffff, "Development"}}; -constexpr TagDetails nikonAVITags[] = {{0x0003, "Xmp.video.Make"}, - {0x0004, "Xmp.video.Model"}, - {0x0005, "Xmp.video.Software"}, - {0x0006, "Xmp.video.Equipment"}, - {0x0007, "Xmp.video.Orientation"}, - {0x0008, "Xmp.video.ExposureTime"}, - {0x0009, "Xmp.video.FNumber"}, - {0x000a, "Xmp.video.ExposureCompensation"}, - {0x000b, "Xmp.video.MaxApertureValue"}, - {0x000c, "Xmp.video.MeteringMode"}, - {0x000f, "Xmp.video.FocalLength"}, - {0x0010, "Xmp.video.XResolution"}, - {0x0011, "Xmp.video.YResolution"}, - {0x0012, "Xmp.video.ResolutionUnit"}, - {0x0013, "Xmp.video.DateTimeOriginal"}, - {0x0014, "Xmp.video.DateTimeDigitized"}, - {0x0016, "Xmp.video.duration"}, - {0x0018, "Xmp.video.FocusMode"}, - {0x001b, "Xmp.video.DigitalZoomRatio"}, - {0x001d, "Xmp.video.ColorMode"}, - {0x001e, "Xmp.video.Sharpness"}, - {0x001f, "Xmp.video.WhiteBalance"}, - {0x0020, "Xmp.video.ColorNoiseReduction"}}; - -enum streamTypeInfo { Audio = 1, MIDI, Text, Video }; - } // namespace Exiv2::Internal +// ***************************************************************************** +// class member definitions namespace Exiv2 { -using namespace Exiv2::Internal; + +enum streamTypeInfo { Audio = 1, MIDI, Text, Video }; RiffVideo::RiffVideo(BasicIo::UniquePtr io) : Image(ImageType::riff, mdNone, std::move(io)) { } // RiffVideo::RiffVideo @@ -433,82 +359,10 @@ std::string RiffVideo::mimeType() const { return "video/riff"; } -/*! - @brief Function used to check equality of a Tags with a - particular string (ignores case while comparing). - @param buf Data buffer that will contain Tag to compare - @param str char* Pointer to string - @return Returns true if the buffer value is equal to string. - */ -bool RiffVideo::equalsRiffTag(Exiv2::DataBuf& buf, const char* str) { - for (size_t i = 0; i < RIFF_TAG_SIZE; i++) - if (toupper(buf.data()[i]) != str[i]) - return false; - return true; -} - void RiffVideo::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) { if (io_->open() != 0) { throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); } - // Ensure this is the correct image type - if (!isRiffType(*io_, true)) { - if (io_->error() || io_->eof()) - throw Error(ErrorCode::kerFailedToReadImageData); - throw Error(ErrorCode::kerNotAnImage, "RIFF"); - } - - bool bPrint = option == kpsBasic || option == kpsRecursive; - if (bPrint || option == kpsXMP || option == kpsIccProfile || option == kpsIptcErase) { - byte data[RIFF_TAG_SIZE * 2]; - io_->read(data, RIFF_TAG_SIZE * 2); - uint64_t filesize = Exiv2::getULong(data + RIFF_TAG_SIZE, littleEndian); - DataBuf chunkId(5); - - if (bPrint) { - out << Internal::indent(depth) << "STRUCTURE OF RIFF FILE: " << io().path() << std::endl; - out << Internal::indent(depth) << Internal::stringFormat(" Chunk | Length | Offset | Payload") - << std::endl; - } - - const uint64_t bufMaxSize = 200; - io_->seek(0, BasicIo::beg); // rewind - while (!io_->eof() && static_cast(io_->tell()) < filesize) { - auto offset = static_cast(io_->tell()); - byte size_buff[RIFF_TAG_SIZE]; - io_->read(chunkId.data(), RIFF_TAG_SIZE); - io_->read(size_buff, RIFF_TAG_SIZE); - uint32_t size = Exiv2::getULong(size_buff, littleEndian); - if (size > bufMaxSize) { - io_->seek(size, BasicIo::cur); - continue; - } - DataBuf payload(offset ? size : RIFF_TAG_SIZE); // header is different from chunks - io_->read(payload.data(), payload.size()); - - if (bPrint) { - out << Internal::indent(depth) - << Internal::stringFormat(" %s | %12u | %12u | ", reinterpret_cast(chunkId.data()), size, - static_cast(offset)) - << Internal::binaryToString(makeSlice(payload, 0, payload.size() > 32 ? 32 : payload.size())) << std::endl; - } - - if (equalsRiffTag(chunkId, RIFF_CHUNK_HEADER_EXIF) && option == kpsRecursive) { - // create MemIo object with the payload, then print the structure - MemIo p(payload.c_data(), payload.size()); - printTiffStructure(p, out, option, depth); - } - - bool bPrintPayload = (equalsRiffTag(chunkId, RIFF_CHUNK_HEADER_XMP) && option == kpsXMP) || - (equalsRiffTag(chunkId, RIFF_CHUNK_HEADER_ICCP) && option == kpsIccProfile); - if (bPrintPayload) { - out.write(reinterpret_cast(payload.data()), payload.size()); - } - - if (offset && io_->tell() % 2) - io_->seek(+1, BasicIo::cur); // skip padding byte on sub-chunks - } - } } // RiffVideo::printStructure void RiffVideo::writeMetadata() { @@ -527,658 +381,386 @@ void RiffVideo::readMetadata() { IoCloser closer(*io_); clearMetadata(); - continueTraversing_ = true; - xmpData_["Xmp.video.FileSize"] = io_->size() / 1048576.; + xmpData_["Xmp.video.FileSize"] = io_->size(); xmpData_["Xmp.video.MimeType"] = mimeType(); - DataBuf buf(RIFF_TAG_SIZE + 1); + HeaderReader header(io_); + xmpData_["Xmp.video.Container"] = header.getId(); - io_->read(buf.data(), RIFF_TAG_SIZE); - xmpData_["Xmp.video.Container"] = buf.data(); + DataBuf FileType = io_->read(DWORD); + xmpData_["Xmp.video.FileType"] = FileType.data(); - io_->read(buf.data(), RIFF_TAG_SIZE); - io_->read(buf.data(), RIFF_TAG_SIZE); - xmpData_["Xmp.video.FileType"] = buf.data(); - - while (continueTraversing_) - decodeBlock(); + decodeBlocks(); } // RiffVideo::readMetadata -void RiffVideo::decodeBlock() { - DataBuf chunk_size(RIFF_TAG_SIZE + 1); - DataBuf chunk_id(RIFF_TAG_SIZE + 1); - - io_->read(chunk_id.data(), RIFF_TAG_SIZE); +RiffVideo::HeaderReader::HeaderReader(BasicIo::UniquePtr& io) { + if (io->size() >= io->tell() + DWORD + DWORD) { + DataBuf IdBuf_ = io->read(DWORD); + id_ = Exiv2::toString(IdBuf_.data()); - if (io_->eof() || equalsRiffTag(chunk_id, RIFF_CHUNK_ID_MOVI) || equalsRiffTag(chunk_id, RIFF_CHUNK_ID_DATA)) { - continueTraversing_ = false; - return; - } - if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_HDRL) || equalsRiffTag(chunk_id, RIFF_CHUNK_ID_STRL)) { - decodeBlock(); - } else { - io_->read(chunk_size.data(), RIFF_TAG_SIZE); - size_t size = Exiv2::getULong(chunk_size.data(), littleEndian); - - tagDecoder(chunk_id, size); - } -} // RiffVideo::decodeBlock - -void RiffVideo::tagDecoder(Exiv2::DataBuf& chunk_id, size_t size) { - uint64_t cur_pos = io_->tell(); - static bool listFlag = false, listEnd = false; - - if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_LIST)) { - listFlag = true; - listEnd = false; - - while (static_cast(io_->tell()) < cur_pos + size && !io_->eof()) { - decodeBlock(); - } - listEnd = true; - io_->seek(cur_pos + size, BasicIo::beg); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_JUNK) && listEnd) { - junkHandler(size); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_AVIH)) { - listFlag = false; - aviHeaderTagsHandler(size); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_STRH)) { - listFlag = false; - streamHandler(size); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_STRF) || equalsRiffTag(chunk_id, RIFF_CHUNK_ID_FMT)) { - listFlag = false; - if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_FMT)) - streamType_ = Audio; - streamFormatHandler(size); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_STRN)) { - listFlag = false; - dateTimeOriginal(size, 1); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_STRD)) { - listFlag = false; - streamDataTagHandler(size); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_IDIT)) { - listFlag = false; - dateTimeOriginal(size); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_INFO)) { - listFlag = false; - infoTagsHandler(); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_NCDT)) { - listFlag = false; - nikonTagsHandler(); - } else if (equalsRiffTag(chunk_id, RIFF_CHUNK_ID_ODML)) { - listFlag = false; - odmlTagsHandler(); - } else if (listFlag) { - skipListData(); - } else { - io_->seek(cur_pos + size, BasicIo::beg); + size_ = readDWORDTag(io); } -} // RiffVideo::tagDecoder +} -void RiffVideo::streamDataTagHandler(size_t size) { - const size_t bufMinSize = 20000; - DataBuf buf(bufMinSize); - uint64_t cur_pos = io_->tell(); +bool RiffVideo::equal(const std::string& str1, const std::string& str2) { + if (str1.size() != str2.size()) + return false; + for (size_t i = 0; i < str1.size(); i++) + if (toupper(str1[i]) != str2[i]) + return false; + return true; +} - io_->read(buf.data(), 8); +void RiffVideo::readList(HeaderReader& header_) { + DataBuf FormTypeBuf_ = io_->read(DWORD); - if (equalsRiffTag(buf, "AVIF")) { - if (size < RIFF_TAG_SIZE) { -#ifndef SUPPRESS_WARNINGS - EXV_ERROR << " Exif Tags found in this RIFF file are not of valid size ." - << " Entries considered invalid. Not Processed.\n"; -#endif - } else { - io_->read(buf.data(), size - RIFF_TAG_SIZE); - - IptcData iptcData; - XmpData xmpData; - DummyTiffHeader tiffHeader(littleEndian); - TiffParserWorker::decode(exifData_, iptcData, xmpData, buf.data(), buf.size(), Tag::root, - TiffMapping::findDecoder, &tiffHeader); - -#ifndef SUPPRESS_WARNINGS - if (!iptcData.empty()) { - EXV_WARNING << "Ignoring IPTC information encoded in the Exif data.\n"; - } - if (!xmpData.empty()) { - EXV_WARNING << "Ignoring XMP information encoded in the Exif data.\n"; - } +#ifdef EXIV2_DEBUG_MESSAGES + EXV_DEBUG << "-> Reading list : id= " << header_.getId() << " type= " << Exiv2::toString(FormTypeBuf_.data()) + << " size= " << header_.getSize() << "(" << io_->tell() << "/" << io_->size() << ")" << std::endl; #endif - } - } - // TODO decode CasioData and ZORA Tag - io_->seek(cur_pos + size, BasicIo::beg); - -} // RiffVideo::streamDataTagHandler - -void RiffVideo::dateTimeOriginal(size_t size, int i) { - uint64_t cur_pos = io_->tell(); - const size_t bufMinSize = 100; - DataBuf buf(bufMinSize); - io_->read(buf.data(), size); - if (!i) - xmpData_["Xmp.video.DateUTC"] = buf.data(); - else - xmpData_["Xmp.video.StreamName"] = buf.data(); - io_->seek(cur_pos + size, BasicIo::beg); -} // RiffVideo::dateTimeOriginal - -void RiffVideo::odmlTagsHandler() { - const size_t bufMinSize = 100; - DataBuf buf(bufMinSize); - io_->seek(-12, BasicIo::cur); - io_->read(buf.data(), RIFF_TAG_SIZE); - size_t size = Exiv2::getULong(buf.data(), littleEndian); - size_t size2 = size; - - uint64_t cur_pos = io_->tell(); - io_->read(buf.data(), RIFF_TAG_SIZE); - size -= RIFF_TAG_SIZE; - - while (size > 0) { - io_->read(buf.data(), RIFF_TAG_SIZE); - size -= RIFF_TAG_SIZE; - if (equalsRiffTag(buf, "DMLH")) { - io_->read(buf.data(), RIFF_TAG_SIZE); - size -= RIFF_TAG_SIZE; - io_->read(buf.data(), RIFF_TAG_SIZE); - size -= RIFF_TAG_SIZE; - xmpData_["Xmp.video.TotalFrameCount"] = Exiv2::getULong(buf.data(), littleEndian); - } + + if (equal(Exiv2::toString(FormTypeBuf_.data()), CHUNK_ID_INFO)) + readInfoListChunk(header_.getSize()); + else if (equal(Exiv2::toString(FormTypeBuf_.data()), CHUNK_ID_MOVI)) { + readMoviList(header_.getSize()); } - io_->seek(cur_pos + size2, BasicIo::beg); -} // RiffVideo::odmlTagsHandler - -void RiffVideo::skipListData() { - DataBuf buf(RIFF_TAG_SIZE + 1); - io_->seek(-12, BasicIo::cur); - io_->read(buf.data(), RIFF_TAG_SIZE); - size_t size = Exiv2::getULong(buf.data(), littleEndian); - - uint64_t cur_pos = io_->tell(); - io_->seek(cur_pos + size, BasicIo::beg); -} // RiffVideo::skipListData - -void RiffVideo::copyTagValue(DataBuf& buf_dest, DataBuf& buf_src, size_t index) { - buf_dest.data()[0] = buf_src.data()[0 + index]; - buf_dest.data()[1] = buf_src.data()[1 + index]; - buf_dest.data()[2] = buf_src.data()[2 + index]; - buf_dest.data()[3] = buf_src.data()[3 + index]; } -void RiffVideo::nikonTagsHandler() { - const size_t bufMinSize = 100; - DataBuf buf(bufMinSize), buf2(RIFF_TAG_SIZE + 1); - io_->seek(-12, BasicIo::cur); - io_->read(buf.data(), RIFF_TAG_SIZE); - - size_t internal_size = 0, tagID = 0, dataSize = 0, tempSize, size = Exiv2::getULong(buf.data(), littleEndian); - tempSize = size; - char str[9] = " . . . "; - uint64_t internal_pos, cur_pos; - internal_pos = cur_pos = io_->tell(); - const TagDetails* td; - double denominator = 1; - io_->read(buf.data(), RIFF_TAG_SIZE); - tempSize -= RIFF_TAG_SIZE; - - while (tempSize > 0) { - std::memset(buf.data(), 0x0, buf.size()); - io_->read(buf.data(), RIFF_TAG_SIZE); - io_->read(buf2.data(), RIFF_TAG_SIZE); - size_t temp = internal_size = Exiv2::getULong(buf2.data(), littleEndian); - internal_pos = io_->tell(); - tempSize -= (internal_size + 8); - - if (equalsRiffTag(buf, "NCVR")) { - while (temp > 3) { - std::memset(buf.data(), 0x0, buf.size()); - io_->read(buf.data(), 2); - tagID = Exiv2::getULong(buf.data(), littleEndian); - io_->read(buf.data(), 2); - dataSize = Exiv2::getULong(buf.data(), littleEndian); - temp -= (RIFF_TAG_SIZE + dataSize); - - if (tagID == 0x0001) { - if (dataSize <= 0) { -#ifndef SUPPRESS_WARNINGS - EXV_ERROR << " Makernotes found in this RIFF file are not of valid size ." - << " Entries considered invalid. Not Processed.\n"; +void RiffVideo::readChunk(HeaderReader& header_) { +#ifdef EXIV2_DEBUG_MESSAGES + EXV_DEBUG << "--> Reading Chunk : [" << header_.getId() << "] size= " << header_.getSize() << "(" << io_->tell() + << "/" << io_->size() << ")" << std::endl; #endif - } else { - io_->read(buf.data(), dataSize); - xmpData_["Xmp.video.MakerNoteType"] = buf.data(); - } - } else if (tagID == 0x0002) { - while (dataSize) { - std::memset(buf.data(), 0x0, buf.size()); - io_->read(buf.data(), 1); - str[(RIFF_TAG_SIZE - dataSize) * 2] = static_cast(Exiv2::getULong(buf.data(), littleEndian) + 48); - --dataSize; - } - xmpData_["Xmp.video.MakerNoteVersion"] = str; - } - } - } else if (equalsRiffTag(buf, "NCTG")) { - while (temp > 3) { - std::memset(buf.data(), 0x0, buf.size()); - io_->read(buf.data(), 2); - tagID = Exiv2::getULong(buf.data(), littleEndian); - io_->read(buf.data(), 2); - dataSize = Exiv2::getULong(buf.data(), littleEndian); - temp -= (RIFF_TAG_SIZE + dataSize); - td = find(nikonAVITags, tagID); - - if (dataSize <= 0) { -#ifndef SUPPRESS_WARNINGS - EXV_ERROR << " Makernotes found in this RIFF file are not of valid size ." - << " Entries considered invalid. Not Processed.\n"; -#endif - } else { - io_->read(buf.data(), dataSize); - - switch (tagID) { - case 0x0003: - case 0x0004: - case 0x0005: - case 0x0006: - case 0x0013: - case 0x0014: - case 0x0018: - case 0x001d: - case 0x001e: - case 0x001f: - case 0x0020: - xmpData_[exvGettext(td->label_)] = buf.data(); - break; - - case 0x0007: - case 0x0010: - case 0x0011: - case 0x000c: - case 0x0012: - xmpData_[exvGettext(td->label_)] = Exiv2::getULong(buf.data(), littleEndian); - break; - - case 0x0008: - case 0x0009: - case 0x000a: - case 0x000b: - case 0x000f: - case 0x001b: - case 0x0016: - copyTagValue(buf2, buf); - denominator = static_cast(Exiv2::getLong(buf2.data(), littleEndian)); - if (denominator != 0) - xmpData_[exvGettext(td->label_)] = Exiv2::getLong(buf.data(), littleEndian) / denominator; - else - xmpData_[exvGettext(td->label_)] = 0; - break; - - default: - break; - } - } - } - } - - else if (equalsRiffTag(buf, "NCTH")) { // TODO Nikon Thumbnail Image - } - else if (equalsRiffTag(buf, "NCVW")) { // TODO Nikon Preview Image - } - - io_->seek(internal_pos + internal_size, BasicIo::beg); + if (equal(header_.getId(), CHUNK_ID_AVIH)) + readAviHeader(); + else if (equal(header_.getId(), CHUNK_ID_STRH)) + readStreamHeader(); + else if (equal(header_.getId(), CHUNK_ID_STRF)) + readStreamFormat(header_.getSize()); + else if (equal(header_.getId(), CHUNK_ID_FMT)) { + streamType_ = Audio; + readStreamFormat(header_.getSize()); + } else if (equal(header_.getId(), CHUNK_ID_STRD)) + readStreamData(header_.getSize()); + else if (equal(header_.getId(), CHUNK_ID_STRN)) + StreamName(header_.getSize()); + else if (equal(header_.getId(), CHUNK_ID_VPRP)) + readVPRPChunk(header_.getSize()); + else if (equal(header_.getId(), CHUNK_ID_IDX1)) + readIndexChunk(header_.getSize()); + else if (equal(header_.getId(), CHUNK_ID_DATA)) + readDataChunk(header_.getSize()); + else if (equal(header_.getId(), CHUNK_ID_JUNK)) + readJunk(header_.getSize()); + else { +#ifdef EXIV2_DEBUG_MESSAGES + EXV_DEBUG << "--> Ignoring Chunk : " << header_.getId() << "] size= " << header_.getSize() << "(" << io_->tell() + << "/" << io_->size() << ")" << std::endl; +#endif + io_->seekOrThrow(io_->tell() + header_.getSize(), BasicIo::beg, ErrorCode::kerFailedToReadImageData); } +} - if (size == 0) { - io_->seek(cur_pos + RIFF_TAG_SIZE, BasicIo::beg); +void RiffVideo::decodeBlocks() { + HeaderReader header(io_); + if (equal(header.getId(), CHUNK_ID_LIST)) { + readList(header); } else { - io_->seek(cur_pos + size, BasicIo::beg); + readChunk(header); } -} // RiffVideo::nikonTagsHandler - -void RiffVideo::infoTagsHandler() { - const size_t bufMinSize = 10000; - DataBuf buf(bufMinSize); - io_->seek(-12, BasicIo::cur); - io_->read(buf.data(), RIFF_TAG_SIZE); - uint32_t infoSize, size = Exiv2::getULong(buf.data(), littleEndian); - uint32_t size_external = size; - const TagVocabulary* tv; - - uint64_t cur_pos = io_->tell(); - io_->read(buf.data(), RIFF_TAG_SIZE); - size -= RIFF_TAG_SIZE; - - while (size >= RIFF_TAG_SIZE) { - io_->read(buf.data(), RIFF_TAG_SIZE); - size -= RIFF_TAG_SIZE; - if (!Exiv2::getULong(buf.data(), littleEndian)) - break; - tv = find(infoTags, Exiv2::toString(buf.data())); - io_->read(buf.data(), RIFF_TAG_SIZE); - size -= RIFF_TAG_SIZE; - infoSize = Exiv2::getULong(buf.data(), littleEndian); - - size -= infoSize; - io_->read(buf.data(), infoSize); - if (infoSize < RIFF_TAG_SIZE) - buf.data()[infoSize] = '\0'; - - if (tv) - xmpData_[exvGettext(tv->label_)] = buf.data(); - else - continue; + + if (!io_->eof() && io_->tell() < io_->size()) { + decodeBlocks(); } - io_->seek(cur_pos + size_external, BasicIo::beg); -} // RiffVideo::infoTagsHandler -void RiffVideo::junkHandler(size_t size) { - DataBuf buf(size), buf2(RIFF_TAG_SIZE); - uint64_t cur_pos = io_->tell(); +} // RiffVideo::decodeBlock - io_->read(buf.data(), RIFF_TAG_SIZE); - //! Pentax Metadata and Tags - if (equalsRiffTag(buf, "PENT")) { - io_->seek(cur_pos + 18, BasicIo::beg); - io_->read(buf.data(), 26); - xmpData_["Xmp.video.Make"] = buf.data(); +void RiffVideo::readAviHeader() { +#ifdef EXIV2_DEBUG_MESSAGES + EXV_DEBUG << "--> dwMicroSecPerFrame = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwMaxBytesPerSec = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwPaddingGranularity = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwFlags = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwTotalFrames = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwInitialFrames = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwStreams = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwSuggestedBufferSize = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwWidth = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwHeight = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwReserved1 = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwReserved2 = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwReserved3 = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwReserved4 = " << readDWORDTag(io_) << std::endl; +#else - io_->read(buf.data(), 50); - xmpData_["Xmp.video.Model"] = buf.data(); + uint32_t TimeBetweenFrames = readDWORDTag(io_); + xmpData_["Xmp.video.MicroSecPerFrame"] = TimeBetweenFrames; + double frame_rate = 1000000. / TimeBetweenFrames; - std::memset(buf.data(), 0x0, buf.size()); - io_->read(buf.data(), 8); - copyTagValue(buf2, buf); - xmpData_["Xmp.video.FNumber"] = - static_cast(Exiv2::getLong(buf.data(), littleEndian)) / Exiv2::getLong(buf2.data(), littleEndian); - ; + xmpData_["Xmp.video.MaxDataRate"] = readDWORDTag(io_); // MaximumDataRate - io_->seek(cur_pos + 131, BasicIo::beg); - io_->read(buf.data(), 26); - xmpData_["Xmp.video.DateTimeOriginal"] = buf.data(); + io_->seekOrThrow(io_->tell() + DWORD * 2, BasicIo::beg, + ErrorCode::kerFailedToReadImageData); // ignore PaddingGranularity and Flags - io_->read(buf.data(), 26); - xmpData_["Xmp.video.DateTimeDigitized"] = buf.data(); + uint32_t frame_count = readDWORDTag(io_); // TotalNumberOfFrames + xmpData_["Xmp.video.FrameCount"] = frame_count; - io_->seek(cur_pos + 299, BasicIo::beg); - std::memset(buf.data(), 0x0, buf.size()); + io_->seekOrThrow(io_->tell() + DWORD, BasicIo::beg, + ErrorCode::kerFailedToReadImageData); // ignore NumberOfInitialFrames - io_->read(buf.data(), 2); - Exiv2::XmpTextValue tv(Exiv2::toString(Exiv2::getLong(buf.data(), littleEndian))); - xmpData_.add(Exiv2::XmpKey("Xmp.xmp.Thumbnails/xmpGImg:width"), &tv); + xmpData_["Xmp.audio.ChannelType"] = getStreamType(readDWORDTag(io_)); // NumberOfStreams - io_->read(buf.data(), 2); - tv.read(Exiv2::toString(Exiv2::getLong(buf.data(), littleEndian))); - xmpData_.add(Exiv2::XmpKey("Xmp.xmp.Thumbnails/xmpGImg:height"), &tv); + xmpData_["Xmp.video.StreamCount"] = readDWORDTag(io_); // SuggestedBufferSize - io_->read(buf.data(), RIFF_TAG_SIZE); + uint32_t width = readDWORDTag(io_); + xmpData_["Xmp.video.Width"] = width; - // TODO - Storing the image Thumbnail in Base64 Format + uint32_t height = readDWORDTag(io_); + xmpData_["Xmp.video.Height"] = height; - } else { - io_->seek(cur_pos, BasicIo::beg); - io_->read(buf.data(), size); - xmpData_["Xmp.video.Junk"] = buf.data(); + io_->seekOrThrow(io_->tell() + DWORD * 4, BasicIo::beg, + ErrorCode::kerFailedToReadImageData); // TimeScale, DataRate, StartTime, DataLength + + fillAspectRatio(width, height); + fillDuration(frame_rate, frame_count); +#endif +} + +void RiffVideo::readStreamHeader() { + std::string stream = readStringTag(io_); + streamType_ = (equal(stream, "VIDS")) ? Video : Audio; + +#ifdef EXIV2_DEBUG_MESSAGES + EXV_DEBUG << "--> fccType = " << stream << std::endl; + EXV_DEBUG << "--> fccHandler = " << readStringTag(io_) << std::endl; + EXV_DEBUG << "--> dwFlags = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> wPriority = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> wLanguage = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwInitialFrames = " << readDWORDTag(io_) << std::endl; // 20 + EXV_DEBUG << "--> dwScale = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwRate = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwStart = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwLength = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> dwSuggestedBufferSize = " << readDWORDTag(io_) << std::endl; // 40 + EXV_DEBUG << "--> dwSampleSize = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> Left = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> top = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> right = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> bottom = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> XXXXXX = " << readDWORDTag(io_) << std::endl; // 56 + +#else + + xmpData_["Xmp.video.Codec"] = readStringTag(io_); // DataHandler + + io_->seekOrThrow(io_->tell() + DWORD * 2 + WORD * 2, BasicIo::beg, + ErrorCode::kerFailedToReadImageData); // dwFlags, wPriority, wLanguage, dwInitialFrames + + uint32_t divisor = readDWORDTag(io_); // TimeScale + + if (divisor) { + double rate = static_cast(readDWORDTag(io_) / divisor); + xmpData_[(streamType_ == Video) ? "Xmp.video.FrameRate" : "Xmp.audio.SampleRate"] = rate; } + io_->seekOrThrow(io_->tell() + DWORD, BasicIo::beg, ErrorCode::kerFailedToReadImageData); // dwStart - io_->seek(cur_pos + size, BasicIo::beg); -} // RiffVideo::junkHandler - -void RiffVideo::aviHeaderTagsHandler(size_t size) { - DataBuf buf(RIFF_TAG_SIZE + 1); - size_t width = 0, height = 0, frame_count = 0; - double frame_rate = 1; - - uint64_t cur_pos = io_->tell(); - - enum aviHeaderTags { frameRate, maxDataRate, frameCount = 4, streamCount = 6, imageWidth_h = 8, imageHeight_h, last }; - for (aviHeaderTags tag = frameRate; tag != aviHeaderTags::last; tag = static_cast(tag + 1)) { - std::memset(buf.data(), 0x0, buf.size()); - io_->read(buf.data(), RIFF_TAG_SIZE); - - switch (tag) { - case frameRate: - xmpData_["Xmp.video.MicroSecPerFrame"] = Exiv2::getULong(buf.data(), littleEndian); - frame_rate = 1000000. / Exiv2::getULong(buf.data(), littleEndian); - break; - case (maxDataRate): - xmpData_["Xmp.video.MaxDataRate"] = Exiv2::getULong(buf.data(), littleEndian) / 1024.; - break; - case frameCount: - frame_count = Exiv2::getULong(buf.data(), littleEndian); - xmpData_["Xmp.video.FrameCount"] = frame_count; - break; - case streamCount: - xmpData_["Xmp.video.StreamCount"] = Exiv2::getULong(buf.data(), littleEndian); - break; - case imageWidth_h: - width = Exiv2::getULong(buf.data(), littleEndian); - xmpData_["Xmp.video.Width"] = width; - break; - case imageHeight_h: - height = Exiv2::getULong(buf.data(), littleEndian); - xmpData_["Xmp.video.Height"] = height; - break; - default: - break; - } + if (divisor) { + double frame_count = static_cast(readDWORDTag(io_) / divisor); // DataLength + xmpData_[(streamType_ == Video) ? "Xmp.video.FrameCount" : "Xmp.audio.FrameCount"] = frame_count; } - fillAspectRatio(width, height); - fillDuration(frame_rate, frame_count); + io_->seekOrThrow(io_->tell() + DWORD, BasicIo::beg, ErrorCode::kerFailedToReadImageData); // dwSuggestedBufferSize - io_->seek(cur_pos + size, BasicIo::beg); -} // RiffVideo::aviHeaderTagsHandler + xmpData_[(streamType_ == Video) ? "Xmp.video.VideoQuality" : "Xmp.video.StreamQuality"] = readDWORDTag(io_); -void RiffVideo::streamHandler(size_t size) { - DataBuf buf(RIFF_TAG_SIZE + 1); - size_t divisor = 1; - uint64_t cur_pos = io_->tell(); + xmpData_[(streamType_ == Video) ? "Xmp.video.VideoSampleSize" : "Xmp.video.StreamSampleSize"] = readDWORDTag(io_); + io_->seekOrThrow(io_->tell() + DWORD * 2, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +#endif +} - io_->read(buf.data(), RIFF_TAG_SIZE); - if (equalsRiffTag(buf, "VIDS")) - streamType_ = Video; - else if (equalsRiffTag(buf, "AUDS")) - streamType_ = Audio; +void RiffVideo::readStreamFormat(uint64_t size_) { + // The structure of the strf chunk depends on the media type. Video streams use the BITMAPINFOHEADER structure, + // whereas audio streams use the WAVEFORMATEX structure. - enum streamHeaderTags { - codec = 1, - sampleRateDivisor = 5, - sampleRate = 6, - sampleCount = 8, - quality = 10, - sampleSize = 11, - last - }; - for (streamHeaderTags tag = codec; tag != streamHeaderTags::last; tag = static_cast(tag + 1)) { - std::memset(buf.data(), 0x0, buf.size()); - io_->read(buf.data(), RIFF_TAG_SIZE); // the position is advanced by the number of bytes read, that's why we need - // to iterate sequentially , not only on switch values. - - switch (tag) { - case codec: - if (streamType_ == Video) - xmpData_["Xmp.video.Codec"] = buf.data(); - else if (streamType_ == Audio) - xmpData_["Xmp.audio.Codec"] = buf.data(); - else - xmpData_["Xmp.video.Codec"] = buf.data(); - break; - case sampleRateDivisor: - divisor = Exiv2::getULong(buf.data(), littleEndian); - break; - case sampleRate: - if (streamType_ == Video) - xmpData_["Xmp.video.FrameRate"] = returnSampleRate(buf, divisor); - else if (streamType_ == Audio) - xmpData_["Xmp.audio.SampleRate"] = returnSampleRate(buf, divisor); - else - xmpData_["Xmp.video.StreamSampleRate"] = returnSampleRate(buf, divisor); - break; - case sampleCount: - if (streamType_ == Video) - xmpData_["Xmp.video.FrameCount"] = Exiv2::getULong(buf.data(), littleEndian); - else if (streamType_ == Audio) - xmpData_["Xmp.audio.SampleCount"] = Exiv2::getULong(buf.data(), littleEndian); - else - xmpData_["Xmp.video.StreamSampleCount"] = Exiv2::getULong(buf.data(), littleEndian); - break; - case quality: - if (streamType_ == Video) - xmpData_["Xmp.video.VideoQuality"] = Exiv2::getULong(buf.data(), littleEndian); - else if (streamType_ != Audio) - xmpData_["Xmp.video.StreamQuality"] = Exiv2::getULong(buf.data(), littleEndian); - break; - case sampleSize: - if (streamType_ == Video) - xmpData_["Xmp.video.VideoSampleSize"] = Exiv2::getULong(buf.data(), littleEndian); - else if (streamType_ != Audio) - xmpData_["Xmp.video.StreamSampleSize"] = Exiv2::getULong(buf.data(), littleEndian); - break; - default: - break; - } +#ifdef EXIV2_DEBUG_MESSAGES + if (streamType_ == Video) { + EXV_DEBUG << "--> wFormatTag = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> nChannels = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> nSamplesPerSec = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> nAvgBytesPerSec = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> nBlockAlign = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> wBitsPerSample = " << readWORDTag(io_) << std::endl; + } else { + EXV_DEBUG << "--> biSize = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biWidth = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biHeight = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biPlanes = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biBitCount = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biCompression = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biSizeImage = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biXPelsPerMeter = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biYPelsPerMeter = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biClrUsed = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> biClrImportant = " << readDWORDTag(io_) << std::endl; } - io_->seek(cur_pos + size, BasicIo::beg); -} // RiffVideo::streamHandler -void RiffVideo::streamFormatHandler(size_t size) { - DataBuf buf(RIFF_TAG_SIZE + 1); - uint64_t cur_pos = io_->tell(); +#else if (streamType_ == Video) { - io_->read(buf.data(), RIFF_TAG_SIZE); - - enum bmptags { - imageWidth, - imageHeight, - planes, - bitDepth, - compression, - imageLength, - pixelsPerMeterX, - pixelsPerMeterY, - numColors, - numImportantColors, - last - }; - for (bmptags tag = imageWidth; tag != bmptags::last; tag = static_cast(tag + 1)) { - std::memset(buf.data(), 0x0, buf.size()); - - switch (tag) { - case imageWidth: // Will be used in case of debugging - io_->read(buf.data(), RIFF_TAG_SIZE); - break; - case imageHeight: // Will be used in case of debugging - io_->read(buf.data(), RIFF_TAG_SIZE); - break; - case planes: - io_->read(buf.data(), 2); - xmpData_["Xmp.video.Planes"] = Exiv2::getUShort(buf.data(), littleEndian); - break; - case bitDepth: - io_->read(buf.data(), 2); - xmpData_["Xmp.video.PixelDepth"] = Exiv2::getUShort(buf.data(), littleEndian); - break; - case compression: - io_->read(buf.data(), RIFF_TAG_SIZE); - xmpData_["Xmp.video.Compressor"] = buf.data(); - break; - case imageLength: - io_->read(buf.data(), RIFF_TAG_SIZE); - xmpData_["Xmp.video.ImageLength"] = Exiv2::getULong(buf.data(), littleEndian); - break; - case pixelsPerMeterX: - io_->read(buf.data(), RIFF_TAG_SIZE); - xmpData_["Xmp.video.PixelPerMeterX"] = Exiv2::getULong(buf.data(), littleEndian); - break; - case pixelsPerMeterY: - io_->read(buf.data(), RIFF_TAG_SIZE); - xmpData_["Xmp.video.PixelPerMeterY"] = Exiv2::getULong(buf.data(), littleEndian); - break; - case numColors: - io_->read(buf.data(), RIFF_TAG_SIZE); - if (Exiv2::getULong(buf.data(), littleEndian) == 0) { - xmpData_["Xmp.video.NumOfColours"] = "Unspecified"; - } else { - xmpData_["Xmp.video.NumOfColours"] = Exiv2::getULong(buf.data(), littleEndian); - } - break; - case numImportantColors: - io_->read(buf.data(), RIFF_TAG_SIZE); - if (Exiv2::getULong(buf.data(), littleEndian)) { - xmpData_["Xmp.video.NumIfImpColours"] = Exiv2::getULong(buf.data(), littleEndian); - } else { - xmpData_["Xmp.video.NumOfImpColours"] = "All"; - } - break; - default: - break; - } - } + io_->seekOrThrow(io_->tell() + DWORD * 3, BasicIo::beg, + ErrorCode::kerFailedToReadImageData); // ignore biSize, biWidth, biHeight + xmpData_["Xmp.video.Planes"] = readWORDTag(io_); + xmpData_["Xmp.video.PixelDepth"] = readWORDTag(io_); + xmpData_["Xmp.video.Compressor"] = readStringTag(io_); + xmpData_["Xmp.video.ImageLength"] = readDWORDTag(io_); + xmpData_["Xmp.video.PixelPerMeterX"] = readQWORDTag(io_); + xmpData_["Xmp.video.PixelPerMeterY"] = readQWORDTag(io_); + uint32_t NumOfColours = readDWORDTag(io_); + if (NumOfColours == 0) + xmpData_["Xmp.video.NumOfColours"] = "Unspecified"; + else + xmpData_["Xmp.video.NumOfColours"] = NumOfColours; + uint32_t NumIfImpColours = readDWORDTag(io_); + if (NumIfImpColours == 0) + xmpData_["Xmp.video.NumIfImpColours"] = "All"; + else + xmpData_["Xmp.video.NumIfImpColours"] = NumIfImpColours; } else if (streamType_ == Audio) { - int c = 0; - const TagDetails* td; - enum audioFormatTags { encoding, numberOfChannels, audioSampleRate, avgBytesPerSec = 4, bitsPerSample = 7, last }; - for (audioFormatTags tag = encoding; tag != audioFormatTags::last; tag = static_cast(tag + 1)) { - io_->read(buf.data(), 2); - - switch (tag) { - case encoding: - td = find(audioEncodingValues, Exiv2::getUShort(buf.data(), littleEndian)); - if (td) { - xmpData_["Xmp.audio.Compressor"] = exvGettext(td->label_); - } else { - xmpData_["Xmp.audio.Compressor"] = Exiv2::getUShort(buf.data(), littleEndian); - } - break; - case numberOfChannels: - c = Exiv2::getUShort(buf.data(), littleEndian); - if (c == 1) - xmpData_["Xmp.audio.ChannelType"] = "Mono"; - else if (c == 2) - xmpData_["Xmp.audio.ChannelType"] = "Stereo"; - else if (c == 5) - xmpData_["Xmp.audio.ChannelType"] = "5.1 Surround Sound"; - else if (c == 7) - xmpData_["Xmp.audio.ChannelType"] = "7.1 Surround Sound"; - else - xmpData_["Xmp.audio.ChannelType"] = "Mono"; - break; - case audioSampleRate: - xmpData_["Xmp.audio.SampleRate"] = Exiv2::getUShort(buf.data(), littleEndian); - break; - case avgBytesPerSec: - xmpData_["Xmp.audio.SampleType"] = Exiv2::getUShort(buf.data(), littleEndian); - break; - case bitsPerSample: - xmpData_["Xmp.audio.BitsPerSample"] = Exiv2::getUShort(buf.data(), littleEndian); - io_->read(buf.data(), 2); - break; - default: - break; - } + uint16_t format_tag = readWORDTag(io_); + auto it = Internal::audioEncodingValues.find(format_tag); + if (it != Internal::audioEncodingValues.end()) { + xmpData_["Xmp.audio.Compressor"] = it->second; + } else { + xmpData_["Xmp.audio.Compressor"] = format_tag; } + + xmpData_["Xmp.audio.ChannelType"] = getStreamType(readDWORDTag(io_)); + xmpData_["Xmp.audio.SampleRate"] = readDWORDTag(io_); // nSamplesPerSec + io_->seekOrThrow(io_->tell() + DWORD, BasicIo::beg, ErrorCode::kerFailedToReadImageData); // nAvgBytesPerSec + xmpData_["Xmp.audio.SampleType"] = readDWORDTag(io_); // nBlockAlign + xmpData_["Xmp.audio.BitsPerSample"] = readDWORDTag(io_); // wBitsPerSample + +#endif + if (xmpData_["Xmp.video.FileType"].toString() == "AVI ") + io_->seekOrThrow(io_->tell() + DWORD, BasicIo::beg, ErrorCode::kerFailedToReadImageData); // cbSize +} +else { + io_->seekOrThrow(io_->tell() + size_, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +} + +} + +void RiffVideo::readStreamData(uint64_t size_) { + io_->seekOrThrow(io_->tell() + size_, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +} + +void RiffVideo::StreamName(uint64_t size_) { + // This element contains a name for the stream. That stream name should only use plain ASCII, especially not UTF-8. + io_->seekOrThrow(io_->tell() + size_, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +} + +void RiffVideo::readInfoListChunk(uint64_t size_) { + uint64_t current_size = DWORD; + while (current_size < size_) { + std::string type = readStringTag(io_); + size_t size = readDWORDTag(io_); + std::string content = readStringTag(io_, size); + auto it = Internal::infoTags.find(type); + if (it != Internal::infoTags.end()) { + xmpData_[it->second] = content; + } + current_size += DWORD * 2 + size; + } +} + +void RiffVideo::readMoviList(uint64_t size_) { + io_->seekOrThrow(io_->tell() + size_ - DWORD, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +} + +void RiffVideo::readVPRPChunk(uint64_t size_) { +#ifdef EXIV2_DEBUG_MESSAGES + EXV_DEBUG << "--> VideoFormatToken = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> VideoStandard = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> VerticalRefreshRate = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> HTotalInT = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> VTotalInLines = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> FrameAspectRatio Height = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> FrameAspectRatio Width = " << readWORDTag(io_) << std::endl; + EXV_DEBUG << "--> FrameWidthInPixels = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> FrameHeightInLines = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> CompressedBMHeight = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> FieldPerFrame = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> CompressedBMWidth = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> ValidBMHeight = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> ValidBMWidth = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> ValidBMXOffset = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> ValidBMYOffset = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> VideoXOffsetInT = " << readDWORDTag(io_) << std::endl; + EXV_DEBUG << "--> VideoYValidStartLine = " << readDWORDTag(io_) << std::endl; +#else + io_->seekOrThrow(io_->tell() + size_, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +#endif +} + +void RiffVideo::readIndexChunk(uint64_t size_) { +#ifdef EXIV2_DEBUG_MESSAGES + uint64_t current_size = 0; + while (current_size < size_) { + EXV_DEBUG << "--> Identifier = " << readStringTag(io_) << "\t(" << current_size << "/" << size_ << ")" + << std::endl; + EXV_DEBUG << "--> Flags = " << readDWORDTag(io_) << "\t(" << current_size << "/" << size_ << ")" + << std::endl; + EXV_DEBUG << "--> Offset = " << readDWORDTag(io_) << "\t(" << current_size << "/" << size_ << ")" + << std::endl; + EXV_DEBUG << "--> Length = " << readDWORDTag(io_) << "\t(" << current_size << "/" << size_ << ")" + << std::endl; + current_size += DWORD * 4; } - io_->seek(cur_pos + size, BasicIo::beg); -} // RiffVideo::streamFormatHandler +#else + io_->seekOrThrow(io_->tell() + size_, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +#endif +} -double RiffVideo::returnSampleRate(Exiv2::DataBuf& buf, size_t divisor) { - return (static_cast(Exiv2::getULong(buf.data(), littleEndian)) / divisor); -} // RiffVideo::returnSampleRate +void RiffVideo::readDataChunk(uint64_t size_) { +#ifdef EXIV2_DEBUG_MESSAGES + EXV_DEBUG << "--> Data = " << readStringTag(size_) << std::endl; + if (size_ % 2 != 0) { + EXV_DEBUG << "--> pad byte = " << readStringTag(1) << std::endl; + } +#else + io_->seekOrThrow(io_->tell() + size_, BasicIo::beg, ErrorCode::kerFailedToReadImageData); + if (size_ % 2 != 0) + io_->seekOrThrow(io_->tell() + 1, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +#endif +} -const char* RiffVideo::printAudioEncoding(uint64_t i) { - const TagDetails* td; - td = find(audioEncodingValues, i); - if (td) - return exvGettext(td->label_); +void RiffVideo::readJunk(uint64_t size_) { + io_->seekOrThrow(io_->tell() + size_, BasicIo::beg, ErrorCode::kerFailedToReadImageData); +} - return "Undefined"; -} // RiffVideo::printAudioEncoding +std::string RiffVideo::getStreamType(uint32_t stream) { + if (stream == 1) + return "Mono"; + else if (stream == 2) + return "Stereo"; + else if (stream == 5) + return "5.1 Surround Sound"; + else if (stream == 7) + return "7.1 Surround Sound"; + else + return "Mono"; +} + +void RiffVideo::fillDuration(double frame_rate, size_t frame_count) { + if (frame_rate == 0) + return; + + auto duration = static_cast(frame_count * 1000. / frame_rate); + xmpData_["Xmp.video.FileDataRate"] = io_->size() / (1048576. * duration); + xmpData_["Xmp.video.Duration"] = duration; // Duration in number of seconds +} // RiffVideo::fillDuration void RiffVideo::fillAspectRatio(size_t width, size_t height) { if (height == 0) @@ -1213,18 +795,10 @@ void RiffVideo::fillAspectRatio(size_t width, size_t height) { break; default: xmpData_["Xmp.video.AspectRatio"] = aspectRatio; + break; } -} // RiffVideo::fillAspectRatio - -void RiffVideo::fillDuration(double frame_rate, size_t frame_count) { - if (frame_rate == 0) - return; - - auto duration = static_cast(frame_count * 1000. / frame_rate); - xmpData_["Xmp.video.FileDataRate"] = io_->size() / (1048576. * duration); - xmpData_["Xmp.video.Duration"] = duration; // Duration in number of seconds -} // RiffVideo::fillDuration +} Image::UniquePtr newRiffInstance(BasicIo::UniquePtr io, bool /*create*/) { auto image = std::make_unique(std::move(io)); @@ -1235,16 +809,15 @@ Image::UniquePtr newRiffInstance(BasicIo::UniquePtr io, bool /*create*/) { } bool isRiffType(BasicIo& iIo, bool advance) { - const int32_t len = 2; const unsigned char RiffVideoId[4] = {'R', 'I', 'F', 'F'}; - byte buf[len]; - iIo.read(buf, len); + byte buf[WORD]; + iIo.read(buf, WORD); if (iIo.error() || iIo.eof()) { return false; } - bool matched = (memcmp(buf, RiffVideoId, len) == 0); + bool matched = (memcmp(buf, RiffVideoId, WORD) == 0); if (!advance || !matched) { - iIo.seek(-len, BasicIo::cur); + iIo.seek(-WORD, BasicIo::cur); } return matched; } diff --git a/test/data/test_reference_files/Free_Test_Data_500KB_WAV.wav.out b/test/data/test_reference_files/Free_Test_Data_500KB_WAV.wav.out index e4713c79..1563901d 100644 --- a/test/data/test_reference_files/Free_Test_Data_500KB_WAV.wav.out +++ b/test/data/test_reference_files/Free_Test_Data_500KB_WAV.wav.out @@ -1,9 +1,9 @@ -Xmp.video.FileSize XmpText 7 0.48901 0.48901 +Xmp.video.FileSize XmpText 6 512764 512764 Xmp.video.MimeType XmpText 10 video/riff video/riff Xmp.video.Container XmpText 4 RIFF RIFF Xmp.video.FileType XmpText 4 WAVE WAVE Xmp.audio.Compressor XmpText 13 Microsoft PCM Microsoft PCM -Xmp.audio.ChannelType XmpText 6 Stereo Stereo -Xmp.audio.SampleRate XmpText 5 44100 44100 -Xmp.audio.SampleType XmpText 5 45328 45328 -Xmp.audio.BitsPerSample XmpText 2 16 16 +Xmp.audio.ChannelType XmpText 4 Mono Mono +Xmp.audio.SampleRate XmpText 10 2970615808 2970615808 +Xmp.audio.SampleType XmpText 10 1633943568 1633943568 +Xmp.audio.BitsPerSample XmpText 10 3536871796 3536871796 diff --git a/test/data/test_reference_files/flame.avi.out b/test/data/test_reference_files/flame.avi.out index 5525e3f1..56d76f1e 100644 --- a/test/data/test_reference_files/flame.avi.out +++ b/test/data/test_reference_files/flame.avi.out @@ -1,11 +1,11 @@ -Xmp.video.FileSize XmpText 8 0.275879 0.275879 +Xmp.video.FileSize XmpText 6 289280 289280 Xmp.video.MimeType XmpText 10 video/riff video/riff Xmp.video.Container XmpText 4 RIFF RIFF Xmp.video.FileType XmpText 4 AVI AVI Xmp.video.MicroSecPerFrame XmpText 5 28571 28571 -Xmp.video.MaxDataRate XmpText 7 93.0918 93.0918 +Xmp.video.MaxDataRate XmpText 5 95326 95326 Xmp.video.FrameCount XmpText 3 110 110 -Xmp.video.StreamCount XmpText 1 1 1 +Xmp.video.StreamCount XmpText 4 3074 3074 Xmp.video.Width XmpText 3 256 256 Xmp.video.Height XmpText 3 240 240 Xmp.video.AspectRatio XmpText 3 1:1 1:1 @@ -21,6 +21,5 @@ Xmp.video.Compressor XmpText 4 IV41 IV41 Xmp.video.ImageLength XmpText 6 138240 138240 Xmp.video.PixelPerMeterX XmpText 1 0 0 Xmp.video.PixelPerMeterY XmpText 1 0 0 -Xmp.video.NumOfColours XmpText 11 Unspecified Unspecified -Xmp.video.NumOfImpColours XmpText 3 All All -Xmp.video.Junk XmpText 0 +Xmp.video.NumOfColours XmpText 10 1263424842 1263424842 +Xmp.video.NumIfImpColours XmpText 4 1816 1816