You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
5.9 KiB
197 lines
5.9 KiB
#include "xmpmeta/jpeg_io.h"
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
#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<char*>(&byte), 1);
|
|
if (!input_stream->good()) {
|
|
// Return an invalid value - no byte can be read as -1.
|
|
return -1;
|
|
}
|
|
return static_cast<int>(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<Section> Parse(const ParseOptions& options,
|
|
std::istream* input_stream) {
|
|
std::vector<Section> 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<Section>();
|
|
}
|
|
|
|
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<Section>& sections,
|
|
std::ostream* output_stream) {
|
|
output_stream->put(0xff);
|
|
output_stream->put(static_cast<unsigned char>(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<int>(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
|