Glymur 0.13.6__py3-none-any.whl → 0.13.8__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-0.13.6.dist-info → Glymur-0.13.8.dist-info}/METADATA +5 -3
- Glymur-0.13.8.dist-info/RECORD +25 -0
- {Glymur-0.13.6.dist-info → Glymur-0.13.8.dist-info}/WHEEL +1 -1
- glymur/_iccprofile.py +73 -72
- glymur/codestream.py +385 -308
- glymur/config.py +15 -14
- glymur/core.py +18 -22
- glymur/jp2box.py +736 -577
- glymur/jp2k.py +185 -149
- glymur/jp2kr.py +62 -48
- glymur/lib/openjp2.py +198 -285
- glymur/lib/tiff.py +1152 -1156
- glymur/options.py +33 -28
- glymur/tiff.py +105 -103
- glymur/version.py +1 -1
- Glymur-0.13.6.dist-info/RECORD +0 -25
- {Glymur-0.13.6.dist-info → Glymur-0.13.8.dist-info}/LICENSE.txt +0 -0
- {Glymur-0.13.6.dist-info → Glymur-0.13.8.dist-info}/entry_points.txt +0 -0
- {Glymur-0.13.6.dist-info → Glymur-0.13.8.dist-info}/top_level.txt +0 -0
glymur/jp2box.py
CHANGED
|
@@ -27,6 +27,7 @@ import warnings
|
|
|
27
27
|
# Third party library imports ...
|
|
28
28
|
try:
|
|
29
29
|
from osgeo import gdal
|
|
30
|
+
|
|
30
31
|
_HAVE_GDAL = True
|
|
31
32
|
except (ImportError, ModuleNotFoundError): # pragma: no cover
|
|
32
33
|
_HAVE_GDAL = False
|
|
@@ -39,10 +40,15 @@ import numpy as np
|
|
|
39
40
|
# Local imports ...
|
|
40
41
|
from .codestream import Codestream
|
|
41
42
|
from .core import (
|
|
42
|
-
_COLORSPACE_MAP_DISPLAY,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
_COLORSPACE_MAP_DISPLAY,
|
|
44
|
+
_COLOR_TYPE_MAP_DISPLAY,
|
|
45
|
+
SRGB,
|
|
46
|
+
GREYSCALE,
|
|
47
|
+
YCC,
|
|
48
|
+
ENUMERATED_COLORSPACE,
|
|
49
|
+
RESTRICTED_ICC_PROFILE,
|
|
50
|
+
ANY_ICC_PROFILE,
|
|
51
|
+
VENDOR_COLOR_METHOD,
|
|
46
52
|
)
|
|
47
53
|
from .lib.tiff import tiff_header, BadTiffTagDatatype
|
|
48
54
|
from . import get_option
|
|
@@ -50,37 +56,38 @@ from ._iccprofile import _ICCProfile
|
|
|
50
56
|
|
|
51
57
|
|
|
52
58
|
_COLORSPACE_METHODS = {
|
|
53
|
-
ENUMERATED_COLORSPACE:
|
|
54
|
-
RESTRICTED_ICC_PROFILE:
|
|
55
|
-
ANY_ICC_PROFILE:
|
|
56
|
-
VENDOR_COLOR_METHOD:
|
|
59
|
+
ENUMERATED_COLORSPACE: "enumerated colorspace",
|
|
60
|
+
RESTRICTED_ICC_PROFILE: "restricted ICC profile",
|
|
61
|
+
ANY_ICC_PROFILE: "any ICC profile",
|
|
62
|
+
VENDOR_COLOR_METHOD: "vendor color method",
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
|
|
60
66
|
_APPROXIMATION_MEASURES = {
|
|
61
|
-
0:
|
|
62
|
-
1:
|
|
63
|
-
2:
|
|
64
|
-
3:
|
|
65
|
-
4:
|
|
67
|
+
0: "JP2 only",
|
|
68
|
+
1: "accurately represents correct colorspace definition",
|
|
69
|
+
2: "approximates correct colorspace definition, exceptional quality",
|
|
70
|
+
3: "approximates correct colorspace definition, reasonable quality",
|
|
71
|
+
4: "approximates correct colorspace definition, poor quality",
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
# Three different UUIDs are given special treatment.
|
|
69
|
-
_GEOTIFF_UUID = UUID(
|
|
70
|
-
_EXIF_UUID = UUID(bytes=b
|
|
71
|
-
_XMP_UUID = UUID(
|
|
75
|
+
_GEOTIFF_UUID = UUID("b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03")
|
|
76
|
+
_EXIF_UUID = UUID(bytes=b"JpgTiffExif->JP2")
|
|
77
|
+
_XMP_UUID = UUID("be7acfcb-97a9-42e8-9c71-999491e3afac")
|
|
72
78
|
|
|
73
79
|
|
|
74
80
|
class InvalidJp2kWarning(UserWarning):
|
|
75
81
|
"""Issue this warning in case the file is technically invalid but we can
|
|
76
82
|
still read the image.
|
|
77
83
|
"""
|
|
84
|
+
|
|
78
85
|
pass
|
|
79
86
|
|
|
80
87
|
|
|
81
88
|
class InvalidJp2kError(RuntimeError):
|
|
82
|
-
"""Raise this exception in case we cannot parse a valid JP2 file.
|
|
83
|
-
|
|
89
|
+
"""Raise this exception in case we cannot parse a valid JP2 file."""
|
|
90
|
+
|
|
84
91
|
pass
|
|
85
92
|
|
|
86
93
|
|
|
@@ -123,8 +130,7 @@ class Jp2kBox(object):
|
|
|
123
130
|
warnings.warn(msg)
|
|
124
131
|
|
|
125
132
|
def write(self, _):
|
|
126
|
-
"""Must be implemented in a subclass.
|
|
127
|
-
"""
|
|
133
|
+
"""Must be implemented in a subclass."""
|
|
128
134
|
msg = f"Writing not supported for {self.longname} box."
|
|
129
135
|
raise NotImplementedError(msg)
|
|
130
136
|
|
|
@@ -134,7 +140,7 @@ class Jp2kBox(object):
|
|
|
134
140
|
for box in self.box:
|
|
135
141
|
boxstr = str(box)
|
|
136
142
|
# Indent the child boxes to make the association clear.
|
|
137
|
-
msg +=
|
|
143
|
+
msg += "\n" + textwrap.indent(boxstr, " " * 4)
|
|
138
144
|
return msg
|
|
139
145
|
|
|
140
146
|
def _write_superbox(self, fptr, box_id):
|
|
@@ -148,7 +154,7 @@ class Jp2kBox(object):
|
|
|
148
154
|
4-byte sequence that identifies the superbox.
|
|
149
155
|
"""
|
|
150
156
|
b = io.BytesIO()
|
|
151
|
-
b.write(struct.pack(
|
|
157
|
+
b.write(struct.pack(">I4s", 0, box_id))
|
|
152
158
|
for box in self.box:
|
|
153
159
|
box.write(b)
|
|
154
160
|
|
|
@@ -156,7 +162,7 @@ class Jp2kBox(object):
|
|
|
156
162
|
|
|
157
163
|
# come back and write the length.
|
|
158
164
|
b.seek(0)
|
|
159
|
-
buffer = struct.pack(
|
|
165
|
+
buffer = struct.pack(">I", box_length)
|
|
160
166
|
b.write(buffer)
|
|
161
167
|
|
|
162
168
|
fptr.write(b.getvalue())
|
|
@@ -186,12 +192,15 @@ class Jp2kBox(object):
|
|
|
186
192
|
# We don't recognize the box ID, so create an UnknownBox and be
|
|
187
193
|
# done with it.
|
|
188
194
|
msg = (
|
|
189
|
-
f
|
|
190
|
-
f
|
|
195
|
+
f"Unrecognized box ({box_id}) "
|
|
196
|
+
f"encountered at byte offset {fptr.tell() - 8}."
|
|
191
197
|
)
|
|
192
198
|
warnings.warn(msg, UserWarning)
|
|
193
199
|
box = UnknownBox(
|
|
194
|
-
box_id,
|
|
200
|
+
box_id,
|
|
201
|
+
offset=start,
|
|
202
|
+
length=num_bytes,
|
|
203
|
+
longname="Unknown"
|
|
195
204
|
)
|
|
196
205
|
|
|
197
206
|
return box
|
|
@@ -200,16 +209,16 @@ class Jp2kBox(object):
|
|
|
200
209
|
box = parser(fptr, start, num_bytes)
|
|
201
210
|
except Exception as err:
|
|
202
211
|
msg = (
|
|
203
|
-
f
|
|
204
|
-
f
|
|
212
|
+
f"Encountered an error while parsing a "
|
|
213
|
+
f"{_BOX_WITH_ID[box_id]} box at byte offset {start}. "
|
|
205
214
|
f'The original error message was "{str(err)}".'
|
|
206
215
|
)
|
|
207
216
|
warnings.warn(msg, UserWarning)
|
|
208
217
|
box = UnknownBox(
|
|
209
|
-
box_id.decode(
|
|
218
|
+
box_id.decode("utf-8"),
|
|
210
219
|
length=num_bytes,
|
|
211
220
|
offset=start,
|
|
212
|
-
longname=
|
|
221
|
+
longname="Unknown",
|
|
213
222
|
)
|
|
214
223
|
|
|
215
224
|
return box
|
|
@@ -245,7 +254,7 @@ class Jp2kBox(object):
|
|
|
245
254
|
warnings.warn(msg, UserWarning)
|
|
246
255
|
return superbox
|
|
247
256
|
|
|
248
|
-
(box_length, box_id) = struct.unpack(
|
|
257
|
+
(box_length, box_id) = struct.unpack(">I4s", read_buffer)
|
|
249
258
|
if box_length == 0:
|
|
250
259
|
# The length of the box is presumed to last until the end of
|
|
251
260
|
# the file. Compute the effective length of the box.
|
|
@@ -254,7 +263,7 @@ class Jp2kBox(object):
|
|
|
254
263
|
elif box_length == 1:
|
|
255
264
|
# The length of the box is in the XL field, a 64-bit value.
|
|
256
265
|
read_buffer = fptr.read(8)
|
|
257
|
-
num_bytes, = struct.unpack(
|
|
266
|
+
(num_bytes,) = struct.unpack(">Q", read_buffer)
|
|
258
267
|
|
|
259
268
|
else:
|
|
260
269
|
# The box_length value really is the length of the box!
|
|
@@ -275,9 +284,9 @@ class Jp2kBox(object):
|
|
|
275
284
|
# The box must be invalid somehow, as the file pointer is
|
|
276
285
|
# positioned past the end of the box.
|
|
277
286
|
msg = (
|
|
278
|
-
f
|
|
279
|
-
f
|
|
280
|
-
|
|
287
|
+
f"{box_id} box may be invalid, the file pointer is "
|
|
288
|
+
f"positioned {fptr.tell() - (start + num_bytes)} bytes "
|
|
289
|
+
"past the end of the box."
|
|
281
290
|
)
|
|
282
291
|
warnings.warn(msg, UserWarning)
|
|
283
292
|
fptr.seek(start + num_bytes)
|
|
@@ -317,13 +326,19 @@ class ColourSpecificationBox(Jp2kBox):
|
|
|
317
326
|
ICC profile header according to ICC profile specification. If
|
|
318
327
|
colorspace is not None, then icc_profile must be empty.
|
|
319
328
|
"""
|
|
320
|
-
|
|
321
|
-
|
|
329
|
+
|
|
330
|
+
longname = "Colour Specification"
|
|
331
|
+
box_id = "colr"
|
|
322
332
|
|
|
323
333
|
def __init__(
|
|
324
|
-
self,
|
|
325
|
-
|
|
326
|
-
|
|
334
|
+
self,
|
|
335
|
+
method=ENUMERATED_COLORSPACE,
|
|
336
|
+
precedence=0,
|
|
337
|
+
approximation=0,
|
|
338
|
+
colorspace=None,
|
|
339
|
+
icc_profile=None,
|
|
340
|
+
length=0,
|
|
341
|
+
offset=-1,
|
|
327
342
|
):
|
|
328
343
|
super().__init__()
|
|
329
344
|
|
|
@@ -345,8 +360,10 @@ class ColourSpecificationBox(Jp2kBox):
|
|
|
345
360
|
def _validate(self, writing=False):
|
|
346
361
|
"""Verify that the box obeys the specifications."""
|
|
347
362
|
if self.colorspace is not None and self.icc_profile is not None:
|
|
348
|
-
msg = (
|
|
349
|
-
|
|
363
|
+
msg = (
|
|
364
|
+
"Colorspace and icc_profile cannot both be set when "
|
|
365
|
+
"creating a ColourSpecificationBox."
|
|
366
|
+
)
|
|
350
367
|
self._dispatch_validation_error(msg, writing=writing)
|
|
351
368
|
|
|
352
369
|
if self.method not in _COLORSPACE_METHODS:
|
|
@@ -411,7 +428,7 @@ class ColourSpecificationBox(Jp2kBox):
|
|
|
411
428
|
|
|
412
429
|
def __str__(self):
|
|
413
430
|
title = Jp2kBox.__str__(self)
|
|
414
|
-
if get_option(
|
|
431
|
+
if get_option("print.short") is True:
|
|
415
432
|
return title
|
|
416
433
|
|
|
417
434
|
lst = []
|
|
@@ -419,66 +436,65 @@ class ColourSpecificationBox(Jp2kBox):
|
|
|
419
436
|
try:
|
|
420
437
|
item = _COLORSPACE_METHODS[self.method]
|
|
421
438
|
except KeyError:
|
|
422
|
-
item = f
|
|
423
|
-
text = f
|
|
439
|
+
item = f"unrecognized value ({self.method})"
|
|
440
|
+
text = f"Method: {item}"
|
|
424
441
|
|
|
425
442
|
lst.append(text)
|
|
426
|
-
text = f
|
|
443
|
+
text = f"Precedence: {self.precedence}"
|
|
427
444
|
lst.append(text)
|
|
428
445
|
|
|
429
446
|
if self.approximation != 0:
|
|
430
447
|
try:
|
|
431
448
|
dispvalue = _APPROXIMATION_MEASURES[self.approximation]
|
|
432
449
|
except KeyError:
|
|
433
|
-
dispvalue = f
|
|
434
|
-
text = f
|
|
450
|
+
dispvalue = f"invalid ({self.approximation})"
|
|
451
|
+
text = f"Approximation: {dispvalue}"
|
|
435
452
|
lst.append(text)
|
|
436
453
|
|
|
437
454
|
if self.colorspace is not None:
|
|
438
455
|
try:
|
|
439
456
|
dispvalue = _COLORSPACE_MAP_DISPLAY[self.colorspace]
|
|
440
457
|
except KeyError:
|
|
441
|
-
dispvalue = f
|
|
442
|
-
text = f
|
|
458
|
+
dispvalue = f"{self.colorspace} (unrecognized)"
|
|
459
|
+
text = f"Colorspace: {dispvalue}"
|
|
443
460
|
else:
|
|
444
461
|
if self.icc_profile is None:
|
|
445
|
-
text =
|
|
462
|
+
text = "ICC Profile: None"
|
|
446
463
|
else:
|
|
447
464
|
text = pprint.pformat(self.icc_profile_header)
|
|
448
|
-
text = textwrap.indent(text,
|
|
449
|
-
text =
|
|
465
|
+
text = textwrap.indent(text, " " * 4)
|
|
466
|
+
text = "\n".join(["ICC Profile:", text])
|
|
450
467
|
|
|
451
468
|
lst.append(text)
|
|
452
469
|
|
|
453
|
-
text =
|
|
470
|
+
text = "\n".join(lst)
|
|
454
471
|
|
|
455
|
-
text =
|
|
472
|
+
text = "\n".join([title, textwrap.indent(text, " " * 4)])
|
|
456
473
|
|
|
457
474
|
return text
|
|
458
475
|
|
|
459
476
|
def write(self, fptr):
|
|
460
|
-
"""Write an Colour Specification box to file.
|
|
461
|
-
"""
|
|
477
|
+
"""Write an Colour Specification box to file."""
|
|
462
478
|
|
|
463
479
|
self._write_validate()
|
|
464
480
|
length = 15 if self.icc_profile is None else 11 + len(self.icc_profile)
|
|
465
|
-
fptr.write(struct.pack(
|
|
481
|
+
fptr.write(struct.pack(">I4s", length, b"colr"))
|
|
466
482
|
|
|
467
483
|
if self.icc_profile is None:
|
|
468
484
|
|
|
469
485
|
buffer = struct.pack(
|
|
470
|
-
|
|
486
|
+
">BBBI",
|
|
471
487
|
self.method,
|
|
472
488
|
self.precedence,
|
|
473
489
|
self.approximation,
|
|
474
|
-
self.colorspace
|
|
490
|
+
self.colorspace,
|
|
475
491
|
)
|
|
476
492
|
fptr.write(buffer)
|
|
477
493
|
|
|
478
494
|
else:
|
|
479
495
|
|
|
480
496
|
buffer = struct.pack(
|
|
481
|
-
|
|
497
|
+
">BBB",
|
|
482
498
|
self.method,
|
|
483
499
|
self.precedence,
|
|
484
500
|
self.approximation,
|
|
@@ -507,12 +523,12 @@ class ColourSpecificationBox(Jp2kBox):
|
|
|
507
523
|
num_bytes = offset + length - fptr.tell()
|
|
508
524
|
read_buffer = fptr.read(num_bytes)
|
|
509
525
|
|
|
510
|
-
lst = struct.unpack_from(
|
|
526
|
+
lst = struct.unpack_from(">BBB", read_buffer, offset=0)
|
|
511
527
|
method, precedence, approximation = lst
|
|
512
528
|
|
|
513
529
|
if method == 1:
|
|
514
530
|
# enumerated colour space
|
|
515
|
-
colorspace, = struct.unpack_from(
|
|
531
|
+
(colorspace,) = struct.unpack_from(">I", read_buffer, offset=3)
|
|
516
532
|
icc_profile = None
|
|
517
533
|
|
|
518
534
|
else:
|
|
@@ -536,7 +552,7 @@ class ColourSpecificationBox(Jp2kBox):
|
|
|
536
552
|
colorspace=colorspace,
|
|
537
553
|
icc_profile=icc_profile,
|
|
538
554
|
length=length,
|
|
539
|
-
offset=offset
|
|
555
|
+
offset=offset,
|
|
540
556
|
)
|
|
541
557
|
|
|
542
558
|
|
|
@@ -561,8 +577,9 @@ class ChannelDefinitionBox(Jp2kBox):
|
|
|
561
577
|
association : list
|
|
562
578
|
index of the associated color
|
|
563
579
|
"""
|
|
564
|
-
|
|
565
|
-
|
|
580
|
+
|
|
581
|
+
box_id = "cdef"
|
|
582
|
+
longname = "Channel Definition"
|
|
566
583
|
|
|
567
584
|
def __init__(self, channel_type, association, index=None, **kwargs):
|
|
568
585
|
super().__init__()
|
|
@@ -580,8 +597,10 @@ class ChannelDefinitionBox(Jp2kBox):
|
|
|
580
597
|
def _validate(self, writing=False):
|
|
581
598
|
"""Verify that the box obeys the specifications."""
|
|
582
599
|
# channel type and association must be specified.
|
|
583
|
-
if not (
|
|
584
|
-
|
|
600
|
+
if not (
|
|
601
|
+
(len(self.index) == len(self.channel_type))
|
|
602
|
+
and (len(self.channel_type) == len(self.association))
|
|
603
|
+
):
|
|
585
604
|
msg = (
|
|
586
605
|
f"The length of the index ({len(self.index)}), "
|
|
587
606
|
f"channel_type ({len(self.channel_type)}), "
|
|
@@ -604,7 +623,7 @@ class ChannelDefinitionBox(Jp2kBox):
|
|
|
604
623
|
|
|
605
624
|
def __str__(self):
|
|
606
625
|
title = Jp2kBox.__str__(self)
|
|
607
|
-
if get_option(
|
|
626
|
+
if get_option("print.short") is True:
|
|
608
627
|
return title
|
|
609
628
|
|
|
610
629
|
lst = []
|
|
@@ -616,13 +635,13 @@ class ChannelDefinitionBox(Jp2kBox):
|
|
|
616
635
|
except KeyError:
|
|
617
636
|
color_type_string = f"invalid ({channel_type})"
|
|
618
637
|
|
|
619
|
-
association = str(association) if association else
|
|
620
|
-
text = f
|
|
638
|
+
association = str(association) if association else "whole image"
|
|
639
|
+
text = f"Channel {index} ({color_type_string}) ==> ({association})"
|
|
621
640
|
lst.append(text)
|
|
622
641
|
|
|
623
|
-
text =
|
|
624
|
-
text = textwrap.indent(text,
|
|
625
|
-
text =
|
|
642
|
+
text = "\n".join(lst)
|
|
643
|
+
text = textwrap.indent(text, " " * 4)
|
|
644
|
+
text = "\n".join([title, text])
|
|
626
645
|
return text
|
|
627
646
|
|
|
628
647
|
def __repr__(self):
|
|
@@ -637,14 +656,16 @@ class ChannelDefinitionBox(Jp2kBox):
|
|
|
637
656
|
"""Write a channel definition box to file."""
|
|
638
657
|
self._validate(writing=True)
|
|
639
658
|
num_components = len(self.association)
|
|
640
|
-
fptr.write(struct.pack(
|
|
641
|
-
fptr.write(struct.pack(
|
|
659
|
+
fptr.write(struct.pack(">I4s", 8 + 2 + num_components * 6, b"cdef"))
|
|
660
|
+
fptr.write(struct.pack(">H", num_components))
|
|
642
661
|
for j in range(num_components):
|
|
643
|
-
fptr.write(
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
662
|
+
fptr.write(
|
|
663
|
+
struct.pack(
|
|
664
|
+
">" + "H" * 3,
|
|
665
|
+
self.index[j],
|
|
666
|
+
self.channel_type[j],
|
|
667
|
+
self.association[j],
|
|
668
|
+
)
|
|
648
669
|
)
|
|
649
670
|
|
|
650
671
|
@classmethod
|
|
@@ -669,11 +690,11 @@ class ChannelDefinitionBox(Jp2kBox):
|
|
|
669
690
|
read_buffer = fptr.read(num_bytes)
|
|
670
691
|
|
|
671
692
|
# Read the number of components.
|
|
672
|
-
num_components, = struct.unpack_from(
|
|
693
|
+
(num_components,) = struct.unpack_from(">H", read_buffer)
|
|
694
|
+
|
|
695
|
+
fmt = ">" + "HHH" * num_components
|
|
696
|
+
data = struct.unpack_from(fmt, read_buffer, offset=2)
|
|
673
697
|
|
|
674
|
-
data = struct.unpack_from(
|
|
675
|
-
'>' + 'HHH' * num_components, read_buffer, offset=2
|
|
676
|
-
)
|
|
677
698
|
index = data[0:num_components * 6:3]
|
|
678
699
|
channel_type = data[1:num_components * 6:3]
|
|
679
700
|
association = data[2:num_components * 6:3]
|
|
@@ -682,7 +703,8 @@ class ChannelDefinitionBox(Jp2kBox):
|
|
|
682
703
|
index=tuple(index),
|
|
683
704
|
channel_type=tuple(channel_type),
|
|
684
705
|
association=tuple(association),
|
|
685
|
-
length=length,
|
|
706
|
+
length=length,
|
|
707
|
+
offset=offset,
|
|
686
708
|
)
|
|
687
709
|
|
|
688
710
|
|
|
@@ -702,8 +724,9 @@ class CodestreamHeaderBox(Jp2kBox):
|
|
|
702
724
|
box : list
|
|
703
725
|
List of boxes contained in this superbox.
|
|
704
726
|
"""
|
|
705
|
-
|
|
706
|
-
|
|
727
|
+
|
|
728
|
+
box_id = "jpch"
|
|
729
|
+
longname = "Codestream Header"
|
|
707
730
|
|
|
708
731
|
def __init__(self, box=None, length=0, offset=-1):
|
|
709
732
|
super().__init__()
|
|
@@ -721,7 +744,7 @@ class CodestreamHeaderBox(Jp2kBox):
|
|
|
721
744
|
|
|
722
745
|
def write(self, fptr):
|
|
723
746
|
"""Write a codestream header box to file."""
|
|
724
|
-
self._write_superbox(fptr, b
|
|
747
|
+
self._write_superbox(fptr, b"jpch")
|
|
725
748
|
|
|
726
749
|
@classmethod
|
|
727
750
|
def parse(cls, fptr, offset, length):
|
|
@@ -766,8 +789,9 @@ class ColourGroupBox(Jp2kBox):
|
|
|
766
789
|
box : list
|
|
767
790
|
List of boxes contained in this superbox.
|
|
768
791
|
"""
|
|
769
|
-
|
|
770
|
-
|
|
792
|
+
|
|
793
|
+
box_id = "cgrp"
|
|
794
|
+
longname = "Colour Group"
|
|
771
795
|
|
|
772
796
|
def __init__(self, box=None, length=0, offset=-1):
|
|
773
797
|
super().__init__()
|
|
@@ -785,15 +809,17 @@ class ColourGroupBox(Jp2kBox):
|
|
|
785
809
|
|
|
786
810
|
def _validate(self, writing=True):
|
|
787
811
|
"""Verify that the box obeys the specifications."""
|
|
788
|
-
if any([box.box_id !=
|
|
789
|
-
msg = (
|
|
790
|
-
|
|
812
|
+
if any([box.box_id != "colr" for box in self.box]):
|
|
813
|
+
msg = (
|
|
814
|
+
"Colour group boxes can only contain colour specification "
|
|
815
|
+
"boxes."
|
|
816
|
+
)
|
|
791
817
|
self._dispatch_validation_error(msg, writing=writing)
|
|
792
818
|
|
|
793
819
|
def write(self, fptr):
|
|
794
820
|
"""Write a colour group box to file."""
|
|
795
821
|
self._validate(writing=True)
|
|
796
|
-
self._write_superbox(fptr, b
|
|
822
|
+
self._write_superbox(fptr, b"cgrp")
|
|
797
823
|
|
|
798
824
|
@classmethod
|
|
799
825
|
def parse(cls, fptr, offset, length):
|
|
@@ -838,8 +864,9 @@ class CompositingLayerHeaderBox(Jp2kBox):
|
|
|
838
864
|
box : list
|
|
839
865
|
List of boxes contained in this superbox.
|
|
840
866
|
"""
|
|
841
|
-
|
|
842
|
-
|
|
867
|
+
|
|
868
|
+
box_id = "jplh"
|
|
869
|
+
longname = "Compositing Layer Header"
|
|
843
870
|
|
|
844
871
|
def __init__(self, box=None, length=0, offset=-1):
|
|
845
872
|
super().__init__()
|
|
@@ -857,7 +884,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
|
|
|
857
884
|
|
|
858
885
|
def write(self, fptr):
|
|
859
886
|
"""Write a compositing layer header box to file."""
|
|
860
|
-
self._write_superbox(fptr, b
|
|
887
|
+
self._write_superbox(fptr, b"jplh")
|
|
861
888
|
|
|
862
889
|
@classmethod
|
|
863
890
|
def parse(cls, fptr, offset, length):
|
|
@@ -905,11 +932,13 @@ class ComponentMappingBox(Jp2kBox):
|
|
|
905
932
|
palette_index : tuple
|
|
906
933
|
Index component from palette
|
|
907
934
|
"""
|
|
908
|
-
box_id = 'cmap'
|
|
909
|
-
longname = 'Component Mapping'
|
|
910
935
|
|
|
911
|
-
|
|
912
|
-
|
|
936
|
+
box_id = "cmap"
|
|
937
|
+
longname = "Component Mapping"
|
|
938
|
+
|
|
939
|
+
def __init__(
|
|
940
|
+
self, component_index, mapping_type, palette_index, length=0, offset=-1
|
|
941
|
+
):
|
|
913
942
|
super().__init__()
|
|
914
943
|
self.component_index = component_index
|
|
915
944
|
self.mapping_type = mapping_type
|
|
@@ -928,7 +957,7 @@ class ComponentMappingBox(Jp2kBox):
|
|
|
928
957
|
|
|
929
958
|
def __str__(self):
|
|
930
959
|
title = Jp2kBox.__str__(self)
|
|
931
|
-
if get_option(
|
|
960
|
+
if get_option("print.short") is True:
|
|
932
961
|
return title
|
|
933
962
|
|
|
934
963
|
lst = []
|
|
@@ -938,32 +967,32 @@ class ComponentMappingBox(Jp2kBox):
|
|
|
938
967
|
if mapping_type == 1:
|
|
939
968
|
# palette mapping
|
|
940
969
|
text = (
|
|
941
|
-
f
|
|
942
|
-
f
|
|
970
|
+
f"Component {component_idx} ==> "
|
|
971
|
+
f"palette column {palette_idx}"
|
|
943
972
|
)
|
|
944
973
|
else:
|
|
945
974
|
# Direct use
|
|
946
|
-
text = f
|
|
975
|
+
text = f"Component {component_idx} ==> {k}"
|
|
947
976
|
lst.append(text)
|
|
948
977
|
|
|
949
|
-
text =
|
|
950
|
-
text = textwrap.indent(text,
|
|
951
|
-
text =
|
|
978
|
+
text = "\n".join(lst)
|
|
979
|
+
text = textwrap.indent(text, " " * 4)
|
|
980
|
+
text = "\n".join([title, text])
|
|
952
981
|
|
|
953
982
|
return text
|
|
954
983
|
|
|
955
984
|
def write(self, fptr):
|
|
956
985
|
"""Write a Component Mapping box to file."""
|
|
957
986
|
length = 8 + 4 * len(self.component_index)
|
|
958
|
-
write_buffer = struct.pack(
|
|
987
|
+
write_buffer = struct.pack(">I4s", length, b"cmap")
|
|
959
988
|
fptr.write(write_buffer)
|
|
960
989
|
|
|
961
990
|
for j in range(len(self.component_index)):
|
|
962
991
|
write_buffer = struct.pack(
|
|
963
|
-
|
|
992
|
+
">HBB",
|
|
964
993
|
self.component_index[j],
|
|
965
994
|
self.mapping_type[j],
|
|
966
|
-
self.palette_index[j]
|
|
995
|
+
self.palette_index[j],
|
|
967
996
|
)
|
|
968
997
|
fptr.write(write_buffer)
|
|
969
998
|
|
|
@@ -989,14 +1018,19 @@ class ComponentMappingBox(Jp2kBox):
|
|
|
989
1018
|
num_components = int(num_bytes / 4)
|
|
990
1019
|
|
|
991
1020
|
read_buffer = fptr.read(num_bytes)
|
|
992
|
-
data = struct.unpack(
|
|
1021
|
+
data = struct.unpack(">" + "HBB" * num_components, read_buffer)
|
|
993
1022
|
|
|
994
1023
|
component_index = data[0:num_bytes:3]
|
|
995
1024
|
mapping_type = data[1:num_bytes:3]
|
|
996
1025
|
palette_index = data[2:num_bytes:3]
|
|
997
1026
|
|
|
998
|
-
return cls(
|
|
999
|
-
|
|
1027
|
+
return cls(
|
|
1028
|
+
component_index,
|
|
1029
|
+
mapping_type,
|
|
1030
|
+
palette_index,
|
|
1031
|
+
length=length,
|
|
1032
|
+
offset=offset
|
|
1033
|
+
)
|
|
1000
1034
|
|
|
1001
1035
|
|
|
1002
1036
|
class ContiguousCodestreamBox(Jp2kBox):
|
|
@@ -1018,11 +1052,17 @@ class ContiguousCodestreamBox(Jp2kBox):
|
|
|
1018
1052
|
main_header_offset : int
|
|
1019
1053
|
offset of main header from start of file
|
|
1020
1054
|
"""
|
|
1021
|
-
box_id = 'jp2c'
|
|
1022
|
-
longname = 'Contiguous Codestream'
|
|
1023
1055
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1056
|
+
box_id = "jp2c"
|
|
1057
|
+
longname = "Contiguous Codestream"
|
|
1058
|
+
|
|
1059
|
+
def __init__(
|
|
1060
|
+
self,
|
|
1061
|
+
codestream=None,
|
|
1062
|
+
main_header_offset=None,
|
|
1063
|
+
length=0,
|
|
1064
|
+
offset=-1
|
|
1065
|
+
):
|
|
1026
1066
|
super().__init__()
|
|
1027
1067
|
self._codestream = codestream
|
|
1028
1068
|
self.length = length
|
|
@@ -1034,18 +1074,20 @@ class ContiguousCodestreamBox(Jp2kBox):
|
|
|
1034
1074
|
|
|
1035
1075
|
@property
|
|
1036
1076
|
def codestream(self):
|
|
1037
|
-
if get_option(
|
|
1077
|
+
if get_option("parse.full_codestream") is True:
|
|
1038
1078
|
header_only = False
|
|
1039
1079
|
else:
|
|
1040
1080
|
header_only = True
|
|
1041
1081
|
if self._codestream is None:
|
|
1042
1082
|
if self._filename is not None:
|
|
1043
|
-
with open(self._filename,
|
|
1083
|
+
with open(self._filename, "rb") as fptr:
|
|
1044
1084
|
fptr.seek(self.main_header_offset)
|
|
1045
|
-
|
|
1046
|
-
fptr,
|
|
1085
|
+
self._codestream = Codestream(
|
|
1086
|
+
fptr,
|
|
1087
|
+
self.length,
|
|
1088
|
+
header_only=header_only
|
|
1047
1089
|
)
|
|
1048
|
-
|
|
1090
|
+
|
|
1049
1091
|
return self._codestream
|
|
1050
1092
|
|
|
1051
1093
|
def __repr__(self):
|
|
@@ -1055,18 +1097,18 @@ class ContiguousCodestreamBox(Jp2kBox):
|
|
|
1055
1097
|
|
|
1056
1098
|
def __str__(self):
|
|
1057
1099
|
title = Jp2kBox.__str__(self)
|
|
1058
|
-
if get_option(
|
|
1100
|
+
if get_option("print.short") is True:
|
|
1059
1101
|
return title
|
|
1060
|
-
if get_option(
|
|
1102
|
+
if get_option("print.codestream") is False:
|
|
1061
1103
|
return title
|
|
1062
1104
|
|
|
1063
1105
|
lst = []
|
|
1064
1106
|
for segment in self.codestream.segment:
|
|
1065
1107
|
lst.append(str(segment))
|
|
1066
1108
|
|
|
1067
|
-
text =
|
|
1068
|
-
text = textwrap.indent(text,
|
|
1069
|
-
text =
|
|
1109
|
+
text = "\n".join(lst)
|
|
1110
|
+
text = textwrap.indent(text, " " * 4)
|
|
1111
|
+
text = "\n".join([title, text])
|
|
1070
1112
|
return text
|
|
1071
1113
|
|
|
1072
1114
|
@classmethod
|
|
@@ -1088,7 +1130,7 @@ class ContiguousCodestreamBox(Jp2kBox):
|
|
|
1088
1130
|
Instance of the current contiguous codestream box.
|
|
1089
1131
|
"""
|
|
1090
1132
|
main_header_offset = fptr.tell()
|
|
1091
|
-
if get_option(
|
|
1133
|
+
if get_option("parse.full_codestream"):
|
|
1092
1134
|
codestream = Codestream(fptr, length, header_only=False)
|
|
1093
1135
|
else:
|
|
1094
1136
|
codestream = None
|
|
@@ -1096,7 +1138,7 @@ class ContiguousCodestreamBox(Jp2kBox):
|
|
|
1096
1138
|
codestream,
|
|
1097
1139
|
main_header_offset=main_header_offset,
|
|
1098
1140
|
length=length,
|
|
1099
|
-
offset=offset
|
|
1141
|
+
offset=offset,
|
|
1100
1142
|
)
|
|
1101
1143
|
box._filename = fptr.name
|
|
1102
1144
|
return box
|
|
@@ -1118,8 +1160,9 @@ class DataReferenceBox(Jp2kBox):
|
|
|
1118
1160
|
DR : list
|
|
1119
1161
|
Data Entry URL boxes.
|
|
1120
1162
|
"""
|
|
1121
|
-
|
|
1122
|
-
|
|
1163
|
+
|
|
1164
|
+
box_id = "dtbl"
|
|
1165
|
+
longname = "Data Reference"
|
|
1123
1166
|
|
|
1124
1167
|
def __init__(self, data_entry_url_boxes=None, length=0, offset=-1):
|
|
1125
1168
|
super().__init__()
|
|
@@ -1134,9 +1177,11 @@ class DataReferenceBox(Jp2kBox):
|
|
|
1134
1177
|
def _validate(self, writing=False):
|
|
1135
1178
|
"""Verify that the box obeys the specifications."""
|
|
1136
1179
|
for box in self.DR:
|
|
1137
|
-
if box.box_id !=
|
|
1138
|
-
msg = (
|
|
1139
|
-
|
|
1180
|
+
if box.box_id != "url ":
|
|
1181
|
+
msg = (
|
|
1182
|
+
"Child boxes of a data reference box can only be data "
|
|
1183
|
+
"entry URL boxes."
|
|
1184
|
+
)
|
|
1140
1185
|
self._dispatch_validation_error(msg, writing=writing)
|
|
1141
1186
|
|
|
1142
1187
|
def _write_validate(self):
|
|
@@ -1153,10 +1198,10 @@ class DataReferenceBox(Jp2kBox):
|
|
|
1153
1198
|
|
|
1154
1199
|
# Very similar to the way a superbox is written.
|
|
1155
1200
|
orig_pos = fptr.tell()
|
|
1156
|
-
fptr.write(struct.pack(
|
|
1201
|
+
fptr.write(struct.pack(">I4s", 0, b"dtbl"))
|
|
1157
1202
|
|
|
1158
1203
|
# Write the number of data entry url boxes.
|
|
1159
|
-
write_buffer = struct.pack(
|
|
1204
|
+
write_buffer = struct.pack(">H", len(self.DR))
|
|
1160
1205
|
fptr.write(write_buffer)
|
|
1161
1206
|
|
|
1162
1207
|
for box in self.DR:
|
|
@@ -1164,12 +1209,12 @@ class DataReferenceBox(Jp2kBox):
|
|
|
1164
1209
|
|
|
1165
1210
|
end_pos = fptr.tell()
|
|
1166
1211
|
fptr.seek(orig_pos)
|
|
1167
|
-
fptr.write(struct.pack(
|
|
1212
|
+
fptr.write(struct.pack(">I", end_pos - orig_pos))
|
|
1168
1213
|
fptr.seek(end_pos)
|
|
1169
1214
|
|
|
1170
1215
|
def __str__(self):
|
|
1171
1216
|
title = Jp2kBox.__str__(self)
|
|
1172
|
-
if get_option(
|
|
1217
|
+
if get_option("print.short") is True:
|
|
1173
1218
|
return title
|
|
1174
1219
|
|
|
1175
1220
|
if len(self.DR) == 0:
|
|
@@ -1178,14 +1223,14 @@ class DataReferenceBox(Jp2kBox):
|
|
|
1178
1223
|
lst = []
|
|
1179
1224
|
for box in self.DR:
|
|
1180
1225
|
lst.append(str(box))
|
|
1181
|
-
text =
|
|
1182
|
-
text = textwrap.indent(text,
|
|
1226
|
+
text = "\n".join(lst)
|
|
1227
|
+
text = textwrap.indent(text, " " * 4)
|
|
1183
1228
|
|
|
1184
|
-
text =
|
|
1229
|
+
text = "\n".join([title, text])
|
|
1185
1230
|
return text
|
|
1186
1231
|
|
|
1187
1232
|
def __repr__(self):
|
|
1188
|
-
msg =
|
|
1233
|
+
msg = "glymur.jp2box.DataReferenceBox()"
|
|
1189
1234
|
return msg
|
|
1190
1235
|
|
|
1191
1236
|
@classmethod
|
|
@@ -1210,7 +1255,7 @@ class DataReferenceBox(Jp2kBox):
|
|
|
1210
1255
|
read_buffer = fptr.read(num_bytes)
|
|
1211
1256
|
|
|
1212
1257
|
# Read the number of data references
|
|
1213
|
-
ndr, = struct.unpack_from(
|
|
1258
|
+
(ndr,) = struct.unpack_from(">H", read_buffer, offset=0)
|
|
1214
1259
|
|
|
1215
1260
|
# Need to keep track of where the next url box starts.
|
|
1216
1261
|
box_offset = 2
|
|
@@ -1220,10 +1265,8 @@ class DataReferenceBox(Jp2kBox):
|
|
|
1220
1265
|
|
|
1221
1266
|
# Create an in-memory binary stream for each URL box.
|
|
1222
1267
|
box_fptr = io.BytesIO(read_buffer[box_offset:])
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
'>I4s', box_buffer, offset=0
|
|
1226
|
-
)
|
|
1268
|
+
buffer = box_fptr.read(8)
|
|
1269
|
+
box_length, box_id = struct.unpack_from(">I4s", buffer, offset=0)
|
|
1227
1270
|
box = DataEntryURLBox.parse(box_fptr, 0, box_length)
|
|
1228
1271
|
|
|
1229
1272
|
# Need to adjust the box start to that of the "real" file.
|
|
@@ -1257,19 +1300,24 @@ class FileTypeBox(Jp2kBox):
|
|
|
1257
1300
|
compatibility_list: list
|
|
1258
1301
|
List of file conformance profiles.
|
|
1259
1302
|
"""
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1303
|
+
|
|
1304
|
+
box_id = "ftyp"
|
|
1305
|
+
longname = "File Type"
|
|
1306
|
+
_valid_cls = ["jp2 ", "jph ", "jpx ", "jpxb"]
|
|
1263
1307
|
|
|
1264
1308
|
def __init__(
|
|
1265
|
-
self,
|
|
1266
|
-
|
|
1309
|
+
self,
|
|
1310
|
+
brand="jp2 ",
|
|
1311
|
+
minor_version=0,
|
|
1312
|
+
compatibility_list=None,
|
|
1313
|
+
length=0,
|
|
1314
|
+
offset=-1,
|
|
1267
1315
|
):
|
|
1268
1316
|
super().__init__()
|
|
1269
1317
|
self.brand = brand
|
|
1270
1318
|
self.minor_version = minor_version
|
|
1271
1319
|
if compatibility_list is None:
|
|
1272
|
-
self.compatibility_list = [
|
|
1320
|
+
self.compatibility_list = ["jp2 "]
|
|
1273
1321
|
else:
|
|
1274
1322
|
self.compatibility_list = compatibility_list
|
|
1275
1323
|
self.length = length
|
|
@@ -1287,19 +1335,19 @@ class FileTypeBox(Jp2kBox):
|
|
|
1287
1335
|
|
|
1288
1336
|
def __str__(self):
|
|
1289
1337
|
title = Jp2kBox.__str__(self)
|
|
1290
|
-
if get_option(
|
|
1338
|
+
if get_option("print.short") is True:
|
|
1291
1339
|
return title
|
|
1292
1340
|
|
|
1293
1341
|
lst = []
|
|
1294
|
-
text = f
|
|
1342
|
+
text = f"Brand: {self.brand}"
|
|
1295
1343
|
lst.append(text)
|
|
1296
|
-
text = f
|
|
1344
|
+
text = f"Compatibility: {self.compatibility_list}"
|
|
1297
1345
|
lst.append(text)
|
|
1298
1346
|
|
|
1299
|
-
text =
|
|
1300
|
-
text = textwrap.indent(text,
|
|
1347
|
+
text = "\n".join(lst)
|
|
1348
|
+
text = textwrap.indent(text, " " * 4)
|
|
1301
1349
|
|
|
1302
|
-
text =
|
|
1350
|
+
text = "\n".join([title, text])
|
|
1303
1351
|
|
|
1304
1352
|
return text
|
|
1305
1353
|
|
|
@@ -1307,7 +1355,7 @@ class FileTypeBox(Jp2kBox):
|
|
|
1307
1355
|
"""
|
|
1308
1356
|
Validate the box before writing to file.
|
|
1309
1357
|
"""
|
|
1310
|
-
if self.brand not in [
|
|
1358
|
+
if self.brand not in ["jp2 ", "jpx ", "jph "]:
|
|
1311
1359
|
msg = (
|
|
1312
1360
|
f"The file type brand was '{self.brand}'. "
|
|
1313
1361
|
f"It should be either 'jp2 ', 'jpx ', or 'jph '."
|
|
@@ -1333,9 +1381,9 @@ class FileTypeBox(Jp2kBox):
|
|
|
1333
1381
|
"""Write a File Type box to file."""
|
|
1334
1382
|
self._validate(writing=True)
|
|
1335
1383
|
length = 16 + 4 * len(self.compatibility_list)
|
|
1336
|
-
fptr.write(struct.pack(
|
|
1384
|
+
fptr.write(struct.pack(">I4s", length, b"ftyp"))
|
|
1337
1385
|
fptr.write(self.brand.encode())
|
|
1338
|
-
fptr.write(struct.pack(
|
|
1386
|
+
fptr.write(struct.pack(">I", self.minor_version))
|
|
1339
1387
|
|
|
1340
1388
|
for item in self.compatibility_list:
|
|
1341
1389
|
fptr.write(item.encode())
|
|
@@ -1361,16 +1409,16 @@ class FileTypeBox(Jp2kBox):
|
|
|
1361
1409
|
num_bytes = offset + length - fptr.tell()
|
|
1362
1410
|
read_buffer = fptr.read(num_bytes)
|
|
1363
1411
|
# Extract the brand, minor version.
|
|
1364
|
-
(brand, minor_version) = struct.unpack_from(
|
|
1365
|
-
brand = brand.decode(
|
|
1412
|
+
(brand, minor_version) = struct.unpack_from(">4sI", read_buffer, 0)
|
|
1413
|
+
brand = brand.decode("utf-8")
|
|
1366
1414
|
|
|
1367
1415
|
# Extract the compatibility list. Each entry has 4 bytes.
|
|
1368
1416
|
num_entries = int((length - 16) / 4)
|
|
1369
1417
|
compatibility_list = []
|
|
1370
1418
|
for j in range(int(num_entries)):
|
|
1371
|
-
entry, = struct.unpack_from(
|
|
1419
|
+
(entry,) = struct.unpack_from(">4s", read_buffer, 8 + j * 4)
|
|
1372
1420
|
try:
|
|
1373
|
-
entry = entry.decode(
|
|
1421
|
+
entry = entry.decode("utf-8")
|
|
1374
1422
|
except UnicodeDecodeError:
|
|
1375
1423
|
# The entry is invalid, but we've got code to catch this
|
|
1376
1424
|
# later on.
|
|
@@ -1383,7 +1431,7 @@ class FileTypeBox(Jp2kBox):
|
|
|
1383
1431
|
minor_version=minor_version,
|
|
1384
1432
|
compatibility_list=compatibility_list,
|
|
1385
1433
|
length=length,
|
|
1386
|
-
offset=offset
|
|
1434
|
+
offset=offset,
|
|
1387
1435
|
)
|
|
1388
1436
|
|
|
1389
1437
|
|
|
@@ -1401,11 +1449,16 @@ class FragmentListBox(Jp2kBox):
|
|
|
1401
1449
|
longname : str
|
|
1402
1450
|
more verbose description of the box.
|
|
1403
1451
|
"""
|
|
1404
|
-
|
|
1405
|
-
|
|
1452
|
+
|
|
1453
|
+
box_id = "flst"
|
|
1454
|
+
longname = "Fragment List"
|
|
1406
1455
|
|
|
1407
1456
|
def __init__(
|
|
1408
|
-
self,
|
|
1457
|
+
self,
|
|
1458
|
+
fragment_offset,
|
|
1459
|
+
fragment_length,
|
|
1460
|
+
data_reference,
|
|
1461
|
+
length=0,
|
|
1409
1462
|
offset=-1
|
|
1410
1463
|
):
|
|
1411
1464
|
super().__init__()
|
|
@@ -1418,8 +1471,9 @@ class FragmentListBox(Jp2kBox):
|
|
|
1418
1471
|
|
|
1419
1472
|
def _validate(self, writing=False):
|
|
1420
1473
|
"""Validate internal correctness."""
|
|
1421
|
-
if (
|
|
1422
|
-
|
|
1474
|
+
if (len(self.fragment_offset) != len(self.fragment_length)) or (
|
|
1475
|
+
len(self.fragment_length) != len(self.data_reference)
|
|
1476
|
+
):
|
|
1423
1477
|
msg = (
|
|
1424
1478
|
f"A FragmentListBox at byte offset {self.offset} has invalid "
|
|
1425
1479
|
f"parameters. The lengths of the fragment offsets, fragment "
|
|
@@ -1440,15 +1494,13 @@ class FragmentListBox(Jp2kBox):
|
|
|
1440
1494
|
def __repr__(self):
|
|
1441
1495
|
msg = "glymur.jp2box.FragmentListBox({0}, {1}, {2})"
|
|
1442
1496
|
msg = msg.format(
|
|
1443
|
-
self.fragment_offset,
|
|
1444
|
-
self.fragment_length,
|
|
1445
|
-
self.data_reference
|
|
1497
|
+
self.fragment_offset, self.fragment_length, self.data_reference
|
|
1446
1498
|
)
|
|
1447
1499
|
return msg
|
|
1448
1500
|
|
|
1449
1501
|
def __str__(self):
|
|
1450
1502
|
title = Jp2kBox.__str__(self)
|
|
1451
|
-
if get_option(
|
|
1503
|
+
if get_option("print.short") is True:
|
|
1452
1504
|
return title
|
|
1453
1505
|
|
|
1454
1506
|
lst = []
|
|
@@ -1460,9 +1512,9 @@ class FragmentListBox(Jp2kBox):
|
|
|
1460
1512
|
text = f"Data Reference {j}: {self.data_reference[j]}"
|
|
1461
1513
|
lst.append(text)
|
|
1462
1514
|
|
|
1463
|
-
text =
|
|
1464
|
-
text = textwrap.indent(text,
|
|
1465
|
-
text =
|
|
1515
|
+
text = "\n".join(lst)
|
|
1516
|
+
text = textwrap.indent(text, " " * 4)
|
|
1517
|
+
text = "\n".join([title, text])
|
|
1466
1518
|
return text
|
|
1467
1519
|
|
|
1468
1520
|
def write(self, fptr):
|
|
@@ -1470,14 +1522,14 @@ class FragmentListBox(Jp2kBox):
|
|
|
1470
1522
|
self._validate(writing=True)
|
|
1471
1523
|
num_items = len(self.fragment_offset)
|
|
1472
1524
|
length = 8 + 2 + num_items * 14
|
|
1473
|
-
fptr.write(struct.pack(
|
|
1474
|
-
fptr.write(struct.pack(
|
|
1525
|
+
fptr.write(struct.pack(">I4s", length, b"flst"))
|
|
1526
|
+
fptr.write(struct.pack(">H", num_items))
|
|
1475
1527
|
for j in range(num_items):
|
|
1476
1528
|
write_buffer = struct.pack(
|
|
1477
|
-
|
|
1529
|
+
">QIH",
|
|
1478
1530
|
self.fragment_offset[j],
|
|
1479
1531
|
self.fragment_length[j],
|
|
1480
|
-
self.data_reference[j]
|
|
1532
|
+
self.data_reference[j],
|
|
1481
1533
|
)
|
|
1482
1534
|
fptr.write(write_buffer)
|
|
1483
1535
|
|
|
@@ -1501,16 +1553,22 @@ class FragmentListBox(Jp2kBox):
|
|
|
1501
1553
|
"""
|
|
1502
1554
|
num_bytes = offset + length - fptr.tell()
|
|
1503
1555
|
read_buffer = fptr.read(num_bytes)
|
|
1504
|
-
num_fragments, = struct.unpack_from(
|
|
1556
|
+
(num_fragments,) = struct.unpack_from(">H", read_buffer, offset=0)
|
|
1557
|
+
|
|
1558
|
+
fmt = ">" + "QIH" * num_fragments
|
|
1559
|
+
lst = struct.unpack_from(fmt, read_buffer, offset=2)
|
|
1505
1560
|
|
|
1506
|
-
lst = struct.unpack_from('>' + 'QIH' * num_fragments,
|
|
1507
|
-
read_buffer,
|
|
1508
|
-
offset=2)
|
|
1509
1561
|
frag_offset = lst[0::3]
|
|
1510
1562
|
frag_len = lst[1::3]
|
|
1511
1563
|
data_reference = lst[2::3]
|
|
1512
|
-
|
|
1513
|
-
|
|
1564
|
+
|
|
1565
|
+
return cls(
|
|
1566
|
+
frag_offset,
|
|
1567
|
+
frag_len,
|
|
1568
|
+
data_reference,
|
|
1569
|
+
length=length,
|
|
1570
|
+
offset=offset
|
|
1571
|
+
)
|
|
1514
1572
|
|
|
1515
1573
|
|
|
1516
1574
|
class FragmentTableBox(Jp2kBox):
|
|
@@ -1529,8 +1587,9 @@ class FragmentTableBox(Jp2kBox):
|
|
|
1529
1587
|
box : list
|
|
1530
1588
|
List containing exactly one FragmentListBox
|
|
1531
1589
|
"""
|
|
1532
|
-
|
|
1533
|
-
|
|
1590
|
+
|
|
1591
|
+
box_id = "ftbl"
|
|
1592
|
+
longname = "Fragment Table"
|
|
1534
1593
|
|
|
1535
1594
|
def __init__(self, box=None, length=0, offset=-1):
|
|
1536
1595
|
super().__init__()
|
|
@@ -1575,15 +1634,17 @@ class FragmentTableBox(Jp2kBox):
|
|
|
1575
1634
|
def _validate(self, writing=False):
|
|
1576
1635
|
"""Self-validate the box before writing."""
|
|
1577
1636
|
box_ids = [box.box_id for box in self.box]
|
|
1578
|
-
if len(box_ids) != 1 or box_ids[0] !=
|
|
1579
|
-
msg = (
|
|
1580
|
-
|
|
1637
|
+
if len(box_ids) != 1 or box_ids[0] != "flst":
|
|
1638
|
+
msg = (
|
|
1639
|
+
"Fragment table boxes must have a single fragment list "
|
|
1640
|
+
"box as a child box."
|
|
1641
|
+
)
|
|
1581
1642
|
self._dispatch_validation_error(msg, writing=writing)
|
|
1582
1643
|
|
|
1583
1644
|
def write(self, fptr):
|
|
1584
1645
|
"""Write a fragment table box to file."""
|
|
1585
1646
|
self._validate(writing=True)
|
|
1586
|
-
self._write_superbox(fptr, b
|
|
1647
|
+
self._write_superbox(fptr, b"ftbl")
|
|
1587
1648
|
|
|
1588
1649
|
|
|
1589
1650
|
class FreeBox(Jp2kBox):
|
|
@@ -1600,8 +1661,9 @@ class FreeBox(Jp2kBox):
|
|
|
1600
1661
|
longname : str
|
|
1601
1662
|
more verbose description of the box.
|
|
1602
1663
|
"""
|
|
1603
|
-
|
|
1604
|
-
|
|
1664
|
+
|
|
1665
|
+
box_id = "free"
|
|
1666
|
+
longname = "Free"
|
|
1605
1667
|
|
|
1606
1668
|
def __init__(self, length=0, offset=-1):
|
|
1607
1669
|
super().__init__()
|
|
@@ -1668,13 +1730,22 @@ class ImageHeaderBox(Jp2kBox):
|
|
|
1668
1730
|
False if the file does not contain intellectual propery rights
|
|
1669
1731
|
information.
|
|
1670
1732
|
"""
|
|
1671
|
-
|
|
1672
|
-
|
|
1733
|
+
|
|
1734
|
+
box_id = "ihdr"
|
|
1735
|
+
longname = "Image Header"
|
|
1673
1736
|
|
|
1674
1737
|
def __init__(
|
|
1675
|
-
self,
|
|
1676
|
-
|
|
1677
|
-
|
|
1738
|
+
self,
|
|
1739
|
+
height,
|
|
1740
|
+
width,
|
|
1741
|
+
num_components=1,
|
|
1742
|
+
signed=False,
|
|
1743
|
+
bits_per_component=8,
|
|
1744
|
+
compression=7,
|
|
1745
|
+
colorspace_unknown=False,
|
|
1746
|
+
ip_provided=False,
|
|
1747
|
+
length=0,
|
|
1748
|
+
offset=-1,
|
|
1678
1749
|
):
|
|
1679
1750
|
"""Examples
|
|
1680
1751
|
--------
|
|
@@ -1707,18 +1778,18 @@ class ImageHeaderBox(Jp2kBox):
|
|
|
1707
1778
|
|
|
1708
1779
|
def __str__(self):
|
|
1709
1780
|
title = Jp2kBox.__str__(self)
|
|
1710
|
-
if get_option(
|
|
1781
|
+
if get_option("print.short") is True:
|
|
1711
1782
|
return title
|
|
1712
1783
|
|
|
1713
1784
|
lst = []
|
|
1714
1785
|
|
|
1715
|
-
text = f
|
|
1786
|
+
text = f"Size: [{self.height} {self.width} {self.num_components}]"
|
|
1716
1787
|
lst.append(text)
|
|
1717
1788
|
|
|
1718
|
-
text = f
|
|
1789
|
+
text = f"Bitdepth: {self.bits_per_component}"
|
|
1719
1790
|
lst.append(text)
|
|
1720
1791
|
|
|
1721
|
-
text = f
|
|
1792
|
+
text = f"Signed: {self.signed}"
|
|
1722
1793
|
lst.append(text)
|
|
1723
1794
|
|
|
1724
1795
|
text = (
|
|
@@ -1727,30 +1798,31 @@ class ImageHeaderBox(Jp2kBox):
|
|
|
1727
1798
|
)
|
|
1728
1799
|
lst.append(text)
|
|
1729
1800
|
|
|
1730
|
-
text = f
|
|
1801
|
+
text = f"Colorspace Unknown: {self.colorspace_unknown}"
|
|
1731
1802
|
lst.append(text)
|
|
1732
1803
|
|
|
1733
|
-
text =
|
|
1734
|
-
text = textwrap.indent(text,
|
|
1735
|
-
text =
|
|
1804
|
+
text = "\n".join(lst)
|
|
1805
|
+
text = textwrap.indent(text, " " * 4)
|
|
1806
|
+
text = "\n".join([title, text])
|
|
1736
1807
|
|
|
1737
1808
|
return text
|
|
1738
1809
|
|
|
1739
1810
|
def write(self, fptr):
|
|
1740
1811
|
"""Write an Image Header box to file."""
|
|
1741
|
-
fptr.write(struct.pack(
|
|
1812
|
+
fptr.write(struct.pack(">I4s", 22, b"ihdr"))
|
|
1742
1813
|
|
|
1743
1814
|
# signedness and bps are stored together in a single byte
|
|
1744
1815
|
bit_depth_signedness = 0x80 if self.signed else 0x00
|
|
1745
1816
|
bit_depth_signedness |= self.bits_per_component - 1
|
|
1746
1817
|
read_buffer = struct.pack(
|
|
1747
|
-
|
|
1748
|
-
self.height,
|
|
1818
|
+
">IIHBBBB",
|
|
1819
|
+
self.height,
|
|
1820
|
+
self.width,
|
|
1749
1821
|
self.num_components,
|
|
1750
1822
|
bit_depth_signedness,
|
|
1751
1823
|
self.compression,
|
|
1752
1824
|
1 if self.colorspace_unknown else 0,
|
|
1753
|
-
1 if self.ip_provided else 0
|
|
1825
|
+
1 if self.ip_provided else 0,
|
|
1754
1826
|
)
|
|
1755
1827
|
fptr.write(read_buffer)
|
|
1756
1828
|
|
|
@@ -1774,23 +1846,28 @@ class ImageHeaderBox(Jp2kBox):
|
|
|
1774
1846
|
"""
|
|
1775
1847
|
# Read the box information
|
|
1776
1848
|
read_buffer = fptr.read(14)
|
|
1777
|
-
params = struct.unpack(
|
|
1849
|
+
params = struct.unpack(">IIHBBBB", read_buffer)
|
|
1778
1850
|
height = params[0]
|
|
1779
1851
|
width = params[1]
|
|
1780
1852
|
num_components = params[2]
|
|
1781
|
-
bits_per_component = (params[3] &
|
|
1853
|
+
bits_per_component = (params[3] & 0x7F) + 1
|
|
1782
1854
|
signed = (params[3] & 0x80) > 1
|
|
1783
1855
|
compression = params[4]
|
|
1784
1856
|
colorspace_unknown = True if params[5] else False
|
|
1785
1857
|
ip_provided = True if params[6] else False
|
|
1786
1858
|
|
|
1787
|
-
return cls(
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1859
|
+
return cls(
|
|
1860
|
+
height,
|
|
1861
|
+
width,
|
|
1862
|
+
num_components=num_components,
|
|
1863
|
+
bits_per_component=bits_per_component,
|
|
1864
|
+
signed=signed,
|
|
1865
|
+
compression=compression,
|
|
1866
|
+
colorspace_unknown=colorspace_unknown,
|
|
1867
|
+
ip_provided=ip_provided,
|
|
1868
|
+
length=length,
|
|
1869
|
+
offset=offset,
|
|
1870
|
+
)
|
|
1794
1871
|
|
|
1795
1872
|
|
|
1796
1873
|
class AssociationBox(Jp2kBox):
|
|
@@ -1809,8 +1886,9 @@ class AssociationBox(Jp2kBox):
|
|
|
1809
1886
|
box : list
|
|
1810
1887
|
List of boxes contained in this superbox.
|
|
1811
1888
|
"""
|
|
1812
|
-
|
|
1813
|
-
|
|
1889
|
+
|
|
1890
|
+
box_id = "asoc"
|
|
1891
|
+
longname = "Association"
|
|
1814
1892
|
|
|
1815
1893
|
def __init__(self, box=None, length=0, offset=-1):
|
|
1816
1894
|
super().__init__()
|
|
@@ -1828,17 +1906,17 @@ class AssociationBox(Jp2kBox):
|
|
|
1828
1906
|
# Is it a GML JP2 box? If so, add the gdal info.
|
|
1829
1907
|
if (
|
|
1830
1908
|
len(self.box) == 2
|
|
1831
|
-
and self.box[0].box_id ==
|
|
1832
|
-
and self.box[0].label ==
|
|
1833
|
-
and self.box[1].box_id ==
|
|
1909
|
+
and self.box[0].box_id == "lbl "
|
|
1910
|
+
and self.box[0].label == "gml.data"
|
|
1911
|
+
and self.box[1].box_id == "asoc"
|
|
1834
1912
|
and len(self.box[1].box) == 2
|
|
1835
|
-
and self.box[1].box[0].box_id ==
|
|
1836
|
-
and self.box[1].box[0].label ==
|
|
1837
|
-
and self.box[1].box[1].box_id ==
|
|
1913
|
+
and self.box[1].box[0].box_id == "lbl "
|
|
1914
|
+
and self.box[1].box[0].label == "gml.root-instance"
|
|
1915
|
+
and self.box[1].box[1].box_id == "xml "
|
|
1838
1916
|
):
|
|
1839
1917
|
options = gdal.InfoOptions(showColorTable=False)
|
|
1840
1918
|
txt = gdal.Info(self._filename, options=options)
|
|
1841
|
-
txt = textwrap.indent(txt,
|
|
1919
|
+
txt = textwrap.indent(txt, " " * 4)
|
|
1842
1920
|
msg = f"{msg}\n{txt}"
|
|
1843
1921
|
|
|
1844
1922
|
return msg
|
|
@@ -1871,7 +1949,7 @@ class AssociationBox(Jp2kBox):
|
|
|
1871
1949
|
|
|
1872
1950
|
def write(self, fptr):
|
|
1873
1951
|
"""Write an association box to file."""
|
|
1874
|
-
self._write_superbox(fptr, b
|
|
1952
|
+
self._write_superbox(fptr, b"asoc")
|
|
1875
1953
|
|
|
1876
1954
|
|
|
1877
1955
|
class BitsPerComponentBox(Jp2kBox):
|
|
@@ -1892,8 +1970,9 @@ class BitsPerComponentBox(Jp2kBox):
|
|
|
1892
1970
|
signed : list
|
|
1893
1971
|
True if signed, false if not, for each component
|
|
1894
1972
|
"""
|
|
1895
|
-
|
|
1896
|
-
|
|
1973
|
+
|
|
1974
|
+
box_id = "bpcc"
|
|
1975
|
+
longname = "Bits Per Component"
|
|
1897
1976
|
|
|
1898
1977
|
def __init__(self, bpc, signed, length=0, offset=-1):
|
|
1899
1978
|
super().__init__()
|
|
@@ -1908,13 +1987,13 @@ class BitsPerComponentBox(Jp2kBox):
|
|
|
1908
1987
|
|
|
1909
1988
|
def __str__(self):
|
|
1910
1989
|
title = Jp2kBox.__str__(self)
|
|
1911
|
-
if get_option(
|
|
1990
|
+
if get_option("print.short") is True:
|
|
1912
1991
|
return title
|
|
1913
1992
|
|
|
1914
|
-
body = f
|
|
1915
|
-
body = textwrap.indent(body,
|
|
1993
|
+
body = f"Bits per component: {self.bpc}\nSigned: {self.signed}"
|
|
1994
|
+
body = textwrap.indent(body, " " * 4)
|
|
1916
1995
|
|
|
1917
|
-
text =
|
|
1996
|
+
text = "\n".join([title, body])
|
|
1918
1997
|
return text
|
|
1919
1998
|
|
|
1920
1999
|
@classmethod
|
|
@@ -1937,7 +2016,7 @@ class BitsPerComponentBox(Jp2kBox):
|
|
|
1937
2016
|
"""
|
|
1938
2017
|
nbytes = length - 8
|
|
1939
2018
|
data = fptr.read(nbytes)
|
|
1940
|
-
bpc = tuple(((x &
|
|
2019
|
+
bpc = tuple(((x & 0x7F) + 1) for x in bytearray(data))
|
|
1941
2020
|
signed = tuple(((x & 0x80) > 0) for x in bytearray(data))
|
|
1942
2021
|
|
|
1943
2022
|
return cls(bpc, signed, length=length, offset=offset)
|
|
@@ -1959,8 +2038,9 @@ class JP2HeaderBox(Jp2kBox):
|
|
|
1959
2038
|
box : list
|
|
1960
2039
|
List of boxes contained in this superbox.
|
|
1961
2040
|
"""
|
|
1962
|
-
|
|
1963
|
-
|
|
2041
|
+
|
|
2042
|
+
box_id = "jp2h"
|
|
2043
|
+
longname = "JP2 Header"
|
|
1964
2044
|
|
|
1965
2045
|
def __init__(self, box=None, length=0, offset=-1):
|
|
1966
2046
|
super().__init__()
|
|
@@ -1978,7 +2058,7 @@ class JP2HeaderBox(Jp2kBox):
|
|
|
1978
2058
|
|
|
1979
2059
|
def write(self, fptr):
|
|
1980
2060
|
"""Write a JP2 Header box to file."""
|
|
1981
|
-
self._write_superbox(fptr, b
|
|
2061
|
+
self._write_superbox(fptr, b"jp2h")
|
|
1982
2062
|
|
|
1983
2063
|
@classmethod
|
|
1984
2064
|
def parse(cls, fptr, offset, length):
|
|
@@ -2023,8 +2103,9 @@ class JPEG2000SignatureBox(Jp2kBox):
|
|
|
2023
2103
|
signature : tuple
|
|
2024
2104
|
Four-byte tuple identifying the file as JPEG 2000.
|
|
2025
2105
|
"""
|
|
2026
|
-
|
|
2027
|
-
|
|
2106
|
+
|
|
2107
|
+
box_id = "jP "
|
|
2108
|
+
longname = "JPEG 2000 Signature"
|
|
2028
2109
|
|
|
2029
2110
|
def __init__(self, signature=(13, 10, 135, 10), length=0, offset=-1):
|
|
2030
2111
|
super().__init__()
|
|
@@ -2033,23 +2114,23 @@ class JPEG2000SignatureBox(Jp2kBox):
|
|
|
2033
2114
|
self.offset = offset
|
|
2034
2115
|
|
|
2035
2116
|
def __repr__(self):
|
|
2036
|
-
return
|
|
2117
|
+
return "glymur.jp2box.JPEG2000SignatureBox()"
|
|
2037
2118
|
|
|
2038
2119
|
def __str__(self):
|
|
2039
2120
|
title = Jp2kBox.__str__(self)
|
|
2040
|
-
if get_option(
|
|
2121
|
+
if get_option("print.short") is True:
|
|
2041
2122
|
return title
|
|
2042
2123
|
|
|
2043
|
-
body =
|
|
2124
|
+
body = "Signature: {0:02x}{1:02x}{2:02x}{3:02x}"
|
|
2044
2125
|
body = body.format(*self.signature)
|
|
2045
|
-
body = textwrap.indent(body,
|
|
2046
|
-
text =
|
|
2126
|
+
body = textwrap.indent(body, " " * 4)
|
|
2127
|
+
text = "\n".join([title, body])
|
|
2047
2128
|
return text
|
|
2048
2129
|
|
|
2049
2130
|
def write(self, fptr):
|
|
2050
2131
|
"""Write a JPEG 2000 Signature box to file."""
|
|
2051
|
-
fptr.write(struct.pack(
|
|
2052
|
-
fptr.write(struct.pack(
|
|
2132
|
+
fptr.write(struct.pack(">I4s", 12, b"jP "))
|
|
2133
|
+
fptr.write(struct.pack(">BBBB", *self.signature))
|
|
2053
2134
|
|
|
2054
2135
|
@classmethod
|
|
2055
2136
|
def parse(cls, fptr, offset, length):
|
|
@@ -2070,7 +2151,7 @@ class JPEG2000SignatureBox(Jp2kBox):
|
|
|
2070
2151
|
Instance of the current JPEG2000 signature box.
|
|
2071
2152
|
"""
|
|
2072
2153
|
read_buffer = fptr.read(4)
|
|
2073
|
-
signature = struct.unpack(
|
|
2154
|
+
signature = struct.unpack(">BBBB", read_buffer)
|
|
2074
2155
|
|
|
2075
2156
|
return cls(signature=signature, length=length, offset=offset)
|
|
2076
2157
|
|
|
@@ -2091,11 +2172,18 @@ class PaletteBox(Jp2kBox):
|
|
|
2091
2172
|
palette : ndarray
|
|
2092
2173
|
Colormap array.
|
|
2093
2174
|
"""
|
|
2094
|
-
longname = 'Palette'
|
|
2095
|
-
box_id = 'pclr'
|
|
2096
2175
|
|
|
2097
|
-
|
|
2098
|
-
|
|
2176
|
+
longname = "Palette"
|
|
2177
|
+
box_id = "pclr"
|
|
2178
|
+
|
|
2179
|
+
def __init__(
|
|
2180
|
+
self,
|
|
2181
|
+
palette,
|
|
2182
|
+
bits_per_component,
|
|
2183
|
+
signed,
|
|
2184
|
+
length=0,
|
|
2185
|
+
offset=-1
|
|
2186
|
+
):
|
|
2099
2187
|
super().__init__()
|
|
2100
2188
|
self.palette = palette
|
|
2101
2189
|
self.bits_per_component = bits_per_component
|
|
@@ -2106,8 +2194,9 @@ class PaletteBox(Jp2kBox):
|
|
|
2106
2194
|
|
|
2107
2195
|
def _validate(self, writing=False):
|
|
2108
2196
|
"""Verify that the box obeys the specifications."""
|
|
2109
|
-
if (
|
|
2110
|
-
|
|
2197
|
+
if (len(self.bits_per_component) != len(self.signed)) or (
|
|
2198
|
+
len(self.signed) != self.palette.shape[1]
|
|
2199
|
+
):
|
|
2111
2200
|
msg = (
|
|
2112
2201
|
"The length of the 'bits_per_component' and the 'signed' "
|
|
2113
2202
|
"members must equal the number of columns of the palette."
|
|
@@ -2131,13 +2220,13 @@ class PaletteBox(Jp2kBox):
|
|
|
2131
2220
|
|
|
2132
2221
|
def __str__(self):
|
|
2133
2222
|
title = Jp2kBox.__str__(self)
|
|
2134
|
-
if get_option(
|
|
2223
|
+
if get_option("print.short") is True:
|
|
2135
2224
|
return title
|
|
2136
2225
|
|
|
2137
|
-
body = f
|
|
2138
|
-
body = textwrap.indent(body,
|
|
2226
|
+
body = f"Size: ({self.palette.shape[0]} x {self.palette.shape[1]})"
|
|
2227
|
+
body = textwrap.indent(body, " " * 4)
|
|
2139
2228
|
|
|
2140
|
-
text =
|
|
2229
|
+
text = "\n".join([title, body])
|
|
2141
2230
|
return text
|
|
2142
2231
|
|
|
2143
2232
|
def write(self, fptr):
|
|
@@ -2153,20 +2242,22 @@ class PaletteBox(Jp2kBox):
|
|
|
2153
2242
|
box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette
|
|
2154
2243
|
|
|
2155
2244
|
# Write the usual (L, T) header.
|
|
2156
|
-
write_buffer = struct.pack(
|
|
2245
|
+
write_buffer = struct.pack(">I4s", int(box_length), b"pclr")
|
|
2157
2246
|
fptr.write(write_buffer)
|
|
2158
2247
|
|
|
2159
2248
|
# NE, NPC
|
|
2160
2249
|
write_buffer = struct.pack(
|
|
2161
|
-
|
|
2250
|
+
">HB",
|
|
2251
|
+
self.palette.shape[0],
|
|
2252
|
+
self.palette.shape[1]
|
|
2162
2253
|
)
|
|
2163
2254
|
fptr.write(write_buffer)
|
|
2164
2255
|
|
|
2165
2256
|
# Bits Per Sample. Signed components aren't supported.
|
|
2166
2257
|
bps_signed = [x - 1 for x in self.bits_per_component]
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
)
|
|
2258
|
+
|
|
2259
|
+
fmt = ">" + "B" * self.palette.shape[1]
|
|
2260
|
+
write_buffer = struct.pack(fmt, *bps_signed)
|
|
2170
2261
|
fptr.write(write_buffer)
|
|
2171
2262
|
|
|
2172
2263
|
# C(i,j)
|
|
@@ -2192,11 +2283,12 @@ class PaletteBox(Jp2kBox):
|
|
|
2192
2283
|
"""
|
|
2193
2284
|
num_bytes = offset + length - fptr.tell()
|
|
2194
2285
|
read_buffer = fptr.read(num_bytes)
|
|
2195
|
-
nrows, ncols = struct.unpack_from(
|
|
2286
|
+
nrows, ncols = struct.unpack_from(">HB", read_buffer, offset=0)
|
|
2287
|
+
|
|
2288
|
+
fmt = ">" + "B" * ncols
|
|
2289
|
+
bps_signed = struct.unpack_from(fmt, read_buffer, offset=3)
|
|
2196
2290
|
|
|
2197
|
-
|
|
2198
|
-
offset=3)
|
|
2199
|
-
bps = [((x & 0x7f) + 1) for x in bps_signed]
|
|
2291
|
+
bps = [((x & 0x7F) + 1) for x in bps_signed]
|
|
2200
2292
|
signed = [((x & 0x80) > 1) for x in bps_signed]
|
|
2201
2293
|
|
|
2202
2294
|
# Are any components signed or differently sized? We don't handle
|
|
@@ -2221,93 +2313,116 @@ class PaletteBox(Jp2kBox):
|
|
|
2221
2313
|
|
|
2222
2314
|
# Map rreq codes to display text.
|
|
2223
2315
|
_READER_REQUIREMENTS_DISPLAY = {
|
|
2224
|
-
0:
|
|
2225
|
-
1:
|
|
2226
|
-
2:
|
|
2227
|
-
3: (
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2316
|
+
0: "File not completely understood",
|
|
2317
|
+
1: "Deprecated - contains no extensions",
|
|
2318
|
+
2: "Contains multiple composition layers",
|
|
2319
|
+
3: (
|
|
2320
|
+
"Deprecated - codestream is compressed using JPEG 2000 and requires "
|
|
2321
|
+
"at least a Profile 0 decoder as defind in ITU-T Rec. T.800 "
|
|
2322
|
+
"| ISO/IEC 15444-1, A.10 Table A.45"
|
|
2323
|
+
),
|
|
2324
|
+
4: "JPEG 2000 Part 1 Profile 1 codestream",
|
|
2325
|
+
5: (
|
|
2326
|
+
"Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 "
|
|
2327
|
+
"| ISO/IEC 15444-1"
|
|
2328
|
+
),
|
|
2329
|
+
6: "Unrestricted JPEG 2000 Part 2 codestream",
|
|
2330
|
+
7: "JPEG codestream as defined in ISO/IEC 10918-1",
|
|
2331
|
+
8: "Deprecated - does not contain opacity",
|
|
2332
|
+
9: "Non-premultiplied opacity channel",
|
|
2333
|
+
10: "Premultiplied opacity channel",
|
|
2334
|
+
11: "Chroma-key based opacity",
|
|
2335
|
+
12: "Deprecated - codestream is contiguous",
|
|
2336
|
+
13: "Fragmented codestream where all fragments are in file and in order",
|
|
2337
|
+
14: (
|
|
2338
|
+
"Fragmented codestream where all fragments are in file "
|
|
2339
|
+
"but are out of order"
|
|
2340
|
+
),
|
|
2341
|
+
15: (
|
|
2342
|
+
"Fragmented codestream where not all fragments are within the file "
|
|
2343
|
+
"but are all in locally accessible files"
|
|
2344
|
+
),
|
|
2345
|
+
16: (
|
|
2346
|
+
"Fragmented codestream where some fragments may be accessible "
|
|
2347
|
+
"only through a URL specified network connection"
|
|
2348
|
+
),
|
|
2349
|
+
17: (
|
|
2350
|
+
"Compositing required to produce rendered result from multiple "
|
|
2351
|
+
"compositing layers"
|
|
2352
|
+
),
|
|
2353
|
+
18: "Deprecated - support for compositing is not required",
|
|
2354
|
+
19: (
|
|
2355
|
+
"Deprecated - contains multiple, discrete layers that should not "
|
|
2356
|
+
"be combined through either animation or compositing"
|
|
2357
|
+
),
|
|
2358
|
+
20: (
|
|
2359
|
+
"Deprecated - compositing layers each contain only a single "
|
|
2360
|
+
"codestream"
|
|
2361
|
+
),
|
|
2362
|
+
21: "At least one compositing layer consists of multiple codestreams",
|
|
2363
|
+
22: "Deprecated - all compositing layers are in the same colourspace",
|
|
2364
|
+
23: (
|
|
2365
|
+
"Colourspace transformations are required to combine compositing "
|
|
2366
|
+
"layers; not all compositing layers are in the same colourspace"
|
|
2367
|
+
),
|
|
2368
|
+
24: "Deprecated - rendered result created without using animation",
|
|
2369
|
+
25: (
|
|
2370
|
+
"Deprecated - animated, but first layer covers entire area and is "
|
|
2371
|
+
"opaque"
|
|
2372
|
+
),
|
|
2373
|
+
26: "First animation layer does not cover entire rendered result",
|
|
2374
|
+
27: "Deprecated - animated, and no layer is reused",
|
|
2375
|
+
28: "Reuse of animation layers",
|
|
2376
|
+
29: "Deprecated - animated, but layers are reused",
|
|
2377
|
+
30: "Some animated frames are non-persistent",
|
|
2378
|
+
31: "Deprecated - rendered result created without using scaling",
|
|
2379
|
+
32: "Rendered result involves scaling within a layer",
|
|
2380
|
+
33: "Rendered result involves scaling between layers",
|
|
2381
|
+
34: "ROI metadata",
|
|
2382
|
+
35: "IPR metadata",
|
|
2383
|
+
36: "Content metadata",
|
|
2384
|
+
37: "History metadata",
|
|
2385
|
+
38: "Creation metadata",
|
|
2386
|
+
39: "JPX digital signatures",
|
|
2387
|
+
40: "JPX checksums",
|
|
2388
|
+
41: "Desires Graphics Arts Reproduction specified",
|
|
2389
|
+
42: "Deprecated - compositing layer uses palettized colour",
|
|
2390
|
+
43: "Deprecated - compositing layer uses restricted ICC profile",
|
|
2391
|
+
44: "Compositing layer uses Any ICC profile",
|
|
2392
|
+
45: "Deprecated - compositing layer uses sRGB enumerated colourspace",
|
|
2393
|
+
46: "Deprecated - compositing layer uses sRGB-grey enumerated colourspace",
|
|
2394
|
+
47: "BiLevel 1 enumerated colourspace",
|
|
2395
|
+
48: "BiLevel 2 enumerated colourspace",
|
|
2396
|
+
49: "YCbCr 1 enumerated colourspace",
|
|
2397
|
+
50: "YCbCr 2 enumerated colourspace",
|
|
2398
|
+
51: "YCbCr 3 enumerated colourspace",
|
|
2399
|
+
52: "PhotoYCC enumerated colourspace",
|
|
2400
|
+
53: "YCCK enumerated colourspace",
|
|
2401
|
+
54: "CMY enumerated colourspace",
|
|
2402
|
+
55: "CMYK enumerated colorspace",
|
|
2403
|
+
56: "CIELab enumerated colourspace with default parameters",
|
|
2404
|
+
57: "CIELab enumerated colourspace with non-default parameters",
|
|
2405
|
+
58: "CIEJab enumerated colourspace with default parameters",
|
|
2406
|
+
59: "CIEJab enumerated colourspace with non-default parameters",
|
|
2407
|
+
60: "e-sRGB enumerated colorspace",
|
|
2408
|
+
61: "ROMM_RGB enumerated colorspace",
|
|
2409
|
+
62: "Non-square samples",
|
|
2410
|
+
63: "Deprecated - compositing layers have labels",
|
|
2411
|
+
64: "Deprecated - codestreams have labels",
|
|
2412
|
+
65: "Deprecated - compositing layers have different colour spaces",
|
|
2413
|
+
66: "Deprecated - compositing layers have different metadata",
|
|
2414
|
+
67: "GIS metadata XML box",
|
|
2415
|
+
68: "JPSEC extensions in codestream as specified by ISO/IEC 15444-8",
|
|
2416
|
+
69: "JP3D extensions in codestream as specified by ISO/IEC 15444-10",
|
|
2417
|
+
70: "Deprecated - compositing layer uses sYCC enumerated colour space",
|
|
2418
|
+
71: "e-sYCC enumerated colourspace",
|
|
2419
|
+
72: (
|
|
2420
|
+
"JPEG 2000 Part 2 codestream as restricted by baseline conformance "
|
|
2421
|
+
"requirements in M.9.2.3"
|
|
2422
|
+
),
|
|
2423
|
+
73: "YPbPr(1125/60) enumerated colourspace",
|
|
2424
|
+
74: "YPbPr(1250/50) enumerated colourspace",
|
|
2425
|
+
}
|
|
2311
2426
|
|
|
2312
2427
|
|
|
2313
2428
|
class ReaderRequirementsBox(Jp2kBox):
|
|
@@ -2338,12 +2453,20 @@ class ReaderRequirementsBox(Jp2kBox):
|
|
|
2338
2453
|
Specifies the compatibility mask for each corresponding vendor
|
|
2339
2454
|
feature.
|
|
2340
2455
|
"""
|
|
2341
|
-
|
|
2342
|
-
|
|
2456
|
+
|
|
2457
|
+
box_id = "rreq"
|
|
2458
|
+
longname = "Reader Requirements"
|
|
2343
2459
|
|
|
2344
2460
|
def __init__(
|
|
2345
|
-
self,
|
|
2346
|
-
|
|
2461
|
+
self,
|
|
2462
|
+
fuam,
|
|
2463
|
+
dcm,
|
|
2464
|
+
standard_flag,
|
|
2465
|
+
standard_mask,
|
|
2466
|
+
vendor_feature,
|
|
2467
|
+
vendor_mask,
|
|
2468
|
+
length=0,
|
|
2469
|
+
offset=-1,
|
|
2347
2470
|
):
|
|
2348
2471
|
super().__init__()
|
|
2349
2472
|
self.fuam = fuam
|
|
@@ -2367,53 +2490,53 @@ class ReaderRequirementsBox(Jp2kBox):
|
|
|
2367
2490
|
standard_flag=self.standard_flag,
|
|
2368
2491
|
standard_mask=self.standard_mask,
|
|
2369
2492
|
vendor_feature=self.vendor_feature,
|
|
2370
|
-
vendor_mask=self.vendor_mask
|
|
2493
|
+
vendor_mask=self.vendor_mask,
|
|
2371
2494
|
)
|
|
2372
2495
|
return msg
|
|
2373
2496
|
|
|
2374
2497
|
def __str__(self):
|
|
2375
2498
|
title = Jp2kBox.__str__(self)
|
|
2376
|
-
if get_option(
|
|
2499
|
+
if get_option("print.short") is True:
|
|
2377
2500
|
return title
|
|
2378
2501
|
|
|
2379
2502
|
lst = []
|
|
2380
2503
|
|
|
2381
|
-
text = f
|
|
2504
|
+
text = f"Fully Understands Aspect Mask: 0x{self.fuam:x}"
|
|
2382
2505
|
lst.append(text)
|
|
2383
2506
|
|
|
2384
|
-
text = f
|
|
2507
|
+
text = f"Display Completely Mask: 0x{self.dcm:x}"
|
|
2385
2508
|
lst.append(text)
|
|
2386
2509
|
|
|
2387
|
-
text =
|
|
2510
|
+
text = "Standard Features and Masks:"
|
|
2388
2511
|
lst.append(text)
|
|
2389
2512
|
|
|
2390
2513
|
lst2 = []
|
|
2391
|
-
text =
|
|
2514
|
+
text = "Feature {flag:03d}: 0x{mask:x} {decoded}"
|
|
2392
2515
|
for j in range(len(self.standard_flag)):
|
|
2393
2516
|
kwargs = {
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2517
|
+
"flag": self.standard_flag[j],
|
|
2518
|
+
"mask": self.standard_mask[j],
|
|
2519
|
+
"decoded": _READER_REQUIREMENTS_DISPLAY[self.standard_flag[j]],
|
|
2397
2520
|
}
|
|
2398
2521
|
lst2.append(text.format(**kwargs))
|
|
2399
|
-
text =
|
|
2400
|
-
text = textwrap.indent(text,
|
|
2522
|
+
text = "\n".join(lst2)
|
|
2523
|
+
text = textwrap.indent(text, " " * 4)
|
|
2401
2524
|
lst.append(text)
|
|
2402
2525
|
|
|
2403
|
-
text =
|
|
2526
|
+
text = "Vendor Features:"
|
|
2404
2527
|
lst.append(text)
|
|
2405
2528
|
|
|
2406
2529
|
lst2 = []
|
|
2407
2530
|
for j in range(len(self.vendor_feature)):
|
|
2408
|
-
text = f
|
|
2531
|
+
text = f"UUID {self.vendor_feature[j]}"
|
|
2409
2532
|
lst2.append(text)
|
|
2410
|
-
text =
|
|
2411
|
-
text = textwrap.indent(text,
|
|
2533
|
+
text = "\n".join(lst2)
|
|
2534
|
+
text = textwrap.indent(text, " " * 4)
|
|
2412
2535
|
lst.append(text)
|
|
2413
2536
|
|
|
2414
|
-
text =
|
|
2415
|
-
text = textwrap.indent(text,
|
|
2416
|
-
text =
|
|
2537
|
+
text = "\n".join(lst)
|
|
2538
|
+
text = textwrap.indent(text, " " * 4)
|
|
2539
|
+
text = "\n".join([title, text])
|
|
2417
2540
|
|
|
2418
2541
|
return text
|
|
2419
2542
|
|
|
@@ -2437,7 +2560,7 @@ class ReaderRequirementsBox(Jp2kBox):
|
|
|
2437
2560
|
"""
|
|
2438
2561
|
num_bytes = offset + length - fptr.tell()
|
|
2439
2562
|
read_buffer = fptr.read(num_bytes)
|
|
2440
|
-
mask_length, = struct.unpack_from(
|
|
2563
|
+
(mask_length,) = struct.unpack_from(">B", read_buffer, offset=0)
|
|
2441
2564
|
|
|
2442
2565
|
# Fully Understands Aspect Mask
|
|
2443
2566
|
# Decodes Completely Mask
|
|
@@ -2447,31 +2570,45 @@ class ReaderRequirementsBox(Jp2kBox):
|
|
|
2447
2570
|
# The mask length tells us the format string to use when unpacking
|
|
2448
2571
|
# from the buffer read from file.
|
|
2449
2572
|
try:
|
|
2450
|
-
mask_format = {1:
|
|
2451
|
-
fuam, dcm = struct.unpack_from(
|
|
2452
|
-
|
|
2573
|
+
mask_format = {1: "B", 2: "H", 4: "I", 8: "Q"}[mask_length]
|
|
2574
|
+
fuam, dcm = struct.unpack_from(
|
|
2575
|
+
">" + mask_format * 2,
|
|
2576
|
+
read_buffer,
|
|
2577
|
+
offset=1
|
|
2578
|
+
)
|
|
2453
2579
|
std_flg_offset = 1 + 2 * mask_length
|
|
2454
|
-
data = _parse_standard_flag(
|
|
2455
|
-
|
|
2580
|
+
data = _parse_standard_flag(
|
|
2581
|
+
read_buffer[std_flg_offset:],
|
|
2582
|
+
mask_length
|
|
2583
|
+
)
|
|
2456
2584
|
standard_flag, standard_mask = data
|
|
2457
2585
|
|
|
2458
2586
|
nflags = len(standard_flag)
|
|
2459
2587
|
vndr_offset = 1 + 2 * mask_length + 2 + (2 + mask_length) * nflags
|
|
2460
|
-
data = _parse_vendor_features(
|
|
2461
|
-
|
|
2588
|
+
data = _parse_vendor_features(
|
|
2589
|
+
read_buffer[vndr_offset:],
|
|
2590
|
+
mask_length
|
|
2591
|
+
)
|
|
2462
2592
|
vendor_feature, vendor_mask = data
|
|
2463
2593
|
|
|
2464
2594
|
except KeyError:
|
|
2465
2595
|
msg = (
|
|
2466
|
-
f
|
|
2467
|
-
f
|
|
2468
|
-
f
|
|
2596
|
+
f"The ReaderRequirements box (rreq) has a mask length of "
|
|
2597
|
+
f"{mask_length} bytes, but only values of 1, 2, 4, or 8 are "
|
|
2598
|
+
f"supported. The box contents will not be interpreted."
|
|
2469
2599
|
)
|
|
2470
2600
|
warnings.warn(msg, UserWarning)
|
|
2471
2601
|
|
|
2472
|
-
return cls(
|
|
2473
|
-
|
|
2474
|
-
|
|
2602
|
+
return cls(
|
|
2603
|
+
fuam,
|
|
2604
|
+
dcm,
|
|
2605
|
+
standard_flag,
|
|
2606
|
+
standard_mask,
|
|
2607
|
+
vendor_feature,
|
|
2608
|
+
vendor_mask,
|
|
2609
|
+
length=length,
|
|
2610
|
+
offset=offset,
|
|
2611
|
+
)
|
|
2475
2612
|
|
|
2476
2613
|
|
|
2477
2614
|
def _parse_standard_flag(read_buffer, mask_length):
|
|
@@ -2488,14 +2625,14 @@ def _parse_standard_flag(read_buffer, mask_length):
|
|
|
2488
2625
|
"""
|
|
2489
2626
|
# The mask length tells us the format string to use when unpacking
|
|
2490
2627
|
# from the buffer read from file.
|
|
2491
|
-
mask_format = {1:
|
|
2628
|
+
mask_format = {1: "B", 2: "H", 4: "I"}[mask_length]
|
|
2492
2629
|
|
|
2493
|
-
num_standard_flags, = struct.unpack_from(
|
|
2630
|
+
(num_standard_flags,) = struct.unpack_from(">H", read_buffer, offset=0)
|
|
2494
2631
|
|
|
2495
2632
|
# Read in standard flags and standard masks. Each standard flag should
|
|
2496
2633
|
# be two bytes, but the standard mask flag is as long as specified by
|
|
2497
2634
|
# the mask length.
|
|
2498
|
-
fmt =
|
|
2635
|
+
fmt = ">" + ("H" + mask_format) * num_standard_flags
|
|
2499
2636
|
data = struct.unpack_from(fmt, read_buffer, offset=2)
|
|
2500
2637
|
|
|
2501
2638
|
standard_flag = data[0:num_standard_flags * 2:2]
|
|
@@ -2518,9 +2655,9 @@ def _parse_vendor_features(read_buffer, mask_length):
|
|
|
2518
2655
|
"""
|
|
2519
2656
|
# The mask length tells us the format string to use when unpacking
|
|
2520
2657
|
# from the buffer read from file.
|
|
2521
|
-
mask_format = {1:
|
|
2658
|
+
mask_format = {1: "B", 2: "H", 4: "I"}[mask_length]
|
|
2522
2659
|
|
|
2523
|
-
num_vendor_features, = struct.unpack_from(
|
|
2660
|
+
(num_vendor_features,) = struct.unpack_from(">H", read_buffer)
|
|
2524
2661
|
|
|
2525
2662
|
# Each vendor feature consists of a 16-byte UUID plus a mask whose
|
|
2526
2663
|
# length is specified by, you guessed it, "mask_length".
|
|
@@ -2532,7 +2669,7 @@ def _parse_vendor_features(read_buffer, mask_length):
|
|
|
2532
2669
|
ubuffer = read_buffer[uslice]
|
|
2533
2670
|
vendor_feature.append(UUID(bytes=ubuffer[0:16]))
|
|
2534
2671
|
|
|
2535
|
-
vmask = struct.unpack(
|
|
2672
|
+
vmask = struct.unpack(">" + mask_format, ubuffer[16:])
|
|
2536
2673
|
vendor_mask.append(vmask)
|
|
2537
2674
|
|
|
2538
2675
|
return vendor_feature, vendor_mask
|
|
@@ -2554,8 +2691,9 @@ class ResolutionBox(Jp2kBox):
|
|
|
2554
2691
|
box : list
|
|
2555
2692
|
List of boxes contained in this superbox.
|
|
2556
2693
|
"""
|
|
2557
|
-
|
|
2558
|
-
|
|
2694
|
+
|
|
2695
|
+
box_id = "res "
|
|
2696
|
+
longname = "Resolution"
|
|
2559
2697
|
|
|
2560
2698
|
def __init__(self, box=None, length=0, offset=-1):
|
|
2561
2699
|
super().__init__()
|
|
@@ -2573,7 +2711,7 @@ class ResolutionBox(Jp2kBox):
|
|
|
2573
2711
|
|
|
2574
2712
|
def write(self, fptr):
|
|
2575
2713
|
"""Write a Resolution super box to file."""
|
|
2576
|
-
self._write_superbox(fptr, b
|
|
2714
|
+
self._write_superbox(fptr, b"res ")
|
|
2577
2715
|
|
|
2578
2716
|
@classmethod
|
|
2579
2717
|
def parse(cls, fptr, offset, length):
|
|
@@ -2618,11 +2756,16 @@ class CaptureResolutionBox(Jp2kBox):
|
|
|
2618
2756
|
vertical_resolution, horizontal_resolution : float
|
|
2619
2757
|
Vertical, horizontal resolution.
|
|
2620
2758
|
"""
|
|
2621
|
-
|
|
2622
|
-
|
|
2759
|
+
|
|
2760
|
+
box_id = "resc"
|
|
2761
|
+
longname = "Capture Resolution"
|
|
2623
2762
|
|
|
2624
2763
|
def __init__(
|
|
2625
|
-
self,
|
|
2764
|
+
self,
|
|
2765
|
+
vertical_resolution,
|
|
2766
|
+
horizontal_resolution,
|
|
2767
|
+
length=0,
|
|
2768
|
+
offset=-1
|
|
2626
2769
|
):
|
|
2627
2770
|
super().__init__()
|
|
2628
2771
|
self.vertical_resolution = vertical_resolution
|
|
@@ -2639,19 +2782,19 @@ class CaptureResolutionBox(Jp2kBox):
|
|
|
2639
2782
|
|
|
2640
2783
|
def __str__(self):
|
|
2641
2784
|
title = Jp2kBox.__str__(self)
|
|
2642
|
-
if get_option(
|
|
2785
|
+
if get_option("print.short") is True:
|
|
2643
2786
|
return title
|
|
2644
2787
|
|
|
2645
2788
|
lst = []
|
|
2646
|
-
text = f
|
|
2789
|
+
text = f"VCR: {self.vertical_resolution}"
|
|
2647
2790
|
lst.append(text)
|
|
2648
|
-
text = f
|
|
2791
|
+
text = f"HCR: {self.horizontal_resolution}"
|
|
2649
2792
|
lst.append(text)
|
|
2650
2793
|
|
|
2651
|
-
text =
|
|
2652
|
-
text = textwrap.indent(text,
|
|
2794
|
+
text = "\n".join(lst)
|
|
2795
|
+
text = textwrap.indent(text, " " * 4)
|
|
2653
2796
|
|
|
2654
|
-
text =
|
|
2797
|
+
text = "\n".join([title, text])
|
|
2655
2798
|
return text
|
|
2656
2799
|
|
|
2657
2800
|
@classmethod
|
|
@@ -2673,9 +2816,9 @@ class CaptureResolutionBox(Jp2kBox):
|
|
|
2673
2816
|
Instance of the current capture resolution box.
|
|
2674
2817
|
"""
|
|
2675
2818
|
read_buffer = fptr.read(10)
|
|
2676
|
-
(rn1, rd1, rn2, rd2, re1, re2) = struct.unpack(
|
|
2677
|
-
vres = rn1 / rd1 * 10
|
|
2678
|
-
hres = rn2 / rd2 * 10
|
|
2819
|
+
(rn1, rd1, rn2, rd2, re1, re2) = struct.unpack(">HHHHbb", read_buffer)
|
|
2820
|
+
vres = rn1 / rd1 * 10**re1
|
|
2821
|
+
hres = rn2 / rd2 * 10**re2
|
|
2679
2822
|
|
|
2680
2823
|
return cls(vres, hres, length=length, offset=offset)
|
|
2681
2824
|
|
|
@@ -2685,12 +2828,12 @@ class CaptureResolutionBox(Jp2kBox):
|
|
|
2685
2828
|
# 4 bytes for length, 4 for the ID, always 10 bytes for the payload
|
|
2686
2829
|
length = 18
|
|
2687
2830
|
|
|
2688
|
-
fptr.write(struct.pack(
|
|
2831
|
+
fptr.write(struct.pack(">I4s", length, b"resc"))
|
|
2689
2832
|
|
|
2690
2833
|
re1, rn1, rd1 = decompose_resolution(self.vertical_resolution)
|
|
2691
2834
|
re2, rn2, rd2 = decompose_resolution(self.horizontal_resolution)
|
|
2692
2835
|
|
|
2693
|
-
buffer = struct.pack(
|
|
2836
|
+
buffer = struct.pack(">HHHHbb", rn1, rd1, rn2, rd2, re1, re2)
|
|
2694
2837
|
fptr.write(buffer)
|
|
2695
2838
|
|
|
2696
2839
|
|
|
@@ -2710,11 +2853,16 @@ class DisplayResolutionBox(Jp2kBox):
|
|
|
2710
2853
|
vertical_resolution, horizontal_resolution : float
|
|
2711
2854
|
Vertical, horizontal resolution.
|
|
2712
2855
|
"""
|
|
2713
|
-
|
|
2714
|
-
|
|
2856
|
+
|
|
2857
|
+
box_id = "resd"
|
|
2858
|
+
longname = "Display Resolution"
|
|
2715
2859
|
|
|
2716
2860
|
def __init__(
|
|
2717
|
-
self,
|
|
2861
|
+
self,
|
|
2862
|
+
vertical_resolution,
|
|
2863
|
+
horizontal_resolution,
|
|
2864
|
+
length=0,
|
|
2865
|
+
offset=-1
|
|
2718
2866
|
):
|
|
2719
2867
|
super().__init__()
|
|
2720
2868
|
self.vertical_resolution = vertical_resolution
|
|
@@ -2729,19 +2877,19 @@ class DisplayResolutionBox(Jp2kBox):
|
|
|
2729
2877
|
|
|
2730
2878
|
def __str__(self):
|
|
2731
2879
|
title = Jp2kBox.__str__(self)
|
|
2732
|
-
if get_option(
|
|
2880
|
+
if get_option("print.short") is True:
|
|
2733
2881
|
return title
|
|
2734
2882
|
|
|
2735
2883
|
lst = []
|
|
2736
|
-
text = f
|
|
2884
|
+
text = f"VDR: {self.vertical_resolution}"
|
|
2737
2885
|
lst.append(text)
|
|
2738
|
-
text = f
|
|
2886
|
+
text = f"HDR: {self.horizontal_resolution}"
|
|
2739
2887
|
lst.append(text)
|
|
2740
2888
|
|
|
2741
|
-
text =
|
|
2742
|
-
text = textwrap.indent(text,
|
|
2889
|
+
text = "\n".join(lst)
|
|
2890
|
+
text = textwrap.indent(text, " " * 4)
|
|
2743
2891
|
|
|
2744
|
-
text =
|
|
2892
|
+
text = "\n".join([title, text])
|
|
2745
2893
|
return text
|
|
2746
2894
|
|
|
2747
2895
|
@classmethod
|
|
@@ -2764,9 +2912,9 @@ class DisplayResolutionBox(Jp2kBox):
|
|
|
2764
2912
|
"""
|
|
2765
2913
|
|
|
2766
2914
|
read_buffer = fptr.read(10)
|
|
2767
|
-
(rn1, rd1, rn2, rd2, re1, re2) = struct.unpack(
|
|
2768
|
-
vres = rn1 / rd1 * 10
|
|
2769
|
-
hres = rn2 / rd2 * 10
|
|
2915
|
+
(rn1, rd1, rn2, rd2, re1, re2) = struct.unpack(">HHHHbb", read_buffer)
|
|
2916
|
+
vres = rn1 / rd1 * 10**re1
|
|
2917
|
+
hres = rn2 / rd2 * 10**re2
|
|
2770
2918
|
|
|
2771
2919
|
return cls(vres, hres, length=length, offset=offset)
|
|
2772
2920
|
|
|
@@ -2776,12 +2924,12 @@ class DisplayResolutionBox(Jp2kBox):
|
|
|
2776
2924
|
# 4 bytes for length, 4 for the ID, always 10 bytes for the payload
|
|
2777
2925
|
length = 18
|
|
2778
2926
|
|
|
2779
|
-
fptr.write(struct.pack(
|
|
2927
|
+
fptr.write(struct.pack(">I4s", length, b"resd"))
|
|
2780
2928
|
|
|
2781
2929
|
re1, rn1, rd1 = decompose_resolution(self.vertical_resolution)
|
|
2782
2930
|
re2, rn2, rd2 = decompose_resolution(self.horizontal_resolution)
|
|
2783
2931
|
|
|
2784
|
-
buffer = struct.pack(
|
|
2932
|
+
buffer = struct.pack(">HHHHbb", rn1, rd1, rn2, rd2, re1, re2)
|
|
2785
2933
|
fptr.write(buffer)
|
|
2786
2934
|
|
|
2787
2935
|
|
|
@@ -2801,8 +2949,9 @@ class LabelBox(Jp2kBox):
|
|
|
2801
2949
|
label : str
|
|
2802
2950
|
Textual label.
|
|
2803
2951
|
"""
|
|
2804
|
-
|
|
2805
|
-
|
|
2952
|
+
|
|
2953
|
+
box_id = "lbl "
|
|
2954
|
+
longname = "Label"
|
|
2806
2955
|
|
|
2807
2956
|
def __init__(self, label, length=0, offset=-1):
|
|
2808
2957
|
super().__init__()
|
|
@@ -2812,13 +2961,13 @@ class LabelBox(Jp2kBox):
|
|
|
2812
2961
|
|
|
2813
2962
|
def __str__(self):
|
|
2814
2963
|
title = Jp2kBox.__str__(self)
|
|
2815
|
-
if get_option(
|
|
2964
|
+
if get_option("print.short") is True:
|
|
2816
2965
|
return title
|
|
2817
2966
|
|
|
2818
|
-
text = f
|
|
2819
|
-
text = textwrap.indent(text,
|
|
2967
|
+
text = f"Label: {self.label}"
|
|
2968
|
+
text = textwrap.indent(text, " " * 4)
|
|
2820
2969
|
|
|
2821
|
-
text =
|
|
2970
|
+
text = "\n".join([title, text])
|
|
2822
2971
|
return text
|
|
2823
2972
|
|
|
2824
2973
|
def __repr__(self):
|
|
@@ -2828,7 +2977,7 @@ class LabelBox(Jp2kBox):
|
|
|
2828
2977
|
def write(self, fptr):
|
|
2829
2978
|
"""Write a Label box to file."""
|
|
2830
2979
|
length = 8 + len(self.label.encode())
|
|
2831
|
-
fptr.write(struct.pack(
|
|
2980
|
+
fptr.write(struct.pack(">I4s", length, b"lbl "))
|
|
2832
2981
|
fptr.write(self.label.encode())
|
|
2833
2982
|
|
|
2834
2983
|
@classmethod
|
|
@@ -2851,7 +3000,7 @@ class LabelBox(Jp2kBox):
|
|
|
2851
3000
|
"""
|
|
2852
3001
|
num_bytes = offset + length - fptr.tell()
|
|
2853
3002
|
read_buffer = fptr.read(num_bytes)
|
|
2854
|
-
label = read_buffer.decode(
|
|
3003
|
+
label = read_buffer.decode("utf-8")
|
|
2855
3004
|
return cls(label, length=length, offset=offset)
|
|
2856
3005
|
|
|
2857
3006
|
|
|
@@ -2872,8 +3021,9 @@ class NumberListBox(Jp2kBox):
|
|
|
2872
3021
|
Descriptors of an entity with which the data contained within the same
|
|
2873
3022
|
Association box is associated.
|
|
2874
3023
|
"""
|
|
2875
|
-
|
|
2876
|
-
|
|
3024
|
+
|
|
3025
|
+
box_id = "nlst"
|
|
3026
|
+
longname = "Number List"
|
|
2877
3027
|
|
|
2878
3028
|
def __init__(self, associations, length=0, offset=-1):
|
|
2879
3029
|
super().__init__()
|
|
@@ -2883,32 +3033,32 @@ class NumberListBox(Jp2kBox):
|
|
|
2883
3033
|
|
|
2884
3034
|
def __str__(self):
|
|
2885
3035
|
title = Jp2kBox.__str__(self)
|
|
2886
|
-
if get_option(
|
|
3036
|
+
if get_option("print.short") is True:
|
|
2887
3037
|
return title
|
|
2888
3038
|
|
|
2889
3039
|
lst = []
|
|
2890
3040
|
for j, association in enumerate(self.associations):
|
|
2891
|
-
text = f
|
|
3041
|
+
text = f"Association[{j}]: "
|
|
2892
3042
|
if association == 0:
|
|
2893
|
-
text +=
|
|
3043
|
+
text += "the rendered result"
|
|
2894
3044
|
elif (association >> 24) == 1:
|
|
2895
3045
|
idx = association & 0x00FFFFFF
|
|
2896
|
-
text += f
|
|
3046
|
+
text += f"codestream {idx}"
|
|
2897
3047
|
elif (association >> 24) == 2:
|
|
2898
3048
|
idx = association & 0x00FFFFFF
|
|
2899
|
-
text += f
|
|
3049
|
+
text += f"compositing layer {idx}"
|
|
2900
3050
|
else:
|
|
2901
|
-
text +=
|
|
3051
|
+
text += "unrecognized"
|
|
2902
3052
|
lst.append(text)
|
|
2903
3053
|
|
|
2904
|
-
body =
|
|
2905
|
-
body = textwrap.indent(body,
|
|
3054
|
+
body = "\n".join(lst)
|
|
3055
|
+
body = textwrap.indent(body, " " * 4)
|
|
2906
3056
|
|
|
2907
|
-
text =
|
|
3057
|
+
text = "\n".join([title, body])
|
|
2908
3058
|
return text
|
|
2909
3059
|
|
|
2910
3060
|
def __repr__(self):
|
|
2911
|
-
msg = f
|
|
3061
|
+
msg = f"glymur.jp2box.NumberListBox(associations={self.associations})"
|
|
2912
3062
|
return msg
|
|
2913
3063
|
|
|
2914
3064
|
@classmethod
|
|
@@ -2932,15 +3082,14 @@ class NumberListBox(Jp2kBox):
|
|
|
2932
3082
|
num_bytes = offset + length - fptr.tell()
|
|
2933
3083
|
raw_data = fptr.read(num_bytes)
|
|
2934
3084
|
num_associations = int(len(raw_data) / 4)
|
|
2935
|
-
lst = struct.unpack(
|
|
3085
|
+
lst = struct.unpack(">" + "I" * num_associations, raw_data)
|
|
2936
3086
|
return cls(lst, length=length, offset=offset)
|
|
2937
3087
|
|
|
2938
3088
|
def write(self, fptr):
|
|
2939
3089
|
"""Write a NumberList box to file."""
|
|
2940
|
-
fptr.write(struct.pack(
|
|
2941
|
-
len(self.associations) * 4 + 8, b'nlst'))
|
|
3090
|
+
fptr.write(struct.pack(">I4s", len(self.associations) * 4 + 8, b"nlst"))
|
|
2942
3091
|
|
|
2943
|
-
fmt =
|
|
3092
|
+
fmt = ">" + "I" * len(self.associations)
|
|
2944
3093
|
write_buffer = struct.pack(fmt, *self.associations)
|
|
2945
3094
|
fptr.write(write_buffer)
|
|
2946
3095
|
|
|
@@ -2961,17 +3110,18 @@ class XMLBox(Jp2kBox):
|
|
|
2961
3110
|
xml : ElementTree object
|
|
2962
3111
|
XML section.
|
|
2963
3112
|
"""
|
|
2964
|
-
|
|
2965
|
-
|
|
3113
|
+
|
|
3114
|
+
box_id = "xml "
|
|
3115
|
+
longname = "XML"
|
|
2966
3116
|
|
|
2967
3117
|
def __init__(self, xml=None, filename=None, length=0, offset=-1):
|
|
2968
3118
|
"""Parameters
|
|
2969
3119
|
----------
|
|
2970
3120
|
xml : ElementTree
|
|
2971
3121
|
An ElementTree object already existing in python.
|
|
2972
|
-
filename : str or
|
|
2973
|
-
File
|
|
2974
|
-
|
|
3122
|
+
filename : file, str, or pathlib.Path
|
|
3123
|
+
File to read. If filename is not None, then the xml keyword
|
|
3124
|
+
argument must be None.
|
|
2975
3125
|
"""
|
|
2976
3126
|
super().__init__()
|
|
2977
3127
|
if filename is not None and xml is not None:
|
|
@@ -2981,7 +3131,7 @@ class XMLBox(Jp2kBox):
|
|
|
2981
3131
|
)
|
|
2982
3132
|
raise RuntimeError(msg)
|
|
2983
3133
|
if filename is not None:
|
|
2984
|
-
self.xml = ET.parse(
|
|
3134
|
+
self.xml = ET.parse(filename)
|
|
2985
3135
|
else:
|
|
2986
3136
|
self.xml = xml
|
|
2987
3137
|
self.length = length
|
|
@@ -2992,26 +3142,28 @@ class XMLBox(Jp2kBox):
|
|
|
2992
3142
|
|
|
2993
3143
|
def __str__(self):
|
|
2994
3144
|
title = Jp2kBox.__str__(self)
|
|
2995
|
-
if get_option(
|
|
3145
|
+
if get_option("print.short") is True:
|
|
2996
3146
|
return title
|
|
2997
|
-
if get_option(
|
|
3147
|
+
if get_option("print.xml") is False:
|
|
2998
3148
|
return title
|
|
2999
3149
|
|
|
3000
3150
|
if self.xml is not None:
|
|
3001
|
-
body =
|
|
3002
|
-
|
|
3003
|
-
|
|
3151
|
+
body = (
|
|
3152
|
+
ET.tostring(self.xml, encoding="utf-8", pretty_print=True)
|
|
3153
|
+
.decode("utf-8")
|
|
3154
|
+
.rstrip()
|
|
3155
|
+
)
|
|
3004
3156
|
else:
|
|
3005
|
-
body =
|
|
3006
|
-
body = textwrap.indent(body,
|
|
3157
|
+
body = "None"
|
|
3158
|
+
body = textwrap.indent(body, " " * 4)
|
|
3007
3159
|
|
|
3008
|
-
text =
|
|
3160
|
+
text = "\n".join([title, body])
|
|
3009
3161
|
return text
|
|
3010
3162
|
|
|
3011
3163
|
def write(self, fptr):
|
|
3012
3164
|
"""Write an XML box to file."""
|
|
3013
|
-
read_buffer = ET.tostring(self.xml.getroot(), encoding=
|
|
3014
|
-
fptr.write(struct.pack(
|
|
3165
|
+
read_buffer = ET.tostring(self.xml.getroot(), encoding="utf-8")
|
|
3166
|
+
fptr.write(struct.pack(">I4s", len(read_buffer) + 8, b"xml "))
|
|
3015
3167
|
fptr.write(read_buffer)
|
|
3016
3168
|
|
|
3017
3169
|
@classmethod
|
|
@@ -3036,48 +3188,48 @@ class XMLBox(Jp2kBox):
|
|
|
3036
3188
|
read_buffer = fptr.read(num_bytes)
|
|
3037
3189
|
|
|
3038
3190
|
try:
|
|
3039
|
-
text = read_buffer.decode(
|
|
3191
|
+
text = read_buffer.decode("utf-8")
|
|
3040
3192
|
except UnicodeDecodeError as err:
|
|
3041
3193
|
# Possibly bad string of bytes to begin with.
|
|
3042
3194
|
# Try to search for <?xml and go from there.
|
|
3043
|
-
decl_start = read_buffer.find(b
|
|
3195
|
+
decl_start = read_buffer.find(b"<?xml")
|
|
3044
3196
|
if decl_start <= -1:
|
|
3045
3197
|
# Nope, that's not it. All is lost.
|
|
3046
3198
|
msg = (
|
|
3047
|
-
f
|
|
3048
|
-
f
|
|
3199
|
+
f"A problem was encountered while parsing an XML box:"
|
|
3200
|
+
f"\n\n\t"
|
|
3049
3201
|
f'"{str(err)}"'
|
|
3050
|
-
f
|
|
3051
|
-
f
|
|
3202
|
+
f"\n\n"
|
|
3203
|
+
f"No XML was retrieved."
|
|
3052
3204
|
)
|
|
3053
3205
|
warnings.warn(msg, UserWarning)
|
|
3054
3206
|
return XMLBox(xml=None, length=length, offset=offset)
|
|
3055
3207
|
|
|
3056
|
-
text = read_buffer[decl_start:].decode(
|
|
3208
|
+
text = read_buffer[decl_start:].decode("utf-8")
|
|
3057
3209
|
|
|
3058
3210
|
# Let the user know that the XML box was problematic.
|
|
3059
3211
|
msg = (
|
|
3060
|
-
f
|
|
3061
|
-
f
|
|
3062
|
-
f
|
|
3212
|
+
f"A UnicodeDecodeError was encountered parsing an XML box "
|
|
3213
|
+
f"at byte position {offset:d} ({err.reason}), but the XML was "
|
|
3214
|
+
f"still recovered."
|
|
3063
3215
|
)
|
|
3064
3216
|
warnings.warn(msg, UserWarning)
|
|
3065
3217
|
|
|
3066
3218
|
# Strip out any trailing nulls, as they can foul up XML parsing.
|
|
3067
3219
|
text = text.rstrip(chr(0))
|
|
3068
|
-
f = io.BytesIO(text.encode(
|
|
3220
|
+
f = io.BytesIO(text.encode("utf-8"))
|
|
3069
3221
|
|
|
3070
3222
|
try:
|
|
3071
3223
|
xml = ET.parse(f)
|
|
3072
3224
|
except ET.ParseError as err:
|
|
3073
3225
|
exc_type, _, _ = sys.exc_info()
|
|
3074
3226
|
msg = (
|
|
3075
|
-
f
|
|
3076
|
-
f
|
|
3077
|
-
f
|
|
3227
|
+
f"{exc_type.__name__} encountered while parsing an XML "
|
|
3228
|
+
f"box at byte offset {offset:d}:"
|
|
3229
|
+
f"\n\n\t"
|
|
3078
3230
|
f'"{err}"'
|
|
3079
|
-
f
|
|
3080
|
-
f
|
|
3231
|
+
f"\n\n"
|
|
3232
|
+
f"No XML was retrieved."
|
|
3081
3233
|
)
|
|
3082
3234
|
warnings.warn(msg, UserWarning)
|
|
3083
3235
|
xml = None
|
|
@@ -3101,8 +3253,9 @@ class UUIDListBox(Jp2kBox):
|
|
|
3101
3253
|
ulst : list
|
|
3102
3254
|
List of UUIDs.
|
|
3103
3255
|
"""
|
|
3104
|
-
|
|
3105
|
-
|
|
3256
|
+
|
|
3257
|
+
box_id = "ulst"
|
|
3258
|
+
longname = "UUID List"
|
|
3106
3259
|
|
|
3107
3260
|
def __init__(self, ulst, length=0, offset=-1):
|
|
3108
3261
|
super().__init__()
|
|
@@ -3116,24 +3269,24 @@ class UUIDListBox(Jp2kBox):
|
|
|
3116
3269
|
|
|
3117
3270
|
def __str__(self):
|
|
3118
3271
|
title = Jp2kBox.__str__(self)
|
|
3119
|
-
if get_option(
|
|
3272
|
+
if get_option("print.short") is True:
|
|
3120
3273
|
return title
|
|
3121
3274
|
|
|
3122
3275
|
lst = []
|
|
3123
3276
|
for j, uuid_item in enumerate(self.ulst):
|
|
3124
|
-
text = f
|
|
3277
|
+
text = f"UUID[{j}]: {uuid_item}"
|
|
3125
3278
|
lst.append(text)
|
|
3126
|
-
body =
|
|
3127
|
-
body = textwrap.indent(body,
|
|
3279
|
+
body = "\n".join(lst)
|
|
3280
|
+
body = textwrap.indent(body, " " * 4)
|
|
3128
3281
|
|
|
3129
|
-
text =
|
|
3282
|
+
text = "\n".join([title, body])
|
|
3130
3283
|
return text
|
|
3131
3284
|
|
|
3132
3285
|
def write(self, fptr):
|
|
3133
3286
|
"""Write a UUID list box to file."""
|
|
3134
3287
|
num_uuids = len(self.ulst)
|
|
3135
3288
|
length = 4 + 4 + 2 + num_uuids * 16
|
|
3136
|
-
write_buffer = struct.pack(
|
|
3289
|
+
write_buffer = struct.pack(">I4sH", length, b"ulst", num_uuids)
|
|
3137
3290
|
fptr.write(write_buffer)
|
|
3138
3291
|
|
|
3139
3292
|
for j in range(num_uuids):
|
|
@@ -3160,7 +3313,7 @@ class UUIDListBox(Jp2kBox):
|
|
|
3160
3313
|
num_bytes = offset + length - fptr.tell()
|
|
3161
3314
|
read_buffer = fptr.read(num_bytes)
|
|
3162
3315
|
|
|
3163
|
-
num_uuids, = struct.unpack_from(
|
|
3316
|
+
(num_uuids,) = struct.unpack_from(">H", read_buffer)
|
|
3164
3317
|
|
|
3165
3318
|
ulst = []
|
|
3166
3319
|
for j in range(num_uuids):
|
|
@@ -3186,8 +3339,9 @@ class UUIDInfoBox(Jp2kBox):
|
|
|
3186
3339
|
box : list
|
|
3187
3340
|
List of boxes contained in this superbox.
|
|
3188
3341
|
"""
|
|
3189
|
-
|
|
3190
|
-
|
|
3342
|
+
|
|
3343
|
+
box_id = "uinf"
|
|
3344
|
+
longname = "UUIDInfo"
|
|
3191
3345
|
|
|
3192
3346
|
def __init__(self, box=None, length=0, offset=-1):
|
|
3193
3347
|
super().__init__()
|
|
@@ -3205,7 +3359,7 @@ class UUIDInfoBox(Jp2kBox):
|
|
|
3205
3359
|
|
|
3206
3360
|
def write(self, fptr):
|
|
3207
3361
|
"""Write a UUIDInfo box to file."""
|
|
3208
|
-
self._write_superbox(fptr, b
|
|
3362
|
+
self._write_superbox(fptr, b"uinf")
|
|
3209
3363
|
|
|
3210
3364
|
@classmethod
|
|
3211
3365
|
def parse(cls, fptr, offset, length):
|
|
@@ -3255,8 +3409,9 @@ class DataEntryURLBox(Jp2kBox):
|
|
|
3255
3409
|
URL : str
|
|
3256
3410
|
Associated URL.
|
|
3257
3411
|
"""
|
|
3258
|
-
|
|
3259
|
-
|
|
3412
|
+
|
|
3413
|
+
box_id = "url "
|
|
3414
|
+
longname = "Data Entry URL"
|
|
3260
3415
|
|
|
3261
3416
|
def __init__(self, version, flag, url, length=0, offset=-1):
|
|
3262
3417
|
super().__init__()
|
|
@@ -3276,10 +3431,13 @@ class DataEntryURLBox(Jp2kBox):
|
|
|
3276
3431
|
|
|
3277
3432
|
length = 8 + 1 + 3 + len(url)
|
|
3278
3433
|
write_buffer = struct.pack(
|
|
3279
|
-
|
|
3280
|
-
length,
|
|
3434
|
+
">I4sBBBB",
|
|
3435
|
+
length,
|
|
3436
|
+
b"url ",
|
|
3281
3437
|
self.version,
|
|
3282
|
-
self.flag[0],
|
|
3438
|
+
self.flag[0],
|
|
3439
|
+
self.flag[1],
|
|
3440
|
+
self.flag[2],
|
|
3283
3441
|
)
|
|
3284
3442
|
fptr.write(write_buffer)
|
|
3285
3443
|
fptr.write(url)
|
|
@@ -3291,17 +3449,17 @@ class DataEntryURLBox(Jp2kBox):
|
|
|
3291
3449
|
|
|
3292
3450
|
def __str__(self):
|
|
3293
3451
|
title = Jp2kBox.__str__(self)
|
|
3294
|
-
if get_option(
|
|
3452
|
+
if get_option("print.short") is True:
|
|
3295
3453
|
return title
|
|
3296
3454
|
|
|
3297
3455
|
body = (
|
|
3298
|
-
f
|
|
3299
|
-
f
|
|
3456
|
+
f"Version: {self.version}\n"
|
|
3457
|
+
f"Flag: {self.flag[0]} {self.flag[1]} {self.flag[2]}\n"
|
|
3300
3458
|
f'URL: "{self.url}"'
|
|
3301
3459
|
)
|
|
3302
|
-
body = textwrap.indent(body,
|
|
3460
|
+
body = textwrap.indent(body, " " * 4)
|
|
3303
3461
|
|
|
3304
|
-
text =
|
|
3462
|
+
text = "\n".join([title, body])
|
|
3305
3463
|
return text
|
|
3306
3464
|
|
|
3307
3465
|
@classmethod
|
|
@@ -3324,11 +3482,11 @@ class DataEntryURLBox(Jp2kBox):
|
|
|
3324
3482
|
"""
|
|
3325
3483
|
num_bytes = offset + length - fptr.tell()
|
|
3326
3484
|
read_buffer = fptr.read(num_bytes)
|
|
3327
|
-
data = struct.unpack_from(
|
|
3485
|
+
data = struct.unpack_from(">BBBB", read_buffer)
|
|
3328
3486
|
version = data[0]
|
|
3329
3487
|
flag = data[1:4]
|
|
3330
3488
|
|
|
3331
|
-
url = read_buffer[4:].decode(
|
|
3489
|
+
url = read_buffer[4:].decode("utf-8").rstrip(chr(0))
|
|
3332
3490
|
return cls(version, flag, url, length=length, offset=offset)
|
|
3333
3491
|
|
|
3334
3492
|
|
|
@@ -3350,10 +3508,11 @@ class UnknownBox(Jp2kBox):
|
|
|
3350
3508
|
longname : str
|
|
3351
3509
|
more verbose description of the box.
|
|
3352
3510
|
"""
|
|
3353
|
-
|
|
3511
|
+
|
|
3512
|
+
def __init__(self, claimed_box_id, length=0, offset=-1, longname=""):
|
|
3354
3513
|
super().__init__()
|
|
3355
3514
|
self.longname = longname
|
|
3356
|
-
self.box_id =
|
|
3515
|
+
self.box_id = "xxxx"
|
|
3357
3516
|
self.claimed_box_id = claimed_box_id
|
|
3358
3517
|
self.length = length
|
|
3359
3518
|
self.offset = offset
|
|
@@ -3364,8 +3523,8 @@ class UnknownBox(Jp2kBox):
|
|
|
3364
3523
|
|
|
3365
3524
|
def __str__(self):
|
|
3366
3525
|
title = Jp2kBox.__str__(self)
|
|
3367
|
-
body = f
|
|
3368
|
-
text =
|
|
3526
|
+
body = f" Claimed ID: {self.claimed_box_id}"
|
|
3527
|
+
text = "\n".join([title, body])
|
|
3369
3528
|
return text
|
|
3370
3529
|
|
|
3371
3530
|
|
|
@@ -3397,8 +3556,9 @@ class UUIDBox(Jp2kBox):
|
|
|
3397
3556
|
16684-1:2012 - Graphic technology -- Extensible metadata platform (XMP)
|
|
3398
3557
|
specification -- Part 1: Data model, serialization and core properties
|
|
3399
3558
|
"""
|
|
3400
|
-
|
|
3401
|
-
|
|
3559
|
+
|
|
3560
|
+
box_id = "uuid"
|
|
3561
|
+
longname = "UUID"
|
|
3402
3562
|
|
|
3403
3563
|
def __init__(self, the_uuid, raw_data, length=0, offset=-1):
|
|
3404
3564
|
"""Parameters
|
|
@@ -3436,29 +3596,29 @@ class UUIDBox(Jp2kBox):
|
|
|
3436
3596
|
"""Private function for parsing UUID payloads if possible."""
|
|
3437
3597
|
if self.uuid == _XMP_UUID:
|
|
3438
3598
|
|
|
3439
|
-
txt = self.raw_data.decode(
|
|
3599
|
+
txt = self.raw_data.decode("utf-8")
|
|
3440
3600
|
|
|
3441
3601
|
# If XMP comes from a TIFF tag, then it should be terminated
|
|
3442
3602
|
# by a null byte. libxml2 now requires that null byte to be
|
|
3443
3603
|
# stripped off before being fed into lxml.
|
|
3444
|
-
elt = ET.fromstring(txt.strip(
|
|
3604
|
+
elt = ET.fromstring(txt.strip("\x00"))
|
|
3445
3605
|
self.data = ET.ElementTree(elt)
|
|
3446
3606
|
|
|
3447
3607
|
elif self.uuid == _GEOTIFF_UUID:
|
|
3448
3608
|
self.data = tiff_header(self.raw_data)
|
|
3449
3609
|
elif self.uuid == _EXIF_UUID:
|
|
3450
|
-
if self.raw_data[0:4].decode(
|
|
3610
|
+
if self.raw_data[0:4].decode("utf-8").lower() == "exif":
|
|
3451
3611
|
# Cut off 'EXIF\0\0' part.
|
|
3452
3612
|
payload = self.raw_data[6:]
|
|
3453
3613
|
self.data = tiff_header(payload)
|
|
3454
|
-
elif self.raw_data[:2].decode(
|
|
3614
|
+
elif self.raw_data[:2].decode("utf-8").lower() in ["ii", "mm"]:
|
|
3455
3615
|
# Missing the exif lead-in, take the data as-is.
|
|
3456
3616
|
payload = self.raw_data
|
|
3457
3617
|
self.data = tiff_header(payload)
|
|
3458
3618
|
else:
|
|
3459
3619
|
msg = (
|
|
3460
|
-
|
|
3461
|
-
|
|
3620
|
+
"A UUID that identified itself as an EXIF UUID could not "
|
|
3621
|
+
"be parsed."
|
|
3462
3622
|
)
|
|
3463
3623
|
warnings.warn(msg)
|
|
3464
3624
|
self.data = None
|
|
@@ -3466,36 +3626,34 @@ class UUIDBox(Jp2kBox):
|
|
|
3466
3626
|
self.data = self.raw_data
|
|
3467
3627
|
|
|
3468
3628
|
def __repr__(self):
|
|
3469
|
-
msg = (
|
|
3470
|
-
"glymur.jp2box.UUIDBox({0}, raw_data=<byte array {1} elements>)"
|
|
3471
|
-
)
|
|
3629
|
+
msg = "glymur.jp2box.UUIDBox({0}, raw_data=<byte array {1} elements>)"
|
|
3472
3630
|
return msg.format(repr(self.uuid), len(self.raw_data))
|
|
3473
3631
|
|
|
3474
3632
|
def __str__(self):
|
|
3475
3633
|
title = Jp2kBox.__str__(self)
|
|
3476
|
-
if get_option(
|
|
3634
|
+
if get_option("print.short") is True:
|
|
3477
3635
|
return title
|
|
3478
3636
|
|
|
3479
|
-
text = f
|
|
3637
|
+
text = f"UUID: {self.uuid}"
|
|
3480
3638
|
if self.uuid == _XMP_UUID:
|
|
3481
|
-
text +=
|
|
3639
|
+
text += " (XMP)"
|
|
3482
3640
|
elif self.uuid == _GEOTIFF_UUID:
|
|
3483
|
-
text +=
|
|
3641
|
+
text += " (GeoTIFF)"
|
|
3484
3642
|
elif self.uuid == _EXIF_UUID:
|
|
3485
|
-
text +=
|
|
3643
|
+
text += " (EXIF)"
|
|
3486
3644
|
else:
|
|
3487
|
-
text +=
|
|
3645
|
+
text += " (unknown)"
|
|
3488
3646
|
|
|
3489
3647
|
lst = [text]
|
|
3490
3648
|
|
|
3491
|
-
if not get_option(
|
|
3649
|
+
if not get_option("print.xml") and self.uuid == _XMP_UUID:
|
|
3492
3650
|
# If it's an XMP UUID, don't print the XML contents.
|
|
3493
3651
|
pass
|
|
3494
3652
|
|
|
3495
3653
|
elif self.uuid == _XMP_UUID:
|
|
3496
|
-
b = ET.tostring(self.data, encoding=
|
|
3497
|
-
s = b.decode(
|
|
3498
|
-
text = f
|
|
3654
|
+
b = ET.tostring(self.data, encoding="utf-8", pretty_print=True)
|
|
3655
|
+
s = b.decode("utf-8").strip()
|
|
3656
|
+
text = f"UUID Data:\n{s}"
|
|
3499
3657
|
lst.append(text)
|
|
3500
3658
|
elif self.uuid == _EXIF_UUID:
|
|
3501
3659
|
s = io.StringIO()
|
|
@@ -3503,35 +3661,35 @@ class UUIDBox(Jp2kBox):
|
|
|
3503
3661
|
if self.data is None:
|
|
3504
3662
|
# If the UUID was malformed, just say so and go on. This
|
|
3505
3663
|
# should not be a showstopper.
|
|
3506
|
-
text =
|
|
3664
|
+
text = "UUID Data: Invalid Exif UUID"
|
|
3507
3665
|
lst.append(text)
|
|
3508
3666
|
else:
|
|
3509
3667
|
with np.printoptions(threshold=4):
|
|
3510
3668
|
pprint.pprint(self.data, stream=s, indent=4)
|
|
3511
|
-
text = f
|
|
3669
|
+
text = f"UUID Data: {s.getvalue().rstrip()}"
|
|
3512
3670
|
lst.append(text)
|
|
3513
3671
|
elif self.uuid == _GEOTIFF_UUID:
|
|
3514
3672
|
|
|
3515
3673
|
options = gdal.InfoOptions(showColorTable=False)
|
|
3516
3674
|
txt = gdal.Info(self._fptr.name, options=options)
|
|
3517
|
-
txt = textwrap.indent(txt,
|
|
3675
|
+
txt = textwrap.indent(txt, " " * 4).rstrip()
|
|
3518
3676
|
|
|
3519
|
-
txt = f
|
|
3677
|
+
txt = f"UUID Data:\n{txt}"
|
|
3520
3678
|
lst.append(txt)
|
|
3521
3679
|
else:
|
|
3522
|
-
text = f
|
|
3680
|
+
text = f"UUID Data: {len(self.raw_data)} bytes"
|
|
3523
3681
|
lst.append(text)
|
|
3524
3682
|
|
|
3525
|
-
body =
|
|
3526
|
-
body = textwrap.indent(body,
|
|
3683
|
+
body = "\n".join(lst)
|
|
3684
|
+
body = textwrap.indent(body, " " * 4)
|
|
3527
3685
|
|
|
3528
|
-
text =
|
|
3686
|
+
text = "\n".join([title, body])
|
|
3529
3687
|
return text
|
|
3530
3688
|
|
|
3531
3689
|
def write(self, fptr):
|
|
3532
3690
|
"""Write a UUID box to file."""
|
|
3533
3691
|
length = 4 + 4 + 16 + len(self.raw_data)
|
|
3534
|
-
write_buffer = struct.pack(
|
|
3692
|
+
write_buffer = struct.pack(">I4s", length, b"uuid")
|
|
3535
3693
|
fptr.write(write_buffer)
|
|
3536
3694
|
fptr.write(self.uuid.bytes)
|
|
3537
3695
|
fptr.write(self.raw_data)
|
|
@@ -3565,35 +3723,36 @@ class UUIDBox(Jp2kBox):
|
|
|
3565
3723
|
|
|
3566
3724
|
# Map each box ID to the corresponding class.
|
|
3567
3725
|
_BOX_WITH_ID = {
|
|
3568
|
-
b
|
|
3569
|
-
b
|
|
3570
|
-
b
|
|
3571
|
-
b
|
|
3572
|
-
b
|
|
3573
|
-
b
|
|
3574
|
-
b
|
|
3575
|
-
b
|
|
3576
|
-
b
|
|
3577
|
-
b
|
|
3578
|
-
b
|
|
3579
|
-
b
|
|
3580
|
-
b
|
|
3581
|
-
b
|
|
3582
|
-
b
|
|
3583
|
-
b
|
|
3584
|
-
b
|
|
3585
|
-
b
|
|
3586
|
-
b
|
|
3587
|
-
b
|
|
3588
|
-
b
|
|
3589
|
-
b
|
|
3590
|
-
b
|
|
3591
|
-
b
|
|
3592
|
-
b
|
|
3593
|
-
b
|
|
3594
|
-
b
|
|
3595
|
-
b
|
|
3596
|
-
b
|
|
3726
|
+
b"asoc": AssociationBox,
|
|
3727
|
+
b"bpcc": BitsPerComponentBox,
|
|
3728
|
+
b"cdef": ChannelDefinitionBox,
|
|
3729
|
+
b"cgrp": ColourGroupBox,
|
|
3730
|
+
b"cmap": ComponentMappingBox,
|
|
3731
|
+
b"colr": ColourSpecificationBox,
|
|
3732
|
+
b"dtbl": DataReferenceBox,
|
|
3733
|
+
b"ftyp": FileTypeBox,
|
|
3734
|
+
b"ihdr": ImageHeaderBox,
|
|
3735
|
+
b"jP ": JPEG2000SignatureBox,
|
|
3736
|
+
b"jpch": CodestreamHeaderBox,
|
|
3737
|
+
b"jplh": CompositingLayerHeaderBox,
|
|
3738
|
+
b"jp2c": ContiguousCodestreamBox,
|
|
3739
|
+
b"free": FreeBox,
|
|
3740
|
+
b"flst": FragmentListBox,
|
|
3741
|
+
b"ftbl": FragmentTableBox,
|
|
3742
|
+
b"jp2h": JP2HeaderBox,
|
|
3743
|
+
b"lbl ": LabelBox,
|
|
3744
|
+
b"nlst": NumberListBox,
|
|
3745
|
+
b"pclr": PaletteBox,
|
|
3746
|
+
b"res ": ResolutionBox,
|
|
3747
|
+
b"resc": CaptureResolutionBox,
|
|
3748
|
+
b"resd": DisplayResolutionBox,
|
|
3749
|
+
b"rreq": ReaderRequirementsBox,
|
|
3750
|
+
b"uinf": UUIDInfoBox,
|
|
3751
|
+
b"ulst": UUIDListBox,
|
|
3752
|
+
b"url ": DataEntryURLBox,
|
|
3753
|
+
b"uuid": UUIDBox,
|
|
3754
|
+
b"xml ": XMLBox,
|
|
3755
|
+
}
|
|
3597
3756
|
|
|
3598
3757
|
|
|
3599
3758
|
def decompose_resolution(value: Number) -> Tuple[int, int, int]:
|