2020-08-02 21:08:46 -04:00
|
|
|
from PIL import Image
|
|
|
|
from PyQt4 import QtGui
|
|
|
|
from apng import APNG
|
|
|
|
import io
|
|
|
|
|
2020-08-03 09:22:29 -04:00
|
|
|
APNG_DISPOSE_OP_NONE = 0
|
|
|
|
APNG_DISPOSE_OP_BACKGROUND = 1
|
|
|
|
APNG_DISPOSE_OP_PREVIOUS = 2
|
|
|
|
APNG_BLEND_OP_SOURCE = 0
|
|
|
|
APNG_BLEND_OP_OVER = 1
|
2020-08-04 00:53:53 -04:00
|
|
|
disposes = ["APNG_DISPOSE_OP_NONE", "APNG_DISPOSE_OP_BACKGROUND", "APNG_DISPOSE_OP_PREVIOUS"]
|
|
|
|
blends = ["APNG_BLEND_OP_SOURCE", "APNG_BLEND_OP_OVER"]
|
2020-08-03 09:22:29 -04:00
|
|
|
|
|
|
|
# layman's terms of above apng constants so i can easily wrap my head around it:
|
|
|
|
#
|
|
|
|
# APNG_DISPOSE_OP_NONE: do nothing with next frame, keep it as it is
|
|
|
|
# APNG_DISPOSE_OP_BACKGROUND: turn the current frame into full transparency before pasting the next frame
|
|
|
|
# APNG_DISPOSE_OP_PREVIOUS: grab the previous frame, and paste the next one into THAT previous frame
|
|
|
|
#
|
|
|
|
# APNG_BLEND_OP_SOURCE: replace frame region
|
|
|
|
# APNG_BLEND_OP_OVER: blend with frame
|
|
|
|
|
2020-08-04 00:53:53 -04:00
|
|
|
def load_apng(file): # this one was hell to implement compared to the three funcs below
|
2020-08-03 09:22:29 -04:00
|
|
|
img = APNG.open(file)
|
|
|
|
frames = []
|
|
|
|
pilframes = []
|
|
|
|
dispose_op = 0
|
|
|
|
|
|
|
|
width, height = img.frames[0][0].width, img.frames[0][0].height
|
2020-08-04 00:53:53 -04:00
|
|
|
outputbuf = Image.new("RGBA", (width, height), (255,255,255,0))
|
|
|
|
prev_frame = None
|
2020-08-03 09:22:29 -04:00
|
|
|
|
|
|
|
for frame, frame_info in img.frames:
|
|
|
|
i = img.frames.index((frame, frame_info))
|
2020-08-03 10:48:02 -04:00
|
|
|
pilframe = Image.open(io.BytesIO(frame.to_bytes())).convert("RGBA")
|
2020-08-03 09:22:29 -04:00
|
|
|
|
2020-08-04 00:53:53 -04:00
|
|
|
if dispose_op == APNG_DISPOSE_OP_BACKGROUND or (i == 0 and dispose_op == APNG_DISPOSE_OP_PREVIOUS):
|
|
|
|
prev_frame = outputbuf.copy()
|
|
|
|
emptyrect = Image.new("RGBA", (img.frames[i-1][0].width, img.frames[i-1][0].height), (255,255,255,0))
|
|
|
|
outputbuf.paste(emptyrect, (img.frames[i-1][1].x_offset, img.frames[i-1][1].y_offset))
|
2020-08-03 09:22:29 -04:00
|
|
|
|
|
|
|
elif dispose_op == APNG_DISPOSE_OP_PREVIOUS:
|
2020-08-04 00:53:53 -04:00
|
|
|
outputbuf = prev_frame.copy()
|
2020-08-03 09:22:29 -04:00
|
|
|
|
|
|
|
else:
|
2020-08-04 00:53:53 -04:00
|
|
|
prev_frame = outputbuf.copy()
|
2020-08-03 09:22:29 -04:00
|
|
|
|
2020-08-03 10:48:02 -04:00
|
|
|
if frame_info:
|
2020-08-04 00:53:53 -04:00
|
|
|
outputbuf.paste(pilframe, (frame_info.x_offset, frame_info.y_offset), pilframe.convert("RGBA") if frame_info.blend_op == APNG_BLEND_OP_OVER else None)
|
2020-08-03 10:48:02 -04:00
|
|
|
else:
|
2020-08-04 00:53:53 -04:00
|
|
|
outputbuf.paste(pilframe, (0, 0))
|
2020-08-03 10:48:02 -04:00
|
|
|
|
2020-08-04 00:53:53 -04:00
|
|
|
final_frame = outputbuf.copy()
|
|
|
|
pilframes.append(final_frame)
|
|
|
|
if frame_info:
|
2025-02-14 21:21:27 -05:00
|
|
|
# convert delay from centiseconds to milliseconds
|
|
|
|
if frame_info.delay_den == 100:
|
|
|
|
delay = frame_info.delay * 10
|
|
|
|
else:
|
|
|
|
delay = frame_info.delay * (1000.0 / frame_info.delay_den)
|
|
|
|
frames.append([final_frame.toqimage(), delay])
|
2020-08-04 00:53:53 -04:00
|
|
|
dispose_op = frame_info.depose_op
|
|
|
|
else:
|
|
|
|
frames.append([final_frame.toqimage(), 0])
|
2020-08-03 09:22:29 -04:00
|
|
|
|
2020-08-04 00:53:53 -04:00
|
|
|
for frame in pilframes: frame.close()
|
2020-08-03 09:22:29 -04:00
|
|
|
return frames
|
2020-08-02 21:08:46 -04:00
|
|
|
|
|
|
|
def load_webp(file):
|
2020-08-03 09:22:29 -04:00
|
|
|
img = Image.open(file)
|
|
|
|
frames = []
|
|
|
|
|
|
|
|
for i in range(img.n_frames):
|
|
|
|
img.seek(i)
|
|
|
|
img.load() # strange thing with Pillow and animated webp's is that the img.info dictionary attr doesn't update unless you call a function like this
|
2020-08-03 09:41:51 -04:00
|
|
|
frames.append([img.toqimage(), img.info["duration"]])
|
2020-08-03 09:22:29 -04:00
|
|
|
|
2020-08-04 23:29:43 -04:00
|
|
|
return frames, img.info["loop"]
|
2020-08-02 21:08:46 -04:00
|
|
|
|
|
|
|
def get_apng_duration(file):
|
|
|
|
img = APNG.open(file)
|
|
|
|
dur = 0
|
2020-08-03 09:22:29 -04:00
|
|
|
|
2020-08-02 21:08:46 -04:00
|
|
|
for frame, frame_info in img.frames:
|
2025-02-21 00:05:19 -05:00
|
|
|
if frame_info:
|
|
|
|
# convert delay from centiseconds to milliseconds
|
|
|
|
if frame_info.delay_den == 100:
|
|
|
|
dur += frame_info.delay * 10
|
|
|
|
else:
|
|
|
|
dur += frame_info.delay * (1000.0 / frame_info.delay_den)
|
2020-08-02 21:08:46 -04:00
|
|
|
|
|
|
|
return dur
|
|
|
|
|
|
|
|
def get_webp_duration(file):
|
|
|
|
img = Image.open(file)
|
|
|
|
dur = 0
|
2020-08-03 09:22:29 -04:00
|
|
|
|
2020-08-02 21:08:46 -04:00
|
|
|
for i in range(img.n_frames):
|
|
|
|
img.seek(i)
|
|
|
|
img.load() # strange thing with Pillow and animated webp's is that the img.info dictionary attr doesn't update unless you call a function like this
|
|
|
|
dur += img.info["duration"]
|
|
|
|
|
|
|
|
return dur
|