Merge branch 'ffmpeg-limiter' into 'develop'

Prevent Media Helper from respawning ffmpeg for bad media

See merge request pleroma/pleroma!4086
This commit is contained in:
Haelwenn 2024-04-17 05:47:54 +00:00
commit 71a0373232
3 changed files with 33 additions and 18 deletions

View File

@ -0,0 +1 @@
Framegrabs with ffmpeg will execute with a 5 second timeout and cache the URLs of failures with a TTL of 15 minutes to prevent excessive retries.

View File

@ -156,6 +156,7 @@ defp cachex_children do
build_cachex("web_resp", limit: 2500), build_cachex("web_resp", limit: 2500),
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10), build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
build_cachex("failed_proxy_url", limit: 2500), build_cachex("failed_proxy_url", limit: 2500),
build_cachex("failed_media_helper_url", default_ttl: :timer.minutes(15), limit: 2_500),
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000), build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
build_cachex("chat_message_id_idempotency_key", build_cachex("chat_message_id_idempotency_key",
expiration: chat_message_id_idempotency_key_expiration(), expiration: chat_message_id_idempotency_key_expiration(),

View File

@ -12,6 +12,8 @@ defmodule Pleroma.Helpers.MediaHelper do
require Logger require Logger
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def missing_dependencies do def missing_dependencies do
Enum.reduce([ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc -> Enum.reduce([ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc ->
if Pleroma.Utils.command_available?(executable) do if Pleroma.Utils.command_available?(executable) do
@ -43,29 +45,40 @@ def image_resize(url, options) do
@spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()} @spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()}
def video_framegrab(url) do def video_framegrab(url) do
with executable when is_binary(executable) <- System.find_executable("ffmpeg"), with executable when is_binary(executable) <- System.find_executable("ffmpeg"),
false <- @cachex.exists?(:failed_media_helper_cache, url),
{:ok, env} <- HTTP.get(url, [], pool: :media), {:ok, env} <- HTTP.get(url, [], pool: :media),
{:ok, pid} <- StringIO.open(env.body) do {:ok, pid} <- StringIO.open(env.body) do
body_stream = IO.binstream(pid, 1) body_stream = IO.binstream(pid, 1)
result = task =
Exile.stream!( Task.async(fn ->
[ Exile.stream!(
executable, [
"-i", executable,
"pipe:0", "-i",
"-vframes", "pipe:0",
"1", "-vframes",
"-f", "1",
"mjpeg", "-f",
"pipe:1" "mjpeg",
], "pipe:1"
input: body_stream, ],
ignore_epipe: true, input: body_stream,
stderr: :disable ignore_epipe: true,
) stderr: :disable
|> Enum.into(<<>>) )
|> Enum.into(<<>>)
end)
{:ok, result} case Task.yield(task, 5_000) do
nil ->
Task.shutdown(task)
@cachex.put(:failed_media_helper_cache, url, nil)
{:error, {:ffmpeg, :timeout}}
result ->
{:ok, result}
end
else else
nil -> {:error, {:ffmpeg, :command_not_found}} nil -> {:error, {:ffmpeg, :command_not_found}}
{:error, _} = error -> error {:error, _} = error -> error