Glymur 0.13.7__py3-none-any.whl → 0.14.0.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glymur/__init__.py +2 -1
- glymur/_core_converter.py +390 -0
- glymur/_iccprofile.py +73 -72
- glymur/codestream.py +385 -308
- glymur/command_line.py +129 -1
- glymur/config.py +15 -14
- glymur/core.py +18 -22
- glymur/jp2box.py +758 -576
- glymur/jp2k.py +185 -149
- glymur/jp2kr.py +59 -45
- glymur/jpeg.py +196 -0
- glymur/lib/openjp2.py +198 -285
- glymur/lib/tiff.py +1251 -1127
- glymur/options.py +33 -28
- glymur/tiff.py +77 -406
- glymur/version.py +1 -1
- {Glymur-0.13.7.dist-info → glymur-0.14.0.post1.dist-info}/METADATA +5 -3
- glymur-0.14.0.post1.dist-info/RECORD +27 -0
- {Glymur-0.13.7.dist-info → glymur-0.14.0.post1.dist-info}/WHEEL +1 -1
- {Glymur-0.13.7.dist-info → glymur-0.14.0.post1.dist-info}/entry_points.txt +1 -0
- Glymur-0.13.7.dist-info/RECORD +0 -25
- {Glymur-0.13.7.dist-info → glymur-0.14.0.post1.dist-info/licenses}/LICENSE.txt +0 -0
- {Glymur-0.13.7.dist-info → glymur-0.14.0.post1.dist-info}/top_level.txt +0 -0
glymur/jp2kr.py
CHANGED
|
@@ -6,6 +6,7 @@ Copyright 2013 John Evans
|
|
|
6
6
|
|
|
7
7
|
License: MIT
|
|
8
8
|
"""
|
|
9
|
+
|
|
9
10
|
# Standard library imports...
|
|
10
11
|
from __future__ import annotations
|
|
11
12
|
from contextlib import ExitStack
|
|
@@ -121,8 +122,8 @@ class Jp2kr(Jp2kBox):
|
|
|
121
122
|
num_components = len(cstr.segment[1].xrsiz)
|
|
122
123
|
else:
|
|
123
124
|
# try to get the image size from the IHDR box
|
|
124
|
-
jp2h = next(filter(lambda x: x.box_id ==
|
|
125
|
-
ihdr = next(filter(lambda x: x.box_id ==
|
|
125
|
+
jp2h = next(filter(lambda x: x.box_id == "jp2h", self.box), None)
|
|
126
|
+
ihdr = next(filter(lambda x: x.box_id == "ihdr", jp2h.box), None)
|
|
126
127
|
|
|
127
128
|
height, width = ihdr.height, ihdr.width
|
|
128
129
|
num_components = ihdr.num_components
|
|
@@ -131,7 +132,7 @@ class Jp2kr(Jp2kBox):
|
|
|
131
132
|
# but if there is a PCLR box, then we need to check
|
|
132
133
|
# that as well, as that turns a single-channel image
|
|
133
134
|
# into a multi-channel image
|
|
134
|
-
pclr = [box for box in jp2h.box if box.box_id ==
|
|
135
|
+
pclr = [box for box in jp2h.box if box.box_id == "pclr"]
|
|
135
136
|
if len(pclr) > 0:
|
|
136
137
|
num_components = len(pclr[0].signed)
|
|
137
138
|
|
|
@@ -203,7 +204,7 @@ class Jp2kr(Jp2kBox):
|
|
|
203
204
|
def layer(self, layer):
|
|
204
205
|
# Set to the indicated value so long as it is valid.
|
|
205
206
|
cod = next(
|
|
206
|
-
filter(lambda x: x.marker_id ==
|
|
207
|
+
filter(lambda x: x.marker_id == "COD", self.codestream.segment),
|
|
207
208
|
None
|
|
208
209
|
)
|
|
209
210
|
if layer < 0 or layer >= cod.layers:
|
|
@@ -281,7 +282,7 @@ class Jp2kr(Jp2kBox):
|
|
|
281
282
|
return msg
|
|
282
283
|
|
|
283
284
|
def __str__(self):
|
|
284
|
-
metadata = [f
|
|
285
|
+
metadata = [f"File: {self.path.name}"]
|
|
285
286
|
if len(self.box) > 0:
|
|
286
287
|
for box in self.box:
|
|
287
288
|
metadata.append(str(box))
|
|
@@ -291,7 +292,7 @@ class Jp2kr(Jp2kBox):
|
|
|
291
292
|
else:
|
|
292
293
|
# Just a codestream, so J2K
|
|
293
294
|
metadata.append(str(self.codestream))
|
|
294
|
-
return
|
|
295
|
+
return "\n".join(metadata)
|
|
295
296
|
|
|
296
297
|
def parse(self, force=False):
|
|
297
298
|
"""Parses the JPEG 2000 file.
|
|
@@ -312,13 +313,13 @@ class Jp2kr(Jp2kBox):
|
|
|
312
313
|
|
|
313
314
|
self.length = self.path.stat().st_size
|
|
314
315
|
|
|
315
|
-
with self.path.open(
|
|
316
|
+
with self.path.open("rb") as fptr:
|
|
316
317
|
|
|
317
318
|
# Make sure we have a JPEG2000 file. It could be either JP2 or
|
|
318
319
|
# J2C. Check for J2C first, single box in that case.
|
|
319
320
|
read_buffer = fptr.read(2)
|
|
320
|
-
signature, = struct.unpack(
|
|
321
|
-
if signature ==
|
|
321
|
+
(signature,) = struct.unpack(">H", read_buffer)
|
|
322
|
+
if signature == 0xFF4F:
|
|
322
323
|
self._codec_format = opj2.CODEC_J2K
|
|
323
324
|
# That's it, we're done. The codestream object is only
|
|
324
325
|
# produced upon explicit request.
|
|
@@ -332,17 +333,17 @@ class Jp2kr(Jp2kBox):
|
|
|
332
333
|
# 3rd 4 bytes should be the box signature (13, 10, 135, 10).
|
|
333
334
|
fptr.seek(0)
|
|
334
335
|
read_buffer = fptr.read(12)
|
|
335
|
-
values = struct.unpack(
|
|
336
|
+
values = struct.unpack(">I4s4B", read_buffer)
|
|
336
337
|
box_length = values[0]
|
|
337
338
|
box_id = values[1]
|
|
338
339
|
signature = values[2:]
|
|
339
340
|
|
|
340
341
|
if (
|
|
341
342
|
box_length != 12
|
|
342
|
-
or box_id != b
|
|
343
|
+
or box_id != b"jP "
|
|
343
344
|
or signature != (13, 10, 135, 10)
|
|
344
345
|
):
|
|
345
|
-
msg = f
|
|
346
|
+
msg = f"{self.filename} is not a JPEG 2000 file."
|
|
346
347
|
raise InvalidJp2kError(msg)
|
|
347
348
|
|
|
348
349
|
# Back up and start again, we know we have a superbox (box of
|
|
@@ -364,11 +365,11 @@ class Jp2kr(Jp2kBox):
|
|
|
364
365
|
raise InvalidJp2kError(msg)
|
|
365
366
|
|
|
366
367
|
ftyp = self.box[1]
|
|
367
|
-
if ftyp.brand !=
|
|
368
|
+
if ftyp.brand != "jp2 ":
|
|
368
369
|
# Don't bother trying to validate JPX.
|
|
369
370
|
return
|
|
370
371
|
|
|
371
|
-
jp2h = next(filter(lambda x: x.box_id ==
|
|
372
|
+
jp2h = next(filter(lambda x: x.box_id == "jp2h", self.box), None)
|
|
372
373
|
if jp2h is None:
|
|
373
374
|
msg = (
|
|
374
375
|
"No JP2 header box was located in the outermost jacket of "
|
|
@@ -377,15 +378,16 @@ class Jp2kr(Jp2kBox):
|
|
|
377
378
|
raise InvalidJp2kError(msg)
|
|
378
379
|
|
|
379
380
|
# An IHDR box is required as the first child box of the JP2H box.
|
|
380
|
-
if jp2h.box[0].box_id !=
|
|
381
|
+
if jp2h.box[0].box_id != "ihdr":
|
|
381
382
|
msg = "A valid IHDR box was not found. The JP2 file is invalid."
|
|
382
383
|
raise InvalidJp2kError(msg)
|
|
383
384
|
|
|
384
385
|
# A jp2-branded file cannot contain an "any ICC profile
|
|
385
|
-
colrs = [box for box in jp2h.box if box.box_id ==
|
|
386
|
+
colrs = [box for box in jp2h.box if box.box_id == "colr"]
|
|
386
387
|
for colr in colrs:
|
|
387
388
|
if colr.method not in (
|
|
388
|
-
core.ENUMERATED_COLORSPACE,
|
|
389
|
+
core.ENUMERATED_COLORSPACE,
|
|
390
|
+
core.RESTRICTED_ICC_PROFILE,
|
|
389
391
|
):
|
|
390
392
|
msg = (
|
|
391
393
|
"Color Specification box method must specify either an "
|
|
@@ -395,7 +397,7 @@ class Jp2kr(Jp2kBox):
|
|
|
395
397
|
warnings.warn(msg, InvalidJp2kWarning)
|
|
396
398
|
|
|
397
399
|
# We need to have one and only one JP2H box if we have a JP2 file.
|
|
398
|
-
num_jp2h_boxes = len([box for box in self.box if box.box_id ==
|
|
400
|
+
num_jp2h_boxes = len([box for box in self.box if box.box_id == "jp2h"])
|
|
399
401
|
if num_jp2h_boxes > 1:
|
|
400
402
|
msg = (
|
|
401
403
|
f"This file has {num_jp2h_boxes} JP2H boxes in the outermost "
|
|
@@ -404,8 +406,8 @@ class Jp2kr(Jp2kBox):
|
|
|
404
406
|
warnings.warn(msg, InvalidJp2kWarning)
|
|
405
407
|
|
|
406
408
|
# We should have one and only one JP2C box if we have a JP2 file.
|
|
407
|
-
num_jp2c_boxes = len([box for box in self.box if box.box_id ==
|
|
408
|
-
if num_jp2c_boxes > 1 and self.box[1].brand ==
|
|
409
|
+
num_jp2c_boxes = len([box for box in self.box if box.box_id == "jp2c"])
|
|
410
|
+
if num_jp2c_boxes > 1 and self.box[1].brand == "jp2 ":
|
|
409
411
|
msg = (
|
|
410
412
|
f"This file has {num_jp2c_boxes} JP2C boxes (images) in the "
|
|
411
413
|
"outermost layer of boxes. All JP2C boxes after the first "
|
|
@@ -424,7 +426,7 @@ class Jp2kr(Jp2kBox):
|
|
|
424
426
|
ihdr_dims = ihdr.height, ihdr.width, ihdr.num_components
|
|
425
427
|
|
|
426
428
|
siz = next(
|
|
427
|
-
filter(lambda x: x.marker_id ==
|
|
429
|
+
filter(lambda x: x.marker_id == "SIZ", self.codestream.segment),
|
|
428
430
|
None
|
|
429
431
|
)
|
|
430
432
|
|
|
@@ -524,7 +526,7 @@ class Jp2kr(Jp2kBox):
|
|
|
524
526
|
0 if rows.start is None else rows.start,
|
|
525
527
|
0 if cols.start is None else cols.start,
|
|
526
528
|
numrows if rows.stop is None else rows.stop,
|
|
527
|
-
numcols if cols.stop is None else cols.stop
|
|
529
|
+
numcols if cols.stop is None else cols.stop,
|
|
528
530
|
)
|
|
529
531
|
data = self._read(area=area, rlevel=rlevel)
|
|
530
532
|
if len(pargs) == 2:
|
|
@@ -545,9 +547,9 @@ class Jp2kr(Jp2kBox):
|
|
|
545
547
|
The image data.
|
|
546
548
|
"""
|
|
547
549
|
|
|
548
|
-
if
|
|
549
|
-
self.ignore_pclr_cmap_cdef = kwargs[
|
|
550
|
-
kwargs.pop(
|
|
550
|
+
if "ignore_pclr_cmap_cdef" in kwargs:
|
|
551
|
+
self.ignore_pclr_cmap_cdef = kwargs["ignore_pclr_cmap_cdef"]
|
|
552
|
+
kwargs.pop("ignore_pclr_cmap_cdef")
|
|
551
553
|
warnings.warn("Use array-style slicing instead.", DeprecationWarning)
|
|
552
554
|
img = self._read(**kwargs)
|
|
553
555
|
return img
|
|
@@ -642,8 +644,8 @@ class Jp2kr(Jp2kBox):
|
|
|
642
644
|
opj2.set_info_handler(codec, None)
|
|
643
645
|
|
|
644
646
|
opj2.setup_decoder(codec, self._dparams)
|
|
645
|
-
if version.openjpeg_version >=
|
|
646
|
-
opj2.codec_set_threads(codec, get_option(
|
|
647
|
+
if version.openjpeg_version >= "2.2.0":
|
|
648
|
+
opj2.codec_set_threads(codec, get_option("lib.num_threads"))
|
|
647
649
|
|
|
648
650
|
raw_image = opj2.read_header(stream, codec)
|
|
649
651
|
stack.callback(opj2.image_destroy, raw_image)
|
|
@@ -652,13 +654,17 @@ class Jp2kr(Jp2kBox):
|
|
|
652
654
|
opj2.set_decoded_components(codec, self._decoded_components)
|
|
653
655
|
|
|
654
656
|
if self._dparams.nb_tile_to_decode:
|
|
655
|
-
opj2.get_decoded_tile(
|
|
656
|
-
|
|
657
|
+
opj2.get_decoded_tile(
|
|
658
|
+
codec, stream, raw_image, self._dparams.tile_index
|
|
659
|
+
)
|
|
657
660
|
else:
|
|
658
661
|
opj2.set_decode_area(
|
|
659
|
-
codec,
|
|
660
|
-
|
|
661
|
-
self._dparams.
|
|
662
|
+
codec,
|
|
663
|
+
raw_image,
|
|
664
|
+
self._dparams.DA_x0,
|
|
665
|
+
self._dparams.DA_y0,
|
|
666
|
+
self._dparams.DA_x1,
|
|
667
|
+
self._dparams.DA_y1,
|
|
662
668
|
)
|
|
663
669
|
opj2.decode(codec, stream, raw_image)
|
|
664
670
|
|
|
@@ -685,7 +691,7 @@ class Jp2kr(Jp2kBox):
|
|
|
685
691
|
|
|
686
692
|
infile = self.filename.encode()
|
|
687
693
|
nelts = opj2.PATH_LEN - len(infile)
|
|
688
|
-
infile += b
|
|
694
|
+
infile += b"0" * nelts
|
|
689
695
|
dparam.infile = infile
|
|
690
696
|
|
|
691
697
|
# Return raw codestream components instead of "interpolating" the
|
|
@@ -700,8 +706,7 @@ class Jp2kr(Jp2kBox):
|
|
|
700
706
|
# Must check the specified rlevel against the maximum.
|
|
701
707
|
cod_seg = next(
|
|
702
708
|
filter(
|
|
703
|
-
lambda x: x.marker_id ==
|
|
704
|
-
self.codestream.segment
|
|
709
|
+
lambda x: x.marker_id == "COD", self.codestream.segment
|
|
705
710
|
),
|
|
706
711
|
None
|
|
707
712
|
)
|
|
@@ -710,8 +715,10 @@ class Jp2kr(Jp2kBox):
|
|
|
710
715
|
# -1 is shorthand for the largest rlevel
|
|
711
716
|
rlevel = max_rlevel
|
|
712
717
|
elif rlevel < -1 or rlevel > max_rlevel:
|
|
713
|
-
msg = (
|
|
714
|
-
|
|
718
|
+
msg = (
|
|
719
|
+
f"rlevel must be in the range [-1, {max_rlevel}] "
|
|
720
|
+
"for this image."
|
|
721
|
+
)
|
|
715
722
|
raise ValueError(msg)
|
|
716
723
|
|
|
717
724
|
dparam.cp_reduce = rlevel
|
|
@@ -736,8 +743,15 @@ class Jp2kr(Jp2kBox):
|
|
|
736
743
|
|
|
737
744
|
self._dparams = dparam
|
|
738
745
|
|
|
739
|
-
def read_bands(
|
|
740
|
-
|
|
746
|
+
def read_bands(
|
|
747
|
+
self,
|
|
748
|
+
rlevel=0,
|
|
749
|
+
layer=0,
|
|
750
|
+
area=None,
|
|
751
|
+
tile=None,
|
|
752
|
+
verbose=False,
|
|
753
|
+
ignore_pclr_cmap_cdef=False,
|
|
754
|
+
):
|
|
741
755
|
"""Read a JPEG 2000 image.
|
|
742
756
|
|
|
743
757
|
The only time you should ever use this method is when the image has
|
|
@@ -776,7 +790,7 @@ class Jp2kr(Jp2kBox):
|
|
|
776
790
|
>>> jp = glymur.Jp2k(jfile)
|
|
777
791
|
>>> components_lst = jp.read_bands(rlevel=1)
|
|
778
792
|
"""
|
|
779
|
-
if version.openjpeg_version <
|
|
793
|
+
if version.openjpeg_version < "2.3.0":
|
|
780
794
|
msg = (
|
|
781
795
|
f"You must have at least version 2.3.0 of OpenJPEG installed "
|
|
782
796
|
f"before using this method. Your version of OpenJPEG is "
|
|
@@ -917,7 +931,7 @@ class Jp2kr(Jp2kBox):
|
|
|
917
931
|
Signed: (False, False, False)
|
|
918
932
|
Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))
|
|
919
933
|
"""
|
|
920
|
-
with self.path.open(
|
|
934
|
+
with self.path.open("rb") as fptr:
|
|
921
935
|
|
|
922
936
|
# if it's just a raw codestream file, it's easy
|
|
923
937
|
if self._codec_format == opj2.CODEC_J2K:
|
|
@@ -925,11 +939,11 @@ class Jp2kr(Jp2kBox):
|
|
|
925
939
|
|
|
926
940
|
# continue assuming JP2, must seek to the JP2C box and past its
|
|
927
941
|
# header
|
|
928
|
-
box = next(filter(lambda x: x.box_id ==
|
|
942
|
+
box = next(filter(lambda x: x.box_id == "jp2c", self.box), None)
|
|
929
943
|
|
|
930
944
|
fptr.seek(box.offset)
|
|
931
945
|
read_buffer = fptr.read(8)
|
|
932
|
-
(box_length, _) = struct.unpack(
|
|
946
|
+
(box_length, _) = struct.unpack(">I4s", read_buffer)
|
|
933
947
|
if box_length == 0:
|
|
934
948
|
# The length of the box is presumed to last until the end
|
|
935
949
|
# of the file. Compute the effective length of the box.
|
|
@@ -937,7 +951,7 @@ class Jp2kr(Jp2kBox):
|
|
|
937
951
|
elif box_length == 1:
|
|
938
952
|
# Seek past the XL field.
|
|
939
953
|
read_buffer = fptr.read(8)
|
|
940
|
-
box_length, = struct.unpack(
|
|
954
|
+
(box_length,) = struct.unpack(">Q", read_buffer)
|
|
941
955
|
|
|
942
956
|
return self._get_codestream(fptr, box_length - 8, header_only)
|
|
943
957
|
|
|
@@ -952,7 +966,7 @@ class Jp2kr(Jp2kBox):
|
|
|
952
966
|
except Exception:
|
|
953
967
|
_, value, traceback = sys.exc_info()
|
|
954
968
|
msg = (
|
|
955
|
-
f
|
|
969
|
+
f"The file is invalid "
|
|
956
970
|
f'because the codestream could not be parsed: "{value}"'
|
|
957
971
|
)
|
|
958
972
|
raise InvalidJp2kError(msg).with_traceback(traceback)
|
glymur/jpeg.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# standard library imports
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
import pathlib
|
|
5
|
+
import struct
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
|
|
8
|
+
# 3rd party library imports
|
|
9
|
+
import imageio.v3 as iio
|
|
10
|
+
|
|
11
|
+
# local imports
|
|
12
|
+
from .jp2k import Jp2k
|
|
13
|
+
from .options import set_option
|
|
14
|
+
from ._core_converter import _2JP2Converter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JPEG2JP2(_2JP2Converter):
|
|
18
|
+
"""
|
|
19
|
+
Attributes
|
|
20
|
+
----------
|
|
21
|
+
create_exif_uuid : bool
|
|
22
|
+
Create a UUIDBox for the Exif metadata. Always True for JPEG.
|
|
23
|
+
jp2_filename : path
|
|
24
|
+
Path to JPEG 2000 file to be written.
|
|
25
|
+
jpeg_filename : path
|
|
26
|
+
Path to JPEG file.
|
|
27
|
+
tilesize : tuple
|
|
28
|
+
The dimensions of a tile in the JP2K file.
|
|
29
|
+
verbosity : int
|
|
30
|
+
Set the level of logging, i.e. WARNING, INFO, etc.
|
|
31
|
+
tags : dict
|
|
32
|
+
Tags retrieved from APP1 segment, if any.
|
|
33
|
+
"""
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
jpeg: pathlib.Path | str,
|
|
37
|
+
jp2: pathlib.Path | str,
|
|
38
|
+
include_icc_profile: bool = False,
|
|
39
|
+
num_threads: int = 1,
|
|
40
|
+
tilesize: Tuple[int, int] | None = None,
|
|
41
|
+
verbosity: int = logging.CRITICAL,
|
|
42
|
+
**kwargs
|
|
43
|
+
):
|
|
44
|
+
super().__init__(True, True, include_icc_profile, tilesize, verbosity)
|
|
45
|
+
|
|
46
|
+
self.jpeg_path = pathlib.Path(jpeg)
|
|
47
|
+
|
|
48
|
+
self.jp2_path = pathlib.Path(jp2)
|
|
49
|
+
if self.jp2_path.exists():
|
|
50
|
+
raise FileExistsError(f'{str(self.jp2_path)} already exists, please delete if you wish to overwrite.') # noqa : E501
|
|
51
|
+
|
|
52
|
+
self.jp2_kwargs = kwargs
|
|
53
|
+
|
|
54
|
+
self.tags = None
|
|
55
|
+
|
|
56
|
+
# This is never set for JPEG
|
|
57
|
+
self.exclude_tags = None
|
|
58
|
+
|
|
59
|
+
if num_threads > 1:
|
|
60
|
+
set_option("lib.num_threads", num_threads)
|
|
61
|
+
|
|
62
|
+
def __enter__(self):
|
|
63
|
+
"""The JPEG2JP2 object must be used with a context manager."""
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def run(self):
|
|
70
|
+
|
|
71
|
+
self.copy_image()
|
|
72
|
+
self.copy_metadata()
|
|
73
|
+
|
|
74
|
+
def copy_metadata(self):
|
|
75
|
+
"""Transfer any EXIF or XMP metadata from the APPx segments."""
|
|
76
|
+
|
|
77
|
+
with self.jpeg_path.open(mode='rb') as f:
|
|
78
|
+
|
|
79
|
+
eof = False
|
|
80
|
+
while not eof:
|
|
81
|
+
|
|
82
|
+
marker = f.read(2)
|
|
83
|
+
|
|
84
|
+
match marker:
|
|
85
|
+
|
|
86
|
+
case b'\xff\xd8':
|
|
87
|
+
# marker-only, SOI
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
case b'\xff\xe0' | b'\xff\xec' | b'\xff\xee':
|
|
91
|
+
self.process_appx_segment(marker, f)
|
|
92
|
+
|
|
93
|
+
case b'\xff\xe1':
|
|
94
|
+
# EXIF using APP1
|
|
95
|
+
self.process_app1_segment(f)
|
|
96
|
+
|
|
97
|
+
case b'\xff\xe2':
|
|
98
|
+
# ICC profile
|
|
99
|
+
self.process_app2_segment(f)
|
|
100
|
+
|
|
101
|
+
case _:
|
|
102
|
+
# We don't care about anything else. No need to scan
|
|
103
|
+
# the file any further, we're done.
|
|
104
|
+
eof = True
|
|
105
|
+
|
|
106
|
+
if self.include_icc_profile and self.icc_profile is not None:
|
|
107
|
+
self.rewrap_for_icc_profile()
|
|
108
|
+
|
|
109
|
+
def process_appx_segment(self, marker, f):
|
|
110
|
+
# APP0 (JFIF) is b'\xff\xe0'
|
|
111
|
+
# APP12 ducky(?) is b'\xff\xec'
|
|
112
|
+
# APP14 Adobe(?) is b'\xff\xee'
|
|
113
|
+
_, n = struct.unpack('BB', marker)
|
|
114
|
+
|
|
115
|
+
msg = f'Skipping APP{n - 224} segment...'
|
|
116
|
+
self.logger.info(msg)
|
|
117
|
+
|
|
118
|
+
data = f.read(2)
|
|
119
|
+
size, = struct.unpack('>H', data)
|
|
120
|
+
_ = f.read(size - 2)
|
|
121
|
+
|
|
122
|
+
def process_app1_segment(self, f):
|
|
123
|
+
"""
|
|
124
|
+
An APP1 segment can contain Exif or XMP data.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
data = f.read(2)
|
|
128
|
+
size, = struct.unpack('>H', data)
|
|
129
|
+
buffer = f.read(size - 2)
|
|
130
|
+
|
|
131
|
+
if buffer[:6] == b'Exif\x00\x00':
|
|
132
|
+
|
|
133
|
+
# ok it is Exif
|
|
134
|
+
|
|
135
|
+
buffer = buffer[6:]
|
|
136
|
+
|
|
137
|
+
bf = io.BytesIO(buffer)
|
|
138
|
+
|
|
139
|
+
self.read_tiff_header(bf)
|
|
140
|
+
self.tags = self.read_ifd(bf)
|
|
141
|
+
self.append_exif_uuid_box()
|
|
142
|
+
|
|
143
|
+
elif buffer[:28] == b'http://ns.adobe.com/xap/1.0/':
|
|
144
|
+
|
|
145
|
+
# XMP APP segment
|
|
146
|
+
self.xmp_data = buffer[29:]
|
|
147
|
+
self.append_xmp_uuid_box()
|
|
148
|
+
|
|
149
|
+
else:
|
|
150
|
+
|
|
151
|
+
offset = f.tell() - 2 - 2 - len(buffer)
|
|
152
|
+
msg = f'Unrecognized APP1 segment at offset {offset}'
|
|
153
|
+
self.logger.warning(msg)
|
|
154
|
+
|
|
155
|
+
def process_app2_segment(self, f):
|
|
156
|
+
"""
|
|
157
|
+
The APP2 segment(s) usually contains an ICC profile. It may be split
|
|
158
|
+
across more than one APP2 segment.
|
|
159
|
+
"""
|
|
160
|
+
data = f.read(2)
|
|
161
|
+
size, = struct.unpack('>H', data)
|
|
162
|
+
buffer = f.read(size - 2)
|
|
163
|
+
|
|
164
|
+
if buffer[:12] == b'ICC_PROFILE\x00':
|
|
165
|
+
|
|
166
|
+
count, nchunks = struct.unpack('BB', buffer[12:14])
|
|
167
|
+
|
|
168
|
+
if not self.include_icc_profile:
|
|
169
|
+
msg = (
|
|
170
|
+
f'ICC profile chunk {count} of {nchunks} detected '
|
|
171
|
+
'(skipped)'
|
|
172
|
+
)
|
|
173
|
+
self.logger.warning(msg)
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
msg = f'Processing ICC profile chunk {count} of {nchunks}...'
|
|
177
|
+
self.logger.info(msg)
|
|
178
|
+
|
|
179
|
+
if count == 1:
|
|
180
|
+
self.icc_profile = b''
|
|
181
|
+
|
|
182
|
+
# accumulate the ICC profile stored in this chunk. it's likely
|
|
183
|
+
# that this is all that there is, though.
|
|
184
|
+
self.icc_profile += bytes(buffer[14:])
|
|
185
|
+
|
|
186
|
+
def copy_image(self):
|
|
187
|
+
"""Transfer the image data from the JPEG to the JP2 file."""
|
|
188
|
+
image = iio.imread(self.jpeg_path)
|
|
189
|
+
|
|
190
|
+
self.jp2 = Jp2k(
|
|
191
|
+
self.jp2_path,
|
|
192
|
+
tilesize=self.tilesize,
|
|
193
|
+
**self.jp2_kwargs
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
self.jp2[:] = image
|