Skip to content

Commit 335dd46

Browse files
author
Steve Canny
committed
img: add _App1Marker.from_stream()
1 parent 95c72b8 commit 335dd46

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

docx/image/jpeg.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,46 @@ class _App1Marker(_Marker):
397397
"""
398398
Represents a JFIF APP1 (Exif) marker segment.
399399
"""
400+
@classmethod
401+
def from_stream(cls, stream, marker_code, offset):
402+
"""
403+
Extract the horizontal and vertical dots-per-inch value from the APP1
404+
header at *offset* in *stream*.
405+
"""
406+
# field off len type notes
407+
# -------------------- --- --- ----- ----------------------------
408+
# segment length 0 2 short
409+
# Exif identifier 2 6 6 chr 'Exif\x00\x00'
410+
# TIFF byte order 8 2 2 chr 'II'=little 'MM'=big endian
411+
# meaning of universe 10 2 2 chr '*\x00' or '\x00*' depending
412+
# IFD0 off fr/II or MM 10 16 long relative to ...?
413+
# -------------------- --- --- ----- ----------------------------
414+
segment_length = stream.read_short(offset)
415+
if cls._is_non_Exif_APP1_segment(stream, offset):
416+
return cls(marker_code, offset, segment_length, 72, 72)
417+
tiff = cls._tiff_from_exif_segment(stream, offset, segment_length)
418+
return cls(
419+
marker_code, offset, segment_length, tiff.horz_dpi, tiff.vert_dpi
420+
)
421+
422+
@classmethod
423+
def _is_non_Exif_APP1_segment(cls, stream, offset):
424+
"""
425+
Return True if the APP1 segment at *offset* in *stream* is NOT an
426+
Exif segment, as determined by the ``'Exif\x00\x00'`` signature at
427+
offset 2 in the segment.
428+
"""
429+
stream.seek(offset+2)
430+
exif_signature = stream.read(6)
431+
return exif_signature != b'Exif\x00\x00'
432+
433+
@classmethod
434+
def _tiff_from_exif_segment(cls, stream, offset, segment_length):
435+
"""
436+
Return a |Tiff| instance parsed from the Exif APP1 segment of
437+
*segment_length* at *offset* in *stream*.
438+
"""
439+
raise NotImplementedError
400440

401441

402442
class _SofMarker(_Marker):

tests/image/test_jpeg.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
_App0Marker, _App1Marker, Exif, Jfif, _JfifMarkers, Jpeg, _Marker,
1818
_MarkerFactory, _MarkerFinder, _MarkerParser, _SofMarker
1919
)
20+
from docx.image.tiff import Tiff
2021

21-
from ..unitutil import class_mock, initializer_mock, instance_mock
22+
from ..unitutil import (
23+
initializer_mock, class_mock, instance_mock, method_mock
24+
)
2225

2326

2427
class DescribeJpeg(object):
@@ -347,6 +350,66 @@ def from_stream_fixture(self, request, _App0Marker__init_):
347350
)
348351

349352

353+
class Describe_App1Marker(object):
354+
355+
def it_can_construct_from_a_stream_and_offset(self, from_stream_fixture):
356+
(stream, marker_code, offset, _App1Marker__init_, length,
357+
_tiff_from_exif_segment_, horz_dpi, vert_dpi) = from_stream_fixture
358+
app1_marker = _App1Marker.from_stream(stream, marker_code, offset)
359+
_tiff_from_exif_segment_.assert_called_once_with(
360+
stream, offset, length
361+
)
362+
_App1Marker__init_.assert_called_once_with(
363+
marker_code, offset, length, horz_dpi, vert_dpi
364+
)
365+
assert isinstance(app1_marker, _App1Marker)
366+
367+
def it_can_construct_from_non_Exif_APP1_segment(self, non_Exif_fixture):
368+
stream, marker_code, offset = non_Exif_fixture[:3]
369+
_App1Marker__init_, length = non_Exif_fixture[3:]
370+
app1_marker = _App1Marker.from_stream(stream, marker_code, offset)
371+
_App1Marker__init_.assert_called_once_with(
372+
marker_code, offset, length, 72, 72
373+
)
374+
assert isinstance(app1_marker, _App1Marker)
375+
376+
# fixtures -------------------------------------------------------
377+
378+
@pytest.fixture
379+
def _App1Marker__init_(self, request):
380+
return initializer_mock(request, _App1Marker)
381+
382+
@pytest.fixture
383+
def from_stream_fixture(
384+
self, request, _App1Marker__init_, _tiff_from_exif_segment_):
385+
bytes_ = b'\x00\x42Exif\x00\x00'
386+
stream_reader = StreamReader(BytesIO(bytes_), BIG_ENDIAN)
387+
marker_code, offset, length = JPEG_MARKER_CODE.APP1, 0, 66
388+
horz_dpi, vert_dpi = 42, 24
389+
return (
390+
stream_reader, marker_code, offset, _App1Marker__init_, length,
391+
_tiff_from_exif_segment_, horz_dpi, vert_dpi
392+
)
393+
394+
@pytest.fixture
395+
def non_Exif_fixture(self, request, _App1Marker__init_):
396+
bytes_ = b'\x00\x42Foobar'
397+
stream_reader = StreamReader(BytesIO(bytes_), BIG_ENDIAN)
398+
marker_code, offset, length = JPEG_MARKER_CODE.APP1, 0, 66
399+
return stream_reader, marker_code, offset, _App1Marker__init_, length
400+
401+
@pytest.fixture
402+
def tiff_(self, request):
403+
return instance_mock(request, Tiff, horz_dpi=42, vert_dpi=24)
404+
405+
@pytest.fixture
406+
def _tiff_from_exif_segment_(self, request, tiff_):
407+
return method_mock(
408+
request, _App1Marker, '_tiff_from_exif_segment',
409+
return_value=tiff_
410+
)
411+
412+
350413
class Describe_SofMarker(object):
351414

352415
def it_can_construct_from_a_stream_and_offset(self, from_stream_fixture):

0 commit comments

Comments
 (0)