mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
Implemented working importer and added unit tests
This commit is contained in:
parent
c8b7dbbe01
commit
1a465b5002
15 changed files with 694 additions and 355 deletions
88
Gopkg.lock
generated
88
Gopkg.lock
generated
|
|
@ -14,33 +14,9 @@
|
|||
revision = "bbdc45bcf55de61b38b4108871199a117aecd1be"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/cenkalti/backoff"
|
||||
packages = ["."]
|
||||
revision = "61153c768f31ee5f130071d08fc82b85208528de"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/ddliu/go-httpclient"
|
||||
packages = ["."]
|
||||
revision = "52a7afc73c57c5b898b5514a5467f8d38decd3ed"
|
||||
version = "v0.5.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dghubble/go-twitter"
|
||||
packages = ["twitter"]
|
||||
revision = "c4115fa44a928413e0b857e0eb47376ffde3a61a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/dghubble/oauth1"
|
||||
packages = ["."]
|
||||
revision = "70562a5920ad9b6ff03ef697c0f90ae569abbd2b"
|
||||
version = "v0.4.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/dghubble/sling"
|
||||
packages = ["."]
|
||||
revision = "eb56e89ac5088bebb12eef3cb4b293300f43608b"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
|
|
@ -55,12 +31,6 @@
|
|||
revision = "95292e44976d1217cf3611dc7c8d9466877d3ed5"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/google/go-querystring"
|
||||
packages = ["query"]
|
||||
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/julienschmidt/httprouter"
|
||||
packages = ["."]
|
||||
|
|
@ -73,30 +43,18 @@
|
|||
packages = ["yaml"]
|
||||
revision = "08cad365cd28a7fba23bb1e57aa43c5e18ad8bb8"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/lastzero/tweethog"
|
||||
packages = ["."]
|
||||
revision = "ac3ce5feaebcb1320109e02d5e477d0a97711793"
|
||||
version = "v0.7.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mailru/easyjson"
|
||||
packages = [".","buffer","jlexer","jwriter"]
|
||||
revision = "32fa128f234d041f196a9f3e0fea5ac9772c08e1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/olivere/elastic"
|
||||
packages = ["config","uritemplates"]
|
||||
revision = "c51e74f9bcab8906a2f6cf5660dac396ba51b3d6"
|
||||
version = "v6.1.4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "go1"
|
||||
name = "github.com/rwcarlsen/goexif"
|
||||
|
|
@ -110,10 +68,10 @@
|
|||
version = "0.2.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/subosito/shorturl"
|
||||
packages = [".","adfly","base","bitly","catchy","cligs","gggg","gitio","googl","isgd","moourl","pendekin","shorl","snipurl","tinyurl","vamu"]
|
||||
revision = "3dc4cee684914f665399d6a5cddc13b1864b36dd"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tensorflow/tensorflow"
|
||||
|
|
@ -133,27 +91,9 @@
|
|||
packages = ["bmp","riff","tiff","tiff/lzw","vp8","vp8l","webp"]
|
||||
revision = "12117c17ca67ffa1ce22e9409f3b0b0a93ac08c7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context","html","html/atom"]
|
||||
revision = "309822c5b9b9f80db67f016069a12628d94fad34"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/olivere/elastic.v6"
|
||||
packages = ["."]
|
||||
revision = "c51e74f9bcab8906a2f6cf5660dac396ba51b3d6"
|
||||
version = "v6.1.4"
|
||||
|
||||
[[projects]]
|
||||
name = "mvdan.cc/xurls"
|
||||
packages = ["."]
|
||||
revision = "d315b61cf6727664f310fa87b3197e9faf2a8513"
|
||||
version = "v1.1.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "70fdd0684ae209f6735c04d5ecffa4c3ad3dc102214afc431b79c530fb2d1d65"
|
||||
inputs-digest = "ea6460db0c53d350b75d5795872ab17c55d0348840b77a27618da6bd75d329ad"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import (
|
|||
"github.com/urfave/cli"
|
||||
"os"
|
||||
"fmt"
|
||||
"gopkg.in/olivere/elastic.v6"
|
||||
"github.com/lastzero/tweethog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -23,7 +21,7 @@ func main() {
|
|||
Name: "config",
|
||||
Usage: "Displays global configuration values",
|
||||
Action: func(c *cli.Context) error {
|
||||
config.SetValuesFromFile(tweethog.GetExpandedFilename(c.GlobalString("config-file")))
|
||||
config.SetValuesFromFile(photoprism.getExpandedFilename(c.GlobalString("config-file")))
|
||||
|
||||
config.SetValuesFromCliContext(c)
|
||||
|
||||
|
|
@ -49,21 +47,17 @@ func main() {
|
|||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
config.SetValuesFromFile(tweethog.GetExpandedFilename(c.GlobalString("config-file")))
|
||||
config.SetValuesFromFile(photoprism.getExpandedFilename(c.GlobalString("config-file")))
|
||||
|
||||
config.SetValuesFromCliContext(c)
|
||||
|
||||
fmt.Println("Welcome to PhotoPrism")
|
||||
fmt.Printf("Importing photos from %s\n", config.ImportPath)
|
||||
|
||||
client, err := elastic.NewClient(elastic.SetURL("http://localhost:9200"))
|
||||
importer := photoprism.NewImporter(config.OriginalsPath)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Problem with elasticsearch :-(")
|
||||
importer.ImportPhotosFromDirectory(config.ImportPath)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
client.ClusterState()
|
||||
fmt.Println("Done.")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
|
|
|||
36
config.go
36
config.go
|
|
@ -18,57 +18,57 @@ func NewConfig() *Config {
|
|||
return &Config{}
|
||||
}
|
||||
|
||||
func (config *Config) SetValuesFromFile(fileName string) error {
|
||||
func (c *Config) SetValuesFromFile(fileName string) error {
|
||||
yamlConfig, err := yaml.ReadFile(fileName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.ConfigFile = fileName
|
||||
c.ConfigFile = fileName
|
||||
|
||||
if OriginalsPath, err := yamlConfig.Get("originals-path"); err == nil {
|
||||
config.OriginalsPath = GetExpandedFilename(OriginalsPath)
|
||||
c.OriginalsPath = getExpandedFilename(OriginalsPath)
|
||||
}
|
||||
|
||||
if ThumbnailsPath, err := yamlConfig.Get("thumbnails-path"); err == nil {
|
||||
config.ThumbnailsPath = GetExpandedFilename(ThumbnailsPath)
|
||||
c.ThumbnailsPath = getExpandedFilename(ThumbnailsPath)
|
||||
}
|
||||
|
||||
if ImportPath, err := yamlConfig.Get("import-path"); err == nil {
|
||||
config.ImportPath = GetExpandedFilename(ImportPath)
|
||||
c.ImportPath = getExpandedFilename(ImportPath)
|
||||
}
|
||||
|
||||
if ExportPath, err := yamlConfig.Get("export-path"); err == nil {
|
||||
config.ExportPath = GetExpandedFilename(ExportPath)
|
||||
c.ExportPath = getExpandedFilename(ExportPath)
|
||||
}
|
||||
|
||||
if DarktableCli, err := yamlConfig.Get("darktable-cli"); err == nil {
|
||||
config.DarktableCli = GetExpandedFilename(DarktableCli)
|
||||
c.DarktableCli = getExpandedFilename(DarktableCli)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (config *Config) SetValuesFromCliContext(c *cli.Context) error {
|
||||
if c.IsSet("originals-path") {
|
||||
config.OriginalsPath = GetExpandedFilename(c.String("originals-path"))
|
||||
func (c *Config) SetValuesFromCliContext(context *cli.Context) error {
|
||||
if context.IsSet("originals-path") {
|
||||
c.OriginalsPath = getExpandedFilename(context.String("originals-path"))
|
||||
}
|
||||
|
||||
if c.IsSet("thumbnails-path") {
|
||||
config.ThumbnailsPath = GetExpandedFilename(c.String("thumbnails-path"))
|
||||
if context.IsSet("thumbnails-path") {
|
||||
c.ThumbnailsPath = getExpandedFilename(context.String("thumbnails-path"))
|
||||
}
|
||||
|
||||
if c.IsSet("import-path") {
|
||||
config.ImportPath = GetExpandedFilename(c.String("import-path"))
|
||||
if context.IsSet("import-path") {
|
||||
c.ImportPath = getExpandedFilename(context.String("import-path"))
|
||||
}
|
||||
|
||||
if c.IsSet("export-path") {
|
||||
config.ExportPath = GetExpandedFilename(c.String("export-path"))
|
||||
if context.IsSet("export-path") {
|
||||
c.ExportPath = getExpandedFilename(context.String("export-path"))
|
||||
}
|
||||
|
||||
if c.IsSet("darktable-cli") {
|
||||
config.DarktableCli = GetExpandedFilename(c.String("darktable-cli"))
|
||||
if context.IsSet("darktable-cli") {
|
||||
c.DarktableCli = getExpandedFilename(context.String("darktable-cli"))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,11 +1,88 @@
|
|||
package photoprism
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const testDataPath = "testdata"
|
||||
const testDataUrl = "https://www.dropbox.com/s/na9p9wwt98l7m5b/import.zip?dl=1"
|
||||
const testDataHash = "ed3bdb2fe86ea662bc863b63e219b47b8d9a74024757007f7979887d"
|
||||
|
||||
var darktableCli = "/Applications/darktable.app/Contents/MacOS/darktable-cli"
|
||||
var testDataZip = getExpandedFilename(testDataPath + "/import.zip")
|
||||
var originalsPath = getExpandedFilename(testDataPath + "/originals")
|
||||
var thumbnailsPath = getExpandedFilename(testDataPath + "/thumbnails")
|
||||
var importPath = getExpandedFilename(testDataPath + "/import")
|
||||
var exportPath = getExpandedFilename(testDataPath + "/export")
|
||||
|
||||
func (c *Config) RemoveTestData(t *testing.T) {
|
||||
os.RemoveAll(c.ImportPath)
|
||||
os.RemoveAll(c.ExportPath)
|
||||
os.RemoveAll(c.OriginalsPath)
|
||||
os.RemoveAll(c.ThumbnailsPath)
|
||||
}
|
||||
|
||||
func (c *Config) DownloadTestData(t *testing.T) {
|
||||
if fileExists(testDataZip) {
|
||||
hash := fileHash(testDataZip)
|
||||
|
||||
if hash != testDataHash {
|
||||
os.Remove(testDataZip)
|
||||
t.Logf("Removed outdated test data zip file (fingerprint %s)\n", hash)
|
||||
}
|
||||
}
|
||||
|
||||
if !fileExists(testDataZip) {
|
||||
fmt.Printf("Downloading latest test data zip file from %s\n", testDataUrl)
|
||||
|
||||
if err := downloadFile(testDataZip, testDataUrl); err != nil {
|
||||
fmt.Printf("Download failed: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) UnzipTestData(t *testing.T) {
|
||||
if _, err := unzip(testDataZip, testDataPath); err != nil {
|
||||
t.Logf("Could not unzip test data: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) InitializeTestData(t *testing.T) {
|
||||
t.Log("Initializing test data")
|
||||
|
||||
c.RemoveTestData(t)
|
||||
|
||||
c.DownloadTestData(t)
|
||||
|
||||
c.UnzipTestData(t)
|
||||
}
|
||||
|
||||
func NewTestConfig() *Config {
|
||||
return &Config{
|
||||
DarktableCli: "/Applications/darktable.app/Contents/MacOS/darktable-cli",
|
||||
OriginalsPath: GetExpandedFilename("photos/originals"),
|
||||
ThumbnailsPath: GetExpandedFilename("photos/thumbnails"),
|
||||
ImportPath: GetExpandedFilename("photos/import"),
|
||||
ExportPath: GetExpandedFilename("photos/export"),
|
||||
DarktableCli: darktableCli,
|
||||
OriginalsPath: originalsPath,
|
||||
ThumbnailsPath: thumbnailsPath,
|
||||
ImportPath: importPath,
|
||||
ExportPath: exportPath,
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConfig(t *testing.T) {
|
||||
c := NewConfig()
|
||||
|
||||
assert.IsType(t, &Config{}, c)
|
||||
}
|
||||
|
||||
func TestConfig_SetValuesFromFile(t *testing.T) {
|
||||
c := NewConfig()
|
||||
|
||||
c.SetValuesFromFile(getExpandedFilename("config.example.yml"))
|
||||
|
||||
assert.Equal(t, getExpandedFilename("photos/originals"), c.OriginalsPath)
|
||||
assert.Equal(t, getExpandedFilename("photos/thumbnails"), c.ThumbnailsPath)
|
||||
assert.Equal(t, getExpandedFilename("photos/import"), c.ImportPath)
|
||||
assert.Equal(t, getExpandedFilename("photos/export"), c.ExportPath)
|
||||
}
|
||||
|
|
|
|||
45
converter.go
45
converter.go
|
|
@ -4,6 +4,8 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Converter struct {
|
||||
|
|
@ -20,7 +22,44 @@ func NewConverter(darktableCli string) *Converter {
|
|||
return &Converter{darktableCli: darktableCli}
|
||||
}
|
||||
|
||||
func (converter *Converter) ConvertToJpeg(image *MediaFile) (*MediaFile, error) {
|
||||
|
||||
func (c *Converter) ConvertAll(path string) {
|
||||
err := filepath.Walk(path, func(filename string, fileInfo os.FileInfo, err error) error {
|
||||
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
mediaFile := NewMediaFile(filename)
|
||||
|
||||
if !mediaFile.Exists() || !mediaFile.IsRaw() {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Converting %s \n", filename)
|
||||
|
||||
if _, err := c.ConvertToJpeg(mediaFile); err != nil {
|
||||
log.Print(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Converter) ConvertToJpeg(image *MediaFile) (*MediaFile, error) {
|
||||
if !image.Exists() {
|
||||
return nil, errors.New("can not convert, file does not exist")
|
||||
}
|
||||
|
||||
if image.IsJpeg() {
|
||||
return image, nil
|
||||
}
|
||||
|
|
@ -40,9 +79,9 @@ func (converter *Converter) ConvertToJpeg(image *MediaFile) (*MediaFile, error)
|
|||
var convertCommand *exec.Cmd
|
||||
|
||||
if _, err := os.Stat(xmpFilename); err == nil {
|
||||
convertCommand = exec.Command(converter.darktableCli, image.filename, xmpFilename, jpegFilename)
|
||||
convertCommand = exec.Command(c.darktableCli, image.filename, xmpFilename, jpegFilename)
|
||||
} else {
|
||||
convertCommand = exec.Command(converter.darktableCli, image.filename, jpegFilename)
|
||||
convertCommand = exec.Command(c.darktableCli, image.filename, jpegFilename)
|
||||
}
|
||||
|
||||
if err := convertCommand.Run(); err != nil {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,90 @@ package photoprism
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
)
|
||||
"os"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewConverter(t *testing.T) {
|
||||
NewConverter("storage")
|
||||
conf := NewTestConfig()
|
||||
|
||||
converter := NewConverter(conf.DarktableCli)
|
||||
|
||||
assert.IsType(t, &Converter{}, converter)
|
||||
}
|
||||
|
||||
func TestConverter_ConvertToJpeg(t *testing.T) {
|
||||
conf := NewTestConfig()
|
||||
|
||||
conf.InitializeTestData(t)
|
||||
|
||||
converter := NewConverter(conf.DarktableCli)
|
||||
|
||||
jpegFilename := conf.ImportPath + "/iphone/IMG_6788.jpg"
|
||||
|
||||
t.Logf("Testing RAW to JPEG converter with %s", jpegFilename)
|
||||
|
||||
imageJpeg,_ := converter.ConvertToJpeg(NewMediaFile(jpegFilename))
|
||||
|
||||
infoJpeg, err := imageJpeg.GetExifData()
|
||||
|
||||
assert.Equal(t, jpegFilename, imageJpeg.filename)
|
||||
|
||||
assert.False(t, infoJpeg == nil || err != nil, "Could not read EXIF data of JPEG image")
|
||||
|
||||
assert.Equal(t, "iPhone SE", infoJpeg.CameraModel)
|
||||
|
||||
rawFilemame := conf.ImportPath + "/raw/IMG_1435.CR2"
|
||||
|
||||
t.Logf("Testing RAW to JPEG converter with %s", rawFilemame)
|
||||
|
||||
imageRaw, _ := converter.ConvertToJpeg(NewMediaFile(rawFilemame))
|
||||
|
||||
assert.True(t, fileExists(conf.ImportPath + "/raw/IMG_1435.jpg"), "Jpeg file was not found - is Darktable installed?")
|
||||
|
||||
assert.NotEqual(t, rawFilemame, imageRaw.filename)
|
||||
|
||||
infoRaw, err := imageRaw.GetExifData()
|
||||
|
||||
assert.False(t, infoRaw == nil || err != nil, "Could not read EXIF data of RAW image")
|
||||
|
||||
assert.Equal(t, "Canon EOS M10", infoRaw.CameraModel)
|
||||
}
|
||||
|
||||
func TestConverter_ConvertAll(t *testing.T) {
|
||||
conf := NewTestConfig()
|
||||
|
||||
conf.InitializeTestData(t)
|
||||
|
||||
converter := NewConverter(conf.DarktableCli)
|
||||
|
||||
converter.ConvertAll(conf.ImportPath)
|
||||
|
||||
jpegFilename := conf.ImportPath + "/raw/IMG_1435.jpg"
|
||||
|
||||
assert.True(t, fileExists(jpegFilename), "Jpeg file was not found - is Darktable installed?")
|
||||
|
||||
image := NewMediaFile(jpegFilename)
|
||||
|
||||
assert.Equal(t, jpegFilename, image.filename, "Filename must be the same")
|
||||
|
||||
infoRaw, err := image.GetExifData()
|
||||
|
||||
assert.False(t, infoRaw == nil || err != nil, "Could not read EXIF data of RAW image")
|
||||
|
||||
assert.Equal(t, "Canon EOS M10", infoRaw.CameraModel, "Camera model should be Canon EOS M10")
|
||||
|
||||
existingJpegFilename := conf.ImportPath + "/raw/20140717_154212_1EC48F8489.jpg"
|
||||
|
||||
oldHash := fileHash(existingJpegFilename)
|
||||
|
||||
os.Remove(existingJpegFilename)
|
||||
|
||||
converter.ConvertAll(conf.ImportPath)
|
||||
|
||||
newHash := fileHash(existingJpegFilename)
|
||||
|
||||
assert.True(t, fileExists(existingJpegFilename), "Jpeg file was not found - is Darktable installed?")
|
||||
|
||||
assert.NotEqual(t, oldHash, newHash, "Fingerprint of old and new JPEG file must not be the same")
|
||||
}
|
||||
|
|
|
|||
126
importer.go
126
importer.go
|
|
@ -6,130 +6,114 @@ import (
|
|||
"log"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"bytes"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Importer struct {
|
||||
originalsPath string
|
||||
converter *Converter
|
||||
removeDotFiles bool
|
||||
removeExistingFiles bool
|
||||
removeEmptyDirectories bool
|
||||
}
|
||||
|
||||
func NewImporter(originalsPath string, converter *Converter) *Importer {
|
||||
func NewImporter(originalsPath string) *Importer {
|
||||
instance := &Importer{
|
||||
originalsPath: originalsPath,
|
||||
converter: converter,
|
||||
removeDotFiles: true,
|
||||
removeExistingFiles: true,
|
||||
removeEmptyDirectories: true,
|
||||
}
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
func (importer *Importer) CreateJpegFromRaw(sourcePath string) {
|
||||
err := filepath.Walk(sourcePath, func(filename string, fileInfo os.FileInfo, err error) error {
|
||||
func (i *Importer) ImportPhotosFromDirectory(importPath string) {
|
||||
var directories []string
|
||||
|
||||
err := filepath.Walk(importPath, func(filename string, fileInfo os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
// log.Print(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
if filename != importPath {
|
||||
directories = append(directories, filename)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.removeDotFiles && strings.HasPrefix(filepath.Base(filename), ".") {
|
||||
os.Remove(filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
mediaFile := NewMediaFile(filename)
|
||||
|
||||
if !mediaFile.Exists() || !mediaFile.IsRaw() {
|
||||
if !mediaFile.Exists() || !mediaFile.IsPhoto() {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Converting %s \n", filename)
|
||||
|
||||
if _, err := importer.converter.ConvertToJpeg(mediaFile); err != nil {
|
||||
log.Print(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (importer *Importer) ImportJpegFromDirectory(sourcePath string) {
|
||||
err := filepath.Walk(sourcePath, func(filename string, fileInfo os.FileInfo, err error) error {
|
||||
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
jpegFile := NewMediaFile(filename)
|
||||
|
||||
if !jpegFile.Exists() || !jpegFile.IsJpeg() {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println(jpegFile.GetFilename() + " -> " + jpegFile.GetCanonicalName())
|
||||
|
||||
log.Println("Getting related files")
|
||||
|
||||
relatedFiles, _ := jpegFile.GetRelatedFiles()
|
||||
relatedFiles, masterFile, _ := mediaFile.GetRelatedFiles()
|
||||
|
||||
for _, relatedMediaFile := range relatedFiles {
|
||||
log.Println("Processing " + relatedMediaFile.GetFilename())
|
||||
if destinationFilename, err := importer.GetDestinationFilename(jpegFile, relatedMediaFile); err == nil {
|
||||
log.Println("Creating directories")
|
||||
if destinationFilename, err := i.GetDestinationFilename(masterFile, relatedMediaFile); err == nil {
|
||||
os.MkdirAll(path.Dir(destinationFilename), os.ModePerm)
|
||||
log.Println("Moving file " + relatedMediaFile.GetFilename())
|
||||
log.Printf("Moving file %s to %s", relatedMediaFile.GetFilename(), destinationFilename)
|
||||
relatedMediaFile.Move(destinationFilename)
|
||||
log.Println("Moved file to " + destinationFilename)
|
||||
} else {
|
||||
log.Println("File already exists: " + relatedMediaFile.GetFilename() + " -> " + destinationFilename)
|
||||
} else if i.removeExistingFiles {
|
||||
relatedMediaFile.Remove()
|
||||
log.Printf("Deleted %s (already exists)", relatedMediaFile.GetFilename())
|
||||
}
|
||||
}
|
||||
|
||||
// mediaFile.Move(importer.originalsPath)
|
||||
// mediaFile.Move(i.originalsPath)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
sort.Slice(directories, func(i, j int) bool {
|
||||
return len(directories[i]) > len(directories[j])
|
||||
})
|
||||
|
||||
if i.removeEmptyDirectories {
|
||||
// Remove empty directories from import path
|
||||
for _, directory := range directories {
|
||||
if directoryIsEmpty(directory) {
|
||||
os.Remove(directory)
|
||||
log.Printf("Deleted empty directory %s", directory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (importer *Importer) GetDestinationFilename(jpegFile *MediaFile, mediaFile *MediaFile) (string, error) {
|
||||
canonicalName := jpegFile.GetCanonicalName()
|
||||
func (i *Importer) GetDestinationFilename(masterFile *MediaFile, mediaFile *MediaFile) (string, error) {
|
||||
canonicalName := masterFile.GetCanonicalName()
|
||||
fileExtension := mediaFile.GetExtension()
|
||||
dateCreated := jpegFile.GetDateCreated()
|
||||
dateCreated := masterFile.GetDateCreated()
|
||||
|
||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
||||
path := importer.originalsPath + "/" + dateCreated.UTC().Format("2006/01")
|
||||
pathName := i.originalsPath + "/" + dateCreated.UTC().Format("2006/01")
|
||||
|
||||
i := 1
|
||||
iteration := 1
|
||||
|
||||
result := path + "/" + canonicalName + fileExtension
|
||||
result := pathName + "/" + canonicalName + fileExtension
|
||||
|
||||
for FileExists(result) {
|
||||
if bytes.Compare(mediaFile.GetHash(), Md5Sum(result)) == 0 {
|
||||
for fileExists(result) {
|
||||
if mediaFile.GetHash() == fileHash(result) {
|
||||
return result, errors.New("File already exists")
|
||||
}
|
||||
|
||||
i++
|
||||
result = path + "/" + canonicalName + "_" + fmt.Sprintf("%02d", i) + fileExtension
|
||||
// log.Println(result)
|
||||
iteration++
|
||||
|
||||
result = pathName + "/" + canonicalName + "_" + fmt.Sprintf("V%d", iteration) + fileExtension
|
||||
}
|
||||
|
||||
// os.MkdirAll(folderPath, os.ModePerm)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (importer *Importer) MoveRelatedFiles() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,37 @@ package photoprism
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestImporter_ImportFromDirectory(t *testing.T) {
|
||||
config := NewTestConfig()
|
||||
func TestNewImporter(t *testing.T) {
|
||||
conf := NewTestConfig()
|
||||
|
||||
converter := NewConverter(config.DarktableCli)
|
||||
importer := NewImporter(config.OriginalsPath, converter)
|
||||
importer := NewImporter(conf.OriginalsPath)
|
||||
|
||||
importer.CreateJpegFromRaw(config.ImportPath)
|
||||
importer.ImportJpegFromDirectory(config.ImportPath)
|
||||
assert.IsType(t, &Importer{}, importer)
|
||||
}
|
||||
|
||||
func TestImporter_ImportPhotosFromDirectory(t *testing.T) {
|
||||
conf := NewTestConfig()
|
||||
|
||||
conf.InitializeTestData(t)
|
||||
|
||||
importer := NewImporter(conf.OriginalsPath)
|
||||
|
||||
importer.ImportPhotosFromDirectory(conf.ImportPath)
|
||||
}
|
||||
|
||||
func TestImporter_GetDestinationFilename(t *testing.T) {
|
||||
conf := NewTestConfig()
|
||||
conf.InitializeTestData(t)
|
||||
importer := NewImporter(conf.OriginalsPath)
|
||||
|
||||
rawFile := NewMediaFile(conf.ImportPath + "/raw/IMG_1435.cr2")
|
||||
|
||||
filename, err := importer.GetDestinationFilename(rawFile, rawFile)
|
||||
|
||||
assert.Empty(t, err)
|
||||
|
||||
assert.Equal(t, conf.OriginalsPath + "/2018/02/20180204_170813_B0770443A5F7.cr2", filename)
|
||||
}
|
||||
198
mediafile.go
198
mediafile.go
|
|
@ -10,7 +10,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
"github.com/djherbis/times"
|
||||
"fmt"
|
||||
"github.com/steakknife/hamming"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -45,48 +45,52 @@ var FileExtensions = map[string]string {
|
|||
type MediaFile struct {
|
||||
filename string
|
||||
dateCreated time.Time
|
||||
hash []byte
|
||||
hash string
|
||||
fileType string
|
||||
mimeType string
|
||||
perceptualHash string
|
||||
tags []string
|
||||
exifData *ExifData
|
||||
}
|
||||
|
||||
func NewMediaFile(filename string) *MediaFile {
|
||||
instance := &MediaFile{filename: filename}
|
||||
instance := &MediaFile{
|
||||
filename: filename,
|
||||
fileType: FileTypeOther,
|
||||
}
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetDateCreated() time.Time {
|
||||
if !mediaFile.dateCreated.IsZero() {
|
||||
return mediaFile.dateCreated
|
||||
func (m *MediaFile) GetDateCreated() time.Time {
|
||||
if !m.dateCreated.IsZero() {
|
||||
return m.dateCreated
|
||||
}
|
||||
|
||||
info, err := mediaFile.GetExifData()
|
||||
info, err := m.GetExifData()
|
||||
|
||||
if err == nil {
|
||||
mediaFile.dateCreated = info.DateTime
|
||||
m.dateCreated = info.DateTime
|
||||
return info.DateTime
|
||||
}
|
||||
|
||||
t, err := times.Stat(mediaFile.GetFilename())
|
||||
t, err := times.Stat(m.GetFilename())
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if t.HasBirthTime() {
|
||||
mediaFile.dateCreated = t.BirthTime()
|
||||
m.dateCreated = t.BirthTime()
|
||||
return t.BirthTime()
|
||||
}
|
||||
|
||||
mediaFile.dateCreated = t.ModTime()
|
||||
m.dateCreated = t.ModTime()
|
||||
return t.ModTime()
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetCameraModel () string {
|
||||
info, err := mediaFile.GetExifData()
|
||||
func (m *MediaFile) GetCameraModel () string {
|
||||
info, err := m.GetExifData()
|
||||
|
||||
var result string
|
||||
|
||||
|
|
@ -97,22 +101,26 @@ func (mediaFile *MediaFile) GetCameraModel () string {
|
|||
return result
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetCanonicalName() string {
|
||||
dateCreated := mediaFile.GetDateCreated().UTC()
|
||||
cameraModel := strings.Replace(mediaFile.GetCameraModel(), " ", "_", -1)
|
||||
func (m *MediaFile) GetCanonicalName() string {
|
||||
dateCreated := m.GetDateCreated().UTC()
|
||||
//cameraModel := strings.Replace(m.GetCameraModel(), " ", "_", -1)
|
||||
|
||||
result := dateCreated.Format("20060102_150405_") + strings.ToUpper(mediaFile.GetHashString()[:8])
|
||||
result := dateCreated.Format("20060102_150405_") + strings.ToUpper(m.GetHash()[:12])
|
||||
|
||||
if cameraModel != "" {
|
||||
/* if cameraModel != "" {
|
||||
result = result + "_" + cameraModel
|
||||
}
|
||||
} */
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetPerceptiveHash() (string, error) {
|
||||
func (m *MediaFile) GetPerceptualHash() (string, error) {
|
||||
if m.perceptualHash != "" {
|
||||
return m.perceptualHash, nil
|
||||
}
|
||||
|
||||
hasher := ish.NewDifferenceHash(8, 8)
|
||||
img, _, err := ish.LoadFile(mediaFile.GetFilename())
|
||||
img, _, err := ish.LoadFile(m.GetFilename())
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -124,56 +132,84 @@ func (mediaFile *MediaFile) GetPerceptiveHash() (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
dhs := hex.EncodeToString(dh)
|
||||
m.perceptualHash = hex.EncodeToString(dh)
|
||||
|
||||
return dhs, nil
|
||||
return m.perceptualHash, nil
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetHash() []byte {
|
||||
if len(mediaFile.hash) == 0 {
|
||||
mediaFile.hash = Md5Sum(mediaFile.GetFilename())
|
||||
func (m *MediaFile) GetPerceptualDistance(perceptualHash string) (int, error) {
|
||||
var hash1, hash2 []byte
|
||||
|
||||
if imageHash, err := m.GetPerceptualHash(); err != nil {
|
||||
return -1, err
|
||||
} else {
|
||||
if decoded, err := hex.DecodeString(imageHash); err != nil {
|
||||
return -1, err
|
||||
} else {
|
||||
hash1 = decoded
|
||||
}
|
||||
}
|
||||
|
||||
return mediaFile.hash
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetHashString() string {
|
||||
return fmt.Sprintf("%x", mediaFile.GetHash())
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetRelatedFiles() (result []*MediaFile, err error) {
|
||||
extension := mediaFile.GetExtension()
|
||||
|
||||
baseFilename := mediaFile.filename[0:len(mediaFile.filename)-len(extension)]
|
||||
|
||||
matches, err := filepath.Glob(baseFilename + "*")
|
||||
|
||||
if err != nil {
|
||||
return result, err
|
||||
if decoded, err := hex.DecodeString(perceptualHash); err != nil {
|
||||
return -1, err
|
||||
} else {
|
||||
hash2 = decoded
|
||||
}
|
||||
|
||||
for _, filename := range matches {
|
||||
result = append(result, NewMediaFile(filename))
|
||||
}
|
||||
result := hamming.Bytes(hash1, hash2)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
func (mediaFile *MediaFile) GetFilename() string {
|
||||
return mediaFile.filename
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) SetFilename(filename string) {
|
||||
mediaFile.filename = filename
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetMimeType() string {
|
||||
if mediaFile.mimeType != "" {
|
||||
return mediaFile.mimeType
|
||||
func (m *MediaFile) GetHash() string {
|
||||
if len(m.hash) == 0 {
|
||||
m.hash = fileHash(m.GetFilename())
|
||||
}
|
||||
|
||||
handle, err := mediaFile.openFile()
|
||||
return m.hash
|
||||
}
|
||||
|
||||
func (m *MediaFile) GetRelatedFiles() (result []*MediaFile, masterFile *MediaFile, err error) {
|
||||
extension := m.GetExtension()
|
||||
|
||||
baseFilename := m.filename[0:len(m.filename)-len(extension)]
|
||||
|
||||
matches, err := filepath.Glob(baseFilename + "*")
|
||||
|
||||
if err != nil {
|
||||
return result, nil, err
|
||||
}
|
||||
|
||||
for _, filename := range matches {
|
||||
resultFile := NewMediaFile(filename)
|
||||
|
||||
if masterFile == nil && resultFile.IsJpeg() {
|
||||
masterFile = resultFile
|
||||
} else if resultFile.IsRaw() {
|
||||
masterFile = resultFile
|
||||
}
|
||||
|
||||
result = append(result, resultFile)
|
||||
}
|
||||
|
||||
return result, masterFile, nil
|
||||
}
|
||||
|
||||
|
||||
func (m *MediaFile) GetFilename() string {
|
||||
return m.filename
|
||||
}
|
||||
|
||||
func (m *MediaFile) SetFilename(filename string) {
|
||||
m.filename = filename
|
||||
}
|
||||
|
||||
func (m *MediaFile) GetMimeType() string {
|
||||
if m.mimeType != "" {
|
||||
return m.mimeType
|
||||
}
|
||||
|
||||
handle, err := m.openFile()
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error: Could not open file to determine mime type")
|
||||
|
|
@ -188,17 +224,17 @@ func (mediaFile *MediaFile) GetMimeType() string {
|
|||
_, err = handle.Read(buffer)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error: Could not read file to determine mime type: " + mediaFile.GetFilename())
|
||||
log.Println("Error: Could not read file to determine mime type: " + m.GetFilename())
|
||||
return ""
|
||||
}
|
||||
|
||||
mediaFile.mimeType = http.DetectContentType(buffer)
|
||||
m.mimeType = http.DetectContentType(buffer)
|
||||
|
||||
return mediaFile.mimeType
|
||||
return m.mimeType
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) openFile() (*os.File, error) {
|
||||
if handle, err := os.Open(mediaFile.filename); err == nil {
|
||||
func (m *MediaFile) openFile() (*os.File, error) {
|
||||
if handle, err := os.Open(m.filename); err == nil {
|
||||
return handle, nil
|
||||
} else {
|
||||
log.Println(err.Error())
|
||||
|
|
@ -206,40 +242,44 @@ func (mediaFile *MediaFile) openFile() (*os.File, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) Exists() bool {
|
||||
return FileExists(mediaFile.GetFilename())
|
||||
func (m *MediaFile) Exists() bool {
|
||||
return fileExists(m.GetFilename())
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) Move(newFilename string) error {
|
||||
if err := os.Rename(mediaFile.filename, newFilename); err != nil {
|
||||
func (m *MediaFile) Remove() error {
|
||||
return os.Remove(m.GetFilename())
|
||||
}
|
||||
|
||||
func (m *MediaFile) Move(newFilename string) error {
|
||||
if err := os.Rename(m.filename, newFilename); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mediaFile.filename = newFilename
|
||||
m.filename = newFilename
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetExtension() string {
|
||||
return strings.ToLower(filepath.Ext(mediaFile.filename))
|
||||
func (m *MediaFile) GetExtension() string {
|
||||
return strings.ToLower(filepath.Ext(m.filename))
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) IsJpeg() bool {
|
||||
return mediaFile.GetMimeType() == MimeTypeJpeg
|
||||
func (m *MediaFile) IsJpeg() bool {
|
||||
return m.GetMimeType() == MimeTypeJpeg
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) HasType(typeString string) bool {
|
||||
func (m *MediaFile) HasType(typeString string) bool {
|
||||
if typeString == FileTypeJpeg {
|
||||
return mediaFile.IsJpeg()
|
||||
return m.IsJpeg()
|
||||
}
|
||||
|
||||
return FileExtensions[mediaFile.GetExtension()] == typeString
|
||||
return FileExtensions[m.GetExtension()] == typeString
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) IsRaw() bool {
|
||||
return mediaFile.HasType(FileTypeRaw)
|
||||
func (m *MediaFile) IsRaw() bool {
|
||||
return m.HasType(FileTypeRaw)
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) IsPhoto() bool {
|
||||
return mediaFile.IsJpeg() || mediaFile.IsRaw()
|
||||
func (m *MediaFile) IsPhoto() bool {
|
||||
return m.IsJpeg() || m.IsRaw()
|
||||
}
|
||||
|
|
@ -6,36 +6,36 @@ import (
|
|||
"time"
|
||||
"errors"
|
||||
"strings"
|
||||
"log"
|
||||
)
|
||||
|
||||
type ExifData struct {
|
||||
DateTime time.Time
|
||||
CameraModel string
|
||||
UniqueID string
|
||||
Lat float64
|
||||
Long float64
|
||||
Thumbnail []byte
|
||||
}
|
||||
|
||||
func (mediaFile *MediaFile) GetExifData() (*ExifData, error) {
|
||||
if mediaFile.exifData != nil {
|
||||
log.Printf("GetExifData() Cache Hit %s", mediaFile.filename)
|
||||
return mediaFile.exifData, nil
|
||||
func (m *MediaFile) GetExifData() (*ExifData, error) {
|
||||
if m == nil {
|
||||
return nil, errors.New("media file is null")
|
||||
}
|
||||
|
||||
log.Printf("GetExifData() Cache Miss %s", mediaFile.filename)
|
||||
|
||||
if !mediaFile.IsJpeg() {
|
||||
// EXIF only works for JPEG
|
||||
return nil, errors.New("MediaFile is not a JPEG")
|
||||
if m.exifData != nil {
|
||||
return m.exifData, nil
|
||||
}
|
||||
|
||||
mediaFile.exifData = &ExifData{}
|
||||
if !m.IsPhoto() {
|
||||
return nil, errors.New("not a JPEG or Raw file")
|
||||
}
|
||||
|
||||
log.Printf("GetExifData() Open File %s", mediaFile.filename)
|
||||
file, err := mediaFile.openFile()
|
||||
m.exifData = &ExifData{}
|
||||
|
||||
file, err := m.openFile()
|
||||
|
||||
if err != nil {
|
||||
return mediaFile.exifData, err
|
||||
return m.exifData, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
|
@ -45,18 +45,29 @@ func (mediaFile *MediaFile) GetExifData() (*ExifData, error) {
|
|||
x, err := exif.Decode(file)
|
||||
|
||||
if err != nil {
|
||||
return mediaFile.exifData, err
|
||||
return m.exifData, err
|
||||
}
|
||||
|
||||
camModel, _ := x.Get(exif.Model)
|
||||
mediaFile.exifData.CameraModel = strings.Replace(camModel.String(), "\"", "", -1)
|
||||
if camModel, err := x.Get(exif.Model); err == nil {
|
||||
m.exifData.CameraModel = strings.Replace(camModel.String(), "\"", "", -1)
|
||||
}
|
||||
|
||||
tm, _ := x.DateTime()
|
||||
mediaFile.exifData.DateTime = tm
|
||||
if tm, err := x.DateTime(); err == nil {
|
||||
m.exifData.DateTime = tm
|
||||
}
|
||||
|
||||
lat, long, _ := x.LatLong()
|
||||
mediaFile.exifData.Lat = lat
|
||||
mediaFile.exifData.Long = long
|
||||
if lat, long, err := x.LatLong(); err == nil {
|
||||
m.exifData.Lat = lat
|
||||
m.exifData.Long = long
|
||||
}
|
||||
|
||||
return mediaFile.exifData, nil
|
||||
if thumbnail, err := x.JpegThumbnail(); err == nil {
|
||||
m.exifData.Thumbnail = thumbnail
|
||||
}
|
||||
|
||||
if uniqueId, err := x.Get(exif.ImageUniqueID); err == nil {
|
||||
m.exifData.UniqueID = uniqueId.String()
|
||||
}
|
||||
|
||||
return m.exifData, nil
|
||||
}
|
||||
|
|
@ -2,30 +2,31 @@ package photoprism
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestImage_GetExifData(t *testing.T) {
|
||||
config := NewTestConfig()
|
||||
func TestMediaFile_GetExifData(t *testing.T) {
|
||||
conf := NewTestConfig()
|
||||
|
||||
converter := NewConverter(config.DarktableCli)
|
||||
conf.InitializeTestData(t)
|
||||
|
||||
image1 := NewMediaFile("storage/import/IMG_9083.jpg")
|
||||
image1 := NewMediaFile(conf.ImportPath + "/iphone/IMG_6788.JPG")
|
||||
|
||||
info1, _ := image1.GetExifData()
|
||||
info, err := image1.GetExifData()
|
||||
|
||||
fmt.Printf("%+v\n", info1)
|
||||
assert.Empty(t, err);
|
||||
|
||||
image2,_ := converter.ConvertToJpeg(NewMediaFile("storage/import/IMG_5901.JPG"))
|
||||
assert.IsType(t, &ExifData{}, info)
|
||||
|
||||
info2, _ := image2.GetExifData()
|
||||
assert.Equal(t, "iPhone SE", info.CameraModel)
|
||||
|
||||
fmt.Printf("%+v\n", info2)
|
||||
image2 := NewMediaFile(conf.ImportPath + "/raw/IMG_1435.cr2")
|
||||
|
||||
image3, _ := converter.ConvertToJpeg(NewMediaFile("storage/import/IMG_9087.CR2"))
|
||||
info, err = image2.GetExifData()
|
||||
|
||||
info3, _ := image3.GetExifData()
|
||||
assert.Empty(t, err)
|
||||
|
||||
fmt.Printf("%+v\n", info3)
|
||||
assert.IsType(t, &ExifData{}, info)
|
||||
|
||||
assert.Equal(t, "Canon EOS M10", info.CameraModel)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,61 +2,89 @@ package photoprism
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMediaFile_ConvertToJpeg(t *testing.T) {
|
||||
converterCommand := "/Applications/darktable.app/Contents/MacOS/darktable-cli"
|
||||
|
||||
converter := NewConverter(converterCommand)
|
||||
|
||||
image1,_ := converter.ConvertToJpeg(NewMediaFile("storage/import/IMG_5901.JPG"))
|
||||
|
||||
info1, _ := image1.GetExifData()
|
||||
|
||||
fmt.Printf("%+v\n", info1)
|
||||
|
||||
image2, _ := converter.ConvertToJpeg(NewMediaFile("storage/import/IMG_9087.CR2"))
|
||||
|
||||
info2, _ := image2.GetExifData()
|
||||
|
||||
fmt.Printf("%+v\n", info2)
|
||||
}
|
||||
|
||||
func TestMediaFile_FindRelatedImages(t *testing.T) {
|
||||
image := NewMediaFile("storage/import/IMG_9079.jpg")
|
||||
conf := NewTestConfig()
|
||||
|
||||
related, err := image.GetRelatedFiles()
|
||||
conf.InitializeTestData(t)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
mediaFile := NewMediaFile(conf.ImportPath + "/raw/20140717_154212_1EC48F8489.cr2")
|
||||
|
||||
expectedBaseFilename := conf.ImportPath + "/raw/20140717_154212_1EC48F8489"
|
||||
|
||||
related, _, err := mediaFile.GetRelatedFiles()
|
||||
|
||||
assert.Empty(t, err)
|
||||
|
||||
assert.Len(t, related, 3)
|
||||
|
||||
for _, result := range related {
|
||||
info, _ := result.GetExifData()
|
||||
fmt.Printf("%s %+v\n", result.GetFilename(), info)
|
||||
filename := result.GetFilename()
|
||||
|
||||
extension := result.GetExtension()
|
||||
|
||||
baseFilename := filename[0:len(filename)-len(extension)]
|
||||
|
||||
assert.Equal(t, expectedBaseFilename, baseFilename)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaFile_GetPerceptiveHash(t *testing.T) {
|
||||
image := NewMediaFile("storage/import/IMG_9079.jpg")
|
||||
conf := NewTestConfig()
|
||||
|
||||
hash, _ := image.GetPerceptiveHash()
|
||||
fmt.Printf("Perceptive Hash (large): %s\n", hash)
|
||||
conf.InitializeTestData(t)
|
||||
|
||||
image2 := NewMediaFile("storage/import/IMG_9079_small.jpg")
|
||||
mediaFile1 := NewMediaFile(conf.ImportPath + "/20130203_193332_0AE340D280.jpg")
|
||||
|
||||
hash2, _ := image2.GetPerceptiveHash()
|
||||
fmt.Printf("Perceptive Hash (small): %s\n", hash2)
|
||||
hash1, _ := mediaFile1.GetPerceptualHash()
|
||||
|
||||
assert.Equal(t, "66debc383325d3bd", hash1)
|
||||
|
||||
mediaFile2 := NewMediaFile(conf.ImportPath + "/20130203_193332_0AE340D280_V2.jpg")
|
||||
|
||||
hash2, _ := mediaFile2.GetPerceptualHash()
|
||||
|
||||
assert.Equal(t, "e6debc393325c3b9", hash2)
|
||||
|
||||
distance, _ := mediaFile1.GetPerceptualDistance(hash2)
|
||||
|
||||
assert.Equal(t, 4, distance)
|
||||
|
||||
mediaFile3 := NewMediaFile(conf.ImportPath + "/iphone/IMG_6788.JPG")
|
||||
hash3, _ := mediaFile3.GetPerceptualHash()
|
||||
|
||||
assert.Equal(t, "f1e2858b171d3e78", hash3)
|
||||
|
||||
distance, _ = mediaFile1.GetPerceptualDistance(hash3)
|
||||
|
||||
assert.Equal(t, 33, distance)
|
||||
}
|
||||
|
||||
|
||||
func TestMediaFile_GetMimeType(t *testing.T) {
|
||||
image1 := NewMediaFile("storage/import/IMG_9083.jpg")
|
||||
conf := NewTestConfig()
|
||||
|
||||
fmt.Println("MimeType: " + image1.GetMimeType())
|
||||
conf.InitializeTestData(t)
|
||||
|
||||
image2 := NewMediaFile("storage/import/IMG_9082.CR2")
|
||||
image1 := NewMediaFile(conf.ImportPath + "/iphone/IMG_6788.JPG")
|
||||
|
||||
fmt.Println("MimeType: " + image2.GetMimeType())
|
||||
assert.Equal(t, "image/jpeg", image1.GetMimeType())
|
||||
|
||||
image2 := NewMediaFile(conf.ImportPath + "/raw/20140717_154212_1EC48F8489.cr2")
|
||||
|
||||
assert.Equal(t, "application/octet-stream", image2.GetMimeType())
|
||||
}
|
||||
|
||||
func TestMediaFile_Exists(t *testing.T) {
|
||||
conf := NewTestConfig()
|
||||
|
||||
mediaFile := NewMediaFile(conf.ImportPath + "/iphone/IMG_6788.JPG")
|
||||
|
||||
assert.True(t, mediaFile.Exists())
|
||||
|
||||
mediaFile = NewMediaFile(conf.ImportPath + "/iphone/IMG_6788_XYZ.JPG")
|
||||
|
||||
assert.False(t, mediaFile.Exists())
|
||||
}
|
||||
2
testdata/.gitignore
vendored
Normal file
2
testdata/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
139
util.go
139
util.go
|
|
@ -6,16 +6,22 @@ import (
|
|||
"os/user"
|
||||
"path/filepath"
|
||||
"os"
|
||||
"crypto/md5"
|
||||
"crypto/sha512"
|
||||
"io"
|
||||
"strings"
|
||||
"archive/zip"
|
||||
"log"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func GetRandomInt(min, max int) int {
|
||||
func getRandomInt(min, max int) int {
|
||||
rand.Seed(time.Now().Unix())
|
||||
return rand.Intn(max-min) + min
|
||||
}
|
||||
|
||||
func GetExpandedFilename(filename string) string {
|
||||
func getExpandedFilename(filename string) string {
|
||||
usr, _ := user.Current()
|
||||
dir := usr.HomeDir
|
||||
|
||||
|
|
@ -28,28 +34,141 @@ func GetExpandedFilename(filename string) string {
|
|||
return result
|
||||
}
|
||||
|
||||
func FileExists (filename string) bool {
|
||||
func fileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func Md5Sum (filename string) []byte {
|
||||
func fileHash(filename string) string {
|
||||
var result []byte
|
||||
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
return result
|
||||
return ""
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := md5.New()
|
||||
hash := sha512.New512_224()
|
||||
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return result
|
||||
return ""
|
||||
}
|
||||
|
||||
return hash.Sum(result)
|
||||
}
|
||||
return hex.EncodeToString(hash.Sum(result))
|
||||
}
|
||||
|
||||
func directoryIsEmpty(path string) bool {
|
||||
f, err := os.Open(path)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Readdirnames(1)
|
||||
|
||||
if err == io.EOF {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func unzip(src, dest string) ([]string, error) {
|
||||
|
||||
var filenames []string
|
||||
|
||||
r, err := zip.OpenReader(src)
|
||||
|
||||
if err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
// Skip directories like __OSX
|
||||
if strings.HasPrefix(f.Name, "__") {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
|
||||
if err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
|
||||
defer rc.Close()
|
||||
|
||||
// Store filename/path for returning and using later on
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
filenames = append(filenames, fpath)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
|
||||
// Make Folder
|
||||
os.MkdirAll(fpath, os.ModePerm)
|
||||
|
||||
} else {
|
||||
|
||||
// Make File
|
||||
var fdir string
|
||||
if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 {
|
||||
fdir = fpath[:lastIndex]
|
||||
}
|
||||
|
||||
err = os.MkdirAll(fdir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return filenames, err
|
||||
}
|
||||
f, err := os.OpenFile(
|
||||
fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, rc)
|
||||
if err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
func downloadFile(filepath string, url string) (err error) {
|
||||
// Create the file
|
||||
out, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Get the data
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check server response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("bad status: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Writer the body to file
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ func TestGetRandomInt(t *testing.T) {
|
|||
max := 50
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
result := GetRandomInt(min, max)
|
||||
result := getRandomInt(min, max)
|
||||
|
||||
if result > max {
|
||||
t.Errorf("Random result must not be bigger than %d", max)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue