Backend: Store and index original file names during import #184
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
1c592464bf
commit
a4070cf55c
|
@ -36,7 +36,7 @@ var rules = LabelRules{
|
|||
Label: "pumpkin",
|
||||
Threshold: 0.400000,
|
||||
Priority: 0,
|
||||
Categories: []string{"vegetables"},
|
||||
Categories: []string{"vegetables", "food"},
|
||||
},
|
||||
"acoustic guitar": {
|
||||
Label: "instrument",
|
||||
|
@ -270,7 +270,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"ashcan": {
|
||||
Label: "",
|
||||
|
@ -330,7 +330,7 @@ var rules = LabelRules{
|
|||
Label: "",
|
||||
Threshold: 0.400000,
|
||||
Priority: 0,
|
||||
Categories: []string{"bakery"},
|
||||
Categories: []string{"bakery", "food"},
|
||||
},
|
||||
"bakery": {
|
||||
Label: "",
|
||||
|
@ -378,7 +378,7 @@ var rules = LabelRules{
|
|||
Label: "",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{"fruit"},
|
||||
Categories: []string{"fruits", "food"},
|
||||
},
|
||||
"band aid": {
|
||||
Label: "portrait",
|
||||
|
@ -606,7 +606,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"belt": {
|
||||
Label: "portrait",
|
||||
|
@ -1005,7 +1005,7 @@ var rules = LabelRules{
|
|||
Categories: []string{"animal"},
|
||||
},
|
||||
"burrito": {
|
||||
Label: "fast food",
|
||||
Label: "food",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
|
@ -1032,7 +1032,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.400000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"cab": {
|
||||
Label: "",
|
||||
|
@ -1104,7 +1104,7 @@ var rules = LabelRules{
|
|||
Label: "pasta",
|
||||
Threshold: 0.200000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"cardigan": {
|
||||
Label: "portrait",
|
||||
|
@ -1245,7 +1245,7 @@ var rules = LabelRules{
|
|||
Categories: []string{"reptile", "animal", "lizard"},
|
||||
},
|
||||
"cheeseburger": {
|
||||
Label: "fast food",
|
||||
Label: "food",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
|
@ -1314,7 +1314,7 @@ var rules = LabelRules{
|
|||
Label: "dessert",
|
||||
Threshold: 0.200000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"chow dog": {
|
||||
Label: "dog",
|
||||
|
@ -1500,7 +1500,7 @@ var rules = LabelRules{
|
|||
Label: "soup",
|
||||
Threshold: 0.200000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"container ship": {
|
||||
Label: "ship",
|
||||
|
@ -1536,7 +1536,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"cornet": {
|
||||
Label: "instrument",
|
||||
|
@ -1650,7 +1650,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"cuirass": {
|
||||
Label: "portrait",
|
||||
|
@ -1665,10 +1665,10 @@ var rules = LabelRules{
|
|||
Categories: []string{"animal"},
|
||||
},
|
||||
"custard apple": {
|
||||
Label: "fruit",
|
||||
Threshold: 0.200000,
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"daisy": {
|
||||
Label: "flower",
|
||||
|
@ -2048,12 +2048,6 @@ var rules = LabelRules{
|
|||
Priority: 0,
|
||||
Categories: []string{"people"},
|
||||
},
|
||||
"fast food": {
|
||||
Label: "fast food",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
},
|
||||
"fence": {
|
||||
Label: "outdoor",
|
||||
Threshold: 0.200000,
|
||||
|
@ -2067,10 +2061,10 @@ var rules = LabelRules{
|
|||
Categories: []string{"animal"},
|
||||
},
|
||||
"fig": {
|
||||
Label: "fruit",
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"file": {
|
||||
Label: "furniture",
|
||||
|
@ -2150,6 +2144,12 @@ var rules = LabelRules{
|
|||
Priority: 0,
|
||||
Categories: []string{},
|
||||
},
|
||||
"food": {
|
||||
Label: "food",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
},
|
||||
"football helmet": {
|
||||
Label: "helmet",
|
||||
Threshold: 0.300000,
|
||||
|
@ -2220,7 +2220,7 @@ var rules = LabelRules{
|
|||
Label: "",
|
||||
Threshold: 0.400000,
|
||||
Priority: 0,
|
||||
Categories: []string{"bakery"},
|
||||
Categories: []string{"bakery", "food"},
|
||||
},
|
||||
"frilled lizard": {
|
||||
Label: "lizard",
|
||||
|
@ -2234,6 +2234,12 @@ var rules = LabelRules{
|
|||
Priority: 0,
|
||||
Categories: []string{"animal"},
|
||||
},
|
||||
"fruits": {
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"frying pan": {
|
||||
Label: "cooking",
|
||||
Threshold: 0.400000,
|
||||
|
@ -2415,10 +2421,10 @@ var rules = LabelRules{
|
|||
Categories: []string{},
|
||||
},
|
||||
"granny smith": {
|
||||
Label: "apple",
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{"fruit"},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"grasshopper": {
|
||||
Label: "grasshopper",
|
||||
|
@ -2670,7 +2676,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"helmet": {
|
||||
Label: "helmet",
|
||||
|
@ -2784,10 +2790,10 @@ var rules = LabelRules{
|
|||
Label: "soup",
|
||||
Threshold: 0.200000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"hotdog": {
|
||||
Label: "fast food",
|
||||
Label: "food",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
|
@ -2844,13 +2850,13 @@ var rules = LabelRules{
|
|||
Label: "dessert",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"ice lolly": {
|
||||
Label: "dessert",
|
||||
Threshold: 0.400000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"ignore": {
|
||||
Label: "",
|
||||
|
@ -2961,10 +2967,10 @@ var rules = LabelRules{
|
|||
Categories: []string{"vegetables"},
|
||||
},
|
||||
"jackfruit": {
|
||||
Label: "fruit",
|
||||
Threshold: 0.500000,
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"jaguar": {
|
||||
Label: "",
|
||||
|
@ -3213,10 +3219,10 @@ var rules = LabelRules{
|
|||
Categories: []string{"reptile", "animal"},
|
||||
},
|
||||
"lemon": {
|
||||
Label: "",
|
||||
Threshold: 0.400000,
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{"fruit"},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"lens cap": {
|
||||
Label: "photography",
|
||||
|
@ -3498,7 +3504,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"matchstick": {
|
||||
Label: "",
|
||||
|
@ -3528,7 +3534,7 @@ var rules = LabelRules{
|
|||
Label: "meat",
|
||||
Threshold: 0.200000,
|
||||
Priority: 0,
|
||||
Categories: []string{"cooking"},
|
||||
Categories: []string{"cooking", "food"},
|
||||
},
|
||||
"medicine chest": {
|
||||
Label: "",
|
||||
|
@ -3879,10 +3885,10 @@ var rules = LabelRules{
|
|||
Categories: []string{"animal"},
|
||||
},
|
||||
"orange": {
|
||||
Label: "",
|
||||
Threshold: 0.400000,
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{"fruit"},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"orangutan": {
|
||||
Label: "ape",
|
||||
|
@ -4206,7 +4212,7 @@ var rules = LabelRules{
|
|||
Label: "",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{"fruit"},
|
||||
Categories: []string{"fruits", "food"},
|
||||
},
|
||||
"ping-pong ball": {
|
||||
Label: "",
|
||||
|
@ -4230,7 +4236,7 @@ var rules = LabelRules{
|
|||
Label: "",
|
||||
Threshold: 0.200000,
|
||||
Priority: 0,
|
||||
Categories: []string{"fast food"},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"plane": {
|
||||
Label: "aircraft",
|
||||
|
@ -4299,10 +4305,10 @@ var rules = LabelRules{
|
|||
Categories: []string{"vehicle"},
|
||||
},
|
||||
"pomegranate": {
|
||||
Label: "fruit",
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"pomeranian dog": {
|
||||
Label: "dog",
|
||||
|
@ -4350,7 +4356,7 @@ var rules = LabelRules{
|
|||
Label: "cooking",
|
||||
Threshold: 0.200000,
|
||||
Priority: 0,
|
||||
Categories: []string{},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"potter's wheel": {
|
||||
Label: "",
|
||||
|
@ -4380,7 +4386,7 @@ var rules = LabelRules{
|
|||
Label: "",
|
||||
Threshold: 0.400000,
|
||||
Priority: 0,
|
||||
Categories: []string{"bakery"},
|
||||
Categories: []string{"bakery", "food"},
|
||||
},
|
||||
"printer": {
|
||||
Label: "office",
|
||||
|
@ -5160,7 +5166,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.400000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"spatula": {
|
||||
Label: "cooking",
|
||||
|
@ -5232,7 +5238,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.400000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"squirrel monkey": {
|
||||
Label: "monkey",
|
||||
|
@ -5337,10 +5343,10 @@ var rules = LabelRules{
|
|||
Categories: []string{},
|
||||
},
|
||||
"strawberry": {
|
||||
Label: "",
|
||||
Label: "fruits",
|
||||
Threshold: 0.300000,
|
||||
Priority: 0,
|
||||
Categories: []string{"fruit"},
|
||||
Categories: []string{"food"},
|
||||
},
|
||||
"streetcar": {
|
||||
Label: "",
|
||||
|
@ -5772,7 +5778,7 @@ var rules = LabelRules{
|
|||
Label: "dessert",
|
||||
Threshold: 0.200000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"trilobite": {
|
||||
Label: "animal",
|
||||
|
@ -5874,7 +5880,7 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
"velvet": {
|
||||
Label: "nature",
|
||||
|
@ -6246,6 +6252,6 @@ var rules = LabelRules{
|
|||
Label: "vegetables",
|
||||
Threshold: 0.330000,
|
||||
Priority: 0,
|
||||
Categories: []string{"dining"},
|
||||
Categories: []string{"dining", "food"},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -3119,61 +3119,74 @@ consomme:
|
|||
threshold: 0.2
|
||||
categories:
|
||||
- dining
|
||||
- food
|
||||
|
||||
hot pot:
|
||||
label: soup
|
||||
threshold: 0.2
|
||||
categories:
|
||||
- dining
|
||||
- food
|
||||
|
||||
trifle:
|
||||
label: dessert
|
||||
threshold: 0.2
|
||||
categories:
|
||||
- dining
|
||||
- food
|
||||
|
||||
ice cream:
|
||||
label: dessert
|
||||
threshold: 0.3
|
||||
categories:
|
||||
- food
|
||||
|
||||
ice lolly:
|
||||
label: dessert
|
||||
threshold: 0.4
|
||||
categories:
|
||||
- food
|
||||
|
||||
chocolate sauce:
|
||||
label: dessert
|
||||
threshold: 0.2
|
||||
categories:
|
||||
- food
|
||||
|
||||
french loaf:
|
||||
threshold: 0.4
|
||||
categories:
|
||||
- bakery
|
||||
- food
|
||||
|
||||
bagel:
|
||||
threshold: 0.4
|
||||
categories:
|
||||
- bakery
|
||||
- food
|
||||
|
||||
pretzel:
|
||||
threshold: 0.4
|
||||
categories:
|
||||
- bakery
|
||||
- food
|
||||
|
||||
fast food:
|
||||
label: fast food
|
||||
food:
|
||||
label: food
|
||||
threshold: 0.33
|
||||
|
||||
cheeseburger:
|
||||
see: fast food
|
||||
see: food
|
||||
|
||||
hotdog:
|
||||
see: fast food
|
||||
see: food
|
||||
|
||||
vegetables:
|
||||
label: vegetables
|
||||
threshold: 0.33
|
||||
categories:
|
||||
- dining
|
||||
- food
|
||||
|
||||
mashed potato:
|
||||
see: vegetables
|
||||
|
@ -3205,7 +3218,7 @@ squash:
|
|||
label: vegetables
|
||||
threshold: 0.4
|
||||
categories:
|
||||
- dining
|
||||
- food
|
||||
|
||||
spaghetti squash:
|
||||
see: squash
|
||||
|
@ -3218,59 +3231,56 @@ acorn squash:
|
|||
threshold: 0.4
|
||||
categories:
|
||||
- vegetables
|
||||
- food
|
||||
|
||||
fruits:
|
||||
label: fruits
|
||||
threshold: 0.3
|
||||
categories:
|
||||
- food
|
||||
|
||||
granny smith:
|
||||
label: apple
|
||||
threshold: 0.3
|
||||
categories:
|
||||
- fruit
|
||||
see: fruits
|
||||
|
||||
strawberry:
|
||||
threshold: 0.3
|
||||
categories:
|
||||
- fruit
|
||||
see: fruits
|
||||
|
||||
orange:
|
||||
threshold: 0.4
|
||||
categories:
|
||||
- fruit
|
||||
see: fruits
|
||||
|
||||
lemon:
|
||||
threshold: 0.4
|
||||
categories:
|
||||
- fruit
|
||||
see: fruits
|
||||
|
||||
fig:
|
||||
label: fruit
|
||||
threshold: 0.3
|
||||
see: fruits
|
||||
|
||||
pineapple:
|
||||
threshold: 0.3
|
||||
categories:
|
||||
- fruit
|
||||
- fruits
|
||||
- food
|
||||
|
||||
banana:
|
||||
threshold: 0.3
|
||||
categories:
|
||||
- fruit
|
||||
- fruits
|
||||
- food
|
||||
|
||||
jackfruit:
|
||||
label: fruit
|
||||
threshold: 0.5
|
||||
see: fruits
|
||||
|
||||
custard apple:
|
||||
label: fruit
|
||||
threshold: 0.2
|
||||
see: fruits
|
||||
|
||||
pomegranate:
|
||||
label: fruit
|
||||
threshold: 0.3
|
||||
see: fruits
|
||||
|
||||
carbonara:
|
||||
label: pasta
|
||||
threshold: 0.2
|
||||
categories:
|
||||
- dining
|
||||
- food
|
||||
|
||||
dough:
|
||||
label: cooking
|
||||
|
@ -3281,18 +3291,21 @@ meat loaf:
|
|||
threshold: 0.2
|
||||
categories:
|
||||
- cooking
|
||||
- food
|
||||
|
||||
pizza:
|
||||
threshold: 0.2
|
||||
categories:
|
||||
- fast food
|
||||
- food
|
||||
|
||||
potpie:
|
||||
label: cooking
|
||||
threshold: 0.2
|
||||
categories:
|
||||
- food
|
||||
|
||||
burrito:
|
||||
see: fast food
|
||||
see: food
|
||||
|
||||
red wine:
|
||||
label: wine
|
||||
|
|
|
@ -18,8 +18,8 @@ func (c *Config) PublicClientConfig() ClientConfig {
|
|||
return c.ClientConfig()
|
||||
}
|
||||
|
||||
jsHash := fs.Hash(c.HttpStaticBuildPath() + "/app.js")
|
||||
cssHash := fs.Hash(c.HttpStaticBuildPath() + "/app.css")
|
||||
jsHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.js")
|
||||
cssHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.css")
|
||||
|
||||
// Feature Flags
|
||||
var flags []string
|
||||
|
@ -196,8 +196,8 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
categories[i].Title = strings.Title(l.LabelName)
|
||||
}
|
||||
|
||||
jsHash := fs.Hash(c.HttpStaticBuildPath() + "/app.js")
|
||||
cssHash := fs.Hash(c.HttpStaticBuildPath() + "/app.css")
|
||||
jsHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.js")
|
||||
cssHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.css")
|
||||
|
||||
// Feature Flags
|
||||
var flags []string
|
||||
|
|
|
@ -12,37 +12,39 @@ import (
|
|||
|
||||
// An image or sidecar file that belongs to a photo
|
||||
type File struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Photo *Photo
|
||||
PhotoID uint `gorm:"index;"`
|
||||
PhotoUUID string `gorm:"type:varbinary(36);index;"`
|
||||
FileUUID string `gorm:"type:varbinary(36);unique_index;"`
|
||||
FileName string `gorm:"type:varbinary(600);unique_index"`
|
||||
FileHash string `gorm:"type:varbinary(128);unique_index"`
|
||||
FileOriginalName string
|
||||
FileType string `gorm:"type:varbinary(32)"`
|
||||
FileMime string `gorm:"type:varbinary(64)"`
|
||||
FilePrimary bool
|
||||
FileSidecar bool
|
||||
FileVideo bool
|
||||
FileMissing bool
|
||||
FileDuplicate bool
|
||||
FilePortrait bool
|
||||
FileWidth int
|
||||
FileHeight int
|
||||
FileOrientation int
|
||||
FileAspectRatio float64
|
||||
FileMainColor string `gorm:"type:varbinary(16);index;"`
|
||||
FileColors string `gorm:"type:binary(9);"`
|
||||
FileLuminance string `gorm:"type:binary(9);"`
|
||||
FileChroma uint
|
||||
FileNotes string `gorm:"type:text"`
|
||||
FileError string `gorm:"type:varbinary(512)"`
|
||||
CreatedAt time.Time
|
||||
CreatedIn int64
|
||||
UpdatedAt time.Time
|
||||
UpdatedIn int64
|
||||
DeletedAt *time.Time `sql:"index"`
|
||||
ID uint `gorm:"primary_key"`
|
||||
Photo *Photo
|
||||
PhotoID uint `gorm:"index;"`
|
||||
PhotoUUID string `gorm:"type:varbinary(36);index;"`
|
||||
FileUUID string `gorm:"type:varbinary(36);unique_index;"`
|
||||
FileName string `gorm:"type:varbinary(600);unique_index"`
|
||||
OriginalName string `gorm:"type:varbinary(600);"`
|
||||
FileHash string `gorm:"type:varbinary(128);unique_index"`
|
||||
FileModified time.Time
|
||||
FileSize int64
|
||||
FileType string `gorm:"type:varbinary(32)"`
|
||||
FileMime string `gorm:"type:varbinary(64)"`
|
||||
FilePrimary bool
|
||||
FileSidecar bool
|
||||
FileVideo bool
|
||||
FileMissing bool
|
||||
FileDuplicate bool
|
||||
FilePortrait bool
|
||||
FileWidth int
|
||||
FileHeight int
|
||||
FileOrientation int
|
||||
FileAspectRatio float64
|
||||
FileMainColor string `gorm:"type:varbinary(16);index;"`
|
||||
FileColors string `gorm:"type:binary(9);"`
|
||||
FileLuminance string `gorm:"type:binary(9);"`
|
||||
FileChroma uint
|
||||
FileNotes string `gorm:"type:text"`
|
||||
FileError string `gorm:"type:varbinary(512)"`
|
||||
CreatedAt time.Time
|
||||
CreatedIn int64
|
||||
UpdatedAt time.Time
|
||||
UpdatedIn int64
|
||||
DeletedAt *time.Time `sql:"index"`
|
||||
}
|
||||
|
||||
func FindFileByHash(db *gorm.DB, fileHash string) (File, error) {
|
||||
|
@ -76,3 +78,15 @@ func (m *File) DownloadFileName() string {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
func (m File) Changed(fileSize int64, fileModified time.Time) bool {
|
||||
if m.FileSize != fileSize {
|
||||
return true
|
||||
}
|
||||
|
||||
if m.FileModified != fileModified {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
// NonCanonical returns true if the file basename is not canonical.
|
||||
func NonCanonical(basename string) bool {
|
||||
if len(basename) != 28 {
|
||||
if len(basename) != 24 {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -22,15 +22,13 @@ func NonCanonical(basename string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// CanonicalName returns a canonical name based on time and hash.
|
||||
func CanonicalName(date time.Time, hash string) string {
|
||||
var postfix string
|
||||
|
||||
if len(hash) > 12 {
|
||||
postfix = strings.ToUpper(hash[:12])
|
||||
// CanonicalName returns a canonical name based on time and CRC32 checksum.
|
||||
func CanonicalName(date time.Time, checksum string) string {
|
||||
if len(checksum) != 8 {
|
||||
checksum = "ERROR000"
|
||||
} else {
|
||||
postfix = "NOTFOUND"
|
||||
checksum = strings.ToUpper(checksum)
|
||||
}
|
||||
|
||||
return date.Format("20060102_150405_") + postfix
|
||||
return date.Format("20060102_150405_") + checksum
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ func (c *Convert) Start(path string) error {
|
|||
}()
|
||||
}
|
||||
|
||||
err := filepath.Walk(path, func(filename string, fileInfo os.FileInfo, err error) error {
|
||||
err := filepath.Walk(path, func(fileName string, fileInfo os.FileInfo, err error) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Errorf("convert: %s [panic]", err)
|
||||
|
@ -67,7 +67,7 @@ func (c *Convert) Start(path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
mf, err := NewMediaFile(filename)
|
||||
mf, err := NewMediaFile(fileName)
|
||||
|
||||
if err != nil || !(mf.IsRaw() || mf.IsHEIF() || mf.IsImageOther()) {
|
||||
return nil
|
||||
|
@ -88,21 +88,21 @@ func (c *Convert) Start(path string) error {
|
|||
}
|
||||
|
||||
// ConvertCommand returns the command for converting files to JPEG, depending on the format.
|
||||
func (c *Convert) ConvertCommand(image *MediaFile, jpegFilename string, xmpFilename string) (result *exec.Cmd, err error) {
|
||||
func (c *Convert) ConvertCommand(image *MediaFile, jpegName string, xmpName string) (result *exec.Cmd, err error) {
|
||||
if image.IsRaw() {
|
||||
if c.conf.SipsBin() != "" {
|
||||
result = exec.Command(c.conf.SipsBin(), "-s format jpeg", image.filename, "--out "+jpegFilename)
|
||||
result = exec.Command(c.conf.SipsBin(), "-s format jpeg", image.fileName, "--out "+jpegName)
|
||||
} else if c.conf.DarktableBin() != "" {
|
||||
if xmpFilename != "" {
|
||||
result = exec.Command(c.conf.DarktableBin(), image.filename, xmpFilename, jpegFilename)
|
||||
if xmpName != "" {
|
||||
result = exec.Command(c.conf.DarktableBin(), image.fileName, xmpName, jpegName)
|
||||
} else {
|
||||
result = exec.Command(c.conf.DarktableBin(), image.filename, jpegFilename)
|
||||
result = exec.Command(c.conf.DarktableBin(), image.fileName, jpegName)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("convert: no binary for raw to jpeg could be found (%s)", image.Filename())
|
||||
return nil, fmt.Errorf("convert: no binary for raw to jpeg could be found (%s)", image.FileName())
|
||||
}
|
||||
} else if image.IsHEIF() {
|
||||
result = exec.Command(c.conf.HeifConvertBin(), image.filename, jpegFilename)
|
||||
result = exec.Command(c.conf.HeifConvertBin(), image.fileName, jpegName)
|
||||
} else {
|
||||
return nil, fmt.Errorf("convert: image type not supported for conversion (%s)", image.Type())
|
||||
}
|
||||
|
@ -113,55 +113,55 @@ func (c *Convert) ConvertCommand(image *MediaFile, jpegFilename string, xmpFilen
|
|||
// ToJpeg converts a single image file to JPEG if possible.
|
||||
func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) {
|
||||
if !image.Exists() {
|
||||
return nil, fmt.Errorf("convert: can not convert to jpeg, file does not exist (%s)", image.Filename())
|
||||
return nil, fmt.Errorf("convert: can not convert to jpeg, file does not exist (%s)", image.FileName())
|
||||
}
|
||||
|
||||
if image.IsJpeg() {
|
||||
return image, nil
|
||||
}
|
||||
|
||||
baseFilename := image.DirectoryBasename()
|
||||
base := image.AbsBase()
|
||||
|
||||
jpegFilename := baseFilename + ".jpg"
|
||||
jpegName := base + ".jpg"
|
||||
|
||||
mediaFile, err := NewMediaFile(jpegFilename)
|
||||
mediaFile, err := NewMediaFile(jpegName)
|
||||
|
||||
if err == nil {
|
||||
return mediaFile, nil
|
||||
}
|
||||
|
||||
if c.conf.ReadOnly() {
|
||||
return nil, fmt.Errorf("convert: disabled in read only mode (%s)", image.Filename())
|
||||
return nil, fmt.Errorf("convert: disabled in read only mode (%s)", image.FileName())
|
||||
}
|
||||
|
||||
log.Infof("convert: \"%s\" -> \"%s\"", image.filename, jpegFilename)
|
||||
fileName := image.RelativeName(c.conf.OriginalsPath())
|
||||
|
||||
fileName := image.RelativeFilename(c.conf.OriginalsPath())
|
||||
log.Infof("convert: %s -> %s", fileName, jpegName)
|
||||
|
||||
xmpFilename := baseFilename + ".xmp"
|
||||
xmpName := base + ".xmp"
|
||||
|
||||
if _, err := os.Stat(xmpFilename); err != nil {
|
||||
xmpFilename = ""
|
||||
if _, err := os.Stat(xmpName); err != nil {
|
||||
xmpName = ""
|
||||
}
|
||||
|
||||
event.Publish("index.converting", event.Data{
|
||||
"fileType": image.Type(),
|
||||
"fileName": fileName,
|
||||
"baseName": filepath.Base(fileName),
|
||||
"xmpName": filepath.Base(xmpFilename),
|
||||
"xmpName": filepath.Base(xmpName),
|
||||
})
|
||||
|
||||
if image.IsImageOther() {
|
||||
_, err = thumb.Jpeg(image.Filename(), jpegFilename)
|
||||
_, err = thumb.Jpeg(image.FileName(), jpegName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewMediaFile(jpegFilename)
|
||||
return NewMediaFile(jpegName)
|
||||
}
|
||||
|
||||
cmd, err := c.ConvertCommand(image, jpegFilename, xmpFilename)
|
||||
cmd, err := c.ConvertCommand(image, jpegName, xmpName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -187,5 +187,5 @@ func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) {
|
|||
return nil, errors.New(stderr.String())
|
||||
}
|
||||
|
||||
return NewMediaFile(jpegFilename)
|
||||
return NewMediaFile(jpegName)
|
||||
}
|
||||
|
|
|
@ -44,13 +44,13 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
|
||||
infoJpeg, err := imageJpeg.MetaData()
|
||||
|
||||
assert.Nilf(t, err, "UpdateExif() failed for "+imageJpeg.Filename())
|
||||
assert.Nilf(t, err, "UpdateExif() failed for "+imageJpeg.FileName())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("%s for %s", err.Error(), imageJpeg.Filename())
|
||||
t.Fatalf("%s for %s", err.Error(), imageJpeg.FileName())
|
||||
}
|
||||
|
||||
assert.Equal(t, jpegFilename, imageJpeg.filename)
|
||||
assert.Equal(t, jpegFilename, imageJpeg.fileName)
|
||||
|
||||
assert.Equal(t, "Canon EOS 7D", infoJpeg.CameraModel)
|
||||
|
||||
|
@ -76,7 +76,7 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
t.Fatal("imageRaw is nil")
|
||||
}
|
||||
|
||||
assert.NotEqual(t, rawFilename, imageRaw.filename)
|
||||
assert.NotEqual(t, rawFilename, imageRaw.fileName)
|
||||
|
||||
infoRaw, err := imageRaw.MetaData()
|
||||
|
||||
|
@ -106,7 +106,7 @@ func TestConvert_Start(t *testing.T) {
|
|||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, jpegFilename, image.filename, "FileName must be the same")
|
||||
assert.Equal(t, jpegFilename, image.fileName, "FileName must be the same")
|
||||
|
||||
infoRaw, err := image.MetaData()
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ type ConvertJob struct {
|
|||
func convertWorker(jobs <-chan ConvertJob) {
|
||||
for job := range jobs {
|
||||
if _, err := job.convert.ToJpeg(job.image); err != nil {
|
||||
log.Warnf("convert: %s (%s)", err.Error(), job.image.Filename())
|
||||
log.Warnf("convert: %s (%s)", err.Error(), job.image.FileName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ func (imp *Import) Start(opt ImportOptions) {
|
|||
|
||||
indexOpt := IndexOptionsAll()
|
||||
|
||||
err := filepath.Walk(importPath, func(filename string, fileInfo os.FileInfo, err error) error {
|
||||
err := filepath.Walk(importPath, func(fileName string, fileInfo os.FileInfo, err error) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Errorf("import: %s [panic]", err)
|
||||
|
@ -89,33 +89,33 @@ func (imp *Import) Start(opt ImportOptions) {
|
|||
return errors.New("import canceled")
|
||||
}
|
||||
|
||||
if err != nil || done[filename] {
|
||||
if err != nil || done[fileName] {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
if filename != importPath {
|
||||
directories = append(directories, filename)
|
||||
if fileName != importPath {
|
||||
directories = append(directories, fileName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(filepath.Base(filename), ".") {
|
||||
done[filename] = true
|
||||
if strings.HasPrefix(filepath.Base(fileName), ".") {
|
||||
done[fileName] = true
|
||||
|
||||
if !opt.RemoveDotFiles {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.Remove(filename); err != nil {
|
||||
log.Errorf("import: could not remove \"%s\" (%s)", filename, err.Error())
|
||||
if err := os.Remove(fileName); err != nil {
|
||||
log.Errorf("import: could not remove \"%s\" (%s)", fileName, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
mf, err := NewMediaFile(filename)
|
||||
mf, err := NewMediaFile(fileName)
|
||||
|
||||
if err != nil || !mf.IsPhoto() {
|
||||
return nil
|
||||
|
@ -132,20 +132,20 @@ func (imp *Import) Start(opt ImportOptions) {
|
|||
var files MediaFiles
|
||||
|
||||
for _, f := range related.files {
|
||||
if done[f.Filename()] {
|
||||
if done[f.FileName()] {
|
||||
continue
|
||||
}
|
||||
|
||||
files = append(files, f)
|
||||
done[f.Filename()] = true
|
||||
done[f.FileName()] = true
|
||||
}
|
||||
|
||||
done[mf.Filename()] = true
|
||||
done[mf.FileName()] = true
|
||||
|
||||
related.files = files
|
||||
|
||||
jobs <- ImportJob{
|
||||
filename: filename,
|
||||
fileName: fileName,
|
||||
related: related,
|
||||
indexOpt: indexOpt,
|
||||
importOpt: opt,
|
||||
|
@ -167,9 +167,9 @@ func (imp *Import) Start(opt ImportOptions) {
|
|||
for _, directory := range directories {
|
||||
if fs.IsEmpty(directory) {
|
||||
if err := os.Remove(directory); err != nil {
|
||||
log.Errorf("import: could not deleted empty directory \"%s\" (%s)", directory, err)
|
||||
log.Errorf("import: could not deleted empty directory %s (%s)", directory, err)
|
||||
} else {
|
||||
log.Infof("import: deleted empty directory \"%s\"", directory)
|
||||
log.Infof("import: deleted empty directory %s", directory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,13 +191,15 @@ func (imp *Import) DestinationFilename(mainFile *MediaFile, mediaFile *MediaFile
|
|||
fileExtension := mediaFile.Extension()
|
||||
dateCreated := mainFile.DateCreated()
|
||||
|
||||
if f, err := entity.FindFileByHash(imp.conf.Db(), mediaFile.Hash()); err == nil {
|
||||
existingFilename := imp.conf.OriginalsPath() + string(os.PathSeparator) + f.FileName
|
||||
return existingFilename, fmt.Errorf("\"%s\" is identical to \"%s\" (%s)", mediaFile.Filename(), f.FileName, mediaFile.Hash())
|
||||
if !mediaFile.IsSidecar() {
|
||||
if f, err := entity.FindFileByHash(imp.conf.Db(), mediaFile.Hash()); err == nil {
|
||||
existingFilename := imp.conf.OriginalsPath() + string(os.PathSeparator) + f.FileName
|
||||
return existingFilename, fmt.Errorf("\"%s\" is identical to \"%s\" (%s)", mediaFile.FileName(), f.FileName, mediaFile.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
||||
pathName := imp.originalsPath() + string(os.PathSeparator) + dateCreated.UTC().Format("2006/01")
|
||||
pathName := imp.originalsPath() + string(os.PathSeparator) + dateCreated.Format("2006/01")
|
||||
|
||||
iteration := 0
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ func TestImport_DestinationFilename(t *testing.T) {
|
|||
|
||||
// TODO: Check for errors!
|
||||
|
||||
assert.Equal(t, conf.OriginalsPath()+"/2019/07/20190705_153230_6E16EB388AD2.cr2", filename)
|
||||
assert.Equal(t, conf.OriginalsPath()+"/2019/07/20190705_153230_C167C6FD.cr2", filename)
|
||||
}
|
||||
|
||||
func TestImport_Start(t *testing.T) {
|
||||
|
|
|
@ -9,11 +9,10 @@ import (
|
|||
)
|
||||
|
||||
type ImportJob struct {
|
||||
filename string
|
||||
fileName string
|
||||
related RelatedFiles
|
||||
indexOpt IndexOptions
|
||||
importOpt ImportOptions
|
||||
path string
|
||||
imp *Import
|
||||
}
|
||||
|
||||
|
@ -26,20 +25,27 @@ func importWorker(jobs <-chan ImportJob) {
|
|||
indexOpt := job.indexOpt
|
||||
importPath := job.importOpt.Path
|
||||
|
||||
if related.main == nil {
|
||||
log.Warnf("import: no main file found for %s", job.fileName)
|
||||
continue
|
||||
}
|
||||
|
||||
originalName := related.main.RelativeName(importPath)
|
||||
|
||||
event.Publish("import.file", event.Data{
|
||||
"fileName": related.main.Filename(),
|
||||
"baseName": filepath.Base(related.main.Filename()),
|
||||
"fileName": originalName,
|
||||
"baseName": filepath.Base(related.main.FileName()),
|
||||
})
|
||||
|
||||
for _, f := range related.files {
|
||||
relativeFilename := f.RelativeFilename(importPath)
|
||||
relativeFilename := f.RelativeName(importPath)
|
||||
|
||||
if destinationFilename, err := imp.DestinationFilename(related.main, f); err == nil {
|
||||
if err := os.MkdirAll(path.Dir(destinationFilename), os.ModePerm); err != nil {
|
||||
log.Errorf("import: could not create directories (%s)", err.Error())
|
||||
}
|
||||
|
||||
if related.main.HasSameFilename(f) {
|
||||
if related.main.HasSameName(f) {
|
||||
destinationMainFilename = destinationFilename
|
||||
log.Infof("import: moving main %s file \"%s\" to \"%s\"", f.Type(), relativeFilename, destinationFilename)
|
||||
} else {
|
||||
|
@ -48,18 +54,18 @@ func importWorker(jobs <-chan ImportJob) {
|
|||
|
||||
if opt.Move {
|
||||
if err := f.Move(destinationFilename); err != nil {
|
||||
log.Errorf("import: could not move file to \"%s\" (%s)", destinationMainFilename, err.Error())
|
||||
log.Errorf("import: could not move file to %s (%s)", destinationMainFilename, err.Error())
|
||||
}
|
||||
} else {
|
||||
if err := f.Copy(destinationFilename); err != nil {
|
||||
log.Errorf("import: could not copy file to \"%s\" (%s)", destinationMainFilename, err.Error())
|
||||
log.Errorf("import: could not copy file to %s (%s)", destinationMainFilename, err.Error())
|
||||
}
|
||||
}
|
||||
} else if opt.RemoveExistingFiles {
|
||||
if err := f.Remove(); err != nil {
|
||||
log.Errorf("import: could not delete file \"%s\" (%s)", f.Filename(), err.Error())
|
||||
log.Errorf("import: could not delete %s (%s)", f.FileName(), err.Error())
|
||||
} else {
|
||||
log.Infof("import: deleted \"%s\" (already exists)", relativeFilename)
|
||||
log.Infof("import: deleted %s (already exists)", relativeFilename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,9 +105,9 @@ func importWorker(jobs <-chan ImportJob) {
|
|||
ind := imp.index
|
||||
|
||||
if related.main != nil {
|
||||
res := ind.MediaFile(related.main, indexOpt)
|
||||
log.Infof("import: %s main %s file \"%s\"", res, related.main.Type(), related.main.RelativeFilename(ind.originalsPath()))
|
||||
done[related.main.Filename()] = true
|
||||
res := ind.MediaFile(related.main, indexOpt, originalName)
|
||||
log.Infof("import: %s main %s file \"%s\"", res, related.main.Type(), related.main.RelativeName(ind.originalsPath()))
|
||||
done[related.main.FileName()] = true
|
||||
} else {
|
||||
log.Warnf("import: no main file for %s (conversion to jpeg failed?)", destinationMainFilename)
|
||||
}
|
||||
|
@ -111,14 +117,14 @@ func importWorker(jobs <-chan ImportJob) {
|
|||
continue
|
||||
}
|
||||
|
||||
if done[f.Filename()] {
|
||||
if done[f.FileName()] {
|
||||
continue
|
||||
}
|
||||
|
||||
res := ind.MediaFile(f, indexOpt)
|
||||
done[f.Filename()] = true
|
||||
res := ind.MediaFile(f, indexOpt, "")
|
||||
done[f.FileName()] = true
|
||||
|
||||
log.Infof("import: %s related %s file \"%s\"", res, f.Type(), f.RelativeFilename(ind.originalsPath()))
|
||||
log.Infof("import: %s related %s file \"%s\"", res, f.Type(), f.RelativeName(ind.originalsPath()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ func (ind *Index) Start(options IndexOptions) map[string]bool {
|
|||
}()
|
||||
}
|
||||
|
||||
err := filepath.Walk(originalsPath, func(filename string, fileInfo os.FileInfo, err error) error {
|
||||
err := filepath.Walk(originalsPath, func(fileName string, fileInfo os.FileInfo, err error) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Errorf("index: %s [panic]", err)
|
||||
|
@ -97,15 +97,15 @@ func (ind *Index) Start(options IndexOptions) map[string]bool {
|
|||
return errors.New("indexing canceled")
|
||||
}
|
||||
|
||||
if err != nil || done[filename] {
|
||||
if err != nil || done[fileName] {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() || strings.HasPrefix(filepath.Base(filename), ".") {
|
||||
if fileInfo.IsDir() || strings.HasPrefix(filepath.Base(fileName), ".") {
|
||||
return nil
|
||||
}
|
||||
|
||||
mf, err := NewMediaFile(filename)
|
||||
mf, err := NewMediaFile(fileName)
|
||||
|
||||
if err != nil || !mf.IsPhoto() {
|
||||
return nil
|
||||
|
@ -122,20 +122,20 @@ func (ind *Index) Start(options IndexOptions) map[string]bool {
|
|||
var files MediaFiles
|
||||
|
||||
for _, f := range related.files {
|
||||
if done[f.Filename()] {
|
||||
if done[f.FileName()] {
|
||||
continue
|
||||
}
|
||||
|
||||
files = append(files, f)
|
||||
done[f.Filename()] = true
|
||||
done[f.FileName()] = true
|
||||
}
|
||||
|
||||
done[mf.Filename()] = true
|
||||
done[mf.FileName()] = true
|
||||
|
||||
related.files = files
|
||||
|
||||
jobs <- IndexJob{
|
||||
filename: mf.Filename(),
|
||||
filename: mf.FileName(),
|
||||
related: related,
|
||||
opt: options,
|
||||
ind: ind,
|
||||
|
|
|
@ -24,7 +24,7 @@ const (
|
|||
|
||||
type IndexResult string
|
||||
|
||||
func (ind *Index) MediaFile(m *MediaFile, o IndexOptions) IndexResult {
|
||||
func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) IndexResult {
|
||||
if m == nil {
|
||||
log.Error("index: media file is nil - you might have found a bug")
|
||||
return indexResultFailed
|
||||
|
@ -39,23 +39,31 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions) IndexResult {
|
|||
var keywords []string
|
||||
|
||||
labels := classify.Labels{}
|
||||
fileBase := m.Basename()
|
||||
fileBase := m.Base()
|
||||
filePath := m.RelativePath(ind.originalsPath())
|
||||
fileName := m.RelativeFilename(ind.originalsPath())
|
||||
fileHash := m.Hash()
|
||||
fileName := m.RelativeName(ind.originalsPath())
|
||||
fileHash := ""
|
||||
fileSize, fileModified := m.Stat()
|
||||
fileChanged := true
|
||||
fileExists := false
|
||||
photoExists := false
|
||||
|
||||
event.Publish("index.indexing", event.Data{
|
||||
"fileHash": fileHash,
|
||||
"fileSize": fileSize,
|
||||
"fileName": fileName,
|
||||
"baseName": filepath.Base(fileName),
|
||||
})
|
||||
|
||||
fileQuery = ind.db.Unscoped().First(&file, "file_hash = ? OR file_name = ?", fileHash, fileName)
|
||||
fileQuery = ind.db.Unscoped().First(&file, "file_name = ?", fileName)
|
||||
fileExists = fileQuery.Error == nil
|
||||
|
||||
if !fileExists && !m.IsSidecar() {
|
||||
fileHash = m.Hash()
|
||||
fileQuery = ind.db.Unscoped().First(&file, "file_hash = ?", fileHash)
|
||||
fileExists = fileQuery.Error == nil
|
||||
}
|
||||
|
||||
if !fileExists {
|
||||
photoQuery = ind.db.Unscoped().First(&photo, "photo_path = ? AND photo_name = ?", filePath, fileBase)
|
||||
|
||||
|
@ -65,7 +73,8 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions) IndexResult {
|
|||
}
|
||||
} else {
|
||||
photoQuery = ind.db.Unscoped().First(&photo, "id = ?", file.PhotoID)
|
||||
fileChanged = file.FileHash != fileHash
|
||||
|
||||
fileChanged = file.Changed(fileSize, fileModified)
|
||||
}
|
||||
|
||||
photoExists = photoQuery.Error == nil
|
||||
|
@ -74,6 +83,15 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions) IndexResult {
|
|||
return indexResultSkipped
|
||||
}
|
||||
|
||||
if fileHash == "" {
|
||||
fileHash = m.Hash()
|
||||
}
|
||||
|
||||
if !photoExists {
|
||||
photo.PhotoPath = filePath
|
||||
photo.PhotoName = fileBase
|
||||
}
|
||||
|
||||
if !file.FilePrimary {
|
||||
if photoExists {
|
||||
if q := ind.db.Where("file_type = 'jpg' AND file_primary = 1 AND photo_id = ?", photo.ID).First(&primaryFile); q.Error != nil {
|
||||
|
@ -161,7 +179,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions) IndexResult {
|
|||
}
|
||||
} else if m.IsXMP() {
|
||||
// TODO: Proof-of-concept for indexing XMP sidecar files
|
||||
if data, err := meta.XMP(m.Filename()); err == nil {
|
||||
if data, err := meta.XMP(m.FileName()); err == nil {
|
||||
if data.Title != "" && !photo.ModifiedTitle {
|
||||
photo.PhotoTitle = data.Title
|
||||
}
|
||||
|
@ -211,6 +229,10 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions) IndexResult {
|
|||
ind.addLabels(photo.ID, labels)
|
||||
}
|
||||
|
||||
if originalName != "" {
|
||||
file.OriginalName = originalName
|
||||
}
|
||||
|
||||
file.PhotoID = photo.ID
|
||||
file.PhotoUUID = photo.PhotoUUID
|
||||
file.FileSidecar = m.IsSidecar()
|
||||
|
@ -218,6 +240,8 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions) IndexResult {
|
|||
file.FileMissing = false
|
||||
file.FileName = fileName
|
||||
file.FileHash = fileHash
|
||||
file.FileSize = fileSize
|
||||
file.FileModified = fileModified
|
||||
file.FileType = string(m.Type())
|
||||
file.FileMime = m.MimeType()
|
||||
file.FileOrientation = m.Orientation()
|
||||
|
@ -248,6 +272,11 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions) IndexResult {
|
|||
keywords = append(keywords, txt.Keywords(fileBase)...)
|
||||
}
|
||||
|
||||
if file.OriginalName != "" {
|
||||
log.Debugf("index: extracting keywords from original file name (%s)", file.OriginalName)
|
||||
keywords = append(keywords, txt.Keywords(file.OriginalName)...)
|
||||
}
|
||||
|
||||
keywords = append(keywords, file.FileMainColor)
|
||||
keywords = append(keywords, labels.Keywords()...)
|
||||
photo.IndexKeywords(keywords, ind.db)
|
||||
|
@ -292,7 +321,7 @@ func (ind *Index) isNSFW(jpeg *MediaFile) bool {
|
|||
return false
|
||||
} else {
|
||||
if nsfwLabels.NSFW() {
|
||||
log.Warnf("index: \"%s\" might contain offensive content", jpeg.Filename())
|
||||
log.Warnf("index: \"%s\" might contain offensive content", jpeg.FileName())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -459,7 +488,7 @@ func (ind *Index) indexLocation(mediaFile *MediaFile, photo *entity.Photo, label
|
|||
}
|
||||
|
||||
if photo.NoTitle() {
|
||||
log.Warnf("index: could not set photo title based on location or labels for \"%s\"", filepath.Base(mediaFile.Filename()))
|
||||
log.Warnf("index: could not set photo title based on location or labels for \"%s\"", filepath.Base(mediaFile.FileName()))
|
||||
} else {
|
||||
log.Infof("index: new photo title is \"%s\"", photo.PhotoTitle)
|
||||
}
|
||||
|
|
|
@ -15,23 +15,23 @@ func indexWorker(jobs <-chan IndexJob) {
|
|||
ind := job.ind
|
||||
|
||||
if related.main != nil {
|
||||
res := ind.MediaFile(related.main, opt)
|
||||
done[related.main.Filename()] = true
|
||||
res := ind.MediaFile(related.main, opt, "")
|
||||
done[related.main.FileName()] = true
|
||||
|
||||
log.Infof("index: %s main %s file \"%s\"", res, related.main.Type(), related.main.RelativeFilename(ind.originalsPath()))
|
||||
log.Infof("index: %s main %s file \"%s\"", res, related.main.Type(), related.main.RelativeName(ind.originalsPath()))
|
||||
} else {
|
||||
log.Warnf("index: no main file for %s (conversion to jpeg failed?)", job.filename)
|
||||
}
|
||||
|
||||
for _, f := range related.files {
|
||||
if done[f.Filename()] {
|
||||
if done[f.FileName()] {
|
||||
continue
|
||||
}
|
||||
|
||||
res := ind.MediaFile(f, opt)
|
||||
done[f.Filename()] = true
|
||||
res := ind.MediaFile(f, opt, "")
|
||||
done[f.FileName()] = true
|
||||
|
||||
log.Infof("index: %s related %s file \"%s\"", res, f.Type(), f.RelativeFilename(ind.originalsPath()))
|
||||
log.Infof("index: %s related %s file \"%s\"", res, f.Type(), f.RelativeName(ind.originalsPath()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,53 +23,64 @@ import (
|
|||
|
||||
// MediaFile represents a single photo, video or sidecar file.
|
||||
type MediaFile struct {
|
||||
filename string
|
||||
dateCreated time.Time
|
||||
timeZone string
|
||||
hash string
|
||||
fileType fs.Type
|
||||
mimeType string
|
||||
perceptualHash string
|
||||
width int
|
||||
height int
|
||||
once sync.Once
|
||||
metaData meta.Data
|
||||
location *entity.Location
|
||||
fileName string
|
||||
fileType fs.Type
|
||||
mimeType string
|
||||
dateCreated time.Time
|
||||
hash string
|
||||
checksum string
|
||||
width int
|
||||
height int
|
||||
once sync.Once
|
||||
metaData meta.Data
|
||||
location *entity.Location
|
||||
}
|
||||
|
||||
// NewMediaFile returns a new MediaFile.
|
||||
func NewMediaFile(filename string) (*MediaFile, error) {
|
||||
if !fs.FileExists(filename) {
|
||||
return nil, fmt.Errorf("file does not exist: %s", filename)
|
||||
// NewMediaFile returns a new media file.
|
||||
func NewMediaFile(fileName string) (*MediaFile, error) {
|
||||
if !fs.FileExists(fileName) {
|
||||
return nil, fmt.Errorf("file does not exist: %s", fileName)
|
||||
}
|
||||
|
||||
instance := &MediaFile{
|
||||
filename: filename,
|
||||
fileName: fileName,
|
||||
fileType: fs.TypeOther,
|
||||
}
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// DateCreated returns the date on which the media file was created.
|
||||
// Stat returns the media file size and modification time.
|
||||
func (m MediaFile) Stat() (size int64, mod time.Time) {
|
||||
s, err := os.Stat(m.FileName())
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("mediafile: unknown size (%s)", err)
|
||||
return -1, time.Now()
|
||||
}
|
||||
|
||||
return s.Size(), s.ModTime()
|
||||
}
|
||||
|
||||
// DateCreated returns the date on which the media file was created in UTC.
|
||||
func (m *MediaFile) DateCreated() time.Time {
|
||||
if !m.dateCreated.IsZero() {
|
||||
return m.dateCreated
|
||||
}
|
||||
|
||||
m.dateCreated = time.Now()
|
||||
m.dateCreated = time.Now().UTC()
|
||||
|
||||
info, err := m.MetaData()
|
||||
|
||||
if err == nil && !info.TakenAt.IsZero() && info.TakenAt.Year() > 1000 {
|
||||
m.dateCreated = info.TakenAt
|
||||
m.dateCreated = info.TakenAt.UTC()
|
||||
|
||||
log.Infof("exif: taken at %s", m.dateCreated.String())
|
||||
|
||||
return m.dateCreated
|
||||
}
|
||||
|
||||
t, err := times.Stat(m.Filename())
|
||||
t, err := times.Stat(m.FileName())
|
||||
|
||||
if err != nil {
|
||||
log.Debug(err.Error())
|
||||
|
@ -78,9 +89,9 @@ func (m *MediaFile) DateCreated() time.Time {
|
|||
}
|
||||
|
||||
if t.HasBirthTime() {
|
||||
m.dateCreated = t.BirthTime()
|
||||
m.dateCreated = t.BirthTime().UTC()
|
||||
} else {
|
||||
m.dateCreated = t.ModTime()
|
||||
m.dateCreated = t.ModTime().UTC()
|
||||
}
|
||||
|
||||
log.Infof("mediafile: taken at %s", m.dateCreated.String())
|
||||
|
@ -206,12 +217,12 @@ func (m *MediaFile) Exposure() string {
|
|||
|
||||
// CanonicalName returns the canonical name of a media file.
|
||||
func (m *MediaFile) CanonicalName() string {
|
||||
return CanonicalName(m.DateCreated().UTC(), m.Hash())
|
||||
return CanonicalName(m.DateCreated(), m.Checksum())
|
||||
}
|
||||
|
||||
// CanonicalNameFromFile returns the canonical name of a file derived from the image name.
|
||||
func (m *MediaFile) CanonicalNameFromFile() string {
|
||||
basename := filepath.Base(m.Filename())
|
||||
basename := filepath.Base(m.FileName())
|
||||
|
||||
if end := strings.Index(basename, "."); end != -1 {
|
||||
return basename[:end] // Length of canonical name: 16 + 12
|
||||
|
@ -226,21 +237,30 @@ func (m *MediaFile) CanonicalNameFromFileWithDirectory() string {
|
|||
return m.Directory() + string(os.PathSeparator) + m.CanonicalNameFromFile()
|
||||
}
|
||||
|
||||
// Hash return a sha1 hash of a MediaFile based on the filename.
|
||||
// Hash returns the SHA1 hash of a media file.
|
||||
func (m *MediaFile) Hash() string {
|
||||
if len(m.hash) == 0 {
|
||||
m.hash = fs.Hash(m.Filename())
|
||||
m.hash = fs.Hash(m.FileName())
|
||||
}
|
||||
|
||||
return m.hash
|
||||
}
|
||||
|
||||
// EditedFilename When editing photos, iPhones create additional files like IMG_E12345.JPG
|
||||
func (m *MediaFile) EditedFilename() string {
|
||||
basename := filepath.Base(m.filename)
|
||||
// Checksum returns the CRC32 checksum of a media file.
|
||||
func (m *MediaFile) Checksum() string {
|
||||
if len(m.checksum) == 0 {
|
||||
m.checksum = fs.Checksum(m.FileName())
|
||||
}
|
||||
|
||||
return m.checksum
|
||||
}
|
||||
|
||||
// EditedName When editing photos, iPhones create additional files like IMG_E12345.JPG
|
||||
func (m *MediaFile) EditedName() string {
|
||||
basename := filepath.Base(m.fileName)
|
||||
|
||||
if strings.ToUpper(basename[:4]) == "IMG_" && strings.ToUpper(basename[:5]) != "IMG_E" {
|
||||
if filename := filepath.Dir(m.filename) + string(os.PathSeparator) + basename[:4] + "E" + basename[4:]; fs.FileExists(filename) {
|
||||
if filename := filepath.Dir(m.fileName) + string(os.PathSeparator) + basename[:4] + "E" + basename[4:]; fs.FileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +270,7 @@ func (m *MediaFile) EditedFilename() string {
|
|||
|
||||
// RelatedFiles returns files which are related to this file.
|
||||
func (m *MediaFile) RelatedFiles() (result RelatedFiles, err error) {
|
||||
baseFilename := m.DirectoryBasename()
|
||||
baseFilename := m.AbsBase()
|
||||
// escape any meta characters in the file name
|
||||
baseFilename = regexp.QuoteMeta(baseFilename)
|
||||
matches, err := filepath.Glob(baseFilename + "*")
|
||||
|
@ -259,7 +279,7 @@ func (m *MediaFile) RelatedFiles() (result RelatedFiles, err error) {
|
|||
return result, err
|
||||
}
|
||||
|
||||
if filename := m.EditedFilename(); filename != "" {
|
||||
if filename := m.EditedName(); filename != "" {
|
||||
matches = append(matches, filename)
|
||||
}
|
||||
|
||||
|
@ -276,7 +296,7 @@ func (m *MediaFile) RelatedFiles() (result RelatedFiles, err error) {
|
|||
result.main = resultFile
|
||||
} else if resultFile.IsHEIF() {
|
||||
result.main = resultFile
|
||||
} else if resultFile.IsJpeg() && len(result.main.Filename()) > len(resultFile.Filename()) {
|
||||
} else if resultFile.IsJpeg() && len(result.main.FileName()) > len(resultFile.FileName()) {
|
||||
result.main = resultFile
|
||||
} else if resultFile.IsImageOther() {
|
||||
result.main = resultFile
|
||||
|
@ -290,34 +310,34 @@ func (m *MediaFile) RelatedFiles() (result RelatedFiles, err error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// Filename returns the filename.
|
||||
func (m MediaFile) Filename() string {
|
||||
return m.filename
|
||||
// FileName returns the filename.
|
||||
func (m MediaFile) FileName() string {
|
||||
return m.fileName
|
||||
}
|
||||
|
||||
// SetFilename sets the filename to the given string.
|
||||
func (m *MediaFile) SetFilename(filename string) {
|
||||
m.filename = filename
|
||||
// SetFileName sets the filename to the given string.
|
||||
func (m *MediaFile) SetFileName(fileName string) {
|
||||
m.fileName = fileName
|
||||
}
|
||||
|
||||
// RelativeFilename returns the relative filename.
|
||||
func (m MediaFile) RelativeFilename(directory string) string {
|
||||
if index := strings.Index(m.filename, directory); index == 0 {
|
||||
// RelativeName returns the relative filename.
|
||||
func (m MediaFile) RelativeName(directory string) string {
|
||||
if index := strings.Index(m.fileName, directory); index == 0 {
|
||||
if index := strings.LastIndex(directory, string(os.PathSeparator)); index == len(directory)-1 {
|
||||
pos := len(directory)
|
||||
return m.filename[pos:]
|
||||
return m.fileName[pos:]
|
||||
} else if index := strings.LastIndex(directory, string(os.PathSeparator)); index != len(directory) {
|
||||
pos := len(directory) + 1
|
||||
return m.filename[pos:]
|
||||
return m.fileName[pos:]
|
||||
}
|
||||
}
|
||||
|
||||
return m.filename
|
||||
return m.fileName
|
||||
}
|
||||
|
||||
// RelativePath returns the relative path without filename.
|
||||
func (m MediaFile) RelativePath(directory string) string {
|
||||
pathname := m.filename
|
||||
pathname := m.fileName
|
||||
|
||||
if i := strings.Index(pathname, directory); i == 0 {
|
||||
if i := strings.LastIndex(directory, string(os.PathSeparator)); i == len(directory)-1 {
|
||||
|
@ -336,23 +356,23 @@ func (m MediaFile) RelativePath(directory string) string {
|
|||
return pathname
|
||||
}
|
||||
|
||||
// RelativeBasename returns the relative filename.
|
||||
func (m MediaFile) RelativeBasename(directory string) string {
|
||||
// RelativeBase returns the relative filename.
|
||||
func (m MediaFile) RelativeBase(directory string) string {
|
||||
if relativePath := m.RelativePath(directory); relativePath != "" {
|
||||
return relativePath + string(os.PathSeparator) + m.Basename()
|
||||
return relativePath + string(os.PathSeparator) + m.Base()
|
||||
}
|
||||
|
||||
return m.Basename()
|
||||
return m.Base()
|
||||
}
|
||||
|
||||
// Directory returns the directory
|
||||
func (m MediaFile) Directory() string {
|
||||
return filepath.Dir(m.filename)
|
||||
return filepath.Dir(m.fileName)
|
||||
}
|
||||
|
||||
// Basename returns the filename base without any extensions and path.
|
||||
func (m MediaFile) Basename() string {
|
||||
basename := filepath.Base(m.Filename())
|
||||
// Base returns the filename base without any extensions and path.
|
||||
func (m MediaFile) Base() string {
|
||||
basename := filepath.Base(m.FileName())
|
||||
|
||||
if end := strings.Index(basename, "."); end != -1 {
|
||||
// ignore everything behind the first dot in the file name
|
||||
|
@ -370,24 +390,24 @@ func (m MediaFile) Basename() string {
|
|||
return basename
|
||||
}
|
||||
|
||||
// DirectoryBasename returns the directory and base filename without any extensions.
|
||||
func (m MediaFile) DirectoryBasename() string {
|
||||
return m.Directory() + string(os.PathSeparator) + m.Basename()
|
||||
// AbsBase returns the directory and base filename without any extensions.
|
||||
func (m MediaFile) AbsBase() string {
|
||||
return m.Directory() + string(os.PathSeparator) + m.Base()
|
||||
}
|
||||
|
||||
// MimeType returns the mimetype.
|
||||
// MimeType returns the mime type.
|
||||
func (m *MediaFile) MimeType() string {
|
||||
if m.mimeType != "" {
|
||||
return m.mimeType
|
||||
}
|
||||
|
||||
m.mimeType = fs.MimeType(m.Filename())
|
||||
m.mimeType = fs.MimeType(m.FileName())
|
||||
|
||||
return m.mimeType
|
||||
}
|
||||
|
||||
func (m *MediaFile) openFile() (*os.File, error) {
|
||||
handle, err := os.Open(m.filename)
|
||||
handle, err := os.Open(m.fileName)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return nil, err
|
||||
|
@ -397,26 +417,30 @@ func (m *MediaFile) openFile() (*os.File, error) {
|
|||
|
||||
// Exists checks if a media file exists by filename.
|
||||
func (m MediaFile) Exists() bool {
|
||||
return fs.FileExists(m.Filename())
|
||||
return fs.FileExists(m.FileName())
|
||||
}
|
||||
|
||||
// Remove a media file.
|
||||
func (m MediaFile) Remove() error {
|
||||
return os.Remove(m.Filename())
|
||||
return os.Remove(m.FileName())
|
||||
}
|
||||
|
||||
// HasSameFilename compares a media file with another media file and returns if
|
||||
// HasSameName compares a media file with another media file and returns if
|
||||
// their filenames are matching or not.
|
||||
func (m MediaFile) HasSameFilename(other *MediaFile) bool {
|
||||
return m.Filename() == other.Filename()
|
||||
func (m MediaFile) HasSameName(f *MediaFile) bool {
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return m.FileName() == f.FileName()
|
||||
}
|
||||
|
||||
// Move file to a new destination with the filename provided in parameter.
|
||||
func (m *MediaFile) Move(newFilename string) error {
|
||||
if err := os.Rename(m.filename, newFilename); err != nil {
|
||||
if err := os.Rename(m.fileName, newFilename); err != nil {
|
||||
log.Debugf("could not rename file, falling back to copy and delete: %s", err.Error())
|
||||
} else {
|
||||
m.filename = newFilename
|
||||
m.fileName = newFilename
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -425,11 +449,11 @@ func (m *MediaFile) Move(newFilename string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove(m.filename); err != nil {
|
||||
if err := os.Remove(m.fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.filename = newFilename
|
||||
m.fileName = newFilename
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -466,7 +490,7 @@ func (m *MediaFile) Copy(destinationFilename string) error {
|
|||
|
||||
// Extension returns the filename extension of this media file.
|
||||
func (m MediaFile) Extension() string {
|
||||
return strings.ToLower(filepath.Ext(m.filename))
|
||||
return strings.ToLower(filepath.Ext(m.fileName))
|
||||
}
|
||||
|
||||
// IsJpeg return true if this media file is a JPEG image.
|
||||
|
@ -545,6 +569,8 @@ func (m MediaFile) IsSidecar() bool {
|
|||
return true
|
||||
case fs.TypeYaml:
|
||||
return true
|
||||
case fs.TypeJson:
|
||||
return true
|
||||
case fs.TypeText:
|
||||
return true
|
||||
case fs.TypeMarkdown:
|
||||
|
@ -572,14 +598,14 @@ func (m MediaFile) IsPhoto() bool {
|
|||
// Jpeg returns a the JPEG version of an image or sidecar file (if exists).
|
||||
func (m *MediaFile) Jpeg() (*MediaFile, error) {
|
||||
if m.IsJpeg() {
|
||||
if !fs.FileExists(m.Filename()) {
|
||||
return nil, fmt.Errorf("jpeg file should exist, but does not: %s", m.Filename())
|
||||
if !fs.FileExists(m.FileName()) {
|
||||
return nil, fmt.Errorf("jpeg file should exist, but does not: %s", m.FileName())
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
jpegFilename := fmt.Sprintf("%s.%s", m.DirectoryBasename(), fs.TypeJpeg)
|
||||
jpegFilename := fmt.Sprintf("%s.%s", m.AbsBase(), fs.TypeJpeg)
|
||||
|
||||
if !fs.FileExists(jpegFilename) {
|
||||
return nil, fmt.Errorf("jpeg file does not exist: %s", jpegFilename)
|
||||
|
@ -590,7 +616,7 @@ func (m *MediaFile) Jpeg() (*MediaFile, error) {
|
|||
|
||||
func (m *MediaFile) decodeDimensions() error {
|
||||
if !m.IsPhoto() {
|
||||
return fmt.Errorf("not a photo: %s", m.Filename())
|
||||
return fmt.Errorf("not a photo: %s", m.FileName())
|
||||
}
|
||||
|
||||
var width, height int
|
||||
|
@ -603,7 +629,7 @@ func (m *MediaFile) decodeDimensions() error {
|
|||
}
|
||||
|
||||
if m.IsJpeg() {
|
||||
file, err := os.Open(m.Filename())
|
||||
file, err := os.Open(m.FileName())
|
||||
|
||||
defer file.Close()
|
||||
|
||||
|
@ -690,7 +716,7 @@ func (m *MediaFile) Thumbnail(path string, typeName string) (filename string, er
|
|||
return "", fmt.Errorf("mediafile: invalid type %s", typeName)
|
||||
}
|
||||
|
||||
thumbnail, err := thumb.FromFile(m.Filename(), m.Hash(), path, thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
thumbnail, err := thumb.FromFile(m.FileName(), m.Hash(), path, thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("mediafile: could not create thumbnail (%s)", err)
|
||||
|
@ -717,9 +743,12 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||
|
||||
defer func() {
|
||||
switch count {
|
||||
case 0: log.Debug(capture.Time(start, fmt.Sprintf("mediafile: no new thumbnails created for %s", m.Basename())))
|
||||
case 1: log.Debug(capture.Time(start, fmt.Sprintf("mediafile: one thumbnail created for %s", m.Basename())))
|
||||
default: log.Debug(capture.Time(start, fmt.Sprintf("mediafile: %d thumbnails created for %s", count, m.Basename())))
|
||||
case 0:
|
||||
log.Debug(capture.Time(start, fmt.Sprintf("mediafile: no new thumbnails created for %s", m.Base())))
|
||||
case 1:
|
||||
log.Debug(capture.Time(start, fmt.Sprintf("mediafile: one thumbnail created for %s", m.Base())))
|
||||
default:
|
||||
log.Debug(capture.Time(start, fmt.Sprintf("mediafile: %d thumbnails created for %s", count, m.Base())))
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -747,10 +776,10 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||
}
|
||||
|
||||
if originalImg == nil {
|
||||
img, err := imaging.Open(m.Filename(), imaging.AutoOrientation(true))
|
||||
img, err := imaging.Open(m.FileName(), imaging.AutoOrientation(true))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("mediafile: can't open \"%s\" (%s)", m.Filename(), err.Error())
|
||||
log.Errorf("mediafile: can't open \"%s\" (%s)", m.FileName(), err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -201,7 +201,7 @@ func TestMediaFileCanonicalName(t *testing.T) {
|
|||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/beach_wood.jpg")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "20180111_110938_EB4B2A989C20", mediaFile.CanonicalName())
|
||||
assert.Equal(t, "20180111_110938_B6B8AB4F", mediaFile.CanonicalName())
|
||||
}
|
||||
|
||||
func TestMediaFileCanonicalNameFromFile(t *testing.T) {
|
||||
|
@ -236,14 +236,14 @@ func TestMediaFile_EditedFilename(t *testing.T) {
|
|||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120.JPG")
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_E4120.JPG", mediaFile.EditedFilename())
|
||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_E4120.JPG", mediaFile.EditedName())
|
||||
})
|
||||
|
||||
t.Run("fern_green.jpg", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/fern_green.jpg")
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "", mediaFile.EditedFilename())
|
||||
assert.Equal(t, "", mediaFile.EditedName())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -264,9 +264,9 @@ func TestMediaFile_RelatedFiles(t *testing.T) {
|
|||
assert.Len(t, related.files, 3)
|
||||
|
||||
for _, result := range related.files {
|
||||
t.Logf("Filename: %s", result.Filename())
|
||||
t.Logf("FileName: %s", result.FileName())
|
||||
|
||||
filename := result.Filename()
|
||||
filename := result.FileName()
|
||||
|
||||
extension := result.Extension()
|
||||
|
||||
|
@ -290,9 +290,9 @@ func TestMediaFile_RelatedFiles(t *testing.T) {
|
|||
assert.Len(t, related.files, 3)
|
||||
|
||||
for _, result := range related.files {
|
||||
t.Logf("Filename: %s", result.Filename())
|
||||
t.Logf("FileName: %s", result.FileName())
|
||||
|
||||
filename := result.Filename()
|
||||
filename := result.FileName()
|
||||
|
||||
extension := result.Extension()
|
||||
|
||||
|
@ -316,12 +316,12 @@ func TestMediaFile_RelatedFiles_Ordering(t *testing.T) {
|
|||
|
||||
assert.Len(t, related.files, 5)
|
||||
|
||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_4120.AAE", related.files[0].Filename())
|
||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_4120.JPG", related.files[1].Filename())
|
||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_4120.AAE", related.files[0].FileName())
|
||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_4120.JPG", related.files[1].FileName())
|
||||
|
||||
for _, result := range related.files {
|
||||
filename := result.Filename()
|
||||
t.Logf("Filename: %s", filename)
|
||||
filename := result.FileName()
|
||||
t.Logf("FileName: %s", filename)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,10 +330,10 @@ func TestMediaFile_SetFilename(t *testing.T) {
|
|||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/turtle_brown_blue.jpg")
|
||||
assert.Nil(t, err)
|
||||
mediaFile.SetFilename("newFilename")
|
||||
assert.Equal(t, "newFilename", mediaFile.filename)
|
||||
mediaFile.SetFilename("turtle_brown_blue")
|
||||
assert.Equal(t, "turtle_brown_blue", mediaFile.filename)
|
||||
mediaFile.SetFileName("newFilename")
|
||||
assert.Equal(t, "newFilename", mediaFile.fileName)
|
||||
mediaFile.SetFileName("turtle_brown_blue")
|
||||
assert.Equal(t, "turtle_brown_blue", mediaFile.fileName)
|
||||
}
|
||||
|
||||
func TestMediaFile_RelativeFilename(t *testing.T) {
|
||||
|
@ -343,20 +343,20 @@ func TestMediaFile_RelativeFilename(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
t.Run("directory with end slash", func(t *testing.T) {
|
||||
filename := mediaFile.RelativeFilename("/go/src/github.com/photoprism/photoprism/assets/resources/")
|
||||
filename := mediaFile.RelativeName("/go/src/github.com/photoprism/photoprism/assets/resources/")
|
||||
assert.Equal(t, "examples/tree_white.jpg", filename)
|
||||
})
|
||||
|
||||
t.Run("directory without end slash", func(t *testing.T) {
|
||||
filename := mediaFile.RelativeFilename("/go/src/github.com/photoprism/photoprism/assets/resources")
|
||||
filename := mediaFile.RelativeName("/go/src/github.com/photoprism/photoprism/assets/resources")
|
||||
assert.Equal(t, "examples/tree_white.jpg", filename)
|
||||
})
|
||||
t.Run("directory not part of filename", func(t *testing.T) {
|
||||
filename := mediaFile.RelativeFilename("xxx/")
|
||||
filename := mediaFile.RelativeName("xxx/")
|
||||
assert.Equal(t, conf.ExamplesPath()+"/tree_white.jpg", filename)
|
||||
})
|
||||
t.Run("directory equals example path", func(t *testing.T) {
|
||||
filename := mediaFile.RelativeFilename("/go/src/github.com/photoprism/photoprism/assets/resources/examples")
|
||||
filename := mediaFile.RelativeName("/go/src/github.com/photoprism/photoprism/assets/resources/examples")
|
||||
assert.Equal(t, "tree_white.jpg", filename)
|
||||
})
|
||||
}
|
||||
|
@ -393,15 +393,15 @@ func TestMediaFile_RelativeBasename(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
t.Run("directory with end slash", func(t *testing.T) {
|
||||
basename := mediaFile.RelativeBasename("/go/src/github.com/photoprism/photoprism/assets/resources/")
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources/")
|
||||
assert.Equal(t, "examples/tree_white", basename)
|
||||
})
|
||||
t.Run("directory without end slash", func(t *testing.T) {
|
||||
basename := mediaFile.RelativeBasename("/go/src/github.com/photoprism/photoprism/assets/resources")
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources")
|
||||
assert.Equal(t, "examples/tree_white", basename)
|
||||
})
|
||||
t.Run("directory equals example path", func(t *testing.T) {
|
||||
basename := mediaFile.RelativeBasename("/go/src/github.com/photoprism/photoprism/assets/resources/examples/")
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources/examples/")
|
||||
assert.Equal(t, "tree_white", basename)
|
||||
})
|
||||
|
||||
|
@ -423,21 +423,21 @@ func TestMediaFile_Basename(t *testing.T) {
|
|||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/limes.jpg")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "limes", mediaFile.Basename())
|
||||
assert.Equal(t, "limes", mediaFile.Base())
|
||||
})
|
||||
t.Run("/IMG_4120 copy.JPG", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120 copy.JPG")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "IMG_4120", mediaFile.Basename())
|
||||
assert.Equal(t, "IMG_4120", mediaFile.Base())
|
||||
})
|
||||
t.Run("/IMG_4120 (1).JPG", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120 (1).JPG")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "IMG_4120", mediaFile.Basename())
|
||||
assert.Equal(t, "IMG_4120", mediaFile.Base())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -525,7 +525,7 @@ func TestMediaFile_Move(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.True(t, fs.FileExists(destName))
|
||||
assert.Equal(t, destName, m.Filename())
|
||||
assert.Equal(t, destName, m.FileName())
|
||||
}
|
||||
|
||||
func TestMediaFile_Copy(t *testing.T) {
|
||||
|
@ -892,7 +892,7 @@ func TestMediaFile_Jpeg(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
file, err := mediaFile.Jpeg()
|
||||
assert.Nil(t, err)
|
||||
assert.FileExists(t, file.filename)
|
||||
assert.FileExists(t, file.fileName)
|
||||
})
|
||||
t.Run("/iphone_7.json", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
|
|
@ -10,8 +10,8 @@ func (f MediaFiles) Len() int {
|
|||
|
||||
// Less compares two files based on the filename.
|
||||
func (f MediaFiles) Less(i, j int) bool {
|
||||
fileName1 := f[i].Filename()
|
||||
fileName2 := f[j].Filename()
|
||||
fileName1 := f[i].FileName()
|
||||
fileName2 := f[j].FileName()
|
||||
|
||||
if len(fileName1) == len(fileName2) {
|
||||
return fileName1 < fileName2
|
||||
|
|
|
@ -6,6 +6,6 @@ import (
|
|||
|
||||
// MetaData returns exif meta data of a media file.
|
||||
func (m *MediaFile) MetaData() (result meta.Data, err error) {
|
||||
m.once.Do(func() { m.metaData, err = meta.Exif(m.Filename()) })
|
||||
m.once.Do(func() { m.metaData, err = meta.Exif(m.FileName()) })
|
||||
return m.metaData, err
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ func (rs *Resample) Start(force bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
fileName := mf.RelativeFilename(originalsPath)
|
||||
fileName := mf.RelativeName(originalsPath)
|
||||
|
||||
event.Publish("index.thumbnails", event.Data{
|
||||
"fileName": fileName,
|
||||
|
|
|
@ -176,7 +176,7 @@ func TestThumb_Create(t *testing.T) {
|
|||
|
||||
res, err := thumb.Create(&img, expectedFilename, 150, 150, thumb.ResampleFit, thumb.ResampleNearestNeighbor)
|
||||
|
||||
if err != nil || res == nil{
|
||||
if err != nil || res == nil {
|
||||
t.Fatal("err should be nil and res should NOT be nil")
|
||||
}
|
||||
|
||||
|
@ -203,7 +203,7 @@ func TestThumb_Create(t *testing.T) {
|
|||
|
||||
res, err := thumb.Create(&img, expectedFilename, -1, 150, thumb.ResampleFit, thumb.ResampleNearestNeighbor)
|
||||
|
||||
if err == nil || res == nil{
|
||||
if err == nil || res == nil {
|
||||
t.Fatal("err and res should NOT be nil")
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,12 @@ package fs
|
|||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Hash returns the sha1 hash of file as string.
|
||||
// Hash returns the SHA1 hash of a file as string.
|
||||
func Hash(filename string) string {
|
||||
var result []byte
|
||||
|
||||
|
@ -27,3 +28,24 @@ func Hash(filename string) string {
|
|||
|
||||
return hex.EncodeToString(hash.Sum(result))
|
||||
}
|
||||
|
||||
// Checksum returns the CRC32 checksum of a file as string.
|
||||
func Checksum(filename string) string {
|
||||
var result []byte
|
||||
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := crc32.New(crc32.MakeTable(crc32.Castagnoli))
|
||||
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(result))
|
||||
}
|
||||
|
|
|
@ -16,3 +16,14 @@ func TestHash(t *testing.T) {
|
|||
assert.Equal(t, "", hash)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChecksum(t *testing.T) {
|
||||
t.Run("existing image", func(t *testing.T) {
|
||||
hash := Checksum("testdata/test.jpg")
|
||||
assert.Equal(t, "5239d867", hash)
|
||||
})
|
||||
t.Run("not existing image", func(t *testing.T) {
|
||||
hash := Checksum("testdata/xxx.jpg")
|
||||
assert.Equal(t, "", hash)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ const (
|
|||
TypeXML Type = "xml"
|
||||
// YAML metadata / config / sidecar file.
|
||||
TypeYaml Type = "yml"
|
||||
// JSON metadata / config / sidecar file.
|
||||
TypeJson Type = "json"
|
||||
// Text config / sidecar file.
|
||||
TypeText Type = "txt"
|
||||
// Markdown text sidecar file.
|
||||
|
@ -106,4 +108,5 @@ var Ext = map[string]Type{
|
|||
".xml": TypeXML,
|
||||
".txt": TypeText,
|
||||
".md": TypeMarkdown,
|
||||
".json": TypeJson,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue