Skip to content

Commit 926d29e

Browse files
committed
Fixed GIF and WebP animation support
1 parent 338b261 commit 926d29e

File tree

7 files changed

+55
-31
lines changed

7 files changed

+55
-31
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,4 @@ cython_debug/
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161161
.vscode/settings.json
162+
.vscode/launch.json

examples/summary.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
with open('./input/heavy.mp4', 'rb') as f:
44
data = f.read()
55

6-
summary = mediaify.encode_video(
7-
data,
8-
mediaify.AnimationSummaryConfig(
9-
frames=60,
10-
framerate=15,
11-
encoding=mediaify.WEBPAnimationEncodeConfig(
12-
resize=mediaify.ResizeConfig(
13-
width=192,
14-
height=108,
15-
)
16-
)
6+
config = mediaify.AnimationSummaryConfig(
7+
frames=60,
8+
framerate=15,
9+
encoding=mediaify.WEBPAnimationEncodeConfig(
10+
resize=mediaify.ResizeConfig(
11+
width=192,
12+
height=108,
13+
),
14+
quality=70,
1715
)
1816
)
1917

18+
summary = mediaify.encode_video(data, config)
19+
2020

2121
filepath = f"./output/summary{summary.ext}"
2222
with open(filepath, "wb") as f:

mediaify/animation/gif.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .utils import (
77
get_animation_duration_in_seconds,
88
get_frame_lengths,
9-
get_animation_frames,
9+
extract_animation_frames,
1010
)
1111

1212
import io
@@ -17,17 +17,23 @@ def encode_as_gif(
1717
pillow: PILImage.Image,
1818
config: GIFEncodeConfig
1919
) -> AnimationFile:
20+
frames = extract_animation_frames(pillow)
21+
2022
if config.resize is not None:
2123
im_size = (pillow.width, pillow.height)
2224
size = calculate_downscale(im_size, config.resize)
23-
pillow = pillow.resize(size, PILImage.LANCZOS)
25+
frames = [
26+
frame.resize(size, PILImage.LANCZOS)
27+
for frame in frames
28+
]
2429

30+
first_frame = frames[0]
2531
buf = io.BytesIO()
26-
pillow.save(
32+
first_frame.save(
2733
fp=buf,
2834
format='gif',
2935
save_all=True, # Save as an animation
30-
append_images=get_animation_frames(pillow),
36+
append_images=frames[1:],
3137
duration=get_frame_lengths(pillow),
3238
optimize=True,
3339
loop=0,
@@ -36,8 +42,8 @@ def encode_as_gif(
3642
return AnimationFile(
3743
data=buf.getvalue(),
3844
mimetype='image/gif',
39-
width=pillow.width,
40-
height=pillow.height,
45+
width=first_frame.width,
46+
height=first_frame.height,
4147
frame_count=pillow.n_frames,
4248
duration=get_animation_duration_in_seconds(pillow),
4349
)

mediaify/animation/utils.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from PIL.Image import Image
2-
from typing import List
1+
from PIL.Image import Image, LANCZOS
2+
from typing import List, Tuple
33

44

55
def get_animation_duration_in_milliseconds(pillow: Image) -> int:
@@ -8,10 +8,17 @@ def get_animation_duration_in_milliseconds(pillow: Image) -> int:
88

99

1010
def get_animation_duration_in_seconds(pillow: Image) -> float:
11-
"Get animation duration in seconds"
11+
"Get animation d`uration in seconds"
1212
return sum(get_frame_lengths(pillow)) / 1000
1313

1414

15+
def resize_animation(pillow: Image, size: "Tuple[int, int]") -> "list[Image]":
16+
return [
17+
frame.resize(size, LANCZOS)
18+
for frame in extract_animation_frames(pillow)
19+
]
20+
21+
1522
def get_frame_lengths(pillow: Image) -> "List[int]":
1623
frame_durations = []
1724
for x in range(pillow.n_frames):
@@ -22,10 +29,11 @@ def get_frame_lengths(pillow: Image) -> "List[int]":
2229
return frame_durations
2330

2431

25-
def get_animation_frames(pillow: Image) -> "List[Image]":
32+
def extract_animation_frames(pillow: Image) -> "List[Image]":
2633
frames = []
2734
for x in range(pillow.n_frames):
2835
pillow.seek(x)
2936
frames.append(pillow.copy())
3037

38+
pillow.seek(0)
3139
return frames

mediaify/animation/webp.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .utils import (
77
get_animation_duration_in_seconds,
88
get_frame_lengths,
9-
get_animation_frames,
9+
extract_animation_frames,
1010
)
1111

1212
import io
@@ -20,31 +20,39 @@ def encode_as_webp(
2020
if not PILFeatures.check("webp_anim"):
2121
raise RuntimeError("WebP animation support not available, WeBP library outdated?")
2222

23+
frames = extract_animation_frames(pillow)
24+
2325
if config.resize is not None:
2426
im_size = (pillow.width, pillow.height)
2527
size = calculate_downscale(im_size, config.resize)
26-
pillow = pillow.resize(size, PILImage.LANCZOS)
28+
frames = [
29+
frame.resize(size, PILImage.LANCZOS)
30+
for frame in frames
31+
]
2732

33+
first_frame = frames[0]
2834
buf = io.BytesIO()
29-
pillow.save(
35+
first_frame.save(
3036
fp=buf,
3137
format='webp',
3238
lossless=config.lossless,
3339
quality=config.quality,
40+
allow_mixed=not config.lossless, # Allow mixed lossy/lossless frames
41+
minimize_size=True,
42+
3443
save_all=True, # Save as an animation
44+
append_images=frames[1:],
3545
transparency=0,
3646
duration=get_frame_lengths(pillow),
3747
background=(0, 0, 0, 0), # Transparent background
38-
minimize_size=True,
39-
allow_mixed=not config.lossless,
4048
disposal=2,
4149
)
4250

4351
return AnimationFile(
4452
data=buf.getvalue(),
4553
mimetype='image/webp',
46-
width=pillow.width,
47-
height=pillow.height,
54+
width=first_frame.width,
55+
height=first_frame.height,
4856
frame_count=pillow.n_frames,
4957
duration=get_animation_duration_in_seconds(pillow),
5058
)

test/data.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,21 +82,21 @@ class TestVideo:
8282
mimetype="image/gif",
8383
width=241,
8484
height=300,
85-
frame_count=6400,
85+
frame_count=128,
8686
)
8787

8888
SINGLE_FRAME_ANIMATION = TestAnimation(
8989
filepath="./test/data/animation/single_frame.gif",
9090
mimetype="image/gif",
9191
width=241,
9292
height=300,
93-
frame_count=50,
93+
frame_count=1,
9494
)
9595

9696
FRACTAL_ANIMATION = TestAnimation(
9797
filepath="./test/data/animation/fractal.gif",
9898
mimetype="image/gif",
9999
width=500,
100100
height=500,
101-
frame_count=600,
101+
frame_count=12,
102102
)

test/image/test_webp.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ def test_encode_as_png():
2222
assert image.width == 100
2323
assert image.height == 100
2424
assert image.mimetype == "image/webp"
25+
assert image.mimetype == "image/webp"
2526
assert validate_image(image.data)

0 commit comments

Comments
 (0)