processors

This commit is contained in:
byte[] 2019-11-25 19:06:40 -05:00
parent 91187d24f1
commit 813ff87f9e
12 changed files with 427 additions and 0 deletions

View file

@ -0,0 +1,59 @@
defmodule Philomena.Analyzers.Gif do
def analyze(file) do
animated? = animated?(file)
duration = duration(animated?, file)
%{
extension: "gif",
mime_type: "image/gif",
animated?: animated?,
duration: duration,
dimensions: dimensions(file)
}
end
defp animated?(file) do
System.cmd("identify", [file])
|> case do
{output, 0} ->
len =
output
|> String.split("\n", parts: 2, trim: true)
|> length()
len > 1
_error ->
nil
end
end
defp duration(false, _file), do: 0.0
defp duration(true, file) do
with {output, 0} <- System.cmd("ffprobe", ["-i", file, "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0"]),
{duration, _} <- Float.parse(output)
do
duration
else
_ ->
0.0
end
end
defp dimensions(file) do
System.cmd("identify", ["-format", "%W %H\n", file])
|> case do
{output, 0} ->
[width, height] =
output
|> String.trim()
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
{width, height}
_error ->
{0, 0}
end
end
end

View file

@ -0,0 +1,28 @@
defmodule Philomena.Analyzers.Jpeg do
def analyze(file) do
%{
extension: "jpg",
mime_type: "image/jpeg",
animated?: false,
duration: 0.0,
dimensions: dimensions(file)
}
end
defp dimensions(file) do
System.cmd("identify", ["-format", "%W %H\n", file])
|> case do
{output, 0} ->
[width, height] =
output
|> String.trim()
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
{width, height}
_error ->
{0, 0}
end
end
end

View file

@ -0,0 +1,45 @@
defmodule Philomena.Analyzers.Png do
def analyze(file) do
animated? = animated?(file)
duration = duration(animated?, file)
%{
extension: "png",
mime_type: "image/png",
animated?: animated?,
duration: duration,
dimensions: dimensions(file)
}
end
defp animated?(file) do
System.cmd("ffprobe", ["-i", file, "-v", "quiet", "-show_entries", "stream=codec_name", "-of", "csv=p=0"])
|> case do
{"apng\n", 0} ->
true
_other ->
false
end
end
# No tooling available for this yet.
defp duration(_animated?, _file), do: 0.0
defp dimensions(file) do
System.cmd("identify", ["-format", "%W %H\n", file])
|> case do
{output, 0} ->
[width, height] =
output
|> String.trim()
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
{width, height}
_error ->
{0, 0}
end
end
end

View file

@ -0,0 +1,29 @@
defmodule Philomena.Analyzers.Svg do
def analyze(file) do
%{
extension: "svg",
mime_type: "image/svg+xml",
animated?: false,
duration: 0.0,
dimensions: dimensions(file)
}
end
# Force use of MSVG to prevent invoking inkscape
defp dimensions(file) do
System.cmd("identify", ["-format", "%W %H\n", "msvg:#{file}"])
|> case do
{output, 0} ->
[width, height] =
output
|> String.trim()
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
{width, height}
_error ->
{0, 0}
end
end
end

View file

@ -0,0 +1,39 @@
defmodule Philomena.Analyzers.Webm do
def analyze(file) do
%{
extension: "webm",
mime_type: "video/webm",
animated?: true,
duration: duration(file),
dimensions: dimensions(file)
}
end
defp duration(file) do
with {output, 0} <- System.cmd("ffprobe", ["-i", file, "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0"]),
{duration, _} <- Float.parse(output)
do
duration
else
_ ->
0.0
end
end
defp dimensions(file) do
System.cmd("ffprobe", ["-i", file, "-show_entries", "stream=width,height", "-v", "quiet", "-of", "csv=p=0"])
|> case do
{output, 0} ->
[width, height] =
output
|> String.trim()
|> String.split(",")
|> Enum.map(&String.to_integer/1)
{width, height}
_error ->
{0, 0}
end
end
end

View file

@ -0,0 +1,59 @@
defmodule Philomena.Processors.Gif do
alias Philomena.Processors.Gif
defstruct [:duration, :file, :palette]
def new(analysis, file) do
%Gif{
file: file,
duration: analysis.duration,
palette: nil
}
end
def strip(processor) do
{processor, processor.file}
end
def preview(processor) do
preview = Briefly.create!(extname: ".png")
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", processor.file, "-ss", to_string(processor.duration / 2), "-frames:v", "1", preview])
{processor, preview}
end
def optimize(processor) do
optimized = Briefly.create!(extname: ".gif")
{_output, 0} =
System.cmd("gifsicle", ["--careful", "-O2", processor.file, "-o", optimized])
{processor, optimized}
end
def scale(processor, {width, height}) do
processor = generate_palette(processor)
scaled = Briefly.create!(extname: ".gif")
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
palette_filter = "paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle"
filter_graph = "#{scale_filter} [x]; [x][1:v] #{palette_filter}"
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", processor.file, "-i", processor.palette, "-lavfi", filter_graph, scaled])
{processor, scaled}
end
defp generate_palette(%{palette: nil} = processor) do
palette = Briefly.create!(extname: ".png")
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", processor.file, "-vf", "palettegen=stats_mode=diff", palette])
%{processor | palette: palette}
end
defp generate_palette(processor), do: processor
end

