Use Magickwand to handle images. (#1212)

* Add MagickWand perf test.

* Use MagickWand.

* Calculate the right thumbnail dimension.

* Remove magick bin file.

* Add building dependencies.

* Build images.

* Add --no-install-recommends

* Remove unused field.

* Fix typo

* Use uint in the worker.

* Add guard

* Always update apt.

* Fix EncodeJpeg() with the quality

* Rename the local error with a better name.

* Add worker.Terminate()

* Fix typo

* Rebase with master and rollback the build workflow.
This commit is contained in:
Googol Lee
2025-06-03 22:04:03 +02:00
committed by GitHub
parent c390f83355
commit 535f8e57fd
14 changed files with 236 additions and 336 deletions

View File

@@ -74,7 +74,6 @@ RUN export $(cat /env) \
&& cp -a include/* /usr/local/include/ \
&& cp -a pkgconfig/* ${PKG_CONFIG_PATH} \
&& cp -a lib/* /usr/local/lib/ \
&& cp -a bin/* /usr/local/bin/ \
&& ldconfig \
&& apt-get install -y ./deb/jellyfin-ffmpeg.deb
@@ -116,8 +115,6 @@ RUN --mount=type=bind,from=api,source=/dependencies/,target=/dependencies/ \
# Install self-building libs
&& cd /dependencies \
&& cp -a lib/*.so* /usr/local/lib/ \
&& cp -a bin/* /usr/local/bin/ \
&& cp -a etc/* /usr/local/etc/ \
&& ldconfig \
&& apt-get install -y ./deb/jellyfin-ffmpeg.deb \
&& ln -s /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/local/bin/ \

View File

@@ -23,6 +23,7 @@ require (
github.com/xor-gate/goexif2 v1.1.0
golang.org/x/crypto v0.38.0
golang.org/x/text v0.25.0
gopkg.in/gographics/imagick.v3 v3.7.2
gopkg.in/vansante/go-ffprobe.v2 v2.2.1
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.6.0

View File

@@ -118,6 +118,8 @@ golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gographics/imagick.v3 v3.7.2 h1:PmsYCf60YS/7f1omBTDaoS6yp4817Wv61S0JpWH4cMc=
gopkg.in/gographics/imagick.v3 v3.7.2/go.mod h1:7I4S9VWdwr88yzYi7g+ZL4H8oZuH9cmSQI7GsZCcYFM=
gopkg.in/vansante/go-ffprobe.v2 v2.2.1 h1:sFV08OT1eZ1yroLCZVClIVd9YySgCh9eGjBWO0oRayI=
gopkg.in/vansante/go-ffprobe.v2 v2.2.1/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -21,6 +21,35 @@ type Dimension struct {
Height int
}
// ThumbnailScale generates a new dimension for thumbnails.
func (d *Dimension) ThumbnailScale() Dimension {
if d.Height == 0 || d.Width == 0 {
return Dimension{Width: 0, Height: 0}
}
aspect := float64(d.Width) / float64(d.Height)
var width, height int
if aspect > 1 {
width = 1024
height = int(1024 / aspect)
} else {
width = int(1024 * aspect)
height = 1024
}
if width > d.Width {
width = d.Width
height = d.Height
}
return Dimension{
Width: width,
Height: height,
}
}
// GetPhotoDimensions returns the dimension of the image `imagePath`.
func GetPhotoDimensions(imagePath string) (Dimension, error) {
w, h, err := executable_worker.Magick.IdentifyDimension(imagePath)
@@ -29,19 +58,30 @@ func GetPhotoDimensions(imagePath string) (Dimension, error) {
}
return Dimension{
Width: w,
Height: h,
Width: int(w),
Height: int(h),
}, nil
}
// EncodeThumbnail encodes a thumbnail of `inputPath`, and store it as `outputPath`.
// It returns the dimension of the thumbnail. The thumbnail will be not bigger than 1024x1024.
func EncodeThumbnail(db *gorm.DB, inputPath string, outputPath string) (Dimension, error) {
if err := executable_worker.Magick.GenerateThumbnail(inputPath, outputPath, 1024, 1024); err != nil {
w, h, err := executable_worker.Magick.IdentifyDimension(inputPath)
if err != nil {
return Dimension{}, fmt.Errorf("can't generate thumbnail of file %q: %w", inputPath, err)
}
return GetPhotoDimensions(outputPath)
origin := Dimension{
Width: int(w),
Height: int(h),
}
thumbnail := origin.ThumbnailScale()
if err := executable_worker.Magick.GenerateThumbnail(inputPath, outputPath, uint(thumbnail.Width), uint(thumbnail.Height)); err != nil {
return Dimension{}, fmt.Errorf("can't generate thumbnail of file %q: %w", inputPath, err)
}
return thumbnail, nil
}
// EncodeMediaData is used to easily decode media data, with a cache so expensive operations are not repeated

View File

@@ -13,16 +13,22 @@ import (
var ErrNoDependency = errors.New("dependency not found")
var ErrDisabledFunction = errors.New("function disabled")
func init() {
Magick = newMagickCli()
// Initialize Initializes all workers. It returns a function to terminate workers, which should be called before the program closing.
func Initialize() func() {
Magick = newMagickWand()
Ffmpeg = newFfmpegCli()
if err := SetFfprobePath(); err != nil {
log.Error("Init ffprobe fail.", "error", err)
}
return func() {
Magick.Terminate()
Magick = nil
}
}
var Magick *MagickCli = nil
var Magick *MagickWand = nil
var Ffmpeg *FfmpegCli = nil
type ExecutableWorker interface {

View File

@@ -1,133 +0,0 @@
package executable_worker
import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"strings"
"github.com/photoview/photoview/api/log"
"github.com/photoview/photoview/api/utils"
)
type MagickCli struct {
path string
err error
}
func newMagickCli() *MagickCli {
if utils.EnvDisableRawProcessing.GetBool() {
log.Warn("Executable magick worker disabled", utils.EnvDisableRawProcessing.GetName(), utils.EnvDisableRawProcessing.GetValue())
return &MagickCli{
err: ErrDisabledFunction,
}
}
path, err := exec.LookPath("magick")
if err != nil {
log.Error("Executable magick worker not found")
return &MagickCli{
err: ErrNoDependency,
}
}
version, err := exec.Command(path, "-version").Output()
if err != nil {
log.Error("Executable magick worker get version error", "error", err)
return &MagickCli{
err: ErrNoDependency,
}
}
log.Info("Found magick executable worker", "version", strings.Split(string(version), "\n")[0])
return &MagickCli{
path: path,
}
}
func (cli *MagickCli) IsInstalled() bool {
return cli.err == nil
}
func (cli *MagickCli) EncodeJpeg(inputPath string, outputPath string, jpegQuality int) error {
if cli.err != nil {
return fmt.Errorf("encoding jpeg %q error: magick: %w", inputPath, cli.err)
}
args := []string{
inputPath,
"-auto-orient",
"-quality", fmt.Sprintf("%d", jpegQuality),
outputPath,
}
cmd := exec.Command(cli.path, args...)
if err := cmd.Run(); err != nil {
return fmt.Errorf("encoding image with \"%s %v\" error: %w", cli.path, args, err)
}
return nil
}
func (cli *MagickCli) GenerateThumbnail(inputPath string, outputPath string, width, height int) error {
if cli.err != nil {
return fmt.Errorf("generate thumbnail %q error: magick: %w", inputPath, cli.err)
}
args := []string{
inputPath + "[0]", // If there are multiple frames (like gif), only thumbnail the first frame.
"-thumbnail",
fmt.Sprintf("%dx%d", width, height),
outputPath,
}
cmd := exec.Command(cli.path, args...)
if err := cmd.Run(); err != nil {
return fmt.Errorf("generate thumbnail with \"%s %v\" error: %w", cli.path, args, err)
}
return nil
}
func (cli *MagickCli) IdentifyDimension(inputPath string) (width, height int, err error) {
if cli.err != nil {
err = fmt.Errorf("identify dimension %q error: magick: %w", inputPath, cli.err)
return
}
args := []string{
"identify",
"-format",
`{"height":%H, "width":%W}`,
inputPath,
}
cmd := exec.Command(cli.path, args...)
var output bytes.Buffer
cmd.Stdout = &output
if e := cmd.Run(); e != nil {
err = fmt.Errorf("identify dimension with \"%s %v\" error: %w", cli.path, args, e)
return
}
ret := struct {
Width *int
Height *int
}{
Width: &width,
Height: &height,
}
if e := json.NewDecoder(&output).Decode(&ret); e != nil {
err = fmt.Errorf("identify dimension with \"%s %v\" error: %w", cli.path, args, e)
return
}
return
}

View File

@@ -1,179 +0,0 @@
package executable_worker
import (
"errors"
"regexp"
"testing"
)
func TestMagickCliNotExist(t *testing.T) {
SetPathWithCurrent(t, "")
Magick = newMagickCli()
if got, want := Magick.err, ErrNoDependency; got != want {
t.Errorf("Magick.err = %v, want: %v", got, want)
}
if Magick.IsInstalled() {
t.Error("MagickCli should not be installed, but is found:", Magick)
}
if got, want := Magick.EncodeJpeg("input", "output", 70), ErrNoDependency; !errors.Is(got, want) {
t.Errorf("Magick.EncodeJpeg() = %v, want: %v", got, want)
}
if got, want := Magick.GenerateThumbnail("input", "output", 100, 100), ErrNoDependency; !errors.Is(got, want) {
t.Errorf("Magick.GenerateThumbnail() = %v, want: %v", got, want)
}
{
_, _, got := Magick.IdentifyDimension("input")
if want := ErrNoDependency; !errors.Is(got, want) {
t.Errorf("Magick.IdentifyDimension() = %v, want: %v", got, want)
}
}
}
func TestMagickCliIgnore(t *testing.T) {
SetPathWithCurrent(t, testdataBinPath)
t.Setenv("PHOTOVIEW_DISABLE_RAW_PROCESSING", "true")
Magick = newMagickCli()
if got, want := Magick.err, ErrDisabledFunction; got != want {
t.Errorf("Magick.err = %v, want: %v", got, want)
}
if Magick.IsInstalled() {
t.Error("MagickCli should not be installed, but is found:", Magick)
}
if got, want := Magick.EncodeJpeg("input", "output", 70), ErrDisabledFunction; !errors.Is(got, want) {
t.Errorf("Magick.EncodeJpeg() = %v, want: %v", got, want)
}
if got, want := Magick.GenerateThumbnail("input", "output", 100, 100), ErrDisabledFunction; !errors.Is(got, want) {
t.Errorf("Magick.GenerateThumbnail() = %v, want: %v", got, want)
}
{
_, _, got := Magick.IdentifyDimension("input")
if want := ErrDisabledFunction; !errors.Is(got, want) {
t.Errorf("Magick.IdentifyDimension() = %v, want: %v", got, want)
}
}
}
func TestMagickCliVersionFail(t *testing.T) {
SetPathWithCurrent(t, testdataBinPath)
t.Setenv("FAIL_WITH", "failure")
Magick = newMagickCli()
if got, want := Magick.err, ErrNoDependency; got != want {
t.Errorf("Magick.err = %v, want: %v", got, want)
}
if Magick.IsInstalled() {
t.Error("MagickCli should not be installed, but is found:", Magick)
}
if got, want := Magick.EncodeJpeg("input", "output", 70), ErrNoDependency; !errors.Is(got, want) {
t.Errorf("Magick.EncodeJpeg() = %v, want: %v", got, want)
}
if got, want := Magick.GenerateThumbnail("input", "output", 100, 100), ErrNoDependency; !errors.Is(got, want) {
t.Errorf("Magick.GenerateThumbnail() = %v, want: %v", got, want)
}
{
_, _, got := Magick.IdentifyDimension("input")
if want := ErrNoDependency; !errors.Is(got, want) {
t.Errorf("Magick.IdentifyDimension() = %v, want: %v", got, want)
}
}
}
func TestMagickCliFail(t *testing.T) {
SetPathWithCurrent(t, testdataBinPath)
Magick = newMagickCli()
if !Magick.IsInstalled() {
t.Fatal("MagickCli should be installed")
}
t.Setenv("FAIL_WITH", "failure")
err := Magick.EncodeJpeg("input", "output", 70)
if err == nil {
t.Fatalf(`MagickCli.EncodeJpeg(...) = nil, should be an error.`)
}
if got, want := err.Error(), `^encoding image with ".*/test_data/mock_bin/magick \[input -auto-orient -quality 70 output\]" error: .*$`; !regexp.MustCompile(want).MatchString(got) {
t.Errorf(`MagickCli.EncodeJpeg(...) = %q, should be matched with reg pattern %q`, got, want)
}
err = Magick.GenerateThumbnail("input", "output", 100, 100)
if err == nil {
t.Fatalf(`MagickCli.GenerateThumbnail(...) = nil, should be an error.`)
}
if got, want := err.Error(), `^generate thumbnail with ".*/test_data/mock_bin/magick \[input\[0\] -thumbnail 100x100 output\]" error: .*$`; !regexp.MustCompile(want).MatchString(got) {
t.Errorf(`MagickCli.GenerateThumbnail(...) = %q, should be matched with reg pattern %q`, got, want)
}
{
_, _, got := Magick.IdentifyDimension("input")
if want := `^identify dimension with ".*/test_data/mock_bin/magick \[identify -format {"height":\%H, "width":\%W} input\]" error: .*$`; !regexp.MustCompile(want).MatchString(got.Error()) {
t.Errorf("Magick.IdentifyDimension() = %v, should be matched with reg pattern %q", got, want)
}
}
}
func TestMagickCliSucceed(t *testing.T) {
SetPathWithCurrent(t, testdataBinPath)
Magick = newMagickCli()
if !Magick.IsInstalled() {
t.Fatal("MagickCli should be installed")
}
t.Run("EncodeJpeg", func(t *testing.T) {
err := Magick.EncodeJpeg("input", "output", 70)
if err != nil {
t.Fatalf("MagickCli.EncodeJpeg(...) = %v, should be nil.", err)
}
})
t.Run("GenerateThumbnail", func(t *testing.T) {
err := Magick.GenerateThumbnail("input", "output", 100, 100)
if err != nil {
t.Fatalf("MagickCli.GenerateThumbnail(...) = %v, should be nil.", err)
}
})
t.Run("IdentifyDimension", func(t *testing.T) {
w, h, err := Magick.IdentifyDimension("input")
if err != nil {
t.Fatalf("MagickCli.IdentifyDimension(...) = %v, should be nil.", err)
}
if got, want := w, 1000; got != want {
t.Errorf("got = %d, want = %d", got, want)
}
if got, want := h, 800; got != want {
t.Errorf("got = %d, want = %d", got, want)
}
})
t.Run("IdentifyDimensionInvalidJSON", func(t *testing.T) {
t.Setenv("INVALID_OUTPUT", `{"width":1000,`)
_, _, err := Magick.IdentifyDimension("input")
if want := `unexpected EOF$`; !regexp.MustCompile(want).MatchString(err.Error()) {
t.Errorf("MagickCli.IdentifyDimension() = error(%v), which should match with regexp %q", err, want)
}
})
}

