perf: convert static animated WebP to PNG
This commit is contained in:
parent
a83bc15208
commit
a0ef9f84be
|
@ -15,6 +15,7 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import numpy as np
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
|
@ -48,7 +49,7 @@ def guess_mime(data: bytes) -> str:
|
||||||
return mime or "image/png"
|
return mime or "image/png"
|
||||||
|
|
||||||
|
|
||||||
def video_to_webp(data: bytes) -> bytes:
|
def _video_to_webp(data: bytes) -> bytes:
|
||||||
mime = guess_mime(data)
|
mime = guess_mime(data)
|
||||||
ext = mimetypes.guess_extension(mime)
|
ext = mimetypes.guess_extension(mime)
|
||||||
with tempfile.NamedTemporaryFile(suffix=ext) as video:
|
with tempfile.NamedTemporaryFile(suffix=ext) as video:
|
||||||
|
@ -74,32 +75,62 @@ def video_to_webp(data: bytes) -> bytes:
|
||||||
return webp.read()
|
return webp.read()
|
||||||
|
|
||||||
|
|
||||||
def video_to_gif(data: bytes, mime: str) -> bytes:
|
def video_to_webp(data: bytes) -> bytes:
|
||||||
|
mime = guess_mime(data)
|
||||||
ext = mimetypes.guess_extension(mime)
|
ext = mimetypes.guess_extension(mime)
|
||||||
if mime.startswith("video/"):
|
# run ffmpeg to fix duration
|
||||||
# run ffmpeg to fix duration
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=ext) as temp:
|
|
||||||
temp.write(data)
|
|
||||||
temp.flush()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=ext) as temp_fixed:
|
|
||||||
print(".", end="", flush=True)
|
|
||||||
result = subprocess.run(["ffmpeg", "-y", "-threads", "auto", "-i", temp.name, "-codec", "copy", temp_fixed.name],
|
|
||||||
capture_output=True)
|
|
||||||
if result.returncode != 0:
|
|
||||||
raise RuntimeError(f"Run ffmpeg failed with code {result.returncode}, Error occurred:\n{result.stderr}")
|
|
||||||
temp_fixed.seek(0)
|
|
||||||
data = temp_fixed.read()
|
|
||||||
data = video_to_webp(data)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=ext) as temp:
|
with tempfile.NamedTemporaryFile(suffix=ext) as temp:
|
||||||
temp.write(data)
|
temp.write(data)
|
||||||
temp.flush()
|
temp.flush()
|
||||||
with tempfile.NamedTemporaryFile(suffix=".gif") as gif:
|
with tempfile.NamedTemporaryFile(suffix=ext) as temp_fixed:
|
||||||
print(".", end="", flush=True)
|
print(".", end="", flush=True)
|
||||||
im = Image.open(temp.name)
|
result = subprocess.run(["ffmpeg", "-y", "-threads", "auto", "-i", temp.name, "-codec", "copy", temp_fixed.name],
|
||||||
|
capture_output=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(f"Run ffmpeg failed with code {result.returncode}, Error occurred:\n{result.stderr}")
|
||||||
|
temp_fixed.seek(0)
|
||||||
|
data = temp_fixed.read()
|
||||||
|
return _video_to_webp(data)
|
||||||
|
|
||||||
|
|
||||||
|
def webp_to_others(data: bytes, mimetype: str) -> bytes:
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".webp") as webp:
|
||||||
|
webp.write(data)
|
||||||
|
webp.flush()
|
||||||
|
ext = mimetypes.guess_extension(mimetype)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=ext) as img:
|
||||||
|
print(".", end="", flush=True)
|
||||||
|
im = Image.open(webp.name)
|
||||||
im.info.pop('background', None)
|
im.info.pop('background', None)
|
||||||
im.save(gif.name, save_all=True, lossless=True, quality=100, method=6)
|
im.save(img.name, save_all=True, lossless=True, quality=100, method=6)
|
||||||
gif.seek(0)
|
img.seek(0)
|
||||||
return gif.read()
|
return img.read()
|
||||||
|
|
||||||
|
|
||||||
|
def is_uniform_animated_webp(data: bytes) -> bool:
|
||||||
|
img = Image.open(BytesIO(data))
|
||||||
|
if img.n_frames <= 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
first_frame = np.array(img)
|
||||||
|
for frame_number in range(1, img.n_frames):
|
||||||
|
img.seek(frame_number)
|
||||||
|
current_frame = np.array(img)
|
||||||
|
if not np.array_equal(first_frame, current_frame):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def webp_to_gif_or_png(data: bytes) -> bytes:
|
||||||
|
# check if the webp is animated
|
||||||
|
image: Image.Image = Image.open(BytesIO(data))
|
||||||
|
is_animated = getattr(image, "is_animated", False)
|
||||||
|
if is_animated and is_uniform_animated_webp(data):
|
||||||
|
return webp_to_others(data, "image/gif")
|
||||||
|
else:
|
||||||
|
# convert to png
|
||||||
|
return webp_to_others(data, "image/png")
|
||||||
|
|
||||||
|
|
||||||
def opermize_gif(data: bytes) -> bytes:
|
def opermize_gif(data: bytes) -> bytes:
|
||||||
|
@ -118,9 +149,6 @@ def opermize_gif(data: bytes) -> bytes:
|
||||||
def _convert_image(data: bytes, mimetype: str) -> (bytes, int, int):
|
def _convert_image(data: bytes, mimetype: str) -> (bytes, int, int):
|
||||||
image: Image.Image = Image.open(BytesIO(data))
|
image: Image.Image = Image.open(BytesIO(data))
|
||||||
new_file = BytesIO()
|
new_file = BytesIO()
|
||||||
suffix = mimetypes.guess_extension(mimetype)
|
|
||||||
if suffix:
|
|
||||||
suffix = suffix[1:]
|
|
||||||
# Determine if the image is a GIF
|
# Determine if the image is a GIF
|
||||||
is_animated = getattr(image, "is_animated", False)
|
is_animated = getattr(image, "is_animated", False)
|
||||||
if is_animated:
|
if is_animated:
|
||||||
|
@ -138,6 +166,9 @@ def _convert_image(data: bytes, mimetype: str) -> (bytes, int, int):
|
||||||
# Get the size of the first frame to determine resizing
|
# Get the size of the first frame to determine resizing
|
||||||
w, h = frames[0].size
|
w, h = frames[0].size
|
||||||
else:
|
else:
|
||||||
|
suffix = mimetypes.guess_extension(mimetype)
|
||||||
|
if suffix:
|
||||||
|
suffix = suffix[1:]
|
||||||
image = image.convert("RGBA")
|
image = image.convert("RGBA")
|
||||||
image.save(new_file, format=suffix)
|
image.save(new_file, format=suffix)
|
||||||
w, h = image.size
|
w, h = image.size
|
||||||
|
@ -155,9 +186,8 @@ def _convert_image(data: bytes, mimetype: str) -> (bytes, int, int):
|
||||||
def _convert_sticker(data: bytes) -> (bytes, str, int, int):
|
def _convert_sticker(data: bytes) -> (bytes, str, int, int):
|
||||||
mimetype = guess_mime(data)
|
mimetype = guess_mime(data)
|
||||||
if mimetype.startswith("video/"):
|
if mimetype.startswith("video/"):
|
||||||
data = video_to_gif(data, mimetype)
|
data = video_to_webp(data)
|
||||||
print(".", end="", flush=True)
|
print(".", end="", flush=True)
|
||||||
mimetype = "image/gif"
|
|
||||||
elif mimetype.startswith("application/gzip"):
|
elif mimetype.startswith("application/gzip"):
|
||||||
print(".", end="", flush=True)
|
print(".", end="", flush=True)
|
||||||
# unzip file
|
# unzip file
|
||||||
|
@ -168,7 +198,7 @@ def _convert_sticker(data: bytes) -> (bytes, str, int, int):
|
||||||
suffix = mimetypes.guess_extension(mimetype)
|
suffix = mimetypes.guess_extension(mimetype)
|
||||||
with tempfile.NamedTemporaryFile(suffix=suffix) as temp:
|
with tempfile.NamedTemporaryFile(suffix=suffix) as temp:
|
||||||
temp.write(data)
|
temp.write(data)
|
||||||
with tempfile.NamedTemporaryFile(suffix=".gif") as gif:
|
with tempfile.NamedTemporaryFile(suffix=".webp") as gif:
|
||||||
# run lottie_convert.py input output
|
# run lottie_convert.py input output
|
||||||
print(".", end="", flush=True)
|
print(".", end="", flush=True)
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -179,7 +209,10 @@ def _convert_sticker(data: bytes) -> (bytes, str, int, int):
|
||||||
raise RuntimeError(f"Run {cmd} failed with code {retcode}, Error occurred:\n{result.stderr}")
|
raise RuntimeError(f"Run {cmd} failed with code {retcode}, Error occurred:\n{result.stderr}")
|
||||||
gif.seek(0)
|
gif.seek(0)
|
||||||
data = gif.read()
|
data = gif.read()
|
||||||
mimetype = "image/gif"
|
mimetype = guess_mime(data)
|
||||||
|
if mimetype == "image/webp":
|
||||||
|
data = webp_to_gif_or_png(data)
|
||||||
|
mimetype = guess_mime(data)
|
||||||
rlt = _convert_image(data, mimetype)
|
rlt = _convert_image(data, mimetype)
|
||||||
data = rlt[0]
|
data = rlt[0]
|
||||||
if mimetype == "image/gif":
|
if mimetype == "image/gif":
|
||||||
|
|
Loading…
Reference in New Issue