mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-07-28 22:28:48 +00:00
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:
@@ -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/ \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
111
api/scanner/media_encoding/executable_worker/magickwand.go
Normal file
111
api/scanner/media_encoding/executable_worker/magickwand.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user