View File

@@ -0,0 +1,111 @@
package executable_worker
import (
"fmt"
"github.com/photoview/photoview/api/log"
"gopkg.in/gographics/imagick.v3/imagick"
)
type MagickWand struct {
initialized bool
}
func newMagickWand() *MagickWand {
imagick.Initialize()
verstr, vernum := imagick.GetVersion()
log.Info("Found magickwand worker: "+verstr, "version", vernum)
return &MagickWand{
initialized: true,
}
}
func (cli *MagickWand) Terminate() {
cli.initialized = false
imagick.Terminate()
}
func (cli *MagickWand) IsInstalled() bool {
return cli != nil && cli.initialized
}
func (cli *MagickWand) EncodeJpeg(inputPath string, outputPath string, jpegQuality uint) error {
if !cli.IsInstalled() {
return fmt.Errorf("ImagickWand is not initialized")
}
wand := imagick.NewMagickWand()
defer wand.Destroy()
if err := wand.ReadImage(inputPath); err != nil {
return fmt.Errorf("ImagickWand read %q error: %w", inputPath, err)
}
if err := wand.SetFormat("JPEG"); err != nil {
return fmt.Errorf("ImagickWand set JPEG format for %q error: %w", inputPath, err)
}
if err := wand.SetImageCompressionQuality(jpegQuality); err != nil {
return fmt.Errorf("ImagickWand set JPEG quality %d for %q error: %w", jpegQuality, inputPath, err)
}
if err := wand.WriteImage(outputPath); err != nil {
return fmt.Errorf("ImagickWand write %q error: %w", outputPath, err)
}
return nil
}
func (cli *MagickWand) GenerateThumbnail(inputPath string, outputPath string, width, height uint) error {
if !cli.IsInstalled() {
return fmt.Errorf("ImagickWand is not initialized")
}
wand := imagick.NewMagickWand()
defer wand.Destroy()
if err := wand.ReadImage(inputPath); err != nil {
return fmt.Errorf("ImagickWand read %q error: %w", inputPath, err)
}
if err := wand.ThumbnailImage(width, height); err != nil {
return fmt.Errorf("ImagickWand generate thumbnail for %q error: %w", inputPath, err)
}
if err := wand.SetFormat("JPEG"); err != nil {
return fmt.Errorf("ImagickWand set JPEG format for %q error: %w", inputPath, err)
}
if err := wand.SetImageCompressionQuality(70); err != nil {
return fmt.Errorf("ImagickWand set JPEG quality %d for %q error: %w", 70, inputPath, err)
}
if err := wand.WriteImage(outputPath); err != nil {
return fmt.Errorf("ImagickWand write %q error: %w", outputPath, err)
}
return nil
}
func (cli *MagickWand) IdentifyDimension(inputPath string) (width, height uint, err error) {
if !cli.IsInstalled() {
err = fmt.Errorf("ImagickWand is not initialized")
return
}
wand := imagick.NewMagickWand()
defer wand.Destroy()
if errRI := wand.ReadImage(inputPath); errRI != nil {
err = fmt.Errorf("ImagickWand read %q error: %w", inputPath, errRI)
return
}
width = wand.GetImageWidth()
height = wand.GetImageHeight()
return
}

