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