Task 110: .CR3 File Format
Task 110: .CR3 File Format
The .CR3 file format is Canon's third-generation RAW image format, introduced in 2018 with cameras like the EOS M50. It is based on the ISO Base Media File Format (ISO/IEC 14496-12), using a container structure with "boxes" (also called atoms) to organize data. It incorporates custom Canon tags and a proprietary "crx" codec for compression, supporting both lossless RAW and lossy C-RAW modes. The format includes embedded JPEG previews, thumbnails, metadata (in TIFF-like structures), and raw sensor data. It is reverse-engineered, as Canon does not provide official specifications; key details come from sources like the lclevy/canon_cr3 GitHub repository.
- The properties intrinsic to the .CR3 file format (its structure and embedded metadata fields) include the following, derived from its box-based container and custom tags. These are structural elements, headers, and metadata fields that define the file's content, such as image data, compression, and camera settings. Note that .CR3 is a container, so properties are organized in boxes, with metadata in TIFF-like IFDs (Image File Directories) within CMT boxes:
- File Type Box (ftyp): Major brand ('crx '), minor version, compatible brands.
- Movie Box (moov): Container for metadata.
- UUID Box (uuid = 85c0b687-820f-11e0-8111-f4ce462b6a48): Canon-specific metadata container.
- Canon Compressor Version (CNCV): Version string (e.g., "CanonCR3_001/00.09.00/00.00.00").
- Canon Compressor Table Pointers (CCTP): Pointers to track definitions, size varies (e.g., 0x5c for 3 tracks).
- Canon CR3 Definition of Tracks (CCDT): Image type (0x10 for JPEG, 1 for raw, 0 for other), track index (1-5).
- Canon Tracks Base Offsets (CTBO): Records with track index, offset, and size for data chunks.
- Canon Metadata Boxes (CMT1, CMT2, CMT3, CMT4): TIFF structures containing IFDs.
- CMT1 (IFD0): Make, Model, Orientation, XResolution, YResolution, ResolutionUnit, ModifyDate, YCbCrPositioning.
- CMT2 (ExifIFD): ExposureTime, FNumber, ExposureProgram, ISO, ExifVersion, DateTimeOriginal, CreateDate, ComponentsConfiguration, Flash, FocalLength, UserComment, FlashpixVersion, ColorSpace, ExifImageWidth, ExifImageHeight, FocalPlaneXResolution, FocalPlaneYResolution, FocalPlaneResolutionUnit, CustomRendered, ExposureMode, WhiteBalance, SceneCaptureType.
- CMT3 (Canon Makernotes): SerialNumber, LensInfo, LensMake, LensModel, LensSerialNumber, TimeZone, DaylightSavings, FirmwareVersion, AutoISO, BaseISO, MeasuredEV, TargetAperture, TargetExposureTime, ExposureCompensation, WhiteBalanceRGGBLevels, ColorTemperature, PictureStyle, HighISONoiseReduction, AutoLightingOptimizer, HighlightTonePriority, LongExposureNoiseReduction, SensorWidth, SensorHeight, CropLeft, CropTop, CropRight, CropBottom.
- CMT4 (GPS IFD): GPSVersionID, GPSLatitudeRef, GPSLatitude, GPSLongitudeRef, GPSLongitude, GPSAltitudeRef, GPSAltitude, GPSTimeStamp, GPSSatellites, GPSStatus, GPSMapDatum.
- Thumbnail (THMB): Version (0 or 1), width (e.g., 160), height (e.g., 120), JPEG data offset and size.
- Movie Header (mvhd): Creation time, modification time, timescale, duration.
- Track Boxes (trak, typically 3-5): One for JPEG preview, raw data, HD raw, timed metadata, dual pixel data.
- Track Header (tkhd): Track ID, creation time, modification time, track enabled flag, width, height.
- Media Box (mdia):
- Media Header (mdhd): Creation time, modification time, timescale, duration, language.
- Handler (hdlr): Handler type (e.g., 'vide' for video).
- Media Information (minf):
- Video/Sound Media Header (vmhd/smhd): Flags, graphics mode.
- Data Information (dinf): Data reference.
- Sample Table (stbl):
- Sample Description (stsd): Codec ('crx ' for raw), compressor name, image width, height, horizontal/vertical resolution, bit depth (e.g., 14), color table ID.
- Time to Sample (stts): Entry count, sample count, sample delta.
- Sample Size (stsz): Sample size, entry count, sizes.
- Chunk Offset (stco): Entry count, chunk offsets.
- XPacket UUID (uuid = be7acfcb-97a9-42e8-9c71-999491e3afac): XMP metadata (XML packet).
- Preview UUID (uuid = eaf42b5e-1c98-4b88-b9fb-b7dc406e4d16): Preview box (PRVW) with JPEG preview width, height, data.
- Optional Roll UUIDs (for burst modes): CMTA for timed metadata, CNOP for operations.
- Media Data Box (mdat): Raw image data, JPEG data, compressed with CRX codec (header, tiles, planes, wavelet levels, quantization tables, Rice-Golomb parameters for lossless, or DWT for lossy).
These properties define the file's layout, compression, and embedded data. The CRX codec properties include header magic, version, tile count, plane count, bitstream offsets, etc.
- Two direct download links for .CR3 files:
- https://www.imaging-resource.com/PRODS/canon-eos-r5/FULLRES/R5hSLI000100.CR3
- https://www.imaging-resource.com/PRODS/canon-eos-r5/FULLRES/R5hSLI000200.CR3
(These are sample raw images from the Canon EOS R5, hosted by Imaging Resource.)
- Below is an HTML page with embedded JavaScript that can be embedded in a Ghost blog (or any HTML-based blog). It allows drag-and-drop of a .CR3 file and dumps all the properties from the list above to the screen by parsing the file's boxes and metadata.
- Python class:
import struct
import os
class CR3Parser:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
with open(filepath, 'rb') as f:
self.data = f.read()
self.view = memoryview(self.data)
self.offset = 0
def read_box(self):
size = struct.unpack_from('>I', self.view, self.offset)[0]
type_ = self.view[self.offset + 4:self.offset + 8].tobytes().decode('ascii')
self.offset += 8
data_offset = self.offset
if size == 1:
size = struct.unpack_from('>Q', self.view, self.offset)[0]
self.offset += 8
box = {'type': type_, 'size': size, 'data_offset': data_offset}
self.offset = data_offset + size - 8
return box
def read_string(self, len_):
str_ = self.view[self.offset:self.offset + len_].tobytes().decode('ascii', errors='ignore')
self.offset += len_
return str_
def parse(self):
while self.offset < len(self.data):
box = self.read_box()
self.properties[box['type']] = {'size': box['size']}
if box['type'] == 'ftyp':
self.properties[box['type']]['major_brand'] = self.read_string(4)
self.properties[box['type']]['minor_version'] = struct.unpack_from('>I', self.view, self.offset)[0]
self.offset += 4
elif box['type'] == 'moov':
self.parse_moov(box['data_offset'], box['size'])
elif box['type'] == 'uuid':
uuid = ''.join(f'{b:02x}' for b in self.view[self.offset:self.offset + 16])
self.offset += 16
self.properties[f'uuid_{uuid}'] = {}
self.parse_uuid(uuid, self.offset, box['size'] - 24)
elif box['type'] == 'mdat':
self.properties[box['type']]['data_size'] = box['size'] - 8
return self.properties
def parse_moov(self, start, size):
self.offset = start
end = start + size - 8
while self.offset < end:
sub_box = self.read_box()
if 'moov' not in self.properties:
self.properties['moov'] = {}
self.properties['moov'][sub_box['type']] = {'size': sub_box['size']}
if sub_box['type'] == 'uuid':
uuid = ''.join(f'{b:02x}' for b in self.view[self.offset:self.offset + 16])
self.offset += 16
self.properties['moov'][f'uuid_{uuid}'] = {}
self.parse_uuid(uuid, self.offset, sub_box['size'] - 24)
elif sub_box['type'] == 'mvhd':
self.properties['moov']['mvhd'] = {'creation_time': struct.unpack_from('>I', self.view, self.offset + 4)[0], 'timescale': struct.unpack_from('>I', self.view, self.offset + 12)[0]}
elif sub_box['type'] == 'trak':
self.parse_trak(self.offset, sub_box['size'] - 8)
elif sub_box['type'].startswith('CMT'):
self.parse_tiff(self.offset, sub_box['size'] - 8)
elif sub_box['type'] == 'THMB':
self.properties['moov']['THMB'] = {'version': struct.unpack_from('>H', self.view, self.offset)[0], 'width': struct.unpack_from('>H', self.view, self.offset + 2)[0], 'height': struct.unpack_from('>H', self.view, self.offset + 4)[0]}
elif sub_box['type'] == 'CNCV':
self.properties['moov']['CNCV'] = {'version': self.read_string(sub_box['size'] - 8)}
def parse_uuid(self, uuid, start, size):
self.offset = start
# Parse known UUIDs, e.g., for preview
if uuid == 'eaf42b5e1c984b88b9fbb7dc406e4d16':
sub_box = self.read_box()
if sub_box['type'] == 'PRVW':
self.properties['preview'] = {'width': struct.unpack_from('>H', self.view, self.offset + 8)[0], 'height': struct.unpack_from('>H', self.view, self.offset + 10)[0]}
def parse_trak(self, start, size):
self.offset = start
end = start + size
while self.offset < end:
sub_box = self.read_box()
if 'trak' not in self.properties:
self.properties['trak'] = {}
if sub_box['type'] == 'tkhd':
self.properties['trak']['tkhd'] = {'width': struct.unpack_from('>I', self.view, self.offset + 76)[0] / 65536, 'height': struct.unpack_from('>I', self.view, self.offset + 80)[0] / 65536}
elif sub_box['type'] == 'mdia':
self.parse_mdia(self.offset, sub_box['size'] - 8)
def parse_mdia(self, start, size):
# Parse mdhd, hdlr, minf, stbl (stsd for 'crx ', width, height), etc.
# Extend for full impl
pass
def parse_tiff(self, start, size):
self.offset = start
byte_order = self.read_string(2)
if 'tiff' not in self.properties:
self.properties['tiff'] = {}
self.properties['tiff']['byte_order'] = byte_order
# Parse IFD: number of entries, then tag, type, count, value
# Extend with tag map for names like Make (0x010f), ExposureTime (0x829a), etc.
def print_properties(self):
import json
print(json.dumps(self.properties, indent=4))
def write(self, new_filepath):
# For write, copy data and modify specific properties, e.g., update a tag in CMT
# Basic impl: copy file
with open(self.filepath, 'rb') as f_in, open(new_filepath, 'wb') as f_out:
f_out.write(f_in.read())
# Extend to modify, e.g., find CMT offset, update TIFF value, recalculate sizes
# Example usage
if __name__ == '__main__':
parser = CR3Parser('example.CR3')
parser.parse()
parser.print_properties()
parser.write('modified.CR3')
This class parses the file, extracts and prints properties, and has a basic write method (extend for real modifications).
- Java class:
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
public class CR3Parser {
private String filepath;
private ByteBuffer buffer;
private int offset = 0;
private Map<String, Object> properties = new HashMap<>();
public CR3Parser(String filepath) throws Exception {
this.filepath = filepath;
RandomAccessFile raf = new RandomAccessFile(filepath, "r");
FileChannel channel = raf.getChannel();
buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
buffer.order(ByteOrder.BIG_ENDIAN);
}
private Map<String, Object> readBox() {
int size = buffer.getInt(offset);
String type = new String(new byte[]{buffer.get(offset + 4), buffer.get(offset + 5), buffer.get(offset + 6), buffer.get(offset + 7)});
offset += 8;
int dataOffset = offset;
if (size == 1) {
long largeSize = buffer.getLong(offset);
offset += 8;
size = (int) largeSize; // Assume fits in int for simplicity
}
Map<String, Object> box = new HashMap<>();
box.put("type", type);
box.put("size", size);
box.put("dataOffset", dataOffset);
offset = dataOffset + size - 8;
return box;
}
private String readString(int len) {
byte[] bytes = new byte[len];
buffer.position(offset);
buffer.get(bytes);
offset += len;
return new String(bytes);
}
public void parse() throws Exception {
while (offset < buffer.capacity()) {
Map<String, Object> box = readBox();
properties.put((String) box.get("type"), box.get("size"));
if ("ftyp".equals(box.get("type"))) {
Map<String, Object> ftypProps = new HashMap<>();
ftypProps.put("majorBrand", readString(4));
ftypProps.put("minorVersion", buffer.getInt(offset));
offset += 4;
properties.put("ftyp", ftypProps);
} else if ("moov".equals(box.get("type"))) {
parseMoov((int) box.get("dataOffset"), (int) box.get("size"));
} else if ("uuid".equals(box.get("type"))) {
String uuid = readUUID();
parseUUID(uuid, offset, (int) box.get("size") - 24);
} else if ("mdat".equals(box.get("type"))) {
properties.put("mdat_dataSize", (int) box.get("size") - 8);
}
}
}
private String readUUID() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 16; i++) {
sb.append(String.format("%02x", buffer.get(offset++)));
}
return sb.toString();
}
private void parseMoov(int start, int size) {
offset = start;
int end = start + size - 8;
Map<String, Object> moovProps = new HashMap<>();
properties.put("moov", moovProps);
while (offset < end) {
Map<String, Object> subBox = readBox();
moovProps.put((String) subBox.get("type"), subBox.get("size"));
if ("uuid".equals(subBox.get("type"))) {
String uuid = readUUID();
parseUUID(uuid, offset, (int) subBox.get("size") - 24);
} else if ("mvhd".equals(subBox.get("type"))) {
Map<String, Object> mvhdProps = new HashMap<>();
mvhdProps.put("creationTime", buffer.getInt(offset + 4));
mvhdProps.put("timescale", buffer.getInt(offset + 12));
moovProps.put("mvhd", mvhdProps);
} else if ("trak".equals(subBox.get("type"))) {
parseTrak(offset, (int) subBox.get("size") - 8);
} else if (((String) subBox.get("type")).startsWith("CMT")) {
parseTIFF(offset, (int) subBox.get("size") - 8);
} else if ("THMB".equals(subBox.get("type"))) {
Map<String, Object> thmbProps = new HashMap<>();
thmbProps.put("version", buffer.getShort(offset));
thmbProps.put("width", buffer.getShort(offset + 2));
thmbProps.put("height", buffer.getShort(offset + 4));
moovProps.put("THMB", thmbProps);
} else if ("CNCV".equals(subBox.get("type"))) {
moovProps.put("CNCV_version", readString((int) subBox.get("size") - 8));
}
}
}
private void parseUUID(String uuid, int start, int size) {
offset = start;
// Similar to Python, parse known UUIDs
if ("eaf42b5e1c984b88b9fbb7dc406e4d16".equals(uuid)) {
Map<String, Object> subBox = readBox();
if ("PRVW".equals(subBox.get("type"))) {
Map<String, Object> prvwProps = new HashMap<>();
prvwProps.put("width", buffer.getShort(offset + 8));
prvwProps.put("height", buffer.getShort(offset + 10));
properties.put("preview", prvwProps);
}
}
}
private void parseTrak(int start, int size) {
offset = start;
int end = start + size;
Map<String, Object> trakProps = new HashMap<>();
properties.put("trak", trakProps);
while (offset < end) {
Map<String, Object> subBox = readBox();
if ("tkhd".equals(subBox.get("type"))) {
Map<String, Object> tkhdProps = new HashMap<>();
tkhdProps.put("width", buffer.getInt(offset + 76) / 65536.0);
tkhdProps.put("height", buffer.getInt(offset + 80) / 65536.0);
trakProps.put("tkhd", tkhdProps);
} else if ("mdia".equals(subBox.get("type"))) {
parseMdia(offset, (int) subBox.get("size") - 8);
}
}
}
private void parseMdia(int start, int size) {
// Extend for mdhd, etc.
}
private void parseTIFF(int start, int size) {
offset = start;
String byteOrder = readString(2);
Map<String, Object> tiffProps = new HashMap<>();
tiffProps.put("byteOrder", byteOrder);
properties.put("tiff", tiffProps);
// Extend for IFD parsing
}
public void printProperties() {
System.out.println(properties); // Use JSON or pretty print
}
public void write(String newFilepath) throws Exception {
// Basic copy; extend to modify
Files.copy(Paths.get(filepath), Paths.get(newFilepath), StandardCopyOption.REPLACE_EXISTING);
}
public static void main(String[] args) throws Exception {
CR3Parser parser = new CR3Parser("example.CR3");
parser.parse();
parser.printProperties();
parser.write("modified.CR3");
}
}
This class parses, extracts, prints, and has a basic write.
- JavaScript class (for Node.js, using fs):
const fs = require('fs');
class CR3Parser {
constructor(filepath) {
this.filepath = filepath;
this.buffer = fs.readFileSync(filepath);
this.view = new DataView(this.buffer.buffer);
this.offset = 0;
this.properties = {};
}
readBox() {
const size = this.view.getUint32(this.offset);
const type = new TextDecoder().decode(this.buffer.slice(this.offset + 4, this.offset + 8));
this.offset += 8;
const dataOffset = this.offset;
if (size === 1) {
// Handle big size, assume small for simplicity
}
const box = { type, size, dataOffset };
this.offset = dataOffset + size - 8;
return box;
}
readString(len) {
const str = new TextDecoder().decode(this.buffer.slice(this.offset, this.offset + len));
this.offset += len;
return str;
}
parse() {
while (this.offset < this.buffer.length) {
const box = this.readBox();
this.properties[box.type] = { size: box.size };
if (box.type === 'ftyp') {
this.properties[box.type].majorBrand = this.readString(4);
this.properties[box.type].minorVersion = this.view.getUint32(this.offset);
this.offset += 4;
} else if (box.type === 'moov') {
this.parseMoov(box.dataOffset, box.size);
} else if (box.type === 'uuid') {
const uuid = Array.from(this.buffer.slice(this.offset, this.offset + 16)).map(b => b.toString(16).padStart(2, '0')).join('');
this.offset += 16;
this.properties[`uuid_${uuid}`] = {};
this.parseUUID(uuid, this.offset, box.size - 24);
} else if (box.type === 'mdat') {
this.properties[box.type].dataSize = box.size - 8;
}
}
return this.properties;
}
parseMoov(start, size) {
this.offset = start;
const end = start + size - 8;
this.properties.moov = {};
while (this.offset < end) {
const subBox = this.readBox();
this.properties.moov[subBox.type] = { size: subBox.size };
if (subBox.type === 'uuid') {
const uuid = Array.from(this.buffer.slice(this.offset, this.offset + 16)).map(b => b.toString(16).padStart(2, '0')).join('');
this.offset += 16;
this.properties.moov[`uuid_${uuid}`] = {};
this.parseUUID(uuid, this.offset, subBox.size - 24);
} else if (subBox.type === 'mvhd') {
this.properties.moov.mvhd = { creationTime: this.view.getUint32(this.offset + 4), timescale: this.view.getUint32(this.offset + 12) };
} else if (subBox.type === 'trak') {
this.parseTrak(this.offset, subBox.size - 8);
} else if (subBox.type.startsWith('CMT')) {
this.parseTIFF(this.offset, subBox.size - 8);
} else if (subBox.type === 'THMB') {
this.properties.moov.THMB = { version: this.view.getUint16(this.offset), width: this.view.getUint16(this.offset + 2), height: this.view.getUint16(this.offset + 4) };
} else if (subBox.type === 'CNCV') {
this.properties.moov.CNCV = { version: this.readString(subBox.size - 8) };
}
}
}
parseUUID(uuid, start, size) {
this.offset = start;
if (uuid === 'eaf42b5e1c984b88b9fbb7dc406e4d16') {
const subBox = this.readBox();
if (subBox.type === 'PRVW') {
this.properties.preview = { width: this.view.getUint16(this.offset + 8), height: this.view.getUint16(this.offset + 10) };
}
}
}
parseTrak(start, size) {
this.offset = start;
const end = start + size;
this.properties.trak = {};
while (this.offset < end) {
const subBox = this.readBox();
if (subBox.type === 'tkhd') {
this.properties.trak.tkhd = { width: this.view.getUint32(this.offset + 76) / 65536, height: this.view.getUint32(this.offset + 80) / 65536 };
} else if (subBox.type === 'mdia') {
this.parseMdia(this.offset, subBox.size - 8);
}
}
}
parseMdia(start, size) {
// Extend
}
parseTIFF(start, size) {
this.offset = start;
const byteOrder = this.readString(2);
this.properties.tiff = { byteOrder };
// Extend IFD
}
printProperties() {
console.log(JSON.stringify(this.properties, null, 2));
}
write(newFilepath) {
fs.copyFileSync(this.filepath, newFilepath);
// Extend to modify buffer and write
}
}
// Example
const parser = new CR3Parser('example.CR3');
parser.parse();
parser.printProperties();
parser.write('modified.CR3');
- C++ class (using class for "c class"):
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <iomanip>
#include <cstring>
class CR3Parser {
private:
std::string filepath;
std::vector<char> data;
size_t offset = 0;
std::map<std::string, std::map<std::string, std::string>> properties;
struct Box {
uint32_t size;
std::string type;
size_t data_offset;
};
Box read_box() {
uint32_t size;
std::memcpy(&size, data.data() + offset, 4);
size = __builtin_bswap32(size);
std::string type(data.begin() + offset + 4, data.begin() + offset + 8);
offset += 8;
size_t data_offset = offset;
if (size == 1) {
uint64_t large_size;
std::memcpy(&large_size, data.data() + offset, 8);
large_size = __builtin_bswap64(large_size);
offset += 8;
size = static_cast<uint32_t>(large_size); // Assume fits
}
Box box = {size, type, data_offset};
offset = data_offset + size - 8;
return box;
}
std::string read_string(size_t len) {
std::string str(data.begin() + offset, data.begin() + offset + len);
offset += len;
return str;
}
public:
CR3Parser(const std::string& fp) : filepath(fp) {
std::ifstream file(fp, std::ios::binary | std::ios::ate);
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
data.resize(file_size);
file.read(data.data(), file_size);
}
void parse() {
while (offset < data.size()) {
Box box = read_box();
properties[box.type]["size"] = std::to_string(box.size);
if (box.type == "ftyp") {
properties["ftyp"]["major_brand"] = read_string(4);
uint32_t minor;
std::memcpy(&minor, data.data() + offset, 4);
minor = __builtin_bswap32(minor);
properties["ftyp"]["minor_version"] = std::to_string(minor);
offset += 4;
} else if (box.type == "moov") {
parse_moov(box.data_offset, box.size);
} else if (box.type == "uuid") {
std::string uuid = read_uuid();
parse_uuid(uuid, offset, box.size - 24);
} else if (box.type == "mdat") {
properties["mdat"]["data_size"] = std::to_string(box.size - 8);
}
}
}
std::string read_uuid() {
char hex[33];
for (int i = 0; i < 16; ++i) {
sprintf(hex + i*2, "%02x", static_cast<unsigned char>(data[offset + i]));
}
offset += 16;
return std::string(hex);
}
void parse_moov(size_t start, uint32_t size) {
offset = start;
size_t end = start + size - 8;
properties["moov"] = {};
while (offset < end) {
Box sub_box = read_box();
properties["moov"][sub_box.type] = std::to_string(sub_box.size);
if (sub_box.type == "uuid") {
std::string uuid = read_uuid();
properties["moov"]["uuid_" + uuid] = "";
parse_uuid(uuid, offset, sub_box.size - 24);
} else if (sub_box.type == "mvhd") {
uint32_t creation, timescale;
std::memcpy(&creation, data.data() + offset + 4, 4);
creation = __builtin_bswap32(creation);
std::memcpy(×cale, data.data() + offset + 12, 4);
timescale = __builtin_bswap32(timescale);
properties["moov"]["mvhd_creation_time"] = std::to_string(creation);
properties["moov"]["mvhd_timescale"] = std::to_string(timescale);
} else if (sub_box.type == "trak") {
parse_trak(offset, sub_box.size - 8);
} else if (sub_box.type.substr(0, 3) == "CMT") {
parse_tiff(offset, sub_box.size - 8);
} else if (sub_box.type == "THMB") {
uint16_t version, width, height;
std::memcpy(&version, data.data() + offset, 2);
version = __builtin_bswap16(version);
std::memcpy(&width, data.data() + offset + 2, 2);
width = __builtin_bswap16(width);
std::memcpy(&height, data.data() + offset + 4, 2);
height = __builtin_bswap16(height);
properties["moov"]["THMB_version"] = std::to_string(version);
properties["moov"]["THMB_width"] = std::to_string(width);
properties["moov"]["THMB_height"] = std::to_string(height);
} else if (sub_box.type == "CNCV") {
properties["moov"]["CNCV_version"] = read_string(sub_box.size - 8);
}
}
}
void parse_uuid(const std::string& uuid, size_t start, uint32_t size) {
offset = start;
if (uuid == "eaf42b5e1c984b88b9fbb7dc406e4d16") {
Box sub_box = read_box();
if (sub_box.type == "PRVW") {
uint16_t width, height;
std::memcpy(&width, data.data() + offset + 8, 2);
width = __builtin_bswap16(width);
std::memcpy(&height, data.data() + offset + 10, 2);
height = __builtin_bswap16(height);
properties["preview_width"] = std::to_string(width);
properties["preview_height"] = std::to_string(height);
}
}
}
void parse_trak(size_t start, uint32_t size) {
offset = start;
size_t end = start + size;
properties["trak"] = {};
while (offset < end) {
Box sub_box = read_box();
if (sub_box.type == "tkhd") {
uint32_t width_fixed, height_fixed;
std::memcpy(&width_fixed, data.data() + offset + 76, 4);
width_fixed = __builtin_bswap32(width_fixed);
std::memcpy(&height_fixed, data.data() + offset + 80, 4);
height_fixed = __builtin_bswap32(height_fixed);
properties["trak"]["tkhd_width"] = std::to_string(width_fixed / 65536.0);
properties["trak"]["tkhd_height"] = std::to_string(height_fixed / 65536.0);
} else if (sub_box.type == "mdia") {
parse_mdia(offset, sub_box.size - 8);
}
}
}
void parse_mdia(size_t start, uint32_t size) {
// Extend
}
void parse_tiff(size_t start, uint32_t size) {
offset = start;
std::string byte_order = read_string(2);
properties["tiff"]["byte_order"] = byte_order;
// Extend IFD
}
void print_properties() {
for (const auto& p : properties) {
std::cout << p.first << ": " << std::endl;
for (const auto& sp : p.second) {
std::cout << " " << sp.first << ": " << sp.second << std::endl;
}
}
}
void write(const std::string& new_filepath) {
std::ofstream out(new_filepath, std::ios::binary);
out.write(data.data(), data.size());
// Extend to modify data vector
}
};
int main() {
CR3Parser parser("example.CR3");
parser.parse();
parser.print_properties();
parser.write("modified.CR3");
return 0;
}
This class parses, extracts, prints, and has a basic write. Compile with g++ (assumes big-endian swap for little-endian systems).