View file

@ -0,0 +1,43 @@
defmodule Philomena.Processors.Jpeg do
alias Philomena.Processors.Jpeg
defstruct [:file]
def new(_analysis, file) do
%Jpeg{file: file}
end
def strip(processor) do
stripped = Briefly.create!(extname: ".jpg")
{_output, 0} =
System.cmd("convert", [processor.file, "-auto-orient", "-strip", stripped])
processor = %{processor | file: stripped}
{processor, stripped}
end
def preview(processor) do
{processor, processor.file}
end
def optimize(processor) do
optimized = Briefly.create!(extname: ".jpg")
{_output, 0} =
System.cmd("jpegtran", ["-optimize", "-outfile", optimized, processor.file])
end
def scale(processor, {width, height}) do
scaled = Briefly.create!(extname: ".jpg")
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", processor.file, "-vf", scale_filter, scaled])
{_output, 0} =
System.cmd("jpegtran", ["-optimize", "-outfile", scaled, scaled])
{processor, scaled}
end
end

View file

@ -0,0 +1,38 @@
defmodule Philomena.Processors.Png do
alias Philomena.Processors.Png
defstruct [:file]
def new(_analysis, file) do
%Png{file: file}
end
def strip(processor) do
{processor, processor.file}
end
def preview(processor) do
{processor, processor.file}
end
def optimize(processor) do
optimized = Briefly.create!(extname: ".png")
{_output, 0} =
System.cmd("optipng", ["-fix", "-i0", "-o2", processor.file, "-out", optimized])
{processor, optimized}
end
def scale(processor, {width, height}) do
scaled = Briefly.create!(extname: ".png")
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", processor.file, "-vf", scale_filter, scaled])
{_output, 0} =
System.cmd("optipng", ["-i0", "-o1", scaled])
{processor, scaled}
end
end

View file

@ -0,0 +1,41 @@
defmodule Philomena.Processors.Svg do
alias Philomena.Processors.Svg
defstruct [:file, :preview]
def new(_analysis, file) do
%Svg{file: file, preview: nil}
end
# FIXME
def strip(processor) do
{processor, processor.file}
end
def preview(processor) do
preview = Briefly.create!(extname: ".png")
{_output, 0} =
System.cmd("inkscape", [processor.file, "--export-png", preview])
processor = %{processor | preview: preview}
{processor, preview}
end
def optimize(processor) do
{processor, processor.file}
end
def scale(processor, {width, height}) do
scaled = Briefly.create!(extname: ".png")
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", processor.preview, "-vf", scale_filter, scaled])
{_output, 0} =
System.cmd("optipng", ["-i0", "-o1", scaled])
{processor, scaled}
end
end

View file

@ -0,0 +1,44 @@
defmodule Philomena.Processors.Webm do
alias Philomena.Processors.Webm
import Bitwise
defstruct [:duration, :file]
def new(analysis, file) do
%Webm{duration: analysis.duration, file: file}
end
def strip(processor) do
{processor, processor.file}
end
def preview(processor) do
preview = Briefly.create!(extname: ".png")
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", processor.file, "-ss", to_string(processor.duration / 2), "-frames:v", "1", preview])
{processor, preview}
end
def optimize(processor) do
{processor, processor.file}
end
def scale(processor, dimensions) do
{width, height} = normalize_dimensions(dimensions)
scaled = Briefly.create!(extname: ".webm")
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", processor.file, "-c:v", "libvpx", "-crf", "10", "-b:v", "5M", "-vf", scale_filter, scaled])
{processor, scaled}
end
# Force dimensions to be a multiple of 2. This is required by the
# libvpx and x264 encoders.
defp normalize_dimensions({width, height}) do
{width &&& (~~~1), height &&& (~~~1)}
end
end

View file

@ -59,6 +59,7 @@ defmodule Philomena.MixProject do
{:bamboo, "~> 1.2"},
{:bamboo_smtp, "~> 1.7"},
{:remote_ip, "~> 0.2.0"},
{:briefly, "~> 0.3.0"}
]
end

View file

@ -2,6 +2,7 @@
"bamboo": {:hex, :bamboo, "1.3.0", "9ab7c054f1c3435464efcba939396c29c5e1b28f73c34e1f169e0881297a3141", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"bamboo_smtp": {:hex, :bamboo_smtp, "1.7.0", "f0d213e18ced1f08b551a72221e9b8cfbf23d592b684e9aa1ef5250f4943ef9b", [:mix], [{:bamboo, "~> 1.2", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.14.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.0.3", "64e0792d5b5064391927bf3b8e436994cafd18ca2d2b76dea5c76e0adcf66b7c", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"briefly": {:hex, :briefly, "0.3.0", "16e6b76d2070ebc9cbd025fa85cf5dbaf52368c4bd896fb482b5a6b95a540c2f", [:mix], [], "hexpm"},
"canada": {:hex, :canada, "1.0.2", "040e4c47609b0a67d5773ac1fbe5e99f840cef173d69b739beda7c98453e0770", [:mix], [], "hexpm"},
"canary": {:hex, :canary, "1.1.1", "4138d5e05db8497c477e4af73902eb9ae06e49dceaa13c2dd9f0b55525ded48b", [:mix], [{:canada, "~> 1.0.1", [hex: :canada, repo: "hexpm", optional: false]}, {:ecto, ">= 1.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},