Convert: Set virtual home in cache path for external CLI commands #2262

This commit is contained in:
Michael Mayer
2022-04-18 13:55:17 +02:00
parent 2b7b43f8e5
commit 27b84365da
30 changed files with 415 additions and 353 deletions

View File

@@ -105,9 +105,9 @@ func AlbumCover(router *gin.RouterGroup) {
var thumbnail string
if conf.ThumbUncached() || size.Uncached() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, f.FileOrientation, size.Options...)
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, f.FileOrientation, size.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, size.Options...)
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, size.Options...)
}
if err != nil {
@@ -219,9 +219,9 @@ func LabelCover(router *gin.RouterGroup) {
var thumbnail string
if conf.ThumbUncached() || size.Uncached() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, f.FileOrientation, size.Options...)
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, f.FileOrientation, size.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, size.Options...)
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, size.Options...)
}
if err != nil {

View File

@@ -114,9 +114,9 @@ func FolderCover(router *gin.RouterGroup) {
var thumbnail string
if conf.ThumbUncached() || size.Uncached() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, f.FileOrientation, size.Options...)
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, f.FileOrientation, size.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, size.Options...)
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, size.Options...)
}
if err != nil {

View File

@@ -40,7 +40,7 @@ func SharePreview(router *gin.RouterGroup) {
return
}
thumbPath := path.Join(conf.ThumbPath(), "share")
thumbPath := path.Join(conf.ThumbCachePath(), "share")
if err := os.MkdirAll(thumbPath, os.ModePerm); err != nil {
log.Error(err)
@@ -101,7 +101,7 @@ func SharePreview(router *gin.RouterGroup) {
return
}
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, f.FileOrientation, size.Options...)
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, f.FileOrientation, size.Options...)
if err != nil {
log.Error(err)
@@ -131,7 +131,7 @@ func SharePreview(router *gin.RouterGroup) {
return
}
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, f.FileOrientation, size.Options...)
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, f.FileOrientation, size.Options...)
if err != nil {
log.Error(err)

View File

@@ -51,7 +51,7 @@ func GetThumb(router *gin.RouterGroup) {
return
}
fileName, err := crop.FromRequest(fileHash, cropArea, cropSize, conf.ThumbPath())
fileName, err := crop.FromRequest(fileHash, cropArea, cropSize, conf.ThumbCachePath())
if err != nil {
log.Warnf("%s: %s", logPrefix, err)
@@ -119,7 +119,7 @@ func GetThumb(router *gin.RouterGroup) {
// Return existing thumbs straight away.
if !download {
if fileName, err := thumb.FileName(fileHash, conf.ThumbPath(), size.Width, size.Height, size.Options...); err == nil && fs.FileExists(fileName) {
if fileName, err := thumb.FileName(fileHash, conf.ThumbCachePath(), size.Width, size.Height, size.Options...); err == nil && fs.FileExists(fileName) {
AddThumbCacheHeader(c)
c.File(fileName)
return
@@ -183,9 +183,9 @@ func GetThumb(router *gin.RouterGroup) {
var thumbnail string
if conf.ThumbUncached() || size.Uncached() {
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, f.FileOrientation, size.Options...)
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, f.FileOrientation, size.Options...)
} else {
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), size.Width, size.Height, size.Options...)
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, size.Options...)
}
if err != nil {

View File

@@ -34,7 +34,7 @@ func thumbsAction(ctx *cli.Context) error {
return err
}
log.Infof("creating thumbnails in %s", clean.Log(conf.ThumbPath()))
log.Infof("creating thumbnails in %s", clean.Log(conf.ThumbCachePath()))
rs := service.Resample()

View File

@@ -74,6 +74,12 @@ func (c *Config) CreateDirectories() error {
return createError(c.StoragePath(), err)
}
if c.CmdCachePath() == "" {
return notFoundError("cmd cache")
} else if err := os.MkdirAll(c.CmdCachePath(), os.ModePerm); err != nil {
return createError(c.CmdCachePath(), err)
}
if c.BackupPath() == "" {
return notFoundError("backup")
} else if err := os.MkdirAll(c.BackupPath(), os.ModePerm); err != nil {
@@ -104,10 +110,10 @@ func (c *Config) CreateDirectories() error {
return createError(c.CachePath(), err)
}
if c.ThumbPath() == "" {
if c.ThumbCachePath() == "" {
return notFoundError("thumbs")
} else if err := os.MkdirAll(c.ThumbPath(), os.ModePerm); err != nil {
return createError(c.ThumbPath(), err)
} else if err := os.MkdirAll(c.ThumbCachePath(), os.ModePerm); err != nil {
return createError(c.ThumbCachePath(), err)
}
if c.ConfigPath() == "" {
@@ -153,12 +159,12 @@ func (c *Config) CreateDirectories() error {
}
if c.DarktableEnabled() {
if cachePath, err := c.CreateDarktableCachePath(); err != nil {
return fmt.Errorf("could not create darktable cache path %s", clean.Log(cachePath))
if dir, err := c.CreateDarktableCachePath(); err != nil {
return fmt.Errorf("could not create darktable cache path %s", clean.Log(dir))
}
if configPath, err := c.CreateDarktableConfigPath(); err != nil {
return fmt.Errorf("could not create darktable cache path %s", clean.Log(configPath))
if dir, err := c.CreateDarktableConfigPath(); err != nil {
return fmt.Errorf("could not create darktable cache path %s", clean.Log(dir))
}
}
@@ -263,11 +269,17 @@ func (c *Config) SidecarWritable() bool {
// TempPath returns a temporary directory name for uploads and downloads.
func (c *Config) TempPath() string {
if c.options.TempPath == "" {
return filepath.Join(os.TempDir(), "photoprism")
if c.options.TempPath != "" {
if c.options.TempPath[0] != '/' {
c.options.TempPath = fs.Abs(c.options.TempPath)
}
} else if dir, err := os.MkdirTemp(os.TempDir(), "photoprism"); err == nil {
c.options.TempPath = dir
} else {
c.options.TempPath = filepath.Join(os.TempDir(), "photoprism")
}
return fs.Abs(c.options.TempPath)
return c.options.TempPath
}
// CachePath returns the path for cache files.
@@ -279,6 +291,16 @@ func (c *Config) CachePath() string {
return fs.Abs(c.options.CachePath)
}
// CmdCachePath returns a path that CLI commands can use as cache directory.
func (c *Config) CmdCachePath() string {
return filepath.Join(c.CachePath(), "cmd")
}
// ThumbCachePath returns the thumbnail storage directory.
func (c *Config) ThumbCachePath() string {
return c.CachePath() + "/thumbnails"
}
// StoragePath returns the path for generated files like cache and index.
func (c *Config) StoragePath() string {
if c.options.StoragePath == "" {

View File

@@ -1,6 +1,7 @@
package config
import (
"strings"
"testing"
"github.com/photoprism/photoprism/pkg/rnd"
@@ -44,7 +45,21 @@ func TestConfig_TempPath(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/temp", c.TempPath())
c.options.TempPath = ""
assert.Equal(t, "/tmp/photoprism", c.TempPath())
if dir := c.TempPath(); dir == "" {
t.Fatal("temp path is empty")
} else if !strings.HasPrefix(dir, "/tmp/photoprism") {
t.Fatalf("unexpected temp path: %s", dir)
}
}
func TestConfig_CmdCachePath(t *testing.T) {
c := NewConfig(CliTestContext())
if dir := c.CmdCachePath(); dir == "" {
t.Fatal("cmd cache path is empty")
} else if !strings.HasPrefix(dir, c.CachePath()) {
t.Fatalf("unexpected cmd cache path: %s", dir)
}
}
func TestConfig_CachePath2(t *testing.T) {

View File

@@ -2,7 +2,8 @@ package config
import (
"os"
"path/filepath"
"github.com/photoprism/photoprism/pkg/fs"
)
// RawEnabled checks if indexing and conversion of RAW files is enabled.
@@ -27,27 +28,21 @@ func (c *Config) DarktableBlacklist() string {
// DarktableConfigPath returns the darktable config directory.
func (c *Config) DarktableConfigPath() string {
if c.options.DarktableConfigPath != "" {
return c.options.DarktableConfigPath
}
return filepath.Join(c.ConfigPath(), "darktable")
return fs.Abs(c.options.DarktableConfigPath)
}
// DarktableCachePath returns the darktable cache directory.
func (c *Config) DarktableCachePath() string {
if c.options.DarktableCachePath != "" {
return c.options.DarktableCachePath
}
return filepath.Join(c.CachePath(), "darktable")
return fs.Abs(c.options.DarktableCachePath)
}
// CreateDarktableCachePath creates and returns the darktable cache directory.
func (c *Config) CreateDarktableCachePath() (string, error) {
cachePath := c.DarktableCachePath()
if err := os.MkdirAll(cachePath, os.ModePerm); err != nil {
if cachePath == "" {
return "", nil
} else if err := os.MkdirAll(cachePath, os.ModePerm); err != nil {
return cachePath, err
} else {
c.options.DarktableCachePath = cachePath
@@ -60,7 +55,9 @@ func (c *Config) CreateDarktableCachePath() (string, error) {
func (c *Config) CreateDarktableConfigPath() (string, error) {
configPath := c.DarktableConfigPath()
if err := os.MkdirAll(configPath, os.ModePerm); err != nil {
if configPath == "" {
return "", nil
} else if err := os.MkdirAll(configPath, os.ModePerm); err != nil {
return configPath, err
} else {
c.options.DarktableConfigPath = configPath

View File

@@ -35,9 +35,11 @@ func (c *Config) Report() (rows [][]string, cols []string) {
// Other paths.
{"storage-path", c.StoragePath()},
{"sidecar-path", c.SidecarPath()},
{"cache-path", c.CachePath()},
{"albums-path", c.AlbumsPath()},
{"backup-path", c.BackupPath()},
{"cache-path", c.CachePath()},
{"cmd-cache-path", c.CmdCachePath()},
{"thumb-cache-path", c.ThumbCachePath()},
{"import-path", c.ImportPath()},
{"assets-path", c.AssetsPath()},
{"static-path", c.StaticPath()},
@@ -142,7 +144,6 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"thumb-size", fmt.Sprintf("%d", c.ThumbSizePrecached())},
{"thumb-size-uncached", fmt.Sprintf("%d", c.ThumbSizeUncached())},
{"thumb-uncached", fmt.Sprintf("%t", c.ThumbUncached())},
{"thumb-path", c.ThumbPath()},
{"jpeg-quality", fmt.Sprintf("%d", c.JpegQuality())},
{"jpeg-size", fmt.Sprintf("%d", c.JpegSize())},

View File

@@ -38,11 +38,6 @@ func (c *Config) ThumbFilter() thumb.ResampleFilter {
}
}
// ThumbPath returns the thumbnail storage directory.
func (c *Config) ThumbPath() string {
return c.CachePath() + "/thumbnails"
}
// ThumbColor returns the color profile name for thumbnails.
func (c *Config) ThumbColor() string {
return c.options.ThumbColor

View File

@@ -159,8 +159,8 @@ func TestConfig_CachePath(t *testing.T) {
func TestConfig_ThumbnailsPath(t *testing.T) {
c := NewConfig(CliTestContext())
assert.True(t, strings.HasPrefix(c.ThumbPath(), "/"))
assert.True(t, strings.HasSuffix(c.ThumbPath(), "storage/testdata/cache/thumbnails"))
assert.True(t, strings.HasPrefix(c.ThumbCachePath(), "/"))
assert.True(t, strings.HasSuffix(c.ThumbCachePath(), "storage/testdata/cache/thumbnails"))
}
func TestConfig_AssetsPath(t *testing.T) {

View File

@@ -122,16 +122,16 @@ var GlobalFlags = []cli.Flag{
Usage: "custom relative or absolute sidecar `PATH` (optional)",
EnvVar: "PHOTOPRISM_SIDECAR_PATH",
},
cli.StringFlag{
Name: "cache-path",
Usage: "custom cache `PATH` for sessions and thumbnail files (optional)",
EnvVar: "PHOTOPRISM_CACHE_PATH",
},
cli.StringFlag{
Name: "backup-path",
Usage: "custom backup `PATH` for index backup files (optional)",
EnvVar: "PHOTOPRISM_BACKUP_PATH",
},
cli.StringFlag{
Name: "cache-path",
Usage: "custom cache `PATH` for sessions and thumbnail files (optional)",
EnvVar: "PHOTOPRISM_CACHE_PATH",
},
cli.StringFlag{
Name: "import-path",
Usage: "base `PATH` from which files can be imported to originals (optional)",
@@ -418,13 +418,13 @@ var GlobalFlags = []cli.Flag{
},
cli.StringFlag{
Name: "darktable-cache-path",
Usage: "custom Darktable cache `PATH` (automatically created if empty)",
Usage: "custom Darktable cache `PATH`",
Value: "",
EnvVar: "PHOTOPRISM_DARKTABLE_CACHE_PATH",
},
cli.StringFlag{
Name: "darktable-config-path",
Usage: "custom Darktable config `PATH` (automatically created if empty)",
Usage: "custom Darktable config `PATH`",
Value: "",
EnvVar: "PHOTOPRISM_DARKTABLE_CONFIG_PATH",
},

View File

@@ -42,8 +42,8 @@ type Options struct {
ResolutionLimit int `yaml:"ResolutionLimit" json:"ResolutionLimit" flag:"resolution-limit"`
StoragePath string `yaml:"StoragePath" json:"-" flag:"storage-path"`
SidecarPath string `yaml:"SidecarPath" json:"-" flag:"sidecar-path"`
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
BackupPath string `yaml:"BackupPath" json:"-" flag:"backup-path"`
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
AssetsPath string `yaml:"AssetsPath" json:"-" flag:"assets-path"`
TempPath string `yaml:"TempPath" json:"-" flag:"temp-path"`

View File

@@ -64,7 +64,7 @@ func (w *CleanUp) Start(opt CleanUpOptions) (thumbs int, orphans int, err error)
}
// Thumbnails storage path.
thumbPath := w.conf.ThumbPath()
thumbPath := w.conf.ThumbCachePath()
// Find and remove orphan thumbnail files.
if err := fastwalk.Walk(thumbPath, func(fileName string, info os.FileMode) error {

View File

@@ -104,7 +104,7 @@ func TestMediaFile_Colors(t *testing.T) {
t.Run("cat_brown.jpg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/cat_brown.jpg"); err == nil {
p, err := mediaFile.Colors(conf.ThumbPath())
p, err := mediaFile.Colors(conf.ThumbCachePath())
t.Log(p, err)
@@ -122,7 +122,7 @@ func TestMediaFile_Colors(t *testing.T) {
t.Run("fern_green.jpg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/fern_green.jpg"); err == nil {
p, err := mediaFile.Colors(conf.ThumbPath())
p, err := mediaFile.Colors(conf.ThumbCachePath())
t.Log(p, err)
@@ -140,7 +140,7 @@ func TestMediaFile_Colors(t *testing.T) {
t.Run("IMG_4120.JPG", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120.JPG"); err == nil {
p, err := mediaFile.Colors(conf.ThumbPath())
p, err := mediaFile.Colors(conf.ThumbCachePath())
t.Log(p, err)
@@ -157,7 +157,7 @@ func TestMediaFile_Colors(t *testing.T) {
t.Run("leaves_gold.jpg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/leaves_gold.jpg"); err == nil {
p, err := mediaFile.Colors(conf.ThumbPath())
p, err := mediaFile.Colors(conf.ThumbCachePath())
t.Log(p, err)
@@ -175,7 +175,7 @@ func TestMediaFile_Colors(t *testing.T) {
t.Run("Random.docx", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/Random.docx")
p, err := mediaFile.Colors(conf.ThumbPath())
p, err := mediaFile.Colors(conf.ThumbCachePath())
assert.Error(t, err, "no color information: not a JPEG file")
t.Log(p)

View File

@@ -77,6 +77,7 @@ func (c *Convert) ToAvc(f *MediaFile, encoder ffmpeg.AvcEncoder, noMutex, force
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
cmd.Env = []string{fmt.Sprintf("HOME=%s", c.conf.CmdCachePath())}
event.Publish("index.converting", event.Data{
"fileType": f.FileType(),

View File

@@ -65,3 +65,107 @@ func TestConvert_ToAvc(t *testing.T) {
assert.Nil(t, avcFile)
})
}
func TestConvert_AvcBitrate(t *testing.T) {
conf := config.TestConfig()
convert := NewConvert(conf)
t.Run("low", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "1M", convert.AvcBitrate(mf))
})
t.Run("medium", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
mf.width = 1280
mf.height = 1024
assert.Equal(t, "16M", convert.AvcBitrate(mf))
})
t.Run("high", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
mf.width = 1920
mf.height = 1080
assert.Equal(t, "25M", convert.AvcBitrate(mf))
})
t.Run("very_high", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
mf.width = 4096
mf.height = 2160
assert.Equal(t, "50M", convert.AvcBitrate(mf))
})
}
func TestConvert_AvcConvertCommand(t *testing.T) {
conf := config.TestConfig()
convert := NewConvert(conf)
t.Run(".mp4", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
r, _, err := convert.AvcConvertCommand(mf, "avc1", "")
if err != nil {
t.Fatal(err)
}
assert.Contains(t, r.Path, "ffmpeg")
assert.Contains(t, r.Args, "mp4")
})
t.Run(".jpg", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "cat_black.jpg")
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
r, _, err := convert.AvcConvertCommand(mf, "avc1", "")
assert.Error(t, err)
assert.Nil(t, r)
})
}

View File

@@ -101,6 +101,7 @@ func (c *Convert) ToJpeg(f *MediaFile, force bool) (*MediaFile, error) {
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
cmd.Env = []string{fmt.Sprintf("HOME=%s", c.conf.CmdCachePath())}
log.Infof("convert: converting %s to %s (%s)", clean.Log(filepath.Base(fileName)), clean.Log(filepath.Base(jpegName)), filepath.Base(cmd.Path))
@@ -135,7 +136,6 @@ func (c *Convert) JpegConvertCommand(f *MediaFile, jpegName string, xmpName stri
result = exec.Command(c.conf.SipsBin(), "-Z", maxSize, "-s", "format", "jpeg", "--out", jpegName, f.FileName())
} else if f.IsRaw() && c.conf.RawEnabled() {
if c.conf.DarktableEnabled() && c.darktableBlacklist.Ok(fileExt) {
cachePath, configPath := conf.DarktableCachePath(), conf.DarktableConfigPath()
var args []string
@@ -155,8 +155,16 @@ func (c *Convert) JpegConvertCommand(f *MediaFile, jpegName string, xmpName stri
args = append(args, "--apply-custom-presets", "false", "--width", maxSize, "--height", maxSize, "--hq", "true", "--upscale", "false")
}
// Set Darktable core storage paths.
args = append(args, "--core", "--configdir", configPath, "--cachedir", cachePath, "--library", ":memory:")
// Set library, config, and cache location.
args = append(args, "--core", "--library", ":memory:")
if dir := conf.DarktableConfigPath(); dir != "" {
args = append(args, "--configdir", dir)
}
if dir := conf.DarktableCachePath(); dir != "" {
args = append(args, "--cachedir", dir)
}
result = exec.Command(c.conf.DarktableBin(), args...)
} else if c.conf.RawtherapeeEnabled() && c.rawtherapeeBlacklist.Ok(fileExt) {

View File

@@ -0,0 +1,106 @@
package photoprism
import (
"os"
"path/filepath"
"testing"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/stretchr/testify/assert"
)
func TestConvert_ToJpeg(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
conf := config.TestConfig()
conf.InitializeTestData(t)
convert := NewConvert(conf)
t.Run("Video", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
outputName := filepath.Join(conf.SidecarPath(), conf.ExamplesPath(), "gopher-video.mp4.jpg")
_ = os.Remove(outputName)
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
jpegFile, err := convert.ToJpeg(mf, false)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, jpegFile.FileName(), outputName)
assert.Truef(t, fs.FileExists(jpegFile.FileName()), "output file does not exist: %s", jpegFile.FileName())
t.Logf("video metadata: %+v", jpegFile.MetaData())
_ = os.Remove(outputName)
})
t.Run("Raw", func(t *testing.T) {
jpegFilename := filepath.Join(conf.ImportPath(), "fern_green.jpg")
assert.Truef(t, fs.FileExists(jpegFilename), "file does not exist: %s", jpegFilename)
t.Logf("Testing RAW to JPEG convert with %s", jpegFilename)
mf, err := NewMediaFile(jpegFilename)
if err != nil {
t.Fatal(err)
}
imageJpeg, err := convert.ToJpeg(mf, false)
if err != nil {
t.Fatal(err)
}
infoJpeg := imageJpeg.MetaData()
assert.Equal(t, jpegFilename, imageJpeg.fileName)
assert.Equal(t, "Canon EOS 7D", infoJpeg.CameraModel)
rawFilename := filepath.Join(conf.ImportPath(), "raw", "IMG_2567.CR2")
jpgFilename := filepath.Join(conf.SidecarPath(), conf.ImportPath(), "raw/IMG_2567.CR2.jpg")
t.Logf("Testing RAW to JPEG convert with %s", rawFilename)
rawMediaFile, err := NewMediaFile(rawFilename)
if err != nil {
t.Fatalf("%s for %s", err.Error(), rawFilename)
}
imageRaw, err := convert.ToJpeg(rawMediaFile, false)
if err != nil {
t.Fatalf("%s for %s", err.Error(), rawFilename)
}
assert.True(t, fs.FileExists(jpgFilename), "Jpeg file was not found - is Darktable installed?")
if imageRaw == nil {
t.Fatal("imageRaw is nil")
}
assert.NotEqual(t, rawFilename, imageRaw.fileName)
infoRaw := imageRaw.MetaData()
assert.Equal(t, "Canon EOS 6D", infoRaw.CameraModel)
_ = os.Remove(jpgFilename)
})
}

View File

@@ -37,6 +37,7 @@ func (c *Convert) ToJson(f *MediaFile) (jsonName string, err error) {
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
cmd.Env = []string{fmt.Sprintf("HOME=%s", c.conf.CmdCachePath())}
// Log exact command for debugging in trace mode.
log.Trace(cmd.String())

View File

@@ -0,0 +1,93 @@
package photoprism
import (
"os"
"path/filepath"
"testing"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/stretchr/testify/assert"
)
func TestConvert_ToJson(t *testing.T) {
conf := config.TestConfig()
convert := NewConvert(conf)
t.Run("gopher-video.mp4", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
jsonName, err := convert.ToJson(mf)
if err != nil {
t.Fatal(err)
}
if jsonName == "" {
t.Fatal("json file name should not be empty")
}
assert.FileExists(t, jsonName)
_ = os.Remove(jsonName)
})
t.Run("IMG_4120.JPG", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "IMG_4120.JPG")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
jsonName, err := convert.ToJson(mf)
if err != nil {
t.Fatal(err)
}
if jsonName == "" {
t.Fatal("json file name should not be empty")
}
assert.FileExists(t, jsonName)
_ = os.Remove(jsonName)
})
t.Run("iphone_7.heic", func(t *testing.T) {
fileName := conf.ExamplesPath() + "/iphone_7.heic"
assert.True(t, fs.FileExists(fileName))
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
jsonName, err := convert.ToJson(mf)
if err != nil {
t.Fatal(err)
}
if jsonName == "" {
t.Fatal("json file name should not be empty")
}
assert.FileExists(t, jsonName)
_ = os.Remove(jsonName)
})
}

View File

@@ -18,183 +18,6 @@ func TestNewConvert(t *testing.T) {
assert.IsType(t, &Convert{}, convert)
}
func TestConvert_ToJpeg(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
conf := config.TestConfig()
conf.InitializeTestData(t)
convert := NewConvert(conf)
t.Run("gopher-video.mp4", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
outputName := filepath.Join(conf.SidecarPath(), conf.ExamplesPath(), "gopher-video.mp4.jpg")
_ = os.Remove(outputName)
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
jpegFile, err := convert.ToJpeg(mf, false)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, jpegFile.FileName(), outputName)
assert.Truef(t, fs.FileExists(jpegFile.FileName()), "output file does not exist: %s", jpegFile.FileName())
t.Logf("video metadata: %+v", jpegFile.MetaData())
_ = os.Remove(outputName)
})
t.Run("fern_green.jpg", func(t *testing.T) {
jpegFilename := filepath.Join(conf.ImportPath(), "fern_green.jpg")
assert.Truef(t, fs.FileExists(jpegFilename), "file does not exist: %s", jpegFilename)
t.Logf("Testing RAW to JPEG convert with %s", jpegFilename)
mf, err := NewMediaFile(jpegFilename)
if err != nil {
t.Fatal(err)
}
imageJpeg, err := convert.ToJpeg(mf, false)
if err != nil {
t.Fatal(err)
}
infoJpeg := imageJpeg.MetaData()
assert.Equal(t, jpegFilename, imageJpeg.fileName)
assert.Equal(t, "Canon EOS 7D", infoJpeg.CameraModel)
rawFilename := filepath.Join(conf.ImportPath(), "raw", "IMG_2567.CR2")
jpgFilename := filepath.Join(conf.SidecarPath(), conf.ImportPath(), "raw/IMG_2567.CR2.jpg")
t.Logf("Testing RAW to JPEG convert with %s", rawFilename)
rawMediaFile, err := NewMediaFile(rawFilename)
if err != nil {
t.Fatalf("%s for %s", err.Error(), rawFilename)
}
imageRaw, err := convert.ToJpeg(rawMediaFile, false)
if err != nil {
t.Fatalf("%s for %s", err.Error(), rawFilename)
}
assert.True(t, fs.FileExists(jpgFilename), "Jpeg file was not found - is Darktable installed?")
if imageRaw == nil {
t.Fatal("imageRaw is nil")
}
assert.NotEqual(t, rawFilename, imageRaw.fileName)
infoRaw := imageRaw.MetaData()
assert.Equal(t, "Canon EOS 6D", infoRaw.CameraModel)
_ = os.Remove(jpgFilename)
})
}
func TestConvert_ToJson(t *testing.T) {
conf := config.TestConfig()
convert := NewConvert(conf)
t.Run("gopher-video.mp4", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
jsonName, err := convert.ToJson(mf)
if err != nil {
t.Fatal(err)
}
if jsonName == "" {
t.Fatal("json file name should not be empty")
}
assert.FileExists(t, jsonName)
_ = os.Remove(jsonName)
})
t.Run("IMG_4120.JPG", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "IMG_4120.JPG")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
jsonName, err := convert.ToJson(mf)
if err != nil {
t.Fatal(err)
}
if jsonName == "" {
t.Fatal("json file name should not be empty")
}
assert.FileExists(t, jsonName)
_ = os.Remove(jsonName)
})
t.Run("iphone_7.heic", func(t *testing.T) {
fileName := conf.ExamplesPath() + "/iphone_7.heic"
assert.True(t, fs.FileExists(fileName))
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
jsonName, err := convert.ToJson(mf)
if err != nil {
t.Fatal(err)
}
if jsonName == "" {
t.Fatal("json file name should not be empty")
}
assert.FileExists(t, jsonName)
_ = os.Remove(jsonName)
})
}
func TestConvert_Start(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
@@ -244,107 +67,3 @@ func TestConvert_Start(t *testing.T) {
assert.NotEqual(t, oldHash, newHash, "Fingerprint of old and new JPEG file must not be the same")
}
func TestConvert_AvcBitrate(t *testing.T) {
conf := config.TestConfig()
convert := NewConvert(conf)
t.Run("low", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "1M", convert.AvcBitrate(mf))
})
t.Run("medium", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
mf.width = 1280
mf.height = 1024
assert.Equal(t, "16M", convert.AvcBitrate(mf))
})
t.Run("high", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
mf.width = 1920
mf.height = 1080
assert.Equal(t, "25M", convert.AvcBitrate(mf))
})
t.Run("very_high", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
assert.Truef(t, fs.FileExists(fileName), "input file does not exist: %s", fileName)
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
mf.width = 4096
mf.height = 2160
assert.Equal(t, "50M", convert.AvcBitrate(mf))
})
}
func TestConvert_AvcConvertCommand(t *testing.T) {
conf := config.TestConfig()
convert := NewConvert(conf)
t.Run(".mp4", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "gopher-video.mp4")
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
r, _, err := convert.AvcConvertCommand(mf, "avc1", "")
if err != nil {
t.Fatal(err)
}
assert.Contains(t, r.Path, "ffmpeg")
assert.Contains(t, r.Args, "mp4")
})
t.Run(".jpg", func(t *testing.T) {
fileName := filepath.Join(conf.ExamplesPath(), "cat_black.jpg")
mf, err := NewMediaFile(fileName)
if err != nil {
t.Fatal(err)
}
r, _, err := convert.AvcConvertCommand(mf, "avc1", "")
assert.Error(t, err)
assert.Nil(t, r)
})
}

View File

@@ -47,7 +47,7 @@ func (imp *Import) originalsPath() string {
// thumbPath returns the thumbnails cache path as string.
func (imp *Import) thumbPath() string {
return imp.conf.ThumbPath()
return imp.conf.ThumbCachePath()
}
// Start imports media files from a directory and converts/indexes them as needed.

View File

@@ -63,7 +63,7 @@ func (ind *Index) originalsPath() string {
}
func (ind *Index) thumbPath() string {
return ind.conf.ThumbPath()
return ind.conf.ThumbCachePath()
}
// Cancel stops the current indexing operation.

View File

@@ -25,7 +25,7 @@ func (ind *Index) Faces(jpeg *MediaFile, expected int) face.Faces {
thumbSize = thumb.Fit1280
}
thumbName, err := jpeg.Thumbnail(Config().ThumbPath(), thumbSize)
thumbName, err := jpeg.Thumbnail(Config().ThumbCachePath(), thumbSize)
if err != nil {
log.Debugf("index: %s in %s (faces)", err, clean.Log(jpeg.BaseName()))

View File

@@ -24,7 +24,7 @@ func (ind *Index) Labels(jpeg *MediaFile) (results classify.Labels) {
var labels classify.Labels
for _, size := range sizes {
filename, err := jpeg.Thumbnail(Config().ThumbPath(), size)
filename, err := jpeg.Thumbnail(Config().ThumbCachePath(), size)
if err != nil {
log.Debugf("%s in %s", err, clean.Log(jpeg.BaseName()))

View File

@@ -354,7 +354,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName, photoUID
switch {
case m.IsJpeg():
// Color information
if p, err := m.Colors(Config().ThumbPath()); err != nil {
if p, err := m.Colors(Config().ThumbCachePath()); err != nil {
log.Debugf("%s while detecting colors", err.Error())
file.FileError = err.Error()
file.FilePrimary = false

View File

@@ -8,7 +8,7 @@ import (
// NSFW returns true if media file might be offensive and detection is enabled.
func (ind *Index) NSFW(m *MediaFile) bool {
filename, err := m.Thumbnail(Config().ThumbPath(), thumb.Fit720)
filename, err := m.Thumbnail(Config().ThumbCachePath(), thumb.Fit720)
if err != nil {
log.Error(err)

View File

@@ -40,7 +40,7 @@ func (w *Resample) Start(force bool) (err error) {
defer mutex.MainWorker.Stop()
originalsPath := w.conf.OriginalsPath()
thumbnailsPath := w.conf.ThumbPath()
thumbnailsPath := w.conf.ThumbCachePath()
jobs := make(chan ResampleJob)

View File

@@ -105,7 +105,7 @@ func (worker *Share) Start() (err error) {
continue
}
srcFileName, err = thumb.FromFile(srcFileName, file.File.FileHash, worker.conf.ThumbPath(), size.Width, size.Height, file.File.FileOrientation, size.Options...)
srcFileName, err = thumb.FromFile(srcFileName, file.File.FileHash, worker.conf.ThumbCachePath(), size.Width, size.Height, file.File.FileOrientation, size.Options...)
if err != nil {
worker.logError(err)