defmodule PhilomenaMedia.Processors do @moduledoc """ Utilities for processing uploads. Processors have 4 functions available: - `versions/1`: Takes a version list and generates a list of files which the processor will generate during the scope of `process/3`. - `process/3`: Takes an analysis result, file path, and version list and generates an "edit script" that represents how to store this file according to the given version list. See `m:Philomena.Images.Thumbnailer` for a usage example. - `post_process/2`: Takes an analysis result and file path and performs optimizations on the upload. See `m:Philomena.Images.Thumbnailer` for a usage example. - `intensities/2`: Takes an analysis result and file path and generates corner intensities, performing. any conversion necessary before processing. See `m:PhilomenaMedia.Intensities` for more information. ## Version lists `process/3` and `post_process/2` take _version lists_ as input. A version list is a structure like the following, which contains pairs of _version names_ and _dimensions_: [ thumb_tiny: {50, 50}, thumb_small: {150, 150}, thumb: {250, 250}, small: {320, 240}, medium: {800, 600}, large: {1280, 1024}, tall: {1024, 4096} ] When calling these functions, it is recommended prefilter the version list based on the media dimensions to avoid generating unnecessary versions which are larger than the original file. See `m:Philomena.Images.Thumbnailer` for an example. ## Edit scripts `process/3` and `post_process/2` return _edit scripts_. An edit script is a list where each entry may be one of the following: {:thumbnails, [copy_requests]} {:replace_original, path} {:intensities, intensities} Within the thumbnail request, a copy request is defined with the following structure: {:copy, path, version_filename} See the respective functions for more information about their return values. """ alias PhilomenaMedia.Analyzers.Result alias PhilomenaMedia.Intensities alias PhilomenaMedia.Processors.{Gif, Jpeg, Png, Svg, Webm} alias PhilomenaMedia.Mime # The name of a version, like :large @type version_name :: atom() @type dimensions :: {integer(), integer()} @type version_list :: [{version_name(), dimensions()}] # The file name of a processed version, like "large.png" @type version_filename :: String.t() # A single file to be copied to satisfy a request for a version name @type copy_request :: {:copy, Path.t(), version_filename()} # A list of thumbnail versions to copy into place @type thumbnails :: {:thumbnails, [copy_request()]} # Replace the original file to strip metadata or losslessly optimize @type replace_original :: {:replace_original, Path.t()} # Apply the computed corner intensities @type intensities :: {:intensities, Intensities.t()} # An edit script, representing the changes to apply to the storage backend # after successful processing @type edit_script :: [thumbnails() | replace_original() | intensities()] @doc """ Returns a processor, with the processor being a module capable of processing this content type, or nil. The allowed MIME types are: - `image/gif` - `image/jpeg` - `image/png` - `image/svg+xml` - `video/webm` > #### Info {: .info} > > This is an interface intended for use when the MIME type is already known. > Using a processor not matched to the file may cause unexpected results. ## Examples iex> PhilomenaMedia.Processors.processor("image/png") PhilomenaMedia.Processors.Png iex> PhilomenaMedia.Processors.processor("application/octet-stream") nil """ @spec processor(Mime.t()) :: module() | nil def processor(content_type) def processor("image/gif"), do: Gif def processor("image/jpeg"), do: Jpeg def processor("image/png"), do: Png def processor("image/svg+xml"), do: Svg def processor("video/webm"), do: Webm def processor(_content_type), do: nil @doc """ Takes a MIME type and filtered version list and generates a list of version files to be generated by `process/2`. List contents may differ based on file type. ## Examples iex> PhilomenaMedia.Processors.versions("image/png", [thumb_tiny: {50, 50}]) ["thumb_tiny.png"] iex> PhilomenaMedia.Processors.versions("video/webm", [thumb_tiny: {50, 50}]) ["full.mp4", "rendered.png", "thumb_tiny.webm", "thumb_tiny.mp4", "thumb_tiny.gif"] """ @spec versions(Mime.t(), version_list()) :: [version_name()] def versions(mime_type, valid_sizes) do processor(mime_type).versions(valid_sizes) end @doc """ Takes an analyzer result, file path, and version list and runs the appropriate processor's `process/3`, processing the media. Returns an edit script to apply changes. Depending on the media type, this make take a long time to execute. ## Example iex> PhilomenaMedia.Processors.process(%Result{...}, "image.png", [thumb_tiny: {50, 50}]) [ intensities: %Intensities{...}, thumbnails: [ {:copy, "/tmp/briefly-5764/vSHsM3kn7k4yvrvZH.png", "thumb_tiny.png"} ] ] """ @spec process(Result.t(), Path.t(), version_list()) :: edit_script() def process(analysis, file, versions) do processor(analysis.mime_type).process(analysis, file, versions) end @doc """ Takes an analyzer result and file path and runs the appropriate processor's `post_process/2`, performing long-running optimizations on the media source file. Returns an edit script to apply changes. Depending on the media type, this make take a long time to execute. This may also be an empty list, if there are no changes to perform. ## Example iex> PhilomenaMedia.Processors.post_process(%Result{...}, "image.gif", [thumb_tiny: {50, 50}]) [replace_original: "/tmp/briefly-5764/cyZSQnmL59XDRoPoaDxr.gif"] """ @spec post_process(Result.t(), Path.t()) :: edit_script() def post_process(analysis, file) do processor(analysis.mime_type).post_process(analysis, file) end @doc """ Takes an analyzer result and file path and runs the appropriate processor's `intensities/2`, returning the corner intensities. This allows for generating intensities for file types that are not directly supported by `m:PhilomenaMedia.Intensities`, and should be the preferred function to call when intensities are needed. ## Example iex> PhilomenaMedia.Processors.intensities(%Result{...}, "video.webm") %Intensities{nw: 111.689148, ne: 116.228048, sw: 93.268433, se: 104.630064} """ @spec intensities(Result.t(), Path.t()) :: Intensities.t() def intensities(analysis, file) do processor(analysis.mime_type).intensities(analysis, file) end end