Move all config files to assets/config and resources to assets/resources

This commit is contained in:
Michael Mayer 2019-06-05 18:25:20 +02:00
parent 0e5b465cb5
commit a6131eeddd
84 changed files with 237 additions and 189 deletions

View file

@ -1,13 +1,12 @@
/assets/photos/*
/assets/cache/*
/assets/database/*
/internal/photoprism/testdata/*
/frontend/node_modules/*
/node_modules
/assets/server/static/build/*
/assets/resources/database/*
/assets/resources/static/build/*
/assets/resources/nasnet
/assets/testdata
/assets/backups
/assets/tensorflow/nasnet
Dockerfile
/photoprism
docker-compose*

4
.gitignore vendored
View file

@ -3,6 +3,8 @@
/assets/photos/originals/*
/assets/photos/import/*
/assets/photos/export/*
/assets/resources/database/*
!/assets/resources/database/.gitignore
/node_modules
/frontend/.eslintcache
/frontend/node_modules/*
@ -11,7 +13,7 @@
/frontend/tests/screenshots
/assets/testdata
/assets/backups
/assets/tensorflow/nasnet
/assets/resources/nasnet
*.log
# Binaries for programs and plugins

View file

@ -15,7 +15,7 @@ endif
all: dep build
dep: dep-tensorflow dep-js dep-go
build: build-js build-go
install: install-bin install-assets install-config
install: install-bin install-assets
test: test-js test-go
fmt: fmt-js fmt-go
upgrade: upgrade-js upgrade-go
@ -28,13 +28,10 @@ install-bin:
install-assets:
mkdir -p /srv/photoprism/photos
mkdir -p /srv/photoprism/cache
mkdir -p /srv/photoprism/server/database
cp -r assets/server/static assets/server/templates /srv/photoprism/server
cp -r assets/tensorflow /srv/photoprism
mkdir -p /srv/photoprism/resources
mkdir -p /srv/photoprism/config
rsync -a -v --ignore-existing assets/config/*.yml /srv/photoprism/config
find /srv/photoprism -name '.*' -type f -delete
install-config:
mkdir -p /etc/photoprism
test -e /etc/photoprism/photoprism.yml || cp -n configs/photoprism.yml /etc/photoprism/photoprism.yml
dep-js:
(cd frontend && npm install)
dep-go:
@ -42,7 +39,7 @@ dep-go:
dep-tensorflow:
scripts/download-nasnet.sh
zip-nasnet:
(cd assets/tensorflow && zip -r nasnet.zip nasnet -x "*/.*" -x "*/version.txt")
(cd assets/resources && zip -r nasnet.zip nasnet -x "*/.*" -x "*/version.txt")
build-js:
(cd frontend && env NODE_ENV=production npm run build)
build-go:

View file

@ -1,6 +1,8 @@
debug: false
darktable-cli: /usr/bin/darktable-cli
assets-path: /srv/photoprism
config-path: /srv/photoprism/config
resources-path: /srv/photoprism/resources
cache-path: /srv/photoprism/cache
originals-path: /srv/photoprism/photos/originals
import-path: /srv/photoprism/photos/import

2
assets/resources/database/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View file

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View file

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

View file

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View file

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View file

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View file

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View file

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View file

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View file

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View file

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View file

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

View file

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View file

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View file

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View file

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View file

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View file

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View file

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View file

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View file

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View file

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 510 KiB

After

Width:  |  Height:  |  Size: 510 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

View file

@ -1,2 +0,0 @@
*
!.gitignore

View file

@ -17,6 +17,8 @@ services:
PHOTOPRISM_SERVER_MODE: "debug"
PHOTOPRISM_ASSETS_PATH: "/go/src/github.com/photoprism/photoprism/assets"
PHOTOPRISM_CACHE_PATH: "/go/src/github.com/photoprism/photoprism/assets/cache"
PHOTOPRISM_RESOURCES_PATH: "/go/src/github.com/photoprism/photoprism/assets/resources"
PHOTOPRISM_CONFIG_PATH: "/go/src/github.com/photoprism/photoprism/assets/config"
PHOTOPRISM_IMPORT_PATH: "/go/src/github.com/photoprism/photoprism/assets/photos/import"
PHOTOPRISM_EXPORT_PATH: "/go/src/github.com/photoprism/photoprism/assets/photos/export"
PHOTOPRISM_ORIGINALS_PATH: "/go/src/github.com/photoprism/photoprism/assets/photos/originals"