View File

@@ -9,6 +9,7 @@ import (
"testing"
"github.com/photoview/photoview/api/scanner/media_encoding/executable_worker"
"gopkg.in/gographics/imagick.v3/imagick"
)
func BenchmarkStdlib(b *testing.B) {
@@ -57,3 +58,36 @@ func BenchmarkMagickCLI(b *testing.B) {
}()
}
}
func BenchmarkMagickWand(b *testing.B) {
dir := b.TempDir()
imagick.Initialize()
defer imagick.Terminate()
for b.Loop() {
func() {
mw := imagick.NewMagickWand()
defer mw.Destroy()
if err := mw.ReadImage("./test_media/real_media/png.png"); err != nil {
b.Fatal("read error:", err)
}
output := filepath.Join(dir, "test.jpg")
defer os.Remove(output)
if err := mw.SetFormat("JPEG"); err != nil {
b.Fatal("set format error:", err)
}
if err := mw.SetImageCompressionQuality(70); err != nil {
b.Fatal("set quality error:", err)
}
if err := mw.WriteImage(output); err != nil {
b.Fatal("write error:", err)
}
}()
}
}

View File

@@ -122,7 +122,7 @@ func TestFullScan(t *testing.T) {
slices.Sort(got)
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("all media diff:\n%s", diff)
t.Errorf("all media diff (-got, +want):\n%s", diff)
}
})
@@ -168,7 +168,7 @@ func TestFullScan(t *testing.T) {
slices.Sort(got)
if diff := cmp.Diff(got, want, cmp.Comparer(equalNameWithoutSuffix)); diff != "" {
t.Errorf("all media diff:\n%s", diff)
t.Errorf("all media diff (-got, +want):\n%s", diff)
}
})
@@ -202,7 +202,7 @@ func TestFullScan(t *testing.T) {
got := groupMediaWithFaces(allImageFaces)
if diff := cmp.Diff(got, wantFaceGroups); diff != "" {
t.Errorf("all media diff:\n%s", diff)
t.Errorf("all media diff (-got, +want):\n%s", diff)
}
})
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/photoview/photoview/api/routes"
"github.com/photoview/photoview/api/scanner/exif"
"github.com/photoview/photoview/api/scanner/face_detection"
"github.com/photoview/photoview/api/scanner/media_encoding/executable_worker"
"github.com/photoview/photoview/api/scanner/periodic_scanner"
"github.com/photoview/photoview/api/scanner/scanner_queue"
"github.com/photoview/photoview/api/server"
@@ -33,6 +34,9 @@ func main() {
log.Println("No .env file found")
}
terminateWorkers := executable_worker.Initialize()
defer terminateWorkers()
devMode := utils.DevelopmentMode()
db, err := database.SetupDatabase()

