Deduplicate lottieconverter calls in tgs_converter
Also fix finding first frame file Fixes #690 Closes #728
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user