blob: 1176b6fa15cdeb4e30428ed994a975582694154d [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "formats/jpeg/extractor.h"
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "constants/labels.h"
#include "formats/asset_box.h"
#include "formats/byte_range.h"
#include "formats/jpeg/c2pa_segment_handler.h"
#include "formats/jpeg/reader.h"
#include "jumbf/utils.h"
#include "riegeli/bytes/reader.h"
#include "utils/byte_readers.h"
namespace credentio {
namespace {
// APP11 Marker and APP11 JPEG Extension Type values are sourced from
// Section A.4 JPEG XT boxes of the ISO/IEC 18477-3:2023
const uint16_t kApp11ExtensionType = 0x4a50;
} // namespace
absl::StatusOr<std::string> JpegExtractor::ExtractManifestStore(
riegeli::Reader& input) const {
C2paSegmentHandler segment_handler;
ABSL_RETURN_IF_ERROR(
IterateOverJpegSegments(input, [&segment_handler](JpegSegment segment) {
segment_handler.RecordSegment(std::move(segment));
return true;
}));
ABSL_ASSIGN_OR_RETURN(auto c2pa_segment,
segment_handler.GetC2paSegment(input));
return std::move(c2pa_segment.manifest_store);
}
absl::StatusOr<std::optional<ByteRange>>
JpegExtractor::ExtractManifestStoreLocation(riegeli::Reader& input,
ExtractOptions options) const {
C2paSegmentHandler c2pa_handler;
ABSL_RETURN_IF_ERROR(IterateOverJpegSegments(
input,
[&c2pa_handler, &options, &input](JpegSegment segment) {
if (options.end_offset >= 0 &&
(segment.offset + segment.length) > options.end_offset) {
// This segment starts or ends beyond the window, ignore, stop reading
// and move the input's position to the declared end.
if (!input.Seek(options.end_offset)) {
return absl::StatusOr<bool>(input.StatusOrAnnotate(
absl::DataLossError("Failed to seek to end offset")));
}
return absl::StatusOr<bool>(false); // stop iteration
}
c2pa_handler.RecordSegment(std::move(segment));
return absl::StatusOr<bool>(true);
},
options.end_offset));
absl::StatusOr<C2paSegmentHandler::Segment> c2pa_segment =
c2pa_handler.GetC2paSegment(input);
if (!c2pa_segment.ok()) {
if (options.requires_c2pa) {
return c2pa_segment.status();
}
return std::nullopt;
}
return ByteRange{
.offset = c2pa_segment->segment.offset,
.length = c2pa_segment->segment.length,
};
}
absl::StatusOr<std::vector<AssetBox>> JpegExtractor::ExtractBoxes(
riegeli::Reader& input, ExtractOptions options) const {
C2paSegmentHandler c2pa_handler;
std::vector<AssetBox> boxes;
ABSL_RETURN_IF_ERROR(IterateOverJpegSegments(
input,
[&boxes, &c2pa_handler](JpegSegment segment) {
boxes.push_back(AssetBox{
.identifier = segment.marker_label,
.byte_range = {.offset = segment.offset, .length = segment.length},
});
c2pa_handler.RecordSegment(std::move(segment));
return true;
},
options.end_offset));
absl::StatusOr<C2paSegmentHandler::Segment> c2pa_segment =
c2pa_handler.GetC2paSegment(input);
if (!c2pa_segment.ok()) {
if (options.requires_c2pa) {
return c2pa_segment.status();
}
return boxes;
}
uint64_t c2pa_start = c2pa_segment->segment.offset;
uint64_t c2pa_end = c2pa_start + c2pa_segment->segment.length;
std::vector<AssetBox> merged_boxes;
merged_boxes.reserve(boxes.size());
for (const auto& box : boxes) {
uint64_t box_start = box.byte_range.offset;
uint64_t box_end = box_start + box.byte_range.length;
if (box_end <= c2pa_start || box_start >= c2pa_end) {
// Box is before or after the C2PA segment, add it.
merged_boxes.push_back(std::move(box));
continue;
}
if (box_end == c2pa_end) {
// Box is the last C2PA Segment, add the box
merged_boxes.push_back(AssetBox{
.identifier = "C2PA",
.byte_range = {.offset = c2pa_start,
.length = c2pa_segment->segment.length},
});
continue;
}
// Box is inside the C2PA segment, skip it.
}
return merged_boxes;
}
bool JpegExtractor::MightBeC2paManifestStore(absl::string_view payload) const {
absl::StatusOr<uint16_t> extension_type = ConsumeUint<uint16_t>(&payload);
if (!extension_type.ok() || *extension_type != kApp11ExtensionType) {
return false;
}
// Segment ID
auto iter_status = SkipBytes<uint16_t>(&payload);
if (!iter_status.ok()) {
return false;
}
// Sequence Number
iter_status = SkipBytes<uint32_t>(&payload);
if (!iter_status.ok()) {
return false;
}
return jumbf::HasDescriptionBoxMatching(payload, kManifestStoreUuid,
kMinimumJumbfDescriptionToggles,
kManifestStoreLabel)
.value_or(false);
}
} // namespace credentio