Skip to content

Commit 5bfb4d9

Browse files
authored
Merge pull request #1968 from Exiv2/mergify/bp/0.27-maintenance/pr-1958
Canon cr3 previews (backport #1958)
2 parents bfa7f62 + 51996a6 commit 5bfb4d9

File tree

4 files changed

+146
-6
lines changed

4 files changed

+146
-6
lines changed

Diff for: include/exiv2/bmffimage.hpp

+22
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,28 @@ namespace Exiv2
105105
void parseXmp(uint64_t length,uint64_t start);
106106
//@}
107107

108+
//@{
109+
/*!
110+
@brief Parse a Canon PRVW or THMB box and add an entry to the set
111+
of native previews.
112+
@param data Buffer containing the box
113+
@param out Logging stream
114+
@param bTrace Controls logging
115+
@param width_offset Index of image width field in data
116+
@param height_offset Index of image height field in data
117+
@param size_offset Index of image size field in data
118+
@param relative_position Location of the start of image data in the file,
119+
relative to the current file position indicator.
120+
*/
121+
void parseCr3Preview(DataBuf &data,
122+
std::ostream &out,
123+
bool bTrace,
124+
uint32_t width_offset,
125+
uint32_t height_offset,
126+
uint32_t size_offset,
127+
uint32_t relative_position);
128+
//@}
129+
108130
//! @name Manipulators
109131
//@{
110132
void readMetadata() /* override */;

Diff for: src/bmffimage.cpp

+59-6
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@
7373
#define TAG_cmt2 0x434D5432 /**< "CMD2" exifID */
7474
#define TAG_cmt3 0x434D5433 /**< "CMT3" canonID */
7575
#define TAG_cmt4 0x434D5434 /**< "CMT4" gpsID */
76-
#define TAG_colr 0x636f6c72 /**< "colr" */
76+
#define TAG_colr 0x636f6c72 /**< "colr" Colour information */
7777
#define TAG_exif 0x45786966 /**< "Exif" Used by JXL*/
78-
#define TAG_xml 0x786d6c20 /**< "xml" Used by JXL*/
78+
#define TAG_xml 0x786d6c20 /**< "xml " Used by JXL*/
79+
#define TAG_thmb 0x54484d42 /**< "THMB" Canon thumbnail */
80+
#define TAG_prvw 0x50525657 /**< "PRVW" Canon preview image */
7981

8082
// *****************************************************************************
8183
// class member definitions
@@ -124,7 +126,7 @@ namespace Exiv2
124126

125127
bool BmffImage::fullBox(uint32_t box)
126128
{
127-
return box == TAG_meta || box == TAG_iinf || box == TAG_iloc;
129+
return box == TAG_meta || box == TAG_iinf || box == TAG_iloc || box == TAG_thmb || box == TAG_prvw;
128130
}
129131

130132
static bool skipBox(uint32_t box)
@@ -264,7 +266,7 @@ namespace Exiv2
264266
enforce(data.size_ - skip >= 4, Exiv2::kerCorruptedMetadata);
265267
flags = getLong(data.pData_ + skip, endian_); // version/flags
266268
version = static_cast<uint8_t>(flags >> 24);
267-
version &= 0x00ffffff;
269+
flags &= 0x00ffffff;
268270
skip += 4;
269271
}
270272

@@ -453,7 +455,12 @@ namespace Exiv2
453455
out << " uuidName " << name << std::endl;
454456
bLF = false;
455457
}
456-
if (name == "cano") {
458+
if (name == "cano" || name == "canp" ) {
459+
if (name == "canp") {
460+
// based on
461+
// https://github.com/lclevy/canon_cr3/blob/7be75d6/parse_cr3.py#L271
462+
io_->seek(8, BasicIo::cur);
463+
}
457464
while (io_->tell() < box_end) {
458465
io_->seek(boxHandler(out,option,box_end,depth + 1), BasicIo::beg);
459466
}
@@ -480,10 +487,20 @@ namespace Exiv2
480487
case TAG_xml:
481488
parseXmp(box_length,io_->tell());
482489
break;
490+
case TAG_thmb:
491+
if (version == 0) {
492+
parseCr3Preview(data, out, bTrace, skip, skip+2, skip+4, skip+12);
493+
}
494+
break;
495+
case TAG_prvw:
496+
if (version == 0) {
497+
parseCr3Preview(data, out, bTrace, skip+2, skip+4, skip+8, skip+12);
498+
}
499+
break;
483500

484501
default: break ; /* do nothing */
485502
}
486-
if ( bLF&& bTrace) out << std::endl;
503+
if (bLF && bTrace) out << std::endl;
487504

488505
// return address of next box
489506
return box_end;
@@ -564,6 +581,42 @@ namespace Exiv2
564581
}
565582
}
566583

