summaryrefslogtreecommitdiff
path: root/src/gcontainer/gcontainer.cc
blob: b97fdd796ef7e79e78b78f9dd087f52031f38b4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include "image_io/gcontainer/gcontainer.h"

#include <fstream>

#include "image_io/base/data_segment.h"
#include "image_io/base/data_segment_data_source.h"
#include "image_io/base/istream_data_source.h"
#include "image_io/base/message_handler.h"
#include "image_io/base/ostream_data_destination.h"
#include "image_io/jpeg/jpeg_info.h"
#include "image_io/jpeg/jpeg_info_builder.h"
#include "image_io/jpeg/jpeg_scanner.h"
#include "image_io/utils/file_utils.h"

namespace photos_editing_formats {
namespace image_io {
namespace gcontainer {
namespace {

using photos_editing_formats::image_io::DataRange;
using photos_editing_formats::image_io::DataSegment;
using photos_editing_formats::image_io::DataSegmentDataSource;
using photos_editing_formats::image_io::IStreamRefDataSource;
using photos_editing_formats::image_io::JpegInfoBuilder;
using photos_editing_formats::image_io::JpegScanner;
using photos_editing_formats::image_io::Message;
using photos_editing_formats::image_io::MessageHandler;
using photos_editing_formats::image_io::OStreamDataDestination;
using std::string;

// Populates first_image_range with the first image (from the header metadata
// to the EOI marker) present in the JPEG file input_file_name. Returns true if
// such a first image is found, false otherwise.
//
// input_jpeg_stream must be a JPEG stream.
// image_data_segment is populated with the DataSegment for
// input_file_name, and is populated only in the successful case.
// first_image_range is populated with the first image found in the input file,
// only if such an image is found.

bool ExtractFirstImageInJpeg(std::istream& input_jpeg_stream,
                             MessageHandler* message_handler,
                             DataRange* first_image_range) {
  if (first_image_range == nullptr) {
    return false;
  }

  // Get the input and output setup.
  if (message_handler) {
    message_handler->ClearMessages();
  }

  // Get the jpeg info and first image range from the input.
  IStreamRefDataSource data_source(input_jpeg_stream);
  JpegInfoBuilder jpeg_info_builder;
  jpeg_info_builder.SetImageLimit(1);
  JpegScanner jpeg_scanner(message_handler);
  jpeg_scanner.Run(&data_source, &jpeg_info_builder);
  data_source.Reset();

  if (jpeg_scanner.HasError()) {
    return false;
  }

  const auto& jpeg_info = jpeg_info_builder.GetInfo();
  const auto& image_ranges = jpeg_info.GetImageRanges();
  if (image_ranges.empty()) {
    if (message_handler) {
      message_handler->ReportMessage(Message::kPrematureEndOfDataError,
                                     "No Images Found");
    }
    return false;
  }

  *first_image_range = image_ranges[0];
  return true;
}

}  // namespace

bool WriteImageAndFiles(const string& input_file_name,
                        const std::vector<string>& other_files,
                        const string& output_file_name) {
  MessageHandler message_handler;
  auto output_stream = OpenOutputFile(output_file_name, &message_handler);
  if (!output_stream) {
    return false;
  }

  OStreamDataDestination output_destination(std::move(output_stream),
                                            &message_handler);
  output_destination.SetName(output_file_name);

  DataRange image_range;
  std::unique_ptr<std::istream> input_stream =
      OpenInputFile(input_file_name, &message_handler);

  if (!ExtractFirstImageInJpeg(*input_stream, &message_handler, &image_range)) {
    return false;
  }

  output_destination.StartTransfer();
  IStreamDataSource data_source(
      OpenInputFile(input_file_name, &message_handler));
  data_source.TransferData(image_range, image_range.GetLength(),
                           &output_destination);

  size_t bytes_transferred = image_range.GetLength();
  for (const string& tack_on_file : other_files) {
    if (tack_on_file.empty()) {
      continue;
    }
    auto tack_on_data_segment = ReadEntireFile(tack_on_file, &message_handler);
    if (!tack_on_data_segment) {
      continue;
    }

    DataSegmentDataSource tack_on_source(tack_on_data_segment);
    DataRange tack_on_range = tack_on_data_segment->GetDataRange();
    bytes_transferred += tack_on_range.GetLength();
    tack_on_source.TransferData(tack_on_range, tack_on_range.GetLength(),
                                &output_destination);
  }

  output_destination.FinishTransfer();
  return output_destination.GetBytesTransferred() == bytes_transferred &&
         !output_destination.HasError();
}

bool ParseFileAfterImage(const std::string& input_file_name,
                         size_t file_start_offset, size_t file_length,
                         std::string* out_file_contents) {
  std::ifstream input_stream(input_file_name);
  if (!input_stream.is_open()) {
    return false;
  }
  return ParseFileAfterImageFromStream(file_start_offset, file_length,
                                       input_stream, out_file_contents);
}

bool ParseFileAfterImageFromStream(size_t start_offset, size_t length,
                                   std::istream& input_jpeg_stream,
                                   std::string* out_contents) {
  if (out_contents == nullptr || start_offset < 0 || length == 0) {
    return false;
  }

  size_t curr_posn = input_jpeg_stream.tellg();
  input_jpeg_stream.seekg(0, input_jpeg_stream.end);
  size_t stream_size = input_jpeg_stream.tellg();
  input_jpeg_stream.seekg(curr_posn, input_jpeg_stream.beg);

  DataRange image_range;
  MessageHandler message_handler;
  if (!ExtractFirstImageInJpeg(input_jpeg_stream, &message_handler,
                               &image_range)) {
    return false;
  }

  size_t image_bytes_end_offset = image_range.GetEnd();
  size_t file_start_in_image = image_bytes_end_offset + start_offset;
  size_t file_end_in_image = file_start_in_image + length;
  if (stream_size < file_end_in_image) {
    // Requested file is past the end of the image file.
    return false;
  }

  // Get the file's contents.
  const DataRange file_range(file_start_in_image, file_end_in_image);
  size_t file_range_size = file_range.GetLength();
  // TODO(miraleung): Consider subclassing image_io/data_destination.h and
  // transferring bytes directly into the string. TBD pending additional mime
  // type getters.
  input_jpeg_stream.seekg(file_range.GetBegin(), input_jpeg_stream.beg);
  out_contents->resize(file_range_size);
  input_jpeg_stream.read(&(*out_contents)[0], file_range_size);
  return true;
}

}  // namespace gcontainer
}  // namespace image_io
}  // namespace photos_editing_formats