Move all config files to assets/config and resources to assets/resources
|
@ -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
|
@ -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
|
||||
|
|
13
Makefile
|
@ -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:
|
||||
|
|
|
@ -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
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
BIN
assets/resources/examples/example.heic
Normal file
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 510 KiB After Width: | Height: | Size: 510 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
2
assets/server/static/build/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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/*).
|
||||
|
|
|
@ -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`",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
89
internal/photoprism/filetypes.go
Normal 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,
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -42,7 +42,7 @@ func TestCreateThumbnailsFromOriginals(t *testing.T) {
|
|||
|
||||
conf.InitializeTestData(t)
|
||||
|
||||
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
|
||||
tensorFlow := NewTensorFlow(conf)
|
||||
|
||||
indexer := NewIndexer(conf, tensorFlow)
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
}
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|