584+
void BmffImage::parseCr3Preview(DataBuf &data,
585+
std::ostream& out,
586+
bool bTrace,
587+
uint32_t width_offset,
588+
uint32_t height_offset,
589+
uint32_t size_offset,
590+
uint32_t relative_position)
591+
{
592+
// Derived from https://github.com/lclevy/canon_cr3
593+
// Only JPEG (version 0) is currently supported
594+
// (relative_position is identical between versions)
595+
long here = io_->tell();
596+
enforce(here >= 0 &&
597+
here <= std::numeric_limits<long>::max() - static_cast<long>(relative_position),
598+
kerCorruptedMetadata);
599+
NativePreview nativePreview;
600+
nativePreview.position_ = here + relative_position;
601+
enforce(4 <= data.size_, kerCorruptedMetadata);
602+
enforce(width_offset <= static_cast<size_t>(data.size_ - 2), kerCorruptedMetadata);
603+
nativePreview.width_ = getShort(data.pData_ + width_offset, endian_);
604+
enforce(height_offset <= static_cast<size_t>(data.size_ - 2), kerCorruptedMetadata);
605+
nativePreview.height_ = getShort(data.pData_ + height_offset, endian_);
606+
enforce(size_offset <= static_cast<size_t>(data.size_ - 4), kerCorruptedMetadata);
607+
nativePreview.size_ = getLong(data.pData_ + size_offset, endian_);
608+
nativePreview.filter_ = "";
609+
nativePreview.mimeType_ = "image/jpeg";
610+
nativePreviews_.push_back(nativePreview);
611+
612+
if (bTrace) {
613+
out << Internal::stringFormat("width,height,size = %u,%u,%u",
614+
nativePreview.width_,
615+
nativePreview.height_,
616+
nativePreview.size_);
617+
}
618+
}
619+
567620
void BmffImage::setComment(const std::string& /*comment*/)
568621
{
569622
// bmff files are read-only

Diff for: test/data/Canon-R6-pruned.CR3

480 KB
Binary file not shown.

Diff for: tests/bugfixes/github/test_issue_1893.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import system_tests
4+
import unittest
5+
from tempfile import TemporaryDirectory
6+
import shutil
7+
import hashlib
8+
import os
9+
10+
# test needs system_tests.BT.vv['enable_bmff']=1
11+
bSkip=system_tests.BT.verbose_version().get('enable_bmff')!='1'
12+
13+
if bSkip:
14+
raise unittest.SkipTest('*** requires enable_bmff=1 ***')
15+
16+
file_basename = 'Canon-R6-pruned.CR3'
17+
previews_expected = (
18+
('Canon-R6-pruned-preview1.jpg', 'a182ef12ac883309b4dfc66b87eac1891286d3ae'),
19+
('Canon-R6-pruned-preview2.jpg', '524a07f1797854e349ae140e2682ba37147fa6b2')
20+
)
21+
22+
class issue_1893_cr3_preview(metaclass=system_tests.CaseMeta):
23+
"""
24+
Check that THMB and PRVW images are extracted from Canon CR3 files
25+
"""
26+
url = "https://github.com/Exiv2/exiv2/issues/1893"
27+
filename = "$data_path/" + file_basename
28+
commands=[] # see setUp()
29+
30+
if bSkip:
31+
retval=[]
32+
stdin=[]
33+
stderr=[]
34+
stdout=[]
35+
print("*** test skipped. requires enable_bmff=1***")
36+
else:
37+
retval = [ 0, 0]
38+
stderr = [ "",""]
39+
stdin = [ "", ""]
40+
stdout = ["""Preview 1: image/jpeg, 160x120 pixels, 16005 bytes
41+
Preview 2: image/jpeg, 1620x1080 pixels, 389450 bytes
42+
""", ""]
43+
44+
def post_tests_hook(self):
45+
if self.commands:
46+
for j, sha1 in previews_expected:
47+
p = os.path.join(self.preview_image_tmp_dir.name, j)
48+
self.assertTrue(os.path.isfile(p))
49+
h = hashlib.sha1(open(p, 'rb').read()).hexdigest()
50+
self.assertEqual(h, sha1)
51+
52+
def setUp(self):
53+
if bSkip:
54+
return
55+
# Avoid polluting the test data directory with extracted previews
56+
self.preview_image_tmp_dir = TemporaryDirectory()
57+
shutil.copy(self.expand_variables(self.filename),
58+
self.preview_image_tmp_dir.name)
59+
p = os.path.join(
60+
self.preview_image_tmp_dir.name,
61+
file_basename)
62+
self.commands = [
63+
self.expand_variables("$exiv2 -pp ") + p,
64+
self.expand_variables("$exiv2 -ep ") + p
65+
]

0 commit comments

Comments
 (0)