Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docker/mediaproc/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM rust:1.91-slim-trixie AS builder

RUN apt update \
&& apt install -y build-essential git libmagic-dev libturbojpeg0-dev libpng-dev \
gifsicle optipng libjpeg-turbo-progs librsvg2-bin librsvg2-dev file imagemagick \
gifsicle optipng libjpeg-turbo-progs librsvg2-bin librsvg2-dev file imagemagick-7.q16hdri \
libx264-dev libx265-dev libvpx-dev libdav1d-dev libaom-dev libopus-dev \
libmp3lame-dev libvorbis-dev libwebp-dev libjxl-dev nasm wget

Expand Down Expand Up @@ -69,7 +69,7 @@ FROM debian:trixie-slim

# Programs and runtime libraries.
RUN apt update \
&& apt install -y gifsicle optipng libjpeg-turbo-progs file imagemagick \
&& apt install -y gifsicle optipng libjpeg-turbo-progs file imagemagick-7.q16hdri \
libvpx9 libmp3lame0 libopus0 libvorbis0a libvorbisenc2 libx264-164 \
libx265-215 librsvg2-2 librsvg2-bin \
&& rm -rf /var/lib/apt/lists/*
Expand Down
53 changes: 53 additions & 0 deletions lib/philomena_media/icc.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
defmodule PhilomenaMedia.Icc do
@moduledoc """
ICC color profile handling for image files.
"""

alias PhilomenaMedia.Remote

@doc """
Returns whether the embedded ICC profile is effectively equivalent to sRGB.

This assumes the file has an embedded profile. If it does not, the extraction
will fail and this function returns `false`.
"""
@spec srgb_profile?(Path.t()) :: boolean()
def srgb_profile?(file) do
profile = Briefly.create!(extname: ".icc")

with {_output, 0} <- Remote.cmd("magick", [file, profile]),
{test, 0} <-
Remote.cmd("magick", [
reference_image(),
"-profile",
profile,
"-profile",
srgb_profile(),
"-depth",
"8",
"RGB:-"
]),
{:ok, reference} <- File.read(reference_rgb()) do
test == reference
else
_ ->
false
end
end

@doc """
Returns the path to the bundled sRGB ICC profile.
"""
@spec srgb_profile() :: Path.t()
def srgb_profile do
Path.join(File.cwd!(), "priv/icc/sRGB.icc")
end

defp reference_image do
Path.join(File.cwd!(), "priv/icc/reference.png")
end

defp reference_rgb do
Path.join(File.cwd!(), "priv/icc/reference.rgb")
end
end
38 changes: 7 additions & 31 deletions lib/philomena_media/processors/jpeg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule PhilomenaMedia.Processors.Jpeg do
alias PhilomenaMedia.Analyzers.Result
alias PhilomenaMedia.Remote
alias PhilomenaMedia.Processors.Processor
alias PhilomenaMedia.Strip
alias PhilomenaMedia.Processors

@behaviour Processor
Expand Down Expand Up @@ -41,39 +42,18 @@ defmodule PhilomenaMedia.Processors.Jpeg do
intensities
end

defp requires_lossy_transformation?(file) do
with {output, 0} <-
Remote.cmd("magick", ["identify", "-format", "%[orientation]\t%[profile:icc]", file]),
[orientation, profile] <- String.split(output, "\t") do
orientation not in ["Undefined", "TopLeft"] or profile != ""
else
_ ->
true
end
end

defp strip(file) do
stripped = Briefly.create!(extname: ".jpg")

# ImageMagick always reencodes the image, resulting in quality loss, so
# be more clever
if requires_lossy_transformation?(file) do
# Transcode: strip EXIF, embedded profile and reorient image
{_output, 0} =
Remote.cmd("magick", [
file,
"-profile",
srgb_profile(),
"-auto-orient",
"-strip",
stripped
])
if Strip.requires_strip?(file) do
# Transcode: normalize orientation, ICC profile and strip metadata
Strip.strip(file, ".jpg")
else
# Transmux only: Strip EXIF without touching orientation
# Transmux only: Strip EXIF without touching pixel data
stripped = Briefly.create!(extname: ".jpg")
validate_return(Remote.cmd("jpegtran", ["-copy", "none", "-outfile", stripped, file]))
stripped
end

stripped
end

defp optimize(file) do
Expand Down Expand Up @@ -107,10 +87,6 @@ defmodule PhilomenaMedia.Processors.Jpeg do
[{:copy, scaled, "#{thumb_name}.jpg"}]
end

defp srgb_profile do
Path.join(File.cwd!(), "priv/icc/sRGB.icc")
end

defp validate_return({_output, ret}) when ret in [@exit_success, @exit_warning] do
:ok
end
Expand Down
26 changes: 20 additions & 6 deletions lib/philomena_media/processors/png.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule PhilomenaMedia.Processors.Png do
alias PhilomenaMedia.Analyzers.Result
alias PhilomenaMedia.Remote
alias PhilomenaMedia.Processors.Processor
alias PhilomenaMedia.Strip
alias PhilomenaMedia.Processors

@behaviour Processor
Expand All @@ -18,14 +19,24 @@ defmodule PhilomenaMedia.Processors.Png do
def process(analysis, file, versions) do
animated? = analysis.animated?

{:ok, intensities} = Intensities.file(file)
stripped = strip(file, animated?)

{:ok, intensities} = Intensities.file(stripped)

scaled = Enum.flat_map(versions, &scale(file, animated?, &1))
scaled = Enum.flat_map(versions, &scale(stripped, animated?, &1))

[
intensities: intensities,
thumbnails: scaled
]
if stripped != file do
[
replace_original: stripped,
intensities: intensities,
thumbnails: scaled
]
else
[
intensities: intensities,
thumbnails: scaled
]
end
end

@spec post_process(Result.t(), Path.t()) :: Processors.edit_script()
Expand All @@ -44,6 +55,9 @@ defmodule PhilomenaMedia.Processors.Png do
intensities
end

defp strip(file, true = _animated?), do: file
defp strip(file, _animated?), do: Strip.strip(file, ".png")

# Sobelow misidentifies removing the .bak file
# sobelow_skip ["Traversal.FileModule"]
defp optimize(file) do
Expand Down
36 changes: 36 additions & 0 deletions lib/philomena_media/strip.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule PhilomenaMedia.Strip do
@moduledoc false

alias PhilomenaMedia.Icc
alias PhilomenaMedia.Remote

@spec requires_strip?(Path.t()) :: boolean()
def requires_strip?(file) do
with {output, 0} <-
Remote.cmd("magick", ["identify", "-format", "%[orientation]\t%[profile:icc]", file]),
[orientation, profile] <- String.split(output, "\t") do
orientation not in ["Undefined", "TopLeft"] or
(profile != "" and not Icc.srgb_profile?(file))
else
_ ->
true
end
end

@spec strip(Path.t(), String.t()) :: Path.t()
def strip(file, extname) do
stripped = Briefly.create!(extname: extname)

{_output, 0} =
Remote.cmd("magick", [
file,
"-profile",
Icc.srgb_profile(),
"-auto-orient",
"-strip",
stripped
])

stripped
end
end
Binary file added priv/icc/reference.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added priv/icc/reference.rgb
Binary file not shown.
Loading