#include "xmpmeta/jpeg_io.h" #include #include #include "android-base/logging.h" namespace dynamic_depth { namespace xmpmeta { namespace { // File markers. // See: http://www.fileformat.info/format/jpeg/egff.htm or // https://en.wikipedia.org/wiki/JPEG const int kSoi = 0xd8; // Start of image marker. const int kApp1 = 0xe1; // Start of EXIF section. const int kSos = 0xda; // Start of scan. // Number of bytes used to store a section's length in a JPEG file. const int kSectionLengthByteSize = 2; // Returns the number of bytes available to be read. Sets the seek position // to the place it was in before calling this function. size_t GetBytesAvailable(std::istream* input_stream) { const std::streamoff pos = input_stream->tellg(); if (pos == -1) { return 0; } input_stream->seekg(0, std::ios::end); if (!input_stream->good()) { return 0; } const std::streamoff end = input_stream->tellg(); if (end == -1) { return 0; } input_stream->seekg(pos); if (end <= pos) { return 0; } return end - pos; } // Returns the first byte in the stream cast to an integer. int ReadByteAsInt(std::istream* input_stream) { unsigned char byte; input_stream->read(reinterpret_cast(&byte), 1); if (!input_stream->good()) { // Return an invalid value - no byte can be read as -1. return -1; } return static_cast(byte); } // Reads the length of a section from 2 bytes. size_t Read2ByteLength(std::istream* input_stream, bool* error) { const int length_high = ReadByteAsInt(input_stream); const int length_low = ReadByteAsInt(input_stream); if (length_high == -1 || length_low == -1) { *error = true; return 0; } *error = false; return length_high << 8 | length_low; } bool HasPrefixString(const string& to_check, const string& prefix) { if (to_check.size() < prefix.size()) { return false; } return std::equal(prefix.begin(), prefix.end(), to_check.begin()); } } // namespace Section::Section(const string& buffer) { marker = kApp1; is_image_section = false; data = buffer; } bool Section::IsMarkerApp1() { return marker == kApp1; } std::vector
Parse(const ParseOptions& options, std::istream* input_stream) { std::vector
sections; // Return early if this is not the start of a JPEG section. if (ReadByteAsInt(input_stream) != 0xff || ReadByteAsInt(input_stream) != kSoi) { LOG(WARNING) << "File's first two bytes does not match the sequence \xff" << kSoi; return std::vector
(); } int chr; // Short for character. while ((chr = ReadByteAsInt(input_stream)) != -1) { if (chr != 0xff) { LOG(WARNING) << "Read non-padding byte: " << chr; return sections; } // Skip padding bytes. while ((chr = ReadByteAsInt(input_stream)) == 0xff) { } if (chr == -1) { LOG(WARNING) << "No more bytes in file available to be read."; return sections; } const int marker = chr; if (marker == kSos) { // kSos indicates the image data will follow and no metadata after that, // so read all data at one time. if (!options.read_meta_only) { Section section; section.marker = marker; section.is_image_section = true; const size_t bytes_available = GetBytesAvailable(input_stream); section.data.resize(bytes_available); input_stream->read(§ion.data[0], bytes_available); if (input_stream->good()) { sections.push_back(section); } } // All sections have been read. return sections; } bool error; const size_t length = Read2ByteLength(input_stream, &error); if (error || length < kSectionLengthByteSize) { // No sections to read. LOG(WARNING) << "No sections to read; section length is " << length; return sections; } const size_t bytes_left = GetBytesAvailable(input_stream); if (length - kSectionLengthByteSize > bytes_left) { LOG(WARNING) << "Invalid section length = " << length << " total bytes available = " << bytes_left; return sections; } if (!options.read_meta_only || marker == kApp1) { Section section; section.marker = marker; section.is_image_section = false; const size_t data_size = length - kSectionLengthByteSize; section.data.resize(data_size); if (section.data.size() != data_size) { LOG(WARNING) << "Discrepancy in section data size " << section.data.size() << "and data size " << data_size; return sections; } input_stream->read(§ion.data[0], section.data.size()); if (input_stream->good() && (options.section_header.empty() || HasPrefixString(section.data, options.section_header))) { sections.push_back(section); // Return if we have specified to return the 1st section with // the given name. if (options.section_header_return_first) { return sections; } } } else { // Skip this section since all EXIF/XMP meta will be in kApp1 section. input_stream->ignore(length - kSectionLengthByteSize); } } return sections; } void WriteSections(const std::vector
& sections, std::ostream* output_stream) { output_stream->put(0xff); output_stream->put(static_cast(kSoi)); for (const Section& section : sections) { output_stream->put(0xff); output_stream->put(section.marker); if (!section.is_image_section) { const int section_length = static_cast(section.data.length()) + 2; // It's not the image data. const int lh = section_length >> 8; const int ll = section_length & 0xff; output_stream->put(lh); output_stream->put(ll); } output_stream->write(section.data.c_str(), section.data.length()); } } } // namespace xmpmeta } // namespace dynamic_depth