Deduplicate lottieconverter calls in tgs_converter

Also fix finding first frame file

Fixes #690
Closes #728
This commit is contained in:
Tulir Asokan
2022-01-05 19:32:58 +02:00
parent da3180e290
commit 113f41d1d2
2 changed files with 51 additions and 97 deletions
+1
View File
@@ -6,6 +6,7 @@
* Fixed syncing contacts throwing an error for new accounts. * Fixed syncing contacts throwing an error for new accounts.
* Fixed migrating from the legacy database if the database schema had been * Fixed migrating from the legacy database if the database schema had been
corrupted (e.g. by using 3rd party tools for SQLite -> Postgres migration). corrupted (e.g. by using 3rd party tools for SQLite -> Postgres migration).
* Fixed converting animated stickers to webm with >33 FPS.
# v0.11.0 (2021-12-28) # v0.11.0 (2021-12-28)
+50 -97
View File
@@ -19,12 +19,15 @@ from __future__ import annotations
from typing import Any, Awaitable, Callable from typing import Any, Awaitable, Callable
import asyncio.subprocess import asyncio.subprocess
import logging import logging
import os
import os.path import os.path
import shutil import shutil
import tempfile import tempfile
from attr import dataclass from attr import dataclass
from mautrix.util import ffmpeg
log: logging.Logger = logging.getLogger("mau.util.tgs") log: logging.Logger = logging.getLogger("mau.util.tgs")
@@ -48,61 +51,49 @@ def abswhich(program: str | None) -> str | None:
lottieconverter = abswhich("lottieconverter") lottieconverter = abswhich("lottieconverter")
ffmpeg = abswhich("ffmpeg")
async def _run_lottieconverter(args: tuple[str, ...], input_data: bytes) -> bytes:
proc = await asyncio.create_subprocess_exec(
lottieconverter,
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate(input_data)
if proc.returncode == 0:
return stdout
else:
err_text = stderr.decode("utf-8") if stderr else f"unknown ({proc.returncode})"
raise ffmpeg.ConverterError(f"lottieconverter error: {err_text}")
if lottieconverter: if lottieconverter:
async def tgs_to_png(file: bytes, width: int, height: int, **_: Any) -> ConvertedSticker: async def tgs_to_png(file: bytes, width: int, height: int, **_: Any) -> ConvertedSticker:
frame = 1 frame = 1
proc = await asyncio.create_subprocess_exec( try:
lottieconverter, converted_png = await _run_lottieconverter(
"-", args=("-", "-", "png", f"{width}x{height}", str(frame)),
"-", input_data=file,
"png",
f"{width}x{height}",
str(frame),
stdout=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate(file)
if proc.returncode == 0:
return ConvertedSticker("image/png", stdout)
else:
log.error(
"lottieconverter error: "
+ (
stderr.decode("utf-8")
if stderr is not None
else f"unknown ({proc.returncode})"
)
) )
return ConvertedSticker("image/png", converted_png)
except ffmpeg.ConverterError as e:
log.error(str(e))
return ConvertedSticker("application/gzip", file) return ConvertedSticker("application/gzip", file)
async def tgs_to_gif( async def tgs_to_gif(
file: bytes, width: int, height: int, fps: int = 25, **_: Any file: bytes, width: int, height: int, fps: int = 25, **_: Any
) -> ConvertedSticker: ) -> ConvertedSticker:
proc = await asyncio.create_subprocess_exec( try:
lottieconverter, converted_gif = await _run_lottieconverter(
"-", args=("-", "-", "gif", f"{width}x{height}", str(fps)),
"-", input_data=file,
"gif",
f"{width}x{height}",
str(fps),
stdout=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate(file)
if proc.returncode == 0:
return ConvertedSticker("image/gif", stdout)
else:
log.error(
"lottieconverter error: "
+ (
stderr.decode("utf-8")
if stderr is not None
else f"unknown ({proc.returncode})"
)
) )
return ConvertedSticker("image/gif", converted_gif)
except ffmpeg.ConverterError as e:
log.error(str(e))
return ConvertedSticker("application/gzip", file) return ConvertedSticker("application/gzip", file)
converters["png"] = tgs_to_png converters["png"] = tgs_to_png
@@ -115,62 +106,24 @@ if lottieconverter and ffmpeg:
) -> ConvertedSticker: ) -> ConvertedSticker:
with tempfile.TemporaryDirectory(prefix="tgs_") as tmpdir: with tempfile.TemporaryDirectory(prefix="tgs_") as tmpdir:
file_template = tmpdir + "/out_" file_template = tmpdir + "/out_"
proc = await asyncio.create_subprocess_exec( try:
lottieconverter, await _run_lottieconverter(
"-", args=("-", file_template, "pngs", f"{width}x{height}", str(fps)),
file_template, input_data=file,
"pngs", )
f"{width}x{height}", first_frame_name = sorted(os.listdir(tmpdir))[0]
str(fps), with open(f"{tmpdir}/{first_frame_name}", "rb") as first_frame_file:
stdout=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE,
)
_, stderr = await proc.communicate(file)
if proc.returncode == 0:
with open(f"{file_template}00.png", "rb") as first_frame_file:
first_frame_data = first_frame_file.read() first_frame_data = first_frame_file.read()
proc = await asyncio.create_subprocess_exec( webm_data = await ffmpeg.convert_path(
ffmpeg, input_args=("-framerate", str(fps), "-pattern_type", "glob"),
"-hide_banner", input_file=f"{file_template}*.png",
"-loglevel", output_args=("-c:v", "libvpx-vp9", "-pix_fmt", "yuva420p", "-f", "webm"),
"error", output_path_override="-",
"-framerate", output_extension=None,
str(fps),
"-pattern_type",
"glob",
"-i",
file_template + "*.png",
"-c:v",
"libvpx-vp9",
"-pix_fmt",
"yuva420p",
"-f",
"webm",
"-",
stdout=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate()
if proc.returncode == 0:
return ConvertedSticker("video/webm", stdout, "image/png", first_frame_data)
else:
log.error(
"ffmpeg error: "
+ (
stderr.decode("utf-8")
if stderr is not None
else f"unknown ({proc.returncode})"
)
)
else:
log.error(
"lottieconverter error: "
+ (
stderr.decode("utf-8")
if stderr is not None
else f"unknown ({proc.returncode})"
)
) )
return ConvertedSticker("video/webm", webm_data, "image/png", first_frame_data)
except ffmpeg.ConverterError as e:
log.error(str(e))
return ConvertedSticker("application/gzip", file) return ConvertedSticker("application/gzip", file)
converters["webm"] = tgs_to_webm converters["webm"] = tgs_to_webm