View file

@ -31,7 +31,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
COPY --from=build /etc/apt/sources.list.d/pmjdebruijn-ubuntu-darktable-release-bionic.list /etc/apt/sources.list.d/pmjdebruijn-ubuntu-darktable-release-bionic.list
COPY --from=build /etc/apt/trusted.gpg.d/pmjdebruijn_ubuntu_darktable-release.gpg /etc/apt/trusted.gpg.d/pmjdebruijn_ubuntu_darktable-release.gpg
COPY --from=build /usr/local/bin/photoprism /usr/local/bin/photoprism
COPY --from=build /etc/photoprism/photoprism.yml /etc/photoprism/photoprism.yml
COPY --from=build /usr/local/lib/libtensorflow.so /usr/local/lib/libtensorflow.so
COPY --from=build /usr/local/lib/libtensorflow_framework.so /usr/local/lib/libtensorflow_framework.so
COPY --from=build /srv/photoprism /srv/photoprism

View file

@ -16,7 +16,7 @@ const PATHS = {
app: path.join(__dirname, "src/app.js"),
js: path.join(__dirname, "src"),
css: path.join(__dirname, "src/css"),
build: path.join(__dirname, "../assets/server/static/build"),
build: path.join(__dirname, "../assets/resources/static/build"),
};
const config = {

View file

@ -18,7 +18,7 @@ import (
//
// Query:
// q: string Query string
// tags: string Tags
// label: string Label
// cat: string Category
// country: string Country code
// camera: int Camera ID

View file

@ -25,6 +25,7 @@ func configAction(ctx *cli.Context) error {
fmt.Printf("read-only %t\n", conf.ReadOnly())
fmt.Printf("log-level %s\n", conf.LogLevel())
fmt.Printf("config-file %s\n", conf.ConfigFile())
fmt.Printf("config-path %s\n", conf.ConfigPath())
fmt.Printf("database-driver %s\n", conf.DatabaseDriver())
fmt.Printf("database-dsn %s\n", conf.DatabaseDsn())
@ -44,6 +45,7 @@ func configAction(ctx *cli.Context) error {
fmt.Printf("export-path %s\n", conf.ExportPath())
fmt.Printf("cache-path %s\n", conf.CachePath())
fmt.Printf("thumbnails-path %s\n", conf.ThumbnailsPath())
fmt.Printf("resources-path %s\n", conf.ResourcesPath())
fmt.Printf("tf-model-path %s\n", conf.TensorFlowModelPath())
fmt.Printf("templates-path %s\n", conf.HttpTemplatesPath())
fmt.Printf("favicons-path %s\n", conf.HttpFaviconsPath())

View file

@ -35,7 +35,7 @@ func importAction(ctx *cli.Context) error {
log.Infof("importing photos from %s", conf.ImportPath())
tensorFlow := photoprism.NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := photoprism.NewTensorFlow(conf)
indexer := photoprism.NewIndexer(conf, tensorFlow)

View file

@ -34,7 +34,7 @@ func indexAction(ctx *cli.Context) error {
conf.MigrateDb()
log.Infof("indexing photos in %s", conf.OriginalsPath())
tensorFlow := photoprism.NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := photoprism.NewTensorFlow(conf)
indexer := photoprism.NewIndexer(conf, tensorFlow)

View file

@ -68,6 +68,10 @@ func (c *Config) CreateDirectories() error {
return err
}
if err := os.MkdirAll(c.ResourcesPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.SqlServerPath(), os.ModePerm); err != nil {
return err
}
@ -181,11 +185,20 @@ func (c *Config) LogLevel() log.Level {
}
}
// TestConfigFile returns the config file name.
// ConfigFile returns the config file name.
func (c *Config) ConfigFile() string {
return c.config.ConfigFile
}
// ConfigPath returns the config path.
func (c *Config) ConfigPath() string {
if c.config.ConfigPath == "" {
return c.AssetsPath() + "/config"
}
return c.config.ConfigPath
}
// SqlServerHost returns the built-in SQL server host name or IP address (empty for all interfaces).
func (c *Config) SqlServerHost() string {
return c.config.SqlServerHost
@ -202,7 +215,7 @@ func (c *Config) SqlServerPath() string {
return c.config.SqlServerPath
}
return c.ServerPath() + "/database"
return c.ResourcesPath() + "/database"
}
// SqlServerPassword returns the password for the built-in database server.
@ -290,19 +303,28 @@ func (c *Config) AssetsPath() string {
return c.config.AssetsPath
}
// TensorFlowModelPath returns the tensorflow model path.
func (c *Config) TensorFlowModelPath() string {
return c.AssetsPath() + "/tensorflow"
// ResourcesPath returns the path to the app resources like static files.
func (c *Config) ResourcesPath() string {
if c.config.ResourcesPath == "" {
return c.AssetsPath() + "/resources"
}
return c.config.ResourcesPath
}
// ServerPath returns the server assets path (static files, templates,...).
func (c *Config) ServerPath() string {
return c.AssetsPath() + "/server"
// ExamplesPath returns the example files path.
func (c *Config) ExamplesPath() string {
return c.ResourcesPath() + "/examples"
}
// TensorFlowModelPath returns the tensorflow model path.
func (c *Config) TensorFlowModelPath() string {
return c.ResourcesPath() + "/nasnet"
}
// HttpTemplatesPath returns the server templates path.
func (c *Config) HttpTemplatesPath() string {
return c.ServerPath() + "/templates"
return c.ResourcesPath() + "/templates"
}
// HttpFaviconsPath returns the favicons path.
@ -312,7 +334,7 @@ func (c *Config) HttpFaviconsPath() string {
// HttpStaticPath returns the static server assets path (//server/static/*).
func (c *Config) HttpStaticPath() string {
return c.ServerPath() + "/static"
return c.ResourcesPath() + "/static"
}
// HttpStaticBuildPath returns the static build path (//server/static/build/*).

View file

@ -23,15 +23,25 @@ var GlobalFlags = []cli.Flag{
cli.StringFlag{
Name: "config-file, c",
Usage: "load configuration from `FILENAME`",
Value: "/etc/photoprism/photoprism.yml",
Value: "~/.config/photoprism/photoprism.yml",
EnvVar: "PHOTOPRISM_CONFIG_FILE",
},
cli.StringFlag{
Name: "config-path",
Usage: "config `PATH`",
EnvVar: "PHOTOPRISM_CONFIG_PATH",
},
cli.StringFlag{
Name: "darktable-cli",
Usage: "darktable command-line executable `FILENAME`",
Value: "/usr/bin/darktable-cli",
EnvVar: "PHOTOPRISM_DARKTABLE_CLI",
},
cli.StringFlag{
Name: "resources-path",
Usage: "resources `PATH`",
EnvVar: "PHOTOPRISM_RESOURCES_PATH",
},
cli.StringFlag{
Name: "originals-path",
Usage: "originals `PATH`",

View file

@ -35,7 +35,9 @@ type Params struct {
ReadOnly bool `yaml:"read-only" flag:"read-only"`
LogLevel string `yaml:"log-level" flag:"log-level"`
ConfigFile string
ConfigPath string `yaml:"config-path" flag:"config-path"`
AssetsPath string `yaml:"assets-path" flag:"assets-path"`
ResourcesPath string `yaml:"resources-path" flag:"resources-path"`
CachePath string `yaml:"cache-path" flag:"cache-path"`
OriginalsPath string `yaml:"originals-path" flag:"originals-path"`
ImportPath string `yaml:"import-path" flag:"import-path"`
@ -80,6 +82,8 @@ func NewParams(ctx *cli.Context) *Params {
}
func (c *Params) expandFilenames() {
c.ConfigPath = util.ExpandedFilename(c.ConfigPath)
c.ResourcesPath = util.ExpandedFilename(c.ResourcesPath)
c.AssetsPath = util.ExpandedFilename(c.AssetsPath)
c.CachePath = util.ExpandedFilename(c.CachePath)
c.OriginalsPath = util.ExpandedFilename(c.OriginalsPath)

View file

@ -11,7 +11,9 @@ import (
)
func TestMediaFile_Colors_Testdata(t *testing.T) {
thumbsPath := "testdata/_tmp"
conf := config.TestConfig()
thumbsPath := conf.CachePath() + "/_tmp"
defer os.RemoveAll(thumbsPath)
@ -51,7 +53,7 @@ func TestMediaFile_Colors_Testdata(t *testing.T) {
},
}
err := filepath.Walk("testdata", func(filename string, fileInfo os.FileInfo, err error) error {
err := filepath.Walk(conf.ExamplesPath(), func(filename string, fileInfo os.FileInfo, err error) error {
if err != nil {
return nil
}

View file

@ -42,9 +42,9 @@ func TestConverter_ConvertToJpeg(t *testing.T) {
assert.Empty(t, err, "ConvertToJpeg() failed")
infoJpeg, err := imageJpeg.ExifData()
infoJpeg, err := imageJpeg.Exif()
assert.Emptyf(t, err, "ExifData() failed")
assert.Emptyf(t, err, "Exif() failed")
assert.Equal(t, jpegFilename, imageJpeg.filename)
@ -66,7 +66,7 @@ func TestConverter_ConvertToJpeg(t *testing.T) {
assert.NotEqual(t, rawFilemame, imageRaw.filename)
infoRaw, err := imageRaw.ExifData()
infoRaw, err := imageRaw.Exif()
assert.False(t, infoRaw == nil || err != nil, "Could not read EXIF data of RAW image")
@ -96,7 +96,7 @@ func TestConverter_ConvertAll(t *testing.T) {
assert.Equal(t, jpegFilename, image.filename, "FileName must be the same")
infoRaw, err := image.ExifData()
infoRaw, err := image.Exif()
assert.False(t, infoRaw == nil || err != nil, "Could not read EXIF data of RAW image")

View file

@ -11,8 +11,8 @@ import (
"github.com/rwcarlsen/goexif/mknote"
)
// ExifData returns information about a single image.
type ExifData struct {
// Exif returns information about a single image.
type Exif struct {
DateTime time.Time
Artist string
CameraMake string
@ -34,10 +34,17 @@ func init() {
exif.RegisterParsers(mknote.All...)
}
// ExifData return ExifData given a single mediaFile.
func (m *MediaFile) ExifData() (*ExifData, error) {
// Exif returns exif meta data of a media file.
func (m *MediaFile) Exif() (result *Exif, err error) {
defer func() {
if e := recover(); e != nil {
result = m.exifData
err = fmt.Errorf("error while parsing exif data: %s", e)
}
}()
if m == nil {
return nil, errors.New("can't parse Exif data: file instance is null")
return nil, errors.New("can't parse exif data: file instance is null")
}
if m.exifData != nil {
@ -45,10 +52,10 @@ func (m *MediaFile) ExifData() (*ExifData, error) {
}
if !m.IsPhoto() {
return nil, errors.New(fmt.Sprintf("file not compatible with Exif: \"%s\"", m.Filename()))
return nil, errors.New(fmt.Sprintf("media file not compatible with exif: \"%s\"", m.Filename()))
}
m.exifData = &ExifData{}
m.exifData = &Exif{}
file, err := m.openFile()
@ -139,5 +146,5 @@ func (m *MediaFile) ExifData() (*ExifData, error) {
m.exifData.Orientation = 1
}
return m.exifData, nil
return m.exifData, err
}

View file

@ -7,42 +7,42 @@ import (
"github.com/stretchr/testify/assert"
)
func TestMediaFile_ExifData(t *testing.T) {
ctx := config.TestConfig()
func TestMediaFile_Exif(t *testing.T) {
conf := config.TestConfig()
ctx.InitializeTestData(t)
conf.InitializeTestData(t)
image1, err := NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
image1, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
info, err := image1.ExifData()
info, err := image1.Exif()
assert.Empty(t, err)
assert.IsType(t, &ExifData{}, info)
assert.IsType(t, &Exif{}, info)
assert.Equal(t, "iPhone SE", info.CameraModel)
}
func TestMediaFile_ExifData_Slow(t *testing.T) {
func TestMediaFile_Exif_Slow(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
ctx := config.TestConfig()
conf := config.TestConfig()
ctx.InitializeTestData(t)
conf.InitializeTestData(t)
image2, err := NewMediaFile(ctx.ImportPath() + "/raw/IMG_1435.CR2")
image2, err := NewMediaFile(conf.ImportPath() + "/raw/IMG_1435.CR2")
assert.Nil(t, err)
info, err := image2.ExifData()
info, err := image2.Exif()
assert.Empty(t, err)
assert.IsType(t, &ExifData{}, info)
assert.IsType(t, &Exif{}, info)
assert.Equal(t, "Canon EOS M10", info.CameraModel)
}

View file

@ -0,0 +1,89 @@
package photoprism
import (
_ "image/gif" // Import for image.
_ "image/jpeg"
_ "image/png"
)
const (
// FileTypeOther is an unkown file format.
FileTypeOther = "unknown"
// FileTypeYaml is a yaml file format.
FileTypeYaml = "yml"
// FileTypeJpeg is a jpeg file format.
FileTypeJpeg = "jpg"
// FileTypePng is a png file format.
FileTypePng = "png"
// FileTypeRaw is a raw file format.
FileTypeRaw = "raw"
// FileTypeXmp is an xmp file format.
FileTypeXmp = "xmp"
// FileTypeAae is an aae file format.
FileTypeAae = "aae"
// FileTypeMovie is a movie file format.
FileTypeMovie = "mov"
// FileTypeHEIF High Efficiency Image File Format
FileTypeHEIF = "heif" // High Efficiency Image File Format
)
const (
// MimeTypeJpeg is jpeg image type
MimeTypeJpeg = "image/jpeg"
)
// FileExtensions lists all the available and supported image file formats.
var FileExtensions = map[string]string{
".crw": FileTypeRaw,
".cr2": FileTypeRaw,
".nef": FileTypeRaw,
".arw": FileTypeRaw,
".dng": FileTypeRaw,
".mov": FileTypeMovie,
".avi": FileTypeMovie,
".yml": FileTypeYaml,
".jpg": FileTypeJpeg,
".thm": FileTypeJpeg,
".jpeg": FileTypeJpeg,
".xmp": FileTypeXmp,
".aae": FileTypeAae,
".heif": FileTypeHEIF,
".heic": FileTypeHEIF,
".3fr": FileTypeRaw,
".ari": FileTypeRaw,
".bay": FileTypeRaw,
".cr3": FileTypeRaw,
".cap": FileTypeRaw,
".data": FileTypeRaw,
".dcs": FileTypeRaw,
".dcr": FileTypeRaw,
".drf": FileTypeRaw,
".eip": FileTypeRaw,
".erf": FileTypeRaw,
".fff": FileTypeRaw,
".gpr": FileTypeRaw,
".iiq": FileTypeRaw,
".k25": FileTypeRaw,
".kdc": FileTypeRaw,
".mdc": FileTypeRaw,
".mef": FileTypeRaw,
".mos": FileTypeRaw,
".mrw": FileTypeRaw,
".nrw": FileTypeRaw,
".obm": FileTypeRaw,
".orf": FileTypeRaw,
".pef": FileTypeRaw,
".ptx": FileTypeRaw,
".pxn": FileTypeRaw,
".r3d": FileTypeRaw,
".raf": FileTypeRaw,
".raw": FileTypeRaw,
".rwl": FileTypeRaw,
".rw2": FileTypeRaw,
".rwz": FileTypeRaw,
".sr2": FileTypeRaw,
".srf": FileTypeRaw,
".srw": FileTypeRaw,
".tif": FileTypeRaw,
".x3f": FileTypeRaw,
}

View file

@ -10,7 +10,7 @@ import (
func TestNewImporter(t *testing.T) {
conf := config.TestConfig()
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(conf)
indexer := NewIndexer(conf, tensorFlow)
@ -26,7 +26,7 @@ func TestImporter_DestinationFilename(t *testing.T) {
conf.InitializeTestData(t)
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(conf)
indexer := NewIndexer(conf, tensorFlow)
@ -54,7 +54,7 @@ func TestImporter_ImportPhotosFromDirectory(t *testing.T) {
conf.InitializeTestData(t)
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(conf)
indexer := NewIndexer(conf, tensorFlow)

View file

@ -45,8 +45,7 @@ func (i *Indexer) thumbnailsPath() string {
return i.conf.ThumbnailsPath()
}
// classifyImage returns all tags of a given mediafile. This function returns
// an empty list in the case of an error.
// classifyImage returns all matching labels for a media file.
func (i *Indexer) classifyImage(jpeg *MediaFile) (results Labels) {
start := time.Now()
@ -125,7 +124,7 @@ func (i *Indexer) indexMediaFile(mediaFile *MediaFile) string {
labels = i.classifyImage(jpeg)
// Read Exif data
if exifData, err := jpeg.ExifData(); err == nil {
if exifData, err := jpeg.Exif(); err == nil {
photo.PhotoLat = exifData.Lat
photo.PhotoLong = exifData.Long
photo.PhotoArtist = exifData.Artist

View file

@ -3,9 +3,6 @@ package photoprism
import (
"fmt"
"image"
_ "image/gif" // Import for image.
_ "image/jpeg"
_ "image/png"
"io"
"net/http"
"os"
@ -19,88 +16,6 @@ import (
"github.com/photoprism/photoprism/internal/util"
)
const (
// FileTypeOther is an unkown file format.
FileTypeOther = "unknown"
// FileTypeYaml is a yaml file format.
FileTypeYaml = "yml"
// FileTypeJpeg is a jpeg file format.
FileTypeJpeg = "jpg"
// FileTypePng is a png file format.
FileTypePng = "png"
// FileTypeRaw is a raw file format.
FileTypeRaw = "raw"
// FileTypeXmp is an xmp file format.
FileTypeXmp = "xmp"
// FileTypeAae is an aae file format.
FileTypeAae = "aae"
// FileTypeMovie is a movie file format.
FileTypeMovie = "mov"
// FileTypeHEIF High Efficiency Image File Format
FileTypeHEIF = "heif" // High Efficiency Image File Format
)
const (
// MimeTypeJpeg is jpeg image type
MimeTypeJpeg = "image/jpeg"
)
// FileExtensions lists all the available and supported image file formats.
var FileExtensions = map[string]string{
".crw": FileTypeRaw,
".cr2": FileTypeRaw,
".nef": FileTypeRaw,
".arw": FileTypeRaw,
".dng": FileTypeRaw,
".mov": FileTypeMovie,
".avi": FileTypeMovie,
".yml": FileTypeYaml,
".jpg": FileTypeJpeg,
".thm": FileTypeJpeg,
".jpeg": FileTypeJpeg,
".xmp": FileTypeXmp,
".aae": FileTypeAae,
".heif": FileTypeHEIF,
".heic": FileTypeHEIF,
".3fr": FileTypeRaw,
".ari": FileTypeRaw,
".bay": FileTypeRaw,
".cr3": FileTypeRaw,
".cap": FileTypeRaw,
".data": FileTypeRaw,
".dcs": FileTypeRaw,
".dcr": FileTypeRaw,
".drf": FileTypeRaw,
".eip": FileTypeRaw,
".erf": FileTypeRaw,
".fff": FileTypeRaw,
".gpr": FileTypeRaw,
".iiq": FileTypeRaw,
".k25": FileTypeRaw,
".kdc": FileTypeRaw,
".mdc": FileTypeRaw,
".mef": FileTypeRaw,
".mos": FileTypeRaw,
".mrw": FileTypeRaw,
".nrw": FileTypeRaw,
".obm": FileTypeRaw,
".orf": FileTypeRaw,
".pef": FileTypeRaw,
".ptx": FileTypeRaw,
".pxn": FileTypeRaw,
".r3d": FileTypeRaw,
".raf": FileTypeRaw,
".raw": FileTypeRaw,
".rwl": FileTypeRaw,
".rw2": FileTypeRaw,
".rwz": FileTypeRaw,
".sr2": FileTypeRaw,
".srf": FileTypeRaw,
".srw": FileTypeRaw,
".tif": FileTypeRaw,
".x3f": FileTypeRaw,
}
// MediaFile represents a single file.
type MediaFile struct {
filename string
@ -109,10 +24,9 @@ type MediaFile struct {
fileType string
mimeType string
perceptualHash string
tags []string
width int
height int
exifData *ExifData
exifData *Exif
location *models.Location
}
@ -138,7 +52,7 @@ func (m *MediaFile) DateCreated() time.Time {
m.dateCreated = time.Now()
info, err := m.ExifData()
info, err := m.Exif()
if err == nil && !info.DateTime.IsZero() {
m.dateCreated = info.DateTime
@ -165,7 +79,7 @@ func (m *MediaFile) DateCreated() time.Time {
// CameraModel returns the camera model with which the mediafile was created.
func (m *MediaFile) CameraModel() string {
info, err := m.ExifData()
info, err := m.Exif()
var result string
@ -178,7 +92,7 @@ func (m *MediaFile) CameraModel() string {
// CameraMake returns the make of the camera with which the file was created.
func (m *MediaFile) CameraMake() string {
info, err := m.ExifData()
info, err := m.Exif()
var result string
@ -191,7 +105,7 @@ func (m *MediaFile) CameraMake() string {
// LensModel returns the lens model of a mediafile.
func (m *MediaFile) LensModel() string {
info, err := m.ExifData()
info, err := m.Exif()
var result string
@ -204,7 +118,7 @@ func (m *MediaFile) LensModel() string {
// LensMake returns the make of the Lens.
func (m *MediaFile) LensMake() string {
info, err := m.ExifData()
info, err := m.Exif()
var result string
@ -217,7 +131,7 @@ func (m *MediaFile) LensMake() string {
// FocalLength return the length of the focal for a file.
func (m *MediaFile) FocalLength() float64 {
info, err := m.ExifData()
info, err := m.Exif()
var result float64
@ -230,7 +144,7 @@ func (m *MediaFile) FocalLength() float64 {
// Aperture returns the aperture with which the mediafile was created.
func (m *MediaFile) Aperture() float64 {
info, err := m.ExifData()
info, err := m.Exif()
var result float64
@ -527,7 +441,7 @@ func (m *MediaFile) decodeDimensions() error {
var width, height int
exif, err := m.ExifData()
exif, err := m.Exif()
if err == nil {
width = exif.Width
@ -606,7 +520,7 @@ func (m *MediaFile) AspectRatio() float64 {
// Orientation returns the orientation of a mediafile.
func (m *MediaFile) Orientation() int {
if exif, err := m.ExifData(); err == nil {
if exif, err := m.Exif(); err == nil {
return exif.Orientation
}

View file

@ -47,7 +47,7 @@ func (m *MediaFile) Location() (*models.Location, error) {
Address: &openstreetmapAddress{},
}
if exifData, err := m.ExifData(); err == nil {
if exifData, err := m.Exif(); err == nil {
if exifData.Lat == 0 && exifData.Long == 0 {
return nil, errors.New("no latitude and longitude in image metadata")
}

View file

@ -85,8 +85,8 @@ type PhotoSearchResult struct {
FileOrientation int
FileAspectRatio float64
// Tags
Tags string
// List of matching labels (tags)
Labels string
}
// NewSearch returns a new Search type with a given path and db instance.

View file

@ -14,13 +14,14 @@ import (
"github.com/disintegration/imaging"
"github.com/photoprism/photoprism/internal/util"
"github.com/photoprism/photoprism/internal/config"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
"gopkg.in/yaml.v2"
)
// TensorFlow if a tensorflow wrapper given a graph, labels and a modelPath.
// TensorFlow if a wrapper for their low-level API.
type TensorFlow struct {
modelPath string
conf *config.Config
model *tf.SavedModel
labels []string
labelRules LabelRules
@ -37,8 +38,8 @@ type LabelRule struct {
type LabelRules map[string]LabelRule
// NewTensorFlow returns a new TensorFlow.
func NewTensorFlow(tensorFlowModelPath string) *TensorFlow {
return &TensorFlow{modelPath: tensorFlowModelPath}
func NewTensorFlow(conf *config.Config) *TensorFlow {
return &TensorFlow{conf: conf}
}
func (t *TensorFlow) loadLabelRules() (err error) {
@ -48,7 +49,7 @@ func (t *TensorFlow) loadLabelRules() (err error) {
t.labelRules = make(LabelRules)
fileName := t.modelPath + "/rules.yml"
fileName := t.conf.ConfigPath() + "/labels.yml"
log.Debugf("loading label rules from \"%s\"", fileName)
@ -69,7 +70,7 @@ func (t *TensorFlow) loadLabelRules() (err error) {
return err
}
// LabelsFromFile returns tags for a jpeg image file.
// LabelsFromFile returns matching labels for a jpeg media file.
func (t *TensorFlow) LabelsFromFile(filename string) (result Labels, err error) {
imageBuffer, err := ioutil.ReadFile(filename)
@ -80,7 +81,7 @@ func (t *TensorFlow) LabelsFromFile(filename string) (result Labels, err error)
return t.Labels(imageBuffer)
}
// Labels returns tags for a jpeg image string.
// Labels returns matching labels for a jpeg media string.
func (t *TensorFlow) Labels(img []byte) (result Labels, err error) {
if err := t.loadModel(); err != nil {
return nil, err
@ -125,7 +126,7 @@ func (t *TensorFlow) loadModel() error {
return nil
}
savedModel := t.modelPath + "/nasnet"
savedModel := t.conf.TensorFlowModelPath()
modelLabels := savedModel + "/labels.txt"
log.Infof("loading image classification model from \"%s\"", savedModel)

View file

@ -9,13 +9,13 @@ import (
)
func TestTensorFlow_LabelsFromFile(t *testing.T) {
ctx := config.TestConfig()
conf := config.TestConfig()
ctx.InitializeTestData(t)
conf.InitializeTestData(t)
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
tensorFlow := NewTensorFlow(conf)
result, err := tensorFlow.LabelsFromFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
result, err := tensorFlow.LabelsFromFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
@ -42,13 +42,13 @@ func TestTensorFlow_Labels(t *testing.T) {
t.Skip("skipping test in short mode.")
}
ctx := config.TestConfig()
conf := config.TestConfig()
ctx.InitializeTestData(t)
conf.InitializeTestData(t)
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
tensorFlow := NewTensorFlow(conf)
if imageBuffer, err := ioutil.ReadFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG"); err != nil {
if imageBuffer, err := ioutil.ReadFile(conf.ImportPath() + "/iphone/IMG_6788.JPG"); err != nil {
t.Error(err)
} else {
result, err := tensorFlow.Labels(imageBuffer)
@ -74,13 +74,13 @@ func TestTensorFlow_Labels_Dog(t *testing.T) {
t.Skip("skipping test in short mode.")
}
ctx := config.TestConfig()
conf := config.TestConfig()
ctx.InitializeTestData(t)
conf.InitializeTestData(t)
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
tensorFlow := NewTensorFlow(conf)
if imageBuffer, err := ioutil.ReadFile(ctx.ImportPath() + "/dog.jpg"); err != nil {
if imageBuffer, err := ioutil.ReadFile(conf.ImportPath() + "/dog.jpg"); err != nil {
t.Error(err)
} else {
result, err := tensorFlow.Labels(imageBuffer)

View file

@ -42,7 +42,7 @@ func TestCreateThumbnailsFromOriginals(t *testing.T) {
conf.InitializeTestData(t)
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(conf)
indexer := NewIndexer(conf, tensorFlow)

View file

@ -9,8 +9,6 @@ import (
"os/user"
"path/filepath"
"strings"
log "github.com/sirupsen/logrus"
)
// Returns true if file exists
@ -23,7 +21,6 @@ func Exists(filename string) bool {
// Returns full path; ~ replaced with actual home directory
func ExpandedFilename(filename string) string {
if filename == "" {
log.Debug("check configuration: empty file or directory name")
return ""
}

View file

@ -4,7 +4,7 @@ TODAY=`date -u +%Y%m%d`
MODEL_NAME="NASNet Mobile"
MODEL_URL="https://dl.photoprism.org/tensorflow/nasnet.zip?$TODAY"
MODEL_PATH="assets/tensorflow/nasnet"
MODEL_PATH="assets/resources/nasnet"
MODEL_ZIP="/tmp/photoprism/nasnet.zip"
MODEL_HASH="cb893eaa93d59eca9e63ab10f76ae60519ecee24 $MODEL_ZIP"
MODEL_VERSION="$MODEL_PATH/version.txt"
@ -14,7 +14,7 @@ echo "Installing $MODEL_NAME for TensorFlow..."
# Create directories
mkdir -p /tmp/photoprism
mkdir -p assets/tensorflow
mkdir -p assets/resources/nasnet
mkdir -p assets/backups
# Check for update
@ -41,7 +41,7 @@ if [[ -e ${MODEL_PATH} ]]; then
fi
# Unzip model
unzip ${MODEL_ZIP} -d assets/tensorflow
unzip ${MODEL_ZIP} -d assets/resources/nasnet
echo "$MODEL_NAME $TODAY $MODEL_HASH" > ${MODEL_VERSION}
echo "Latest $MODEL_NAME installed."