commit: 71a03732327409fca07c83da35e372307223f515
parent d80e0d6873a61159a68b337d7f42f46cbdad8e9d
Author: Haelwenn <contact+git.pleroma.social@hacktivis.me>
Date: Wed, 17 Apr 2024 05:47:54 +0000
Merge branch 'ffmpeg-limiter' into 'develop'
Prevent Media Helper from respawning ffmpeg for bad media
See merge request pleroma/pleroma!4086
Diffstat:
3 files changed, 33 insertions(+), 18 deletions(-)
diff --git a/changelog.d/ffmpeg-limiter.add b/changelog.d/ffmpeg-limiter.add
@@ -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.
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
@@ -156,6 +156,7 @@ defmodule Pleroma.Application do
build_cachex("web_resp", limit: 2500),
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
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("chat_message_id_idempotency_key",
expiration: chat_message_id_idempotency_key_expiration(),
diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex
@@ -12,6 +12,8 @@ defmodule Pleroma.Helpers.MediaHelper do
require Logger
+ @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+
def missing_dependencies do
Enum.reduce([ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc ->
if Pleroma.Utils.command_available?(executable) do
@@ -43,29 +45,40 @@ defmodule Pleroma.Helpers.MediaHelper do
@spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()}
def video_framegrab(url) do
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, pid} <- StringIO.open(env.body) do
body_stream = IO.binstream(pid, 1)
- result =
- Exile.stream!(
- [
- executable,
- "-i",
- "pipe:0",
- "-vframes",
- "1",
- "-f",
- "mjpeg",
- "pipe:1"
- ],
- input: body_stream,
- ignore_epipe: true,
- stderr: :disable
- )
- |> Enum.into(<<>>)
+ task =
+ Task.async(fn ->
+ Exile.stream!(
+ [
+ executable,
+ "-i",
+ "pipe:0",
+ "-vframes",
+ "1",
+ "-f",
+ "mjpeg",
+ "pipe:1"
+ ],
+ input: body_stream,
+ ignore_epipe: true,
+ stderr: :disable
+ )
+ |> Enum.into(<<>>)
+ end)
+
+ case Task.yield(task, 5_000) do
+ nil ->
+ Task.shutdown(task)
+ @cachex.put(:failed_media_helper_cache, url, nil)
+ {:error, {:ffmpeg, :timeout}}
- {:ok, result}
+ result ->
+ {:ok, result}
+ end
else
nil -> {:error, {:ffmpeg, :command_not_found}}
{:error, _} = error -> error