Skip to content

Commit

Permalink
Merge 0777554 into a021d49
Browse files Browse the repository at this point in the history
  • Loading branch information
wiredfool committed Jan 1, 2017
2 parents a021d49 + 0777554 commit 3e94556
Show file tree
Hide file tree
Showing 12 changed files with 79 additions and 13 deletions.
1 change: 1 addition & 0 deletions PIL/DcxImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def _open(self):
self._offset.append(offset)

self.__fp = self.fp
self._exclusive_fp = False
self.seek(0)

@property
Expand Down
1 change: 1 addition & 0 deletions PIL/FliImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def _open(self):
# set things up to decode first frame
self.__frame = -1
self.__fp = self.fp
self._exclusive_fp = False
self.__rewind = self.fp.tell()
self._n_frames = None
self._is_animated = None
Expand Down
41 changes: 35 additions & 6 deletions PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def _open(self):
break

self.__fp = self.fp # FIXME: hack
self._exclusive_fp = False
self.__rewind = self.fp.tell()
self._n_frames = None
self._is_animated = None
Expand All @@ -97,7 +98,7 @@ def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
while not self.__fp.closed:
self.seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
Expand All @@ -121,6 +122,8 @@ def is_animated(self):
def seek(self, frame):
if frame == self.__frame:
return
if self._n_frames and frame >= self._n_frames:
raise EOFError("No more images in GIF file")
if frame < self.__frame:
self._seek(0)

Expand All @@ -130,13 +133,14 @@ def seek(self, frame):
self._seek(f)
except EOFError:
self.seek(last_frame)
raise EOFError("no more images in GIF file")
raise EOFError("No more images in GIF file")

def _seek(self, frame):

if frame == 0:
# rewind
self.__offset = 0
self.__tile_end = 0
self.dispose = None
self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1
self.__frame = -1
Expand All @@ -146,7 +150,11 @@ def _seek(self, frame):
else:
# ensure that the previous frame was loaded
if not self.im:
# file pointer could close here.
self.load()
if self.__fp.closed:
raise EOFError("No more images in GIF file")


if frame != self.__frame + 1:
raise ValueError("cannot seek to frame %d" % frame)
Expand All @@ -155,12 +163,11 @@ def _seek(self, frame):
self.tile = []

self.fp = self.__fp
if self.__offset:
if self.__tile_end:
# backup to last frame
self.fp.seek(self.__offset)
while self.data():
pass
self.fp.seek(self.__tile_end)
self.__offset = 0
self.__tile_end = 0

if self.dispose:
self.im.paste(self.dispose, self.dispose_extent)
Expand Down Expand Up @@ -241,6 +248,11 @@ def _seek(self, frame):
(x0, y0, x1, y1),
self.__offset,
(bits, interlace))]

while self.data():
pass
self.__tile_end = self.fp.tell()

break

else:
Expand Down Expand Up @@ -280,6 +292,23 @@ def tell(self):
def load_end(self):
ImageFile.ImageFile.load_end(self)

# Set exclusive_fp to close if possible
if self.__frame == 0 and (self._n_frames is None and
self._is_animated is None):
try:
pos = self.__fp.tell()
if self.__tile_end:
self.__fp.seek(self.__tile_end)
# We've seeked to exactly the end of the image,
# check to see if there's more
if len(self.__fp.read(10)) < 10:
raise EOFError
self.__fp.seek(pos)
except EOFError:
self._exclusive_fp = True
self._n_frames = 1
self._is_animated = False

# if the disposal method is 'do not dispose', transparent
# pixels should show the content of the previous frame
if self._prev_im and self.disposal_method == 1:
Expand Down
1 change: 1 addition & 0 deletions PIL/ImImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ def _open(self):
self.__offset = offs = self.fp.tell()

self.__fp = self.fp # FIXME: hack
self._exclusive_fp = False

if self.rawmode[:2] == "F;":

Expand Down
7 changes: 7 additions & 0 deletions PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -2297,6 +2297,7 @@ def open(fp, mode="r"):
if mode != "r":
raise ValueError("bad mode %r" % mode)

exclusive_fp = False
filename = ""
if isPath(fp):
filename = fp
Expand All @@ -2310,11 +2311,13 @@ def open(fp, mode="r"):

if filename:
fp = builtins.open(filename, "rb")
exclusive_fp = True