View File

@@ -8,6 +8,7 @@ import (
"testing"
"github.com/joho/godotenv"
"github.com/photoview/photoview/api/scanner/media_encoding/executable_worker"
"github.com/photoview/photoview/api/utils"
"gorm.io/gorm"
)
@@ -49,6 +50,9 @@ func IntegrationTestRun(m *testing.M) int {
faceModelsPath := path.Join(path.Dir(file), "..", "data", "models")
utils.ConfigureTestFaceRecognitionModelsPath(faceModelsPath)
terminateWorkers := executable_worker.Initialize()
defer terminateWorkers()
result := m.Run()
test_dbm.Close()

View File

@@ -3,13 +3,22 @@ set -eu
: ${DEB_HOST_ARCH=`dpkg --print-architecture`}
echo Arch: ${DEB_HOST_ARCH}
apt-get update
# Install libheif dependencies
apt-get install -y --no-install-recommends libdav1d-dev:${DEB_HOST_ARCH} libde265-dev:${DEB_HOST_ARCH} libjpeg62-turbo-dev:${DEB_HOST_ARCH} libopenh264-dev:${DEB_HOST_ARCH} libpng-dev:${DEB_HOST_ARCH} libnuma-dev:${DEB_HOST_ARCH} zlib1g-dev:${DEB_HOST_ARCH}
# Install libraw dependencies
apt-get install -y --no-install-recommends libjpeg62-turbo-dev:${DEB_HOST_ARCH} liblcms2-dev:${DEB_HOST_ARCH} zlib1g-dev:${DEB_HOST_ARCH}
# Install ImageMagick dependencies
apt-get install -y --no-install-recommends libjxl-dev:${DEB_HOST_ARCH} liblcms2-dev:${DEB_HOST_ARCH} liblqr-1-0-dev:${DEB_HOST_ARCH} libdjvulibre-dev:${DEB_HOST_ARCH} libjpeg62-turbo-dev:${DEB_HOST_ARCH} libopenjp2-7-dev:${DEB_HOST_ARCH} libopenexr-dev:${DEB_HOST_ARCH} libpng-dev:${DEB_HOST_ARCH} libtiff-dev:${DEB_HOST_ARCH} libwebp-dev:${DEB_HOST_ARCH} libxml2-dev:${DEB_HOST_ARCH} libfftw3-dev:${DEB_HOST_ARCH} zlib1g-dev:${DEB_HOST_ARCH} liblzma-dev:${DEB_HOST_ARCH} libbz2-dev:${DEB_HOST_ARCH}
# Install go-face dependencies
apt-get install -y \
libdlib-dev:${DEB_HOST_ARCH} libblas-dev:${DEB_HOST_ARCH} libatlas-base-dev:${DEB_HOST_ARCH} liblapack-dev:${DEB_HOST_ARCH} libjpeg62-turbo-dev:${DEB_HOST_ARCH}
apt-get install -y --no-install-recommends libdlib-dev:${DEB_HOST_ARCH} libblas-dev:${DEB_HOST_ARCH} libatlas-base-dev:${DEB_HOST_ARCH} liblapack-dev:${DEB_HOST_ARCH} libjpeg62-turbo-dev:${DEB_HOST_ARCH}
# Install gomagic dependencies
apt-get install -y libmagic-dev:${DEB_HOST_ARCH}
apt-get install -y --no-install-recommends libmagic-dev:${DEB_HOST_ARCH}
# Install tools for development
apt-get install -y reflex sqlite3
apt-get install -y --no-install-recommends reflex sqlite3

View File

@@ -2,15 +2,19 @@
set -eu
apt-get update
apt-get install -y curl exiftool
apt-get install -y --no-install-recommends curl exiftool
# libheif dependencies
apt-get install -y libdav1d6 librav1e0 libde265-0 libx265-199 libjpeg62-turbo libopenh264-7 libpng16-16 libnuma1 zlib1g
apt-get install -y --no-install-recommends libdav1d6 librav1e0 libde265-0 libx265-199 libjpeg62-turbo libopenh264-7 libpng16-16 libnuma1 zlib1g
# libraw dependencies
apt-get install -y libjpeg62-turbo liblcms2-2 zlib1g libgomp1
apt-get install -y --no-install-recommends libjpeg62-turbo liblcms2-2 zlib1g libgomp1
# ImageMagick dependencies
apt-get install -y libjxl0.7 liblcms2-2 liblqr-1-0 libdjvulibre21 libjpeg62-turbo libopenjp2-7 libopenexr-3-1-30 libpng16-16 libtiff6 libwebpmux3 libwebpdemux2 libwebp7 libxml2 zlib1g liblzma5 libbz2-1.0 libgomp1
apt-get install -y --no-install-recommends libjxl0.7 liblcms2-2 liblqr-1-0 libdjvulibre21 libjpeg62-turbo libopenjp2-7 libopenexr-3-1-30 libpng16-16 libtiff6 libwebpmux3 libwebpdemux2 libwebp7 libxml2 zlib1g liblzma5 libbz2-1.0 libgomp1
# go-face dependencies
apt-get install -y libdlib19.1 libblas3 liblapack3 libjpeg62-turbo
# libmagic dependencies
apt-get install -y libmagic1
apt-get install -y --no-install-recommends libdlib19.1 libblas3 liblapack3 libjpeg62-turbo
# gomagic dependencies
apt-get install -y --no-install-recommends libmagic1