philomena/lib/philomena_media/processors/webm.ex

263 lines
6.4 KiB
Elixir
Raw Normal View History

defmodule PhilomenaMedia.Processors.Webm do
@moduledoc false
alias PhilomenaMedia.Features
alias PhilomenaMedia.Intensities
alias PhilomenaMedia.Analyzers.Result
alias PhilomenaMedia.Remote
alias PhilomenaMedia.GifPreview
alias PhilomenaMedia.Processors.Processor
alias PhilomenaMedia.Processors
2019-11-26 01:06:40 +01:00
import Bitwise
@behaviour Processor
@spec versions(Processors.version_list()) :: [Processors.version_filename()]
def versions(sizes) do
webm_versions = Enum.map(sizes, fn {name, _} -> "#{name}.webm" end)
mp4_versions = Enum.map(sizes, fn {name, _} -> "#{name}.mp4" end)
2022-02-09 01:17:39 +01:00
gif_versions =
sizes
|> Enum.filter(fn {name, _} -> name in [:thumb_tiny, :thumb_small, :thumb] end)
|> Enum.map(fn {name, _} -> "#{name}.gif" end)
["full.mp4", "rendered.png"] ++ webm_versions ++ mp4_versions ++ gif_versions
end
@spec process(Result.t(), Path.t(), Processors.version_list()) :: Processors.edit_script()
2019-11-26 03:57:47 +01:00
def process(analysis, file, versions) do
dimensions = analysis.dimensions
duration = analysis.duration
stripped = strip(file)
preview = preview(duration, stripped)
2024-12-23 16:52:46 +01:00
decoder = select_decoder(file)
mp4 = scale_mp4_only(decoder, stripped, dimensions, dimensions)
2019-11-26 01:06:40 +01:00
2019-11-26 03:57:47 +01:00
{:ok, intensities} = Intensities.file(preview)
{:ok, features} = Features.file(preview)
2019-11-26 03:57:47 +01:00
2024-12-23 16:52:46 +01:00
scaled = Enum.flat_map(versions, &scale(decoder, stripped, duration, dimensions, &1))
mp4 = [{:copy, mp4, "full.mp4"}]
2019-11-26 01:06:40 +01:00
[
replace_original: stripped,
2019-11-26 03:57:47 +01:00
intensities: intensities,
features: features,
thumbnails: scaled ++ mp4 ++ [{:copy, preview, "rendered.png"}]
]
2019-11-26 01:06:40 +01:00
end
@spec post_process(Result.t(), Path.t()) :: Processors.edit_script()
def post_process(_analysis, _file), do: []
2019-11-26 03:57:47 +01:00
@spec features(Result.t(), Path.t()) :: Features.t()
def features(analysis, file) do
{:ok, features} = Features.file(preview(analysis.duration, file))
features
end
@spec intensities(Result.t(), Path.t()) :: Intensities.t()
2019-11-29 01:11:05 +01:00
def intensities(analysis, file) do
{:ok, intensities} = Intensities.file(preview(analysis.duration, file))
intensities
end
2019-11-26 03:57:47 +01:00
defp preview(duration, file) do
2019-11-26 01:06:40 +01:00
preview = Briefly.create!(extname: ".png")
{_output, 0} = Remote.cmd("mediathumb", [file, to_string(duration / 2), preview])
2019-11-26 03:57:47 +01:00
preview
end
defp strip(file) do
stripped = Briefly.create!(extname: ".webm")
{_output, 0} =
Remote.cmd("ffmpeg", [
"-loglevel",
"0",
"-y",
"-i",
file,
"-map_metadata",
"-1",
"-c",
"copy",
"-map",
"0",
stripped
])
stripped
end
2024-12-23 16:52:46 +01:00
defp scale(decoder, file, duration, dimensions, {thumb_name, target_dimensions}) do
{webm, mp4} = scale_videos(decoder, file, dimensions, target_dimensions)
2019-11-26 03:57:47 +01:00
cond do
thumb_name in [:thumb, :thumb_small, :thumb_tiny] ->
2024-12-23 16:52:46 +01:00
gif = scale_gif(decoder, file, duration, dimensions, target_dimensions)
2019-11-26 03:57:47 +01:00
[
{:copy, webm, "#{thumb_name}.webm"},
{:copy, mp4, "#{thumb_name}.mp4"},
{:copy, gif, "#{thumb_name}.gif"}
]
true ->
[
{:copy, webm, "#{thumb_name}.webm"},
{:copy, mp4, "#{thumb_name}.mp4"}
]
end
2019-11-26 01:06:40 +01:00
end
2024-12-23 16:52:46 +01:00
defp scale_videos(decoder, file, dimensions, target_dimensions) do
2024-07-02 06:25:44 +02:00
filter = scale_filter(dimensions, target_dimensions)
2019-11-26 03:57:47 +01:00
webm = Briefly.create!(extname: ".webm")
2020-01-11 05:20:19 +01:00
mp4 = Briefly.create!(extname: ".mp4")
2019-11-26 01:06:40 +01:00
{_output, 0} =
Remote.cmd("ffmpeg", [
2020-01-11 05:20:19 +01:00
"-loglevel",
"0",
"-y",
2024-12-23 16:52:46 +01:00
"-c:v",
decoder,
2020-01-11 05:20:19 +01:00
"-i",
file,
"-c:v",
"libvpx",
2020-04-03 03:45:21 +02:00
"-deadline",
2020-01-11 05:20:19 +01:00
"good",
"-cpu-used",
2020-04-03 03:45:21 +02:00
"5",
2020-01-11 05:20:19 +01:00
"-auto-alt-ref",
"0",
2020-04-03 03:45:21 +02:00
"-qmin",
"15",
"-qmax",
"35",
2020-01-11 05:20:19 +01:00
"-crf",
2020-04-03 03:45:21 +02:00
"31",
2020-01-11 05:20:19 +01:00
"-vf",
2024-07-02 06:25:44 +02:00
filter,
2020-04-03 03:45:21 +02:00
"-threads",
2020-05-29 02:55:58 +02:00
"4",
"-max_muxing_queue_size",
2020-05-26 23:30:22 +02:00
"4096",
2020-05-29 04:14:27 +02:00
"-slices",
"8",
2020-05-29 02:55:58 +02:00
webm,
2020-01-11 05:20:19 +01:00
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-profile:v",
"main",
"-preset",
"medium",
"-crf",
"18",
"-b:v",
"5M",
"-vf",
2024-07-02 06:25:44 +02:00
filter,
2020-04-03 03:45:21 +02:00
"-threads",
2020-05-29 02:55:58 +02:00
"4",
"-max_muxing_queue_size",
2020-05-26 23:30:22 +02:00
"4096",
2020-01-11 05:20:19 +01:00
mp4
])
2019-11-26 03:57:47 +01:00
{webm, mp4}
end
2024-12-23 16:52:46 +01:00
defp scale_mp4_only(decoder, file, dimensions, target_dimensions) do
2024-07-02 06:25:44 +02:00
filter = scale_filter(dimensions, target_dimensions)
2020-05-29 04:14:27 +02:00
mp4 = Briefly.create!(extname: ".mp4")
{_output, 0} =
Remote.cmd("ffmpeg", [
2020-05-29 04:14:27 +02:00
"-loglevel",
"0",
"-y",
2024-12-23 16:52:46 +01:00
"-c:v",
decoder,
2020-05-29 04:14:27 +02:00
"-i",
file,
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-profile:v",
"main",
"-preset",
"medium",
"-crf",
"18",
"-b:v",
"5M",
"-vf",
2024-07-02 06:25:44 +02:00
filter,
2020-05-29 04:14:27 +02:00
"-threads",
"4",
"-max_muxing_queue_size",
"4096",
mp4
])
mp4
end
2024-12-23 16:52:46 +01:00
defp scale_gif(decoder, file, duration, dimensions, target_dimensions) do
2024-07-02 06:25:44 +02:00
{width, height} = box_dimensions(dimensions, target_dimensions)
2019-11-26 03:57:47 +01:00
gif = Briefly.create!(extname: ".gif")
2024-12-23 16:52:46 +01:00
GifPreview.preview(decoder, file, gif, duration, {width, height})
2020-01-11 05:20:19 +01:00
2019-11-26 03:57:47 +01:00
gif
end
2024-12-23 16:52:46 +01:00
defp select_decoder(file) do
{output, 0} =
Remote.cmd("ffprobe", [
2024-12-23 16:52:46 +01:00
"-loglevel",
"0",
"-select_streams",
"v:0",
"-show_entries",
"stream=codec_name",
"-of",
"default=noprint_wrappers=1:nokey=1",
"-i",
file
])
# Mediatools verifies that we only have one video stream and that it is
# one of the supported formats, so the following is safe to do:
case output do
"vp8\n" -> "libvpx"
"vp9\n" -> "libvpx-vp9"
"av1\n" -> "av1"
end
end
2024-07-02 06:25:44 +02:00
defp scale_filter(dimensions, target_dimensions) do
{width, height} = box_dimensions(dimensions, target_dimensions)
"scale=w=#{width}:h=#{height},setsar=1"
end
2019-11-27 02:45:57 +01:00
# x264 requires image dimensions to be a multiple of 2
# -2 = ~1
2024-07-02 06:25:44 +02:00
defp box_dimensions({width, height}, {target_width, target_height}) do
2020-01-11 05:20:19 +01:00
ratio = min(target_width / width, target_height / height)
new_width = min(max(trunc(width * ratio) &&& -2, 2), target_width)
2019-11-27 02:45:57 +01:00
new_height = min(max(trunc(height * ratio) &&& -2, 2), target_height)
{new_width, new_height}
2019-11-26 01:06:40 +01:00
end
2019-12-22 05:57:59 +01:00
end