try:
fp.seek(0)
except (AttributeError, io.UnsupportedOperation):
fp = io.BytesIO(fp.read())
exclusive_fp = True

prefix = fp.read(16)

Expand Down Expand Up @@ -2343,8 +2346,12 @@ def _open_core(fp, filename, prefix):
im = _open_core(fp, filename, prefix)

if im:
if getattr(im, '_exclusive_fp', 'undef') is None:
im._exclusive_fp = exclusive_fp
return im

if exclusive_fp:
fp.close()
raise IOError("cannot identify image file %r"
% (filename if filename else fp))

Expand Down
16 changes: 13 additions & 3 deletions PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,13 @@ def __init__(self, fp=None, filename=None):
# filename
self.fp = open(fp, "rb")
self.filename = fp
self._exclusive_fp = True
else:
# stream
self.fp = fp
self.filename = filename
# can be overridden
self._exclusive_fp = None

try:
self._open()
Expand All @@ -100,6 +103,9 @@ def __init__(self, fp=None, filename=None):
KeyError, # unsupported mode
EOFError, # got header but not the first frame
struct.error) as v:
# close the file only if we have opened it this constructor
if self._exclusive_fp:
self.fp.close()
raise SyntaxError(v)

if not self.mode or self.size[0] <= 0:
Expand All @@ -115,6 +121,8 @@ def verify(self):

# raise exception if something's wrong. must be called
# directly after open, and closes file when finished.
if self._exclusive_fp:
self.fp.close()
self.fp = None

def load(self):
Expand Down Expand Up @@ -237,14 +245,16 @@ def load(self):
self.tile = []
self.readonly = readonly

self.fp = None # might be shared
self.load_end()

if self._exclusive_fp:
self.fp.close()
self.fp = None

if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
# still raised if decoder fails to return anything
raise_ioerror(err_code)

self.load_end()

return Image.Image.load(self)

def load_prepare(self):
Expand Down
1 change: 1 addition & 0 deletions PIL/MicImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def _open(self):
raise SyntaxError("not an MIC file; no image entries")

self.__fp = self.fp
self._exclusive_fp = False
self.frame = 0

if len(self.images) > 1:
Expand Down
1 change: 1 addition & 0 deletions PIL/MpoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def _open(self):
assert self.__framecount == len(self.__mpoffsets)
del self.info['mpoffset'] # no longer needed
self.__fp = self.fp # FIXME: hack
self._exclusive_fp = False
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self.__frame = 0
self.offset = 0
Expand Down
1 change: 1 addition & 0 deletions PIL/SpiderImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def _open(self):
("raw", (0, 0) + self.size, offset,
(self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack
self._exclusive_fp = False

@property
def n_frames(self):
Expand Down
1 change: 1 addition & 0 deletions PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ def _open(self):
self.__first = self.__next = self.tag_v2.next
self.__frame = -1
self.__fp = self.fp
self._exclusive_fp = False
self._frame_pos = []
self._n_frames = None
self._is_animated = None
Expand Down
2 changes: 0 additions & 2 deletions PIL/XpmImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@ def load_read(self, bytes):
for i in range(ysize):
s[i] = self.fp.readline()[1:xsize+1].ljust(xsize)

self.fp = None

return b"".join(s)

#
Expand Down
19 changes: 17 additions & 2 deletions Tests/test_file_gif.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,18 +211,33 @@ def test_seek(self):
except EOFError:
self.assertEqual(framecount, 5)

def test_n_frames(self):
def test_n_frames_single(self):
im = Image.open(TEST_GIF)

self.assertTrue(im.fp)
fp = im.fp
im.load()
self.assertEqual(im.fp, None)
self.assertTrue(fp.closed)

self.assertEqual(im.n_frames, 1)
self.assertFalse(im.is_animated)

def test_n_frames_multi(self):
im = Image.open("Tests/images/iss634.gif")

self.assertTrue(im.fp)
fp = im.fp
im.load()
self.assertEqual(im.fp, None)
self.assertFalse(fp.closed)

self.assertEqual(im.n_frames, 42)
self.assertTrue(im.is_animated)


def test_eoferror(self):
im = Image.open(TEST_GIF)

n_frames = im.n_frames
while True:
n_frames -= 1
Expand Down

0 comments on commit 3e94556

Please sign in to comment.