mirror of
https://github.com/penpot/penpot.git
synced 2026-07-01 20:05:26 +00:00
🔥 Remove im4java dependency and replace with direct ImageMagick CLI calls
- Replace im4java Java library with direct 'magick' CLI calls via shell/exec! - Add PENPOT_IMAGEMAGICK_* config env vars for resource limits (thread, memory, map, area, disk, time, width, height) - Use configurable ImageMagick environment with sensible defaults matching policy.xml - Remove -Dim4java.useV7=true JVM flag from startup scripts - Remove org.im4java/im4java from deps.edn - All ImageMagick commands now use shell/exec! with 60s timeout and resource limits Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>
This commit is contained in:
parent
fa89624ae2
commit
3686d08052
@ -52,10 +52,6 @@
|
||||
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.4"}
|
||||
|
||||
org.jsoup/jsoup {:mvn/version "1.22.2"}
|
||||
org.im4java/im4java
|
||||
{:git/tag "1.4.0-penpot-2"
|
||||
:git/sha "e2b3e16"
|
||||
:git/url "https://github.com/penpot/im4java"}
|
||||
|
||||
at.yawk.lz4/lz4-java
|
||||
{:mvn/version "1.11.0"}
|
||||
|
||||
@ -77,7 +77,6 @@ export JAVA_OPTS="\
|
||||
-Djdk.attach.allowAttachSelf \
|
||||
-Dlog4j2.configurationFile=log4j2-devenv.xml \
|
||||
-Djdk.tracePinnedThreads=full \
|
||||
-Dim4java.useV7=true \
|
||||
-XX:+UnlockExperimentalVMOptions \
|
||||
-XX:+UseShenandoahGC \
|
||||
-XX:+UseCompactObjectHeaders \
|
||||
|
||||
@ -18,7 +18,7 @@ if [ -f ./environ ]; then
|
||||
source ./environ
|
||||
fi
|
||||
|
||||
export JAVA_OPTS="-Dim4java.useV7=true -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --sun-misc-unsafe-memory-access=allow --enable-native-access=ALL-UNNAMED --enable-preview $JVM_OPTS $JAVA_OPTS"
|
||||
export JAVA_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --sun-misc-unsafe-memory-access=allow --enable-native-access=ALL-UNNAMED --enable-preview $JVM_OPTS $JAVA_OPTS"
|
||||
|
||||
ENTRYPOINT=${1:-app.main};
|
||||
|
||||
|
||||
@ -127,6 +127,17 @@
|
||||
|
||||
[:media-max-file-size {:optional true} ::sm/int]
|
||||
[:font-max-file-size {:optional true} ::sm/int]
|
||||
|
||||
;; ImageMagick resource limits (PENPOT_IMAGEMAGICK_*)
|
||||
[:imagemagick-thread-limit {:optional true} :string]
|
||||
[:imagemagick-memory-limit {:optional true} :string]
|
||||
[:imagemagick-map-limit {:optional true} :string]
|
||||
[:imagemagick-area-limit {:optional true} :string]
|
||||
[:imagemagick-disk-limit {:optional true} :string]
|
||||
[:imagemagick-time-limit {:optional true} :string]
|
||||
[:imagemagick-width-limit {:optional true} :string]
|
||||
[:imagemagick-height-limit {:optional true} :string]
|
||||
|
||||
[:deletion-delay {:optional true} ::ct/duration]
|
||||
[:file-clean-delay {:optional true} ::ct/duration]
|
||||
[:telemetry-enabled {:optional true} ::sm/boolean]
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
[app.media.sanitize :as sanitize]
|
||||
[app.storage :as-alias sto]
|
||||
[app.storage.tmp :as tmp]
|
||||
[app.util.shell :as shell]
|
||||
[buddy.core.bytes :as bb]
|
||||
[buddy.core.codecs :as bc]
|
||||
[clojure.java.shell :as sh]
|
||||
@ -34,9 +35,7 @@
|
||||
java.io.InputStream
|
||||
javax.xml.parsers.SAXParserFactory
|
||||
javax.xml.XMLConstants
|
||||
org.apache.commons.io.IOUtils
|
||||
org.im4java.core.ConvertCmd
|
||||
org.im4java.core.IMOperation))
|
||||
org.apache.commons.io.IOUtils))
|
||||
|
||||
(def schema:upload
|
||||
[:map {:title "Upload"}
|
||||
@ -152,16 +151,70 @@
|
||||
;; Related info on how thumbnails generation
|
||||
;; http://www.imagemagick.org/Usage/thumbnails/
|
||||
|
||||
(def ^:private imagemagick-defaults
|
||||
"Default environment variables for ImageMagick resource limits.
|
||||
These are the soft ceiling — policy.xml is the hard ceiling."
|
||||
{"MAGICK_THREAD_LIMIT" "2"
|
||||
"MAGICK_MEMORY_LIMIT" "256MiB"
|
||||
"MAGICK_MAP_LIMIT" "512MiB"
|
||||
"MAGICK_AREA_LIMIT" "128MP"
|
||||
"MAGICK_DISK_LIMIT" "1GiB"
|
||||
"MAGICK_TIME_LIMIT" "30"})
|
||||
|
||||
(defn- imagemagick-env
|
||||
"Returns environment variables for ImageMagick commands.
|
||||
Reads individual PENPOT_IMAGEMAGICK_* config values, falling back to defaults."
|
||||
[]
|
||||
(cond-> imagemagick-defaults
|
||||
(cf/get :imagemagick-thread-limit)
|
||||
(assoc "MAGICK_THREAD_LIMIT" (cf/get :imagemagick-thread-limit))
|
||||
|
||||
(cf/get :imagemagick-memory-limit)
|
||||
(assoc "MAGICK_MEMORY_LIMIT" (cf/get :imagemagick-memory-limit))
|
||||
|
||||
(cf/get :imagemagick-map-limit)
|
||||
(assoc "MAGICK_MAP_LIMIT" (cf/get :imagemagick-map-limit))
|
||||
|
||||
(cf/get :imagemagick-area-limit)
|
||||
(assoc "MAGICK_AREA_LIMIT" (cf/get :imagemagick-area-limit))
|
||||
|
||||
(cf/get :imagemagick-disk-limit)
|
||||
(assoc "MAGICK_DISK_LIMIT" (cf/get :imagemagick-disk-limit))
|
||||
|
||||
(cf/get :imagemagick-time-limit)
|
||||
(assoc "MAGICK_TIME_LIMIT" (cf/get :imagemagick-time-limit))
|
||||
|
||||
(cf/get :imagemagick-width-limit)
|
||||
(assoc "MAGICK_WIDTH_LIMIT" (cf/get :imagemagick-width-limit))
|
||||
|
||||
(cf/get :imagemagick-height-limit)
|
||||
(assoc "MAGICK_HEIGHT_LIMIT" (cf/get :imagemagick-height-limit))))
|
||||
|
||||
(defn- exec-magick!
|
||||
"Execute an ImageMagick command with resource limits.
|
||||
`args` is a vector of string arguments to pass to `magick`."
|
||||
[system args]
|
||||
(let [cmd (into ["magick"] args)
|
||||
result (shell/exec! system
|
||||
:cmd cmd
|
||||
:env (imagemagick-env)
|
||||
:timeout 60)]
|
||||
(when (not= 0 (:exit result))
|
||||
(ex/raise :type :internal
|
||||
:code :imagemagick-error
|
||||
:hint (str "ImageMagick command failed: " (:err result))
|
||||
:cmd cmd
|
||||
:exit (:exit result)))
|
||||
result))
|
||||
|
||||
(defn- generic-process
|
||||
[{:keys [input format operation] :as params}]
|
||||
[{:keys [input format convert-args] :as params}]
|
||||
(let [{:keys [path mtype]} input
|
||||
format (or (cm/mtype->format mtype) format)
|
||||
ext (cm/format->extension format)
|
||||
tmp (tmp/tempfile :prefix "penpot.media." :suffix ext)]
|
||||
|
||||
(doto (ConvertCmd.)
|
||||
(.run operation (into-array (map str [path tmp]))))
|
||||
|
||||
tmp (tmp/tempfile :prefix "penpot.media." :suffix ext)
|
||||
args (into [(str path)] (conj (vec convert-args) (str tmp)))]
|
||||
(exec-magick! nil args)
|
||||
(assoc params
|
||||
:format format
|
||||
:mtype (cm/format->mtype format)
|
||||
@ -171,36 +224,24 @@
|
||||
(defmethod process :generic-thumbnail
|
||||
[params]
|
||||
(let [{:keys [quality width height] :as params}
|
||||
(check-thumbnail-params params)
|
||||
|
||||
operation
|
||||
(doto (IMOperation.)
|
||||
(.addImage)
|
||||
(.autoOrient)
|
||||
(.strip)
|
||||
(.thumbnail ^Integer (int width) ^Integer (int height) ">")
|
||||
(.quality (double quality))
|
||||
(.addImage))]
|
||||
|
||||
(generic-process (assoc params :operation operation))))
|
||||
(check-thumbnail-params params)]
|
||||
(generic-process
|
||||
(assoc params
|
||||
:convert-args ["-auto-orient" "-strip"
|
||||
"-thumbnail" (str width "x" height ">")
|
||||
"-quality" (str quality)]))))
|
||||
|
||||
(defmethod process :profile-thumbnail
|
||||
[params]
|
||||
(let [{:keys [quality width height] :as params}
|
||||
(check-thumbnail-params params)
|
||||
|
||||
operation
|
||||
(doto (IMOperation.)
|
||||
(.addImage)
|
||||
(.autoOrient)
|
||||
(.strip)
|
||||
(.thumbnail ^Integer (int width) ^Integer (int height) "^")
|
||||
(.gravity "center")
|
||||
(.extent (int width) (int height))
|
||||
(.quality (double quality))
|
||||
(.addImage))]
|
||||
|
||||
(generic-process (assoc params :operation operation))))
|
||||
(check-thumbnail-params params)]
|
||||
(generic-process
|
||||
(assoc params
|
||||
:convert-args ["-auto-orient" "-strip"
|
||||
"-thumbnail" (str width "x" height "^")
|
||||
"-gravity" "center"
|
||||
"-extent" (str width "x" height)
|
||||
"-quality" (str quality)]))))
|
||||
|
||||
(defn get-basic-info-from-svg
|
||||
[{:keys [tag attrs] :as data}]
|
||||
@ -233,8 +274,8 @@
|
||||
(defn- get-dimensions-with-orientation [^String path]
|
||||
;; Image magick doesn't give info about exif rotation so we use the identify command
|
||||
;; If we are processing an animated gif we use the first frame with -scene 0
|
||||
(let [dim-result (sh/sh "identify" "-format" "%w %h\n" path)
|
||||
orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)]
|
||||
(let [dim-result (exec-magick! nil ["identify" "-format" "%w %h\n" path])
|
||||
orient-result (exec-magick! nil ["identify" "-format" "%[EXIF:Orientation]\n" path])]
|
||||
(when (= 0 (:exit dim-result))
|
||||
(let [[w h] (-> (:out dim-result)
|
||||
str/trim
|
||||
@ -260,7 +301,7 @@
|
||||
(merge input info {:ts (ct/now) :size (fs/size path)}))
|
||||
|
||||
(let [path-str (str path)
|
||||
identify-res (sh/sh "identify" "-format" "image/%[magick]\n" path-str)
|
||||
identify-res (exec-magick! nil ["identify" "-format" "image/%[magick]\n" path-str])
|
||||
;; identify prints one line per frame (animated GIFs, etc.); we take the first one
|
||||
mtype' (if (zero? (:exit identify-res))
|
||||
(-> identify-res
|
||||
@ -291,13 +332,6 @@
|
||||
:size (fs/size path)
|
||||
:ts (ct/now))))))
|
||||
|
||||
(defmethod process-error org.im4java.core.InfoException
|
||||
[error]
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-image
|
||||
:hint "invalid image"
|
||||
:cause error))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; IMAGE HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user