philomena/lib/philomena_media/processors/jpeg.ex

119 lines
3 KiB
Elixir
Raw Permalink Normal View History

defmodule PhilomenaMedia.Processors.Jpeg do
@moduledoc false
2019-11-26 01:06:40 +01:00
alias PhilomenaMedia.Intensities
alias PhilomenaMedia.Analyzers.Result
alias PhilomenaMedia.Processors.Processor
alias PhilomenaMedia.Processors
@behaviour Processor
2024-07-02 20:54:54 +02:00
@exit_success 0
@exit_warning 2
@spec versions(Processors.version_list()) :: [Processors.version_filename()]
def versions(sizes) do
Enum.map(sizes, fn {name, _} -> "#{name}.jpg" end)
end
@spec process(Result.t(), Path.t(), Processors.version_list()) :: Processors.edit_script()
def process(_analysis, file, versions) do
2019-11-26 03:57:47 +01:00
stripped = optimize(strip(file))
2019-11-26 01:06:40 +01:00
2019-11-26 03:57:47 +01:00
{:ok, intensities} = Intensities.file(stripped)
scaled = Enum.flat_map(versions, &scale(stripped, &1))
2019-11-26 03:57:47 +01:00
[
2019-11-26 03:57:47 +01:00
replace_original: stripped,
intensities: intensities,
thumbnails: scaled
]
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 intensities(Result.t(), Path.t()) :: Intensities.t()
2019-11-29 01:11:05 +01:00
def intensities(_analysis, file) do
{:ok, intensities} = Intensities.file(file)
intensities
end
defp requires_lossy_transformation?(file) do
with {output, 0} <-
System.cmd("identify", ["-format", "%[orientation]\t%[profile:icc]", file]),
[orientation, profile] <- String.split(output, "\t") do
2022-07-18 15:20:34 +02:00
orientation not in ["Undefined", "TopLeft"] or profile != ""
else
_ ->
true
end
end
2019-11-26 03:57:47 +01:00
defp strip(file) do
2019-11-26 01:06:40 +01:00
stripped = Briefly.create!(extname: ".jpg")
# ImageMagick always reencodes the image, resulting in quality loss, so
# be more clever
case requires_lossy_transformation?(file) do
true ->
# Transcode: strip EXIF, embedded profile and reorient image
{_output, 0} =
System.cmd("convert", [
file,
"-profile",
srgb_profile(),
"-auto-orient",
"-strip",
stripped
])
_ ->
# Transmux only: Strip EXIF without touching orientation
2024-07-02 20:54:54 +02:00
validate_return(System.cmd("jpegtran", ["-copy", "none", "-outfile", stripped, file]))
end
2019-11-26 03:57:47 +01:00
stripped
end
2019-11-26 01:06:40 +01:00
2019-11-26 03:57:47 +01:00
defp optimize(file) do
optimized = Briefly.create!(extname: ".jpg")
2024-07-02 20:54:54 +02:00
validate_return(System.cmd("jpegtran", ["-optimize", "-outfile", optimized, file]))
2019-11-26 01:06:40 +01:00
2019-11-26 03:57:47 +01:00
optimized
2019-11-26 01:06:40 +01:00
end
defp scale(file, {thumb_name, {width, height}}) do
2019-11-26 01:06:40 +01:00
scaled = Briefly.create!(extname: ".jpg")
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
{_output, 0} =
2020-04-29 17:12:40 +02:00
System.cmd("ffmpeg", [
"-loglevel",
"0",
"-y",
"-i",
file,
"-vf",
scale_filter,
"-q:v",
"1",
scaled
])
2020-01-11 05:20:19 +01:00
{_output, 0} = System.cmd("jpegtran", ["-optimize", "-outfile", scaled, scaled])
2019-11-26 01:06:40 +01:00
[{:copy, scaled, "#{thumb_name}.jpg"}]
2019-11-26 01:06:40 +01:00
end
defp srgb_profile do
Path.join(File.cwd!(), "priv/icc/sRGB.icc")
end
2024-07-02 20:54:54 +02:00
defp validate_return({_output, ret}) when ret in [@exit_success, @exit_warning] do
:ok
end
2020-01-11 05:20:19 +01:00
end