mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-30 14:57:59 +01:00
processors
This commit is contained in:
parent
91187d24f1
commit
813ff87f9e
12 changed files with 427 additions and 0 deletions
59
lib/philomena/analyzers/gif.ex
Normal file
59
lib/philomena/analyzers/gif.ex
Normal 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
|
28
lib/philomena/analyzers/jpeg.ex
Normal file
28
lib/philomena/analyzers/jpeg.ex
Normal 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
|
45
lib/philomena/analyzers/png.ex
Normal file
45
lib/philomena/analyzers/png.ex
Normal 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
|
29
lib/philomena/analyzers/svg.ex
Normal file
29
lib/philomena/analyzers/svg.ex
Normal 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
|
39
lib/philomena/analyzers/webm.ex
Normal file
39
lib/philomena/analyzers/webm.ex
Normal 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
|
59
lib/philomena/processors/gif.ex
Normal file
59
lib/philomena/processors/gif.ex
Normal 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
|
43
lib/philomena/processors/jpeg.ex
Normal file
43
lib/philomena/processors/jpeg.ex
Normal 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
|
38
lib/philomena/processors/png.ex
Normal file
38
lib/philomena/processors/png.ex
Normal 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
|
41
lib/philomena/processors/svg.ex
Normal file
41
lib/philomena/processors/svg.ex
Normal 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
|
44
lib/philomena/processors/webm.ex
Normal file
44
lib/philomena/processors/webm.ex
Normal 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
|
1
mix.exs
1
mix.exs
|
@ -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
|
||||
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -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"},
|
||||
|
|
Loading…
Reference in a new issue