2024-06-02 06:15:09 +02:00
|
|
|
defmodule PhilomenaMedia.Processors.Webm do
|
|
|
|
@moduledoc false
|
|
|
|
|
|
|
|
alias PhilomenaMedia.Intensities
|
|
|
|
alias PhilomenaMedia.Analyzers.Result
|
|
|
|
alias PhilomenaMedia.Processors.Processor
|
|
|
|
alias PhilomenaMedia.Processors
|
2019-11-26 01:06:40 +01:00
|
|
|
import Bitwise
|
|
|
|
|
2024-06-02 06:15:09 +02:00
|
|
|
@behaviour Processor
|
|
|
|
|
|
|
|
@spec versions(Processors.version_list()) :: [Processors.version_filename()]
|
2022-02-08 04:12:40 +01:00
|
|
|
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
|
|
|
|
2022-02-08 04:12:40 +01:00
|
|
|
gif_versions =
|
|
|
|
sizes
|
|
|
|
|> Enum.filter(fn {name, _} -> name in [:thumb_tiny, :thumb_small, :thumb] end)
|
|
|
|
|> Enum.map(fn {name, _} -> "#{name}.gif" end)
|
|
|
|
|
2023-03-30 18:28:36 +02:00
|
|
|
["full.mp4", "rendered.png"] ++ webm_versions ++ mp4_versions ++ gif_versions
|
2022-02-08 04:12:40 +01:00
|
|
|
end
|
|
|
|
|
2024-06-02 06:15:09 +02:00
|
|
|
@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
|
2021-03-13 01:17:11 +01:00
|
|
|
stripped = strip(file)
|
|
|
|
preview = preview(duration, stripped)
|
|
|
|
palette = gif_palette(stripped, duration)
|
|
|
|
mp4 = scale_mp4_only(stripped, dimensions, dimensions)
|
2019-11-26 01:06:40 +01:00
|
|
|
|
2019-11-26 03:57:47 +01:00
|
|
|
{:ok, intensities} = Intensities.file(preview)
|
|
|
|
|
2022-02-08 04:30:39 +01:00
|
|
|
scaled = Enum.flat_map(versions, &scale(stripped, palette, duration, dimensions, &1))
|
|
|
|
mp4 = [{:copy, mp4, "full.mp4"}]
|
2019-11-26 01:06:40 +01:00
|
|
|
|
2024-06-02 06:15:09 +02:00
|
|
|
[
|
2021-03-13 01:17:11 +01:00
|
|
|
replace_original: stripped,
|
2019-11-26 03:57:47 +01:00
|
|
|
intensities: intensities,
|
2022-02-08 04:30:39 +01:00
|
|
|
thumbnails: scaled ++ mp4 ++ [{:copy, preview, "rendered.png"}]
|
2024-06-02 06:15:09 +02:00
|
|
|
]
|
2019-11-26 01:06:40 +01:00
|
|
|
end
|
|
|
|
|
2024-06-02 06:15:09 +02:00
|
|
|
@spec post_process(Result.t(), Path.t()) :: Processors.edit_script()
|
|
|
|
def post_process(_analysis, _file), do: []
|
2019-11-26 03:57:47 +01:00
|
|
|
|
2024-06-02 06:15:09 +02:00
|
|
|
@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")
|
|
|
|
|
2020-05-19 04:44:43 +02:00
|
|
|
{_output, 0} = System.cmd("mediathumb", [file, to_string(duration / 2), preview])
|
2019-11-26 03:57:47 +01:00
|
|
|
|
|
|
|
preview
|
|
|
|
end
|
|
|
|
|
2021-03-13 01:17:11 +01:00
|
|
|
defp strip(file) do
|
|
|
|
stripped = Briefly.create!(extname: ".webm")
|
|
|
|
|
|
|
|
{_output, 0} =
|
|
|
|
System.cmd("ffmpeg", [
|
|
|
|
"-loglevel",
|
|
|
|
"0",
|
|
|
|
"-y",
|
|
|
|
"-i",
|
|
|
|
file,
|
|
|
|
"-map_metadata",
|
|
|
|
"-1",
|
|
|
|
"-c",
|
|
|
|
"copy",
|
|
|
|
"-map",
|
|
|
|
"0",
|
|
|
|
stripped
|
|
|
|
])
|
|
|
|
|
|
|
|
stripped
|
|
|
|
end
|
|
|
|
|
2022-02-08 04:30:39 +01:00
|
|
|
defp scale(file, palette, duration, dimensions, {thumb_name, target_dimensions}) do
|
|
|
|
{webm, mp4} = scale_videos(file, dimensions, target_dimensions)
|
2019-11-26 03:57:47 +01:00
|
|
|
|
|
|
|
cond do
|
|
|
|
thumb_name in [:thumb, :thumb_small, :thumb_tiny] ->
|
2022-02-08 04:30:39 +01:00
|
|
|
gif = scale_gif(file, palette, duration, 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
|
|
|
|
|
2020-05-29 04:14:27 +02:00
|
|
|
defp scale_videos(file, dimensions, target_dimensions) do
|
2019-11-27 02:45:57 +01:00
|
|
|
{width, height} = box_dimensions(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-27 02:45:57 +01:00
|
|
|
scale_filter = "scale=w=#{width}:h=#{height}"
|
2019-11-26 01:06:40 +01:00
|
|
|
|
|
|
|
{_output, 0} =
|
2020-01-11 05:20:19 +01:00
|
|
|
System.cmd("ffmpeg", [
|
|
|
|
"-loglevel",
|
|
|
|
"0",
|
|
|
|
"-y",
|
|
|
|
"-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",
|
|
|
|
scale_filter,
|
2020-04-03 03:45:21 +02:00
|
|
|
"-threads",
|
2020-05-29 02:55:58 +02:00
|
|
|
"4",
|
2020-05-14 23:18:14 +02:00
|
|
|
"-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",
|
|
|
|
scale_filter,
|
2020-04-03 03:45:21 +02:00
|
|
|
"-threads",
|
2020-05-29 02:55:58 +02:00
|
|
|
"4",
|
2020-05-14 23:18:14 +02:00
|
|
|
"-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
|
|
|
|
|
2020-05-29 04:14:27 +02:00
|
|
|
defp scale_mp4_only(file, dimensions, target_dimensions) do
|
|
|
|
{width, height} = box_dimensions(dimensions, target_dimensions)
|
|
|
|
mp4 = Briefly.create!(extname: ".mp4")
|
|
|
|
scale_filter = "scale=w=#{width}:h=#{height}"
|
|
|
|
|
|
|
|
{_output, 0} =
|
|
|
|
System.cmd("ffmpeg", [
|
|
|
|
"-loglevel",
|
|
|
|
"0",
|
|
|
|
"-y",
|
|
|
|
"-i",
|
|
|
|
file,
|
|
|
|
"-c:v",
|
|
|
|
"libx264",
|
|
|
|
"-pix_fmt",
|
|
|
|
"yuv420p",
|
|
|
|
"-profile:v",
|
|
|
|
"main",
|
|
|
|
"-preset",
|
|
|
|
"medium",
|
|
|
|
"-crf",
|
|
|
|
"18",
|
|
|
|
"-b:v",
|
|
|
|
"5M",
|
|
|
|
"-vf",
|
|
|
|
scale_filter,
|
|
|
|
"-threads",
|
|
|
|
"4",
|
|
|
|
"-max_muxing_queue_size",
|
|
|
|
"4096",
|
|
|
|
mp4
|
|
|
|
])
|
|
|
|
|
|
|
|
mp4
|
|
|
|
end
|
|
|
|
|
2020-05-27 18:31:31 +02:00
|
|
|
defp scale_gif(file, palette, duration, {width, height}) do
|
2019-11-26 03:57:47 +01:00
|
|
|
gif = Briefly.create!(extname: ".gif")
|
2020-01-11 05:20:19 +01:00
|
|
|
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
|
2019-11-26 03:57:47 +01:00
|
|
|
palette_filter = "paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle"
|
2020-05-27 18:31:31 +02:00
|
|
|
rate_filter = rate_filter(duration)
|
2020-05-20 01:19:53 +02:00
|
|
|
filter_graph = "[0:v]#{scale_filter},#{rate_filter}[x];[x][1:v]#{palette_filter}"
|
2019-11-26 03:57:47 +01:00
|
|
|
|
|
|
|
{_output, 0} =
|
2020-01-11 05:20:19 +01:00
|
|
|
System.cmd("ffmpeg", [
|
|
|
|
"-loglevel",
|
|
|
|
"0",
|
|
|
|
"-y",
|
|
|
|
"-i",
|
|
|
|
file,
|
|
|
|
"-i",
|
|
|
|
palette,
|
|
|
|
"-lavfi",
|
|
|
|
filter_graph,
|
|
|
|
"-r",
|
|
|
|
"2",
|
|
|
|
gif
|
|
|
|
])
|
|
|
|
|
2019-11-26 03:57:47 +01:00
|
|
|
gif
|
|
|
|
end
|
|
|
|
|
2020-05-29 04:14:27 +02:00
|
|
|
defp gif_palette(file, duration) do
|
2019-11-26 03:57:47 +01:00
|
|
|
palette = Briefly.create!(extname: ".png")
|
2020-05-29 04:14:27 +02:00
|
|
|
palette_filter = "palettegen=stats_mode=diff"
|
|
|
|
rate_filter = rate_filter(duration)
|
|
|
|
filter_graph = "#{rate_filter},#{palette_filter}"
|
2019-11-26 03:57:47 +01:00
|
|
|
|
|
|
|
{_output, 0} =
|
2020-01-11 05:20:19 +01:00
|
|
|
System.cmd("ffmpeg", [
|
|
|
|
"-loglevel",
|
|
|
|
"0",
|
|
|
|
"-y",
|
|
|
|
"-i",
|
|
|
|
file,
|
|
|
|
"-vf",
|
2020-05-29 04:14:27 +02:00
|
|
|
filter_graph,
|
2020-01-11 05:20:19 +01:00
|
|
|
palette
|
|
|
|
])
|
2019-11-26 01:06:40 +01:00
|
|
|
|
2019-11-26 03:57:47 +01:00
|
|
|
palette
|
2019-11-26 01:06:40 +01:00
|
|
|
end
|
|
|
|
|
2019-11-27 02:45:57 +01:00
|
|
|
# x264 requires image dimensions to be a multiple of 2
|
|
|
|
# -2 = ~1
|
|
|
|
def 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
|
2020-05-27 18:31:31 +02:00
|
|
|
|
|
|
|
# Avoid division by zero
|
|
|
|
def rate_filter(duration) when duration > 0.5, do: "fps=1/#{duration / 10},settb=1/2,setpts=N"
|
2020-05-27 19:01:35 +02:00
|
|
|
def rate_filter(_duration), do: "setpts=N/TB/2"
|
2019-12-22 05:57:59 +01:00
|
|
|
end
|