Skip to content

Commit

Permalink
Merge pull request #1968 from Exiv2/mergify/bp/0.27-maintenance/pr-1958
Browse files Browse the repository at this point in the history
Canon cr3 previews (backport #1958)
  • Loading branch information
clanmills authored Oct 21, 2021
2 parents bfa7f62 + 51996a6 commit 5bfb4d9
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 6 deletions.
22 changes: 22 additions & 0 deletions include/exiv2/bmffimage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ namespace Exiv2
void parseXmp(uint64_t length,uint64_t start);
//@}

//@{
/*!
@brief Parse a Canon PRVW or THMB box and add an entry to the set
of native previews.
@param data Buffer containing the box
@param out Logging stream
@param bTrace Controls logging
@param width_offset Index of image width field in data
@param height_offset Index of image height field in data
@param size_offset Index of image size field in data
@param relative_position Location of the start of image data in the file,
relative to the current file position indicator.
*/
void parseCr3Preview(DataBuf &data,
std::ostream &out,
bool bTrace,
uint32_t width_offset,
uint32_t height_offset,
uint32_t size_offset,
uint32_t relative_position);
//@}

//! @name Manipulators
//@{
void readMetadata() /* override */;
Expand Down
65 changes: 59 additions & 6 deletions src/bmffimage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@
#define TAG_cmt2 0x434D5432 /**< "CMD2" exifID */
#define TAG_cmt3 0x434D5433 /**< "CMT3" canonID */
#define TAG_cmt4 0x434D5434 /**< "CMT4" gpsID */
#define TAG_colr 0x636f6c72 /**< "colr" */
#define TAG_colr 0x636f6c72 /**< "colr" Colour information */
#define TAG_exif 0x45786966 /**< "Exif" Used by JXL*/
#define TAG_xml 0x786d6c20 /**< "xml" Used by JXL*/
#define TAG_xml 0x786d6c20 /**< "xml " Used by JXL*/
#define TAG_thmb 0x54484d42 /**< "THMB" Canon thumbnail */
#define TAG_prvw 0x50525657 /**< "PRVW" Canon preview image */

// *****************************************************************************
// class member definitions
Expand Down Expand Up @@ -124,7 +126,7 @@ namespace Exiv2

bool BmffImage::fullBox(uint32_t box)
{
return box == TAG_meta || box == TAG_iinf || box == TAG_iloc;
return box == TAG_meta || box == TAG_iinf || box == TAG_iloc || box == TAG_thmb || box == TAG_prvw;
}

static bool skipBox(uint32_t box)
Expand Down Expand Up @@ -264,7 +266,7 @@ namespace Exiv2
enforce(data.size_ - skip >= 4, Exiv2::kerCorruptedMetadata);
flags = getLong(data.pData_ + skip, endian_); // version/flags
version = static_cast<uint8_t>(flags >> 24);
version &= 0x00ffffff;
flags &= 0x00ffffff;
skip += 4;
}

Expand Down Expand Up @@ -453,7 +455,12 @@ namespace Exiv2
out << " uuidName " << name << std::endl;
bLF = false;
}
if (name == "cano") {
if (name == "cano" || name == "canp" ) {
if (name == "canp") {
// based on
// https://github.com/lclevy/canon_cr3/blob/7be75d6/parse_cr3.py#L271
io_->seek(8, BasicIo::cur);
}
while (io_->tell() < box_end) {
io_->seek(boxHandler(out,option,box_end,depth + 1), BasicIo::beg);
}
Expand All @@ -480,10 +487,20 @@ namespace Exiv2
case TAG_xml:
parseXmp(box_length,io_->tell());
break;
case TAG_thmb:
if (version == 0) {
parseCr3Preview(data, out, bTrace, skip, skip+2, skip+4, skip+12);
}
break;
case TAG_prvw:
if (version == 0) {
parseCr3Preview(data, out, bTrace, skip+2, skip+4, skip+8, skip+12);
}
break;

default: break ; /* do nothing */
}
if ( bLF&& bTrace) out << std::endl;
if (bLF && bTrace) out << std::endl;

// return address of next box
return box_end;
Expand Down Expand Up @@ -564,6 +581,42 @@ namespace Exiv2
}
}

void BmffImage::parseCr3Preview(DataBuf &data,
std::ostream& out,
bool bTrace,
uint32_t width_offset,
uint32_t height_offset,
uint32_t size_offset,
uint32_t relative_position)
{
// Derived from https://github.com/lclevy/canon_cr3
// Only JPEG (version 0) is currently supported
// (relative_position is identical between versions)
long here = io_->tell();
enforce(here >= 0 &&
here <= std::numeric_limits<long>::max() - static_cast<long>(relative_position),
kerCorruptedMetadata);
NativePreview nativePreview;
nativePreview.position_ = here + relative_position;
enforce(4 <= data.size_, kerCorruptedMetadata);
enforce(width_offset <= static_cast<size_t>(data.size_ - 2), kerCorruptedMetadata);
nativePreview.width_ = getShort(data.pData_ + width_offset, endian_);
enforce(height_offset <= static_cast<size_t>(data.size_ - 2), kerCorruptedMetadata);
nativePreview.height_ = getShort(data.pData_ + height_offset, endian_);
enforce(size_offset <= static_cast<size_t>(data.size_ - 4), kerCorruptedMetadata);
nativePreview.size_ = getLong(data.pData_ + size_offset, endian_);
nativePreview.filter_ = "";
nativePreview.mimeType_ = "image/jpeg";
nativePreviews_.push_back(nativePreview);

if (bTrace) {
out << Internal::stringFormat("width,height,size = %u,%u,%u",
nativePreview.width_,
nativePreview.height_,
nativePreview.size_);
}
}

void BmffImage::setComment(const std::string& /*comment*/)
{
// bmff files are read-only
Expand Down
Binary file added test/data/Canon-R6-pruned.CR3
Binary file not shown.
65 changes: 65 additions & 0 deletions tests/bugfixes/github/test_issue_1893.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-

import system_tests
import unittest
from tempfile import TemporaryDirectory
import shutil
import hashlib
import os

# test needs system_tests.BT.vv['enable_bmff']=1
bSkip=system_tests.BT.verbose_version().get('enable_bmff')!='1'

if bSkip:
raise unittest.SkipTest('*** requires enable_bmff=1 ***')

file_basename = 'Canon-R6-pruned.CR3'
previews_expected = (
('Canon-R6-pruned-preview1.jpg', 'a182ef12ac883309b4dfc66b87eac1891286d3ae'),
('Canon-R6-pruned-preview2.jpg', '524a07f1797854e349ae140e2682ba37147fa6b2')
)

class issue_1893_cr3_preview(metaclass=system_tests.CaseMeta):
"""
Check that THMB and PRVW images are extracted from Canon CR3 files
"""
url = "https://github.com/Exiv2/exiv2/issues/1893"
filename = "$data_path/" + file_basename
commands=[] # see setUp()

if bSkip:
retval=[]
stdin=[]
stderr=[]
stdout=[]
print("*** test skipped. requires enable_bmff=1***")
else:
retval = [ 0, 0]
stderr = [ "",""]
stdin = [ "", ""]
stdout = ["""Preview 1: image/jpeg, 160x120 pixels, 16005 bytes
Preview 2: image/jpeg, 1620x1080 pixels, 389450 bytes
""", ""]

def post_tests_hook(self):
if self.commands:
for j, sha1 in previews_expected:
p = os.path.join(self.preview_image_tmp_dir.name, j)
self.assertTrue(os.path.isfile(p))
h = hashlib.sha1(open(p, 'rb').read()).hexdigest()
self.assertEqual(h, sha1)

def setUp(self):
if bSkip:
return
# Avoid polluting the test data directory with extracted previews
self.preview_image_tmp_dir = TemporaryDirectory()
shutil.copy(self.expand_variables(self.filename),
self.preview_image_tmp_dir.name)
p = os.path.join(
self.preview_image_tmp_dir.name,
file_basename)
self.commands = [
self.expand_variables("$exiv2 -pp ") + p,
self.expand_variables("$exiv2 -ep ") + p
]

0 comments on commit 5bfb4d9

Please sign in to comment.