HEIF/AVIF/DNG: Improve conversion to JPEG #1246 #2726 #2291 #2593

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2022-10-07 16:45:49 +02:00
parent 2034110c5d
commit 1b89915cc4
14 changed files with 170 additions and 96 deletions

View file

@ -50,6 +50,7 @@ test-go: reset-sqlite run-test-go
test-pkg: reset-sqlite run-test-pkg test-pkg: reset-sqlite run-test-pkg
test-api: reset-sqlite run-test-api test-api: reset-sqlite run-test-api
test-commands: reset-sqlite run-test-commands test-commands: reset-sqlite run-test-commands
test-photoprism: reset-sqlite run-test-photoprism
test-short: reset-sqlite run-test-short test-short: reset-sqlite run-test-short
test-mariadb: reset-acceptance run-test-mariadb test-mariadb: reset-acceptance run-test-mariadb
acceptance-run-chromium: storage/acceptance acceptance-auth-sqlite-restart acceptance-auth acceptance-auth-sqlite-stop acceptance-sqlite-restart acceptance acceptance-sqlite-stop acceptance-run-chromium: storage/acceptance acceptance-auth-sqlite-restart acceptance-auth acceptance-auth-sqlite-stop acceptance-sqlite-restart acceptance acceptance-sqlite-stop
@ -263,6 +264,9 @@ run-test-api:
run-test-commands: run-test-commands:
$(info Running all CLI command tests...) $(info Running all CLI command tests...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -tags slow -timeout 20m ./internal/commands/... $(GOTEST) -parallel 2 -count 1 -cpu 2 -tags slow -timeout 20m ./internal/commands/...
run-test-photoprism:
$(info Running all Go tests in "/internal/photoprism"...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -tags slow -timeout 20m ./internal/photoprism/...
test-parallel: test-parallel:
$(info Running all Go tests in parallel mode...) $(info Running all Go tests in parallel mode...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -tags slow -timeout 20m ./pkg/... ./internal/... $(GOTEST) -parallel 2 -count 1 -cpu 2 -tags slow -timeout 20m ./pkg/... ./internal/...

11
go.mod
View file

@ -13,15 +13,14 @@ require (
github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d
github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20221003165014-8ecc4f52edca github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20221003165014-8ecc4f52edca
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/esimov/pigo v1.4.5 github.com/esimov/pigo v1.4.6
github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.8.1 github.com/gin-gonic/gin v1.8.1
github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 github.com/golang/geo v0.0.0-20210211234256-740aa86cb551
github.com/google/open-location-code/go v0.0.0-20220922185916-75f4f40254f8 github.com/google/open-location-code/go v0.0.0-20220922185916-75f4f40254f8
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/gosimple/slug v1.13.0 github.com/gosimple/slug v1.13.1
github.com/h2non/filetype v1.1.3
github.com/jinzhu/gorm v1.9.16 github.com/jinzhu/gorm v1.9.16
github.com/jinzhu/inflection v1.0.0 github.com/jinzhu/inflection v1.0.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
@ -48,8 +47,8 @@ require (
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6
github.com/urfave/cli v1.22.10 github.com/urfave/cli v1.22.10
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b
golang.org/x/net v0.0.0-20221002022538-bcab6841153b golang.org/x/net v0.0.0-20221004154528-8021a29435af
gonum.org/v1/gonum v0.12.0 gonum.org/v1/gonum v0.12.0
gopkg.in/photoprism/go-tz.v2 v2.1.1 gopkg.in/photoprism/go-tz.v2 v2.1.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@ -79,6 +78,8 @@ require (
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
) )
require github.com/gabriel-vasile/mimetype v1.4.1
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect

19
go.sum
View file

@ -153,11 +153,13 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/esimov/pigo v1.4.5 h1:ySG0QqMh02VNALvHnx04L1ScRu66N6XA5vLLga8GiLg= github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
github.com/esimov/pigo v1.4.5/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w= github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
@ -299,13 +301,11 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosimple/slug v1.13.0 h1:w4W2sU2a/JcAkI+LN316Cn/NE4CXopoXto9aloYTic0= github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
github.com/gosimple/slug v1.13.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -479,8 +479,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -583,8 +583,9 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

View file

@ -41,6 +41,11 @@ func (c *Config) DisableExifTool() bool {
return c.options.DisableExifTool return c.options.DisableExifTool
} }
// ExifToolEnabled checks if the use of ExifTool is possible.
func (c *Config) ExifToolEnabled() bool {
return !c.DisableExifTool()
}
// DisableTensorFlow checks if all features depending on TensorFlow should be disabled. // DisableTensorFlow checks if all features depending on TensorFlow should be disabled.
func (c *Config) DisableTensorFlow() bool { func (c *Config) DisableTensorFlow() bool {
if LowMem && !c.options.DisableTensorFlow { if LowMem && !c.options.DisableTensorFlow {

View file

@ -59,6 +59,14 @@ func TestConfig_DisableExifTool(t *testing.T) {
assert.True(t, c.DisableExifTool()) assert.True(t, c.DisableExifTool())
} }
func TestConfig_ExifToolEnabled(t *testing.T) {
c := NewConfig(CliTestContext())
assert.True(t, c.ExifToolEnabled())
c.options.ExifToolBin = "XXX"
assert.False(t, c.ExifToolEnabled())
}
func TestConfig_DisableFaces(t *testing.T) { func TestConfig_DisableFaces(t *testing.T) {
c := NewConfig(CliTestContext()) c := NewConfig(CliTestContext())
assert.False(t, c.DisableFaces()) assert.False(t, c.DisableFaces())

View file

@ -550,7 +550,7 @@ var Flags = CliFlags{
Flag: cli.StringFlag{ Flag: cli.StringFlag{
Name: "darktable-blacklist", Name: "darktable-blacklist",
Usage: "do not use Darktable to convert files with these `EXTENSIONS`", Usage: "do not use Darktable to convert files with these `EXTENSIONS`",
Value: "dng", Value: "",
EnvVar: "PHOTOPRISM_DARKTABLE_BLACKLIST", EnvVar: "PHOTOPRISM_DARKTABLE_BLACKLIST",
}}, }},
CliFlag{ CliFlag{
@ -578,7 +578,7 @@ var Flags = CliFlags{
Flag: cli.StringFlag{ Flag: cli.StringFlag{
Name: "rawtherapee-blacklist", Name: "rawtherapee-blacklist",
Usage: "do not use RawTherapee to convert files with these `EXTENSIONS`", Usage: "do not use RawTherapee to convert files with these `EXTENSIONS`",
Value: "avif,avifs", Value: "dng",
EnvVar: "PHOTOPRISM_RAWTHERAPEE_BLACKLIST", EnvVar: "PHOTOPRISM_RAWTHERAPEE_BLACKLIST",
}}, }},
CliFlag{ CliFlag{
@ -598,7 +598,7 @@ var Flags = CliFlags{
CliFlag{ CliFlag{
Flag: cli.StringFlag{ Flag: cli.StringFlag{
Name: "heifconvert-bin", Name: "heifconvert-bin",
Usage: "HEIC/HEIF image conversion `COMMAND`", Usage: "HEIC/HEIF/AVIF image conversion `COMMAND`",
Value: "heif-convert", Value: "heif-convert",
EnvVar: "PHOTOPRISM_HEIFCONVERT_BIN", EnvVar: "PHOTOPRISM_HEIFCONVERT_BIN",
}}, }},

View file

@ -99,7 +99,7 @@ func (c *Convert) Start(path string, force bool) (err error) {
f, err := NewMediaFile(fileName) f, err := NewMediaFile(fileName)
if err != nil || f.Empty() || !(f.IsRaw() || f.IsHEIF() || f.IsAVIF() || f.IsImageOther() || f.IsVideo()) { if err != nil || f.Empty() || !(f.IsRaw() || f.IsHEIC() || f.IsAVIF() || f.IsImageOther() || f.IsVideo()) {
return nil return nil
} }

View file

@ -4,14 +4,16 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"time" "time"
"github.com/gabriel-vasile/mimetype"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
@ -32,6 +34,8 @@ func (c *Convert) ToJpeg(f *MediaFile, force bool) (*MediaFile, error) {
return f, nil return f, nil
} }
var err error
jpegName := fs.ImageJPEG.FindFirst(f.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), false) jpegName := fs.ImageJPEG.FindFirst(f.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), false)
mediaFile, err := NewMediaFile(jpegName) mediaFile, err := NewMediaFile(jpegName)
@ -56,7 +60,7 @@ func (c *Convert) ToJpeg(f *MediaFile, force bool) (*MediaFile, error) {
} }
fileName := f.RelName(c.conf.OriginalsPath()) fileName := f.RelName(c.conf.OriginalsPath())
xmpName := fs.XmpFile.Find(f.FileName(), false) xmpName := fs.SidecarXMP.Find(f.FileName(), false)
event.Publish("index.converting", event.Data{ event.Publish("index.converting", event.Data{
"fileType": f.FileType(), "fileType": f.FileType(),
@ -81,10 +85,12 @@ func (c *Convert) ToJpeg(f *MediaFile, force bool) (*MediaFile, error) {
return NewMediaFile(jpegName) return NewMediaFile(jpegName)
} }
cmd, useMutex, err := c.JpegConvertCommand(f, jpegName, xmpName) cmds, useMutex, err := c.JpegConvertCommands(f, jpegName, xmpName)
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(cmds) == 0 {
return nil, fmt.Errorf("file type %s not supported", f.FileType())
} }
if useMutex { if useMutex {
@ -98,6 +104,7 @@ func (c *Convert) ToJpeg(f *MediaFile, force bool) (*MediaFile, error) {
return NewMediaFile(jpegName) return NewMediaFile(jpegName)
} }
for _, cmd := range cmds {
// Fetch command output. // Fetch command output.
var out bytes.Buffer var out bytes.Buffer
var stderr bytes.Buffer var stderr bytes.Buffer
@ -111,34 +118,64 @@ func (c *Convert) ToJpeg(f *MediaFile, force bool) (*MediaFile, error) {
log.Trace(cmd.String()) log.Trace(cmd.String())
// Run convert command. // Run convert command.
if err := cmd.Run(); err != nil { if err = cmd.Run(); err != nil {
if stderr.String() != "" { if stderr.String() != "" {
return nil, errors.New(stderr.String()) err = errors.New(stderr.String())
}
log.Tracef("convert: %s (%s)", err, filepath.Base(cmd.Path))
continue
} else if fs.FileExistsNotEmpty(jpegName) {
log.Infof("convert: %s created in %s (%s)", clean.Log(filepath.Base(jpegName)), time.Since(start), filepath.Base(cmd.Path))
break
} else if res := out.Bytes(); len(res) < 512 || !mimetype.Detect(res).Is(fs.MimeTypeJpeg) {
continue
} else if err = os.WriteFile(jpegName, res, os.ModePerm); err != nil {
log.Tracef("convert: %s (%s)", err, filepath.Base(cmd.Path))
continue
} else { } else {
return nil, err break
} }
} }
log.Infof("convert: %s created in %s (%s)", clean.Log(filepath.Base(jpegName)), time.Since(start), filepath.Base(cmd.Path)) // Ok?
if err != nil {
return nil, err
}
return NewMediaFile(jpegName) return NewMediaFile(jpegName)
} }
// JpegConvertCommand returns the command for converting files to JPEG, depending on the format. // JpegConvertCommands returns the command for converting files to JPEG, depending on the format.
func (c *Convert) JpegConvertCommand(f *MediaFile, jpegName string, xmpName string) (result *exec.Cmd, useMutex bool, err error) { func (c *Convert) JpegConvertCommands(f *MediaFile, jpegName string, xmpName string) (result []*exec.Cmd, useMutex bool, err error) {
result = make([]*exec.Cmd, 0, 2)
if f == nil { if f == nil {
return result, useMutex, fmt.Errorf("file is nil - possible bug") return result, useMutex, fmt.Errorf("file is nil - possible bug")
} }
// Find conversion command depending on the file type and runtime environment.
fileExt := f.Extension() fileExt := f.Extension()
maxSize := strconv.Itoa(c.conf.JpegSize()) maxSize := strconv.Itoa(c.conf.JpegSize())
// Select conversion command depending on the file type and runtime environment. // Apple Scriptable image processing system: https://ss64.com/osx/sips.html
if (f.IsRaw() || f.IsHEIF() || f.IsAVIF()) && c.conf.SipsEnabled() && c.sipsBlacklist.Ok(fileExt) { if (f.IsRaw() || f.IsHEIC() || f.IsAVIF()) && c.conf.SipsEnabled() && c.sipsBlacklist.Allow(fileExt) {
result = exec.Command(c.conf.SipsBin(), "-Z", maxSize, "-s", "format", "jpeg", "--out", jpegName, f.FileName()) result = append(result, exec.Command(c.conf.SipsBin(), "-Z", maxSize, "-s", "format", "jpeg", "--out", jpegName, f.FileName()))
} else if f.IsRaw() && c.conf.RawEnabled() || f.IsAVIF() { }
if c.conf.DarktableEnabled() && c.darktableBlacklist.Ok(fileExt) {
// Use heif-convert for HEIC/HEIF and AVIF image files.
if (f.IsHEIC() || f.IsAVIF()) && c.conf.HeifConvertEnabled() {
result = append(result, exec.Command(c.conf.HeifConvertBin(), "-q", c.conf.JpegQuality().String(), f.FileName(), jpegName))
}
// Video thumbnails can be created with FFmpeg.
if f.IsVideo() && c.conf.FFmpegEnabled() {
result = append(result, exec.Command(c.conf.FFmpegBin(), "-y", "-i", f.FileName(), "-ss", "00:00:00.001", "-vframes", "1", jpegName))
}
// RAW files may be concerted with Darktable and Rawtherapee.
if f.IsRaw() && c.conf.RawEnabled() {
if c.conf.DarktableEnabled() && c.darktableBlacklist.Allow(fileExt) {
var args []string var args []string
// Set RAW, XMP, and JPEG filenames. // Set RAW, XMP, and JPEG filenames.
@ -168,28 +205,37 @@ func (c *Convert) JpegConvertCommand(f *MediaFile, jpegName string, xmpName stri
args = append(args, "--cachedir", dir) args = append(args, "--cachedir", dir)
} }
result = exec.Command(c.conf.DarktableBin(), args...) result = append(result, exec.Command(c.conf.DarktableBin(), args...))
} else if c.conf.RawtherapeeEnabled() && c.rawtherapeeBlacklist.Ok(fileExt) { }
if c.conf.RawtherapeeEnabled() && c.rawtherapeeBlacklist.Allow(fileExt) {
jpegQuality := fmt.Sprintf("-j%d", c.conf.JpegQuality()) jpegQuality := fmt.Sprintf("-j%d", c.conf.JpegQuality())
profile := filepath.Join(conf.AssetsPath(), "profiles", "raw.pp3") profile := filepath.Join(conf.AssetsPath(), "profiles", "raw.pp3")
args := []string{"-o", jpegName, "-p", profile, "-s", "-d", jpegQuality, "-js3", "-b8", "-c", f.FileName()} args := []string{"-o", jpegName, "-p", profile, "-s", "-d", jpegQuality, "-js3", "-b8", "-c", f.FileName()}
result = exec.Command(c.conf.RawtherapeeBin(), args...) result = append(result, exec.Command(c.conf.RawtherapeeBin(), args...))
} else {
return nil, useMutex, fmt.Errorf("no suitable converter found")
} }
} else if f.IsVideo() && c.conf.FFmpegEnabled() { }
result = exec.Command(c.conf.FFmpegBin(), "-y", "-i", f.FileName(), "-ss", "00:00:00.001", "-vframes", "1", jpegName)
} else if f.IsHEIF() && c.conf.HeifConvertEnabled() { // Extract preview image from DNG files.
result = exec.Command(c.conf.HeifConvertBin(), "-q", c.conf.JpegQuality().String(), f.FileName(), jpegName) if f.IsDNG() && c.conf.ExifToolEnabled() {
} else { // Example: exiftool -b -PreviewImage -w IMG_4691.DNG.jpg IMG_4691.DNG
return nil, useMutex, fmt.Errorf("file type %s not supported", f.FileType()) result = append(result, exec.Command(c.conf.ExifToolBin(), "-q", "-q", "-b", "-PreviewImage", f.FileName()))
}
// No suitable converter found?
if len(result) == 0 {
return result, useMutex, fmt.Errorf("file type %s not supported", f.FileType())
} }
// Log convert command in trace mode only as it exposes server internals. // Log convert command in trace mode only as it exposes server internals.
if result != nil { for i, cmd := range result {
log.Tracef("convert: %s", result.String()) if i == 0 {
log.Tracef("convert: %s", cmd.String())
} else {
log.Tracef("convert: %s (alternative)", cmd.String())
}
} }
return result, useMutex, nil return result, useMutex, nil

View file

@ -245,7 +245,7 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
photo.PhotoStack = entity.IsStackable photo.PhotoStack = entity.IsStackable
} }
if yamlName := fs.YamlFile.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); yamlName != "" { if yamlName := fs.SidecarYAML.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); yamlName != "" {
if err := photo.LoadFromYaml(yamlName); err != nil { if err := photo.LoadFromYaml(yamlName); err != nil {
log.Errorf("index: %s in %s (restore from yaml)", err.Error(), logName) log.Errorf("index: %s in %s (restore from yaml)", err.Error(), logName)
} else { } else {
@ -431,7 +431,7 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
log.Warn(err.Error()) log.Warn(err.Error())
file.FileError = err.Error() file.FileError = err.Error()
} }
case m.IsRaw(), m.IsHEIF(), m.IsImageOther(): case m.IsRaw(), m.IsDNG(), m.IsHEIC(), m.IsAVIF(), m.IsImageOther():
if metaData := m.MetaData(); metaData.Error == nil { if metaData := m.MetaData(); metaData.Error == nil {
// Update basic metadata. // Update basic metadata.
photo.SetTitle(metaData.Title, entity.SrcMeta) photo.SetTitle(metaData.Title, entity.SrcMeta)

View file

@ -3,12 +3,11 @@ package photoprism
import ( import (
"testing" "testing"
"github.com/photoprism/photoprism/internal/entity"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/internal/classify" "github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/face" "github.com/photoprism/photoprism/internal/face"
"github.com/photoprism/photoprism/internal/nsfw" "github.com/photoprism/photoprism/internal/nsfw"
) )

View file

@ -352,7 +352,7 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e
matches = append(matches, name) matches = append(matches, name)
} }
isHEIF := false isHEIC := false
for _, fileName := range matches { for _, fileName := range matches {
f, fileErr := NewMediaFile(fileName) f, fileErr := NewMediaFile(fileName)
@ -371,14 +371,16 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e
result.Main = f result.Main = f
} else if f.IsRaw() { } else if f.IsRaw() {
result.Main = f result.Main = f
} else if f.IsDNG() {
result.Main = f
} else if f.IsAVIF() { } else if f.IsAVIF() {
result.Main = f result.Main = f
} else if f.IsHEIF() { } else if f.IsHEIC() {
isHEIF = true isHEIC = true
result.Main = f result.Main = f
} else if f.IsImageOther() { } else if f.IsImageOther() {
result.Main = f result.Main = f
} else if f.IsVideo() && !isHEIF { } else if f.IsVideo() && !isHEIC {
result.Main = f result.Main = f
} else if result.Main != nil && f.IsJpeg() { } else if result.Main != nil && f.IsJpeg() {
if result.Main.IsJpeg() && len(result.Main.FileName()) > len(f.FileName()) { if result.Main.IsJpeg() && len(result.Main.FileName()) > len(f.FileName()) {
@ -709,7 +711,7 @@ func (m *MediaFile) Extension() string {
// IsJpeg return true if this media file is a JPEG image. // IsJpeg return true if this media file is a JPEG image.
func (m *MediaFile) IsJpeg() bool { func (m *MediaFile) IsJpeg() bool {
// Don't import/use existing thumbnail files (we create our own) // Don't import/use existing thumbnail files (we create our own)
if m.Extension() == ".thm" { if m.Extension() == fs.ExtTHM {
return false return false
} }
@ -731,9 +733,14 @@ func (m *MediaFile) IsTiff() bool {
return m.HasFileType(fs.ImageTIFF) && m.MimeType() == fs.MimeTypeTiff return m.HasFileType(fs.ImageTIFF) && m.MimeType() == fs.MimeTypeTiff
} }
// IsHEIF returns true if this is a High Efficiency Image File Format image. // IsDNG returns true if this is a Adobe Digital Negative image.
func (m *MediaFile) IsHEIF() bool { func (m *MediaFile) IsDNG() bool {
return m.MimeType() == fs.MimeTypeHEIF return m.MimeType() == fs.MimeTypeDNG
}
// IsHEIC returns true if this is a High Efficiency Image File Format image.
func (m *MediaFile) IsHEIC() bool {
return m.MimeType() == fs.MimeTypeHEIC
} }
// IsAVIF returns true if this is an AV1 Image File Format image. // IsAVIF returns true if this is an AV1 Image File Format image.
@ -768,7 +775,7 @@ func (m *MediaFile) IsAnimated() bool {
// IsJson return true if this media file is a json sidecar file. // IsJson return true if this media file is a json sidecar file.
func (m *MediaFile) IsJson() bool { func (m *MediaFile) IsJson() bool {
return m.HasFileType(fs.JsonFile) return m.HasFileType(fs.SidecarJSON)
} }
// FileType returns the file type (jpg, gif, tiff,...). // FileType returns the file type (jpg, gif, tiff,...).
@ -780,12 +787,14 @@ func (m *MediaFile) FileType() fs.Type {
return fs.ImagePNG return fs.ImagePNG
case m.IsGif(): case m.IsGif():
return fs.ImageGIF return fs.ImageGIF
case m.IsAVIF():
return fs.ImageAVIF
case m.IsHEIF():
return fs.ImageHEIF
case m.IsBitmap(): case m.IsBitmap():
return fs.ImageBMP return fs.ImageBMP
case m.IsDNG():
return fs.ImageDNG
case m.IsAVIF():
return fs.ImageAVIF
case m.IsHEIC():
return fs.ImageHEIF
default: default:
return fs.FileType(m.fileName) return fs.FileType(m.fileName)
} }
@ -807,12 +816,12 @@ func (m *MediaFile) HasFileType(fileType fs.Type) bool {
// IsRaw returns true if this is a RAW file. // IsRaw returns true if this is a RAW file.
func (m *MediaFile) IsRaw() bool { func (m *MediaFile) IsRaw() bool {
return m.HasFileType(fs.RawImage) return m.HasFileType(fs.ImageRaw) || m.IsDNG()
} }
// IsXMP returns true if this is a XMP sidecar file. // IsXMP returns true if this is a XMP sidecar file.
func (m *MediaFile) IsXMP() bool { func (m *MediaFile) IsXMP() bool {
return m.FileType() == fs.XmpFile return m.FileType() == fs.SidecarXMP
} }
// InOriginals checks if the file is stored in the 'originals' folder. // InOriginals checks if the file is stored in the 'originals' folder.
@ -852,12 +861,12 @@ func (m *MediaFile) IsImageNative() bool {
// IsImage checks if the file is an image // IsImage checks if the file is an image
func (m *MediaFile) IsImage() bool { func (m *MediaFile) IsImage() bool {
return m.IsImageNative() || m.IsRaw() || m.IsAVIF() || m.IsHEIF() return m.IsImageNative() || m.IsRaw() || m.IsDNG() || m.IsAVIF() || m.IsHEIC()
} }
// IsLive checks if the file is a live photo. // IsLive checks if the file is a live photo.
func (m *MediaFile) IsLive() bool { func (m *MediaFile) IsLive() bool {
if m.IsHEIF() { if m.IsHEIC() {
return fs.VideoMOV.FindFirst(m.FileName(), []string{}, Config().OriginalsPath(), false) != "" return fs.VideoMOV.FindFirst(m.FileName(), []string{}, Config().OriginalsPath(), false) != ""
} }
@ -870,12 +879,12 @@ func (m *MediaFile) IsLive() bool {
// ExifSupported returns true if parsing exif metadata is supported for the media file type. // ExifSupported returns true if parsing exif metadata is supported for the media file type.
func (m *MediaFile) ExifSupported() bool { func (m *MediaFile) ExifSupported() bool {
return m.IsJpeg() || m.IsRaw() || m.IsHEIF() || m.IsPng() || m.IsTiff() return m.IsJpeg() || m.IsRaw() || m.IsHEIC() || m.IsPng() || m.IsTiff()
} }
// IsMedia returns true if this is a media file (photo or video, not sidecar or other). // IsMedia returns true if this is a media file (photo or video, not sidecar or other).
func (m *MediaFile) IsMedia() bool { func (m *MediaFile) IsMedia() bool {
return m.IsJpeg() || m.IsVideo() || m.IsRaw() || m.IsAVIF() || m.IsHEIF() || m.IsImageOther() return m.IsImageNative() || m.IsVideo() || m.IsRaw() || m.IsDNG() || m.IsAVIF() || m.IsHEIC()
} }
// Jpeg returns the JPEG version of the media file (if exists). // Jpeg returns the JPEG version of the media file (if exists).

View file

@ -6,7 +6,6 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/meta" "github.com/photoprism/photoprism/internal/meta"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
@ -17,7 +16,7 @@ func (m *MediaFile) HasSidecarJson() bool {
return true return true
} }
return fs.JsonFile.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false) != "" return fs.SidecarJSON.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false) != ""
} }
// SidecarJsonName returns the corresponding JSON sidecar file name as used by Google Photos (and potentially other apps). // SidecarJsonName returns the corresponding JSON sidecar file name as used by Google Photos (and potentially other apps).
@ -84,7 +83,7 @@ func (m *MediaFile) MetaData() (result meta.Data) {
// Parse regular JSON sidecar files ("img_1234.json") // Parse regular JSON sidecar files ("img_1234.json")
if !m.IsSidecar() { if !m.IsSidecar() {
if jsonFiles := fs.JsonFile.FindAll(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false); len(jsonFiles) == 0 { if jsonFiles := fs.SidecarJSON.FindAll(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false); len(jsonFiles) == 0 {
log.Tracef("metadata: found no additional sidecar file for %s", clean.Log(filepath.Base(m.FileName()))) log.Tracef("metadata: found no additional sidecar file for %s", clean.Log(filepath.Base(m.FileName())))
} else { } else {
for _, jsonFile := range jsonFiles { for _, jsonFile := range jsonFiles {

View file

@ -870,8 +870,10 @@ func TestMediaFile_MimeType(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, "image/tiff", mediaFile.MimeType())
assert.Equal(t, "image/dng", mediaFile.MimeType())
assert.True(t, mediaFile.IsDNG())
assert.True(t, mediaFile.IsRaw())
}) })
t.Run("iphone_7.xmp", func(t *testing.T) { t.Run("iphone_7.xmp", func(t *testing.T) {
@ -879,7 +881,7 @@ func TestMediaFile_MimeType(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, "", mediaFile.MimeType()) assert.Equal(t, "text/plain", mediaFile.MimeType())
}) })
t.Run("iphone_7.json", func(t *testing.T) { t.Run("iphone_7.json", func(t *testing.T) {
@ -887,14 +889,14 @@ func TestMediaFile_MimeType(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, "", mediaFile.MimeType()) assert.Equal(t, "application/json", mediaFile.MimeType())
}) })
t.Run("fox.profile0.8bpc.yuv420.avif", func(t *testing.T) { t.Run("fox.profile0.8bpc.yuv420.avif", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/fox.profile0.8bpc.yuv420.avif") mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/fox.profile0.8bpc.yuv420.avif")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, "image/avif", mediaFile.MimeType()) assert.Equal(t, fs.MimeTypeAVIF, mediaFile.MimeType())
assert.True(t, mediaFile.IsAVIF()) assert.True(t, mediaFile.IsAVIF())
}) })
t.Run("iphone_7.heic", func(t *testing.T) { t.Run("iphone_7.heic", func(t *testing.T) {
@ -902,15 +904,15 @@ func TestMediaFile_MimeType(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, "image/heif", mediaFile.MimeType()) assert.Equal(t, fs.MimeTypeHEIC, mediaFile.MimeType())
assert.True(t, mediaFile.IsHEIF()) assert.True(t, mediaFile.IsHEIC())
}) })
t.Run("IMG_4120.AAE", func(t *testing.T) { t.Run("IMG_4120.AAE", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120.AAE") mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120.AAE")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, "", mediaFile.MimeType()) assert.Equal(t, fs.MimeTypeXML, mediaFile.MimeType())
}) })
t.Run("earth.mov", func(t *testing.T) { t.Run("earth.mov", func(t *testing.T) {
@ -1130,28 +1132,28 @@ func TestMediaFile_IsHEIF(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, false, mediaFile.IsHEIF()) assert.Equal(t, false, mediaFile.IsHEIC())
}) })
t.Run("iphone_7.heic", func(t *testing.T) { t.Run("iphone_7.heic", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic") mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, true, mediaFile.IsHEIF()) assert.Equal(t, true, mediaFile.IsHEIC())
}) })
t.Run("canon_eos_6d.dng", func(t *testing.T) { t.Run("canon_eos_6d.dng", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng") mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, false, mediaFile.IsHEIF()) assert.Equal(t, false, mediaFile.IsHEIC())
}) })
t.Run("elephants.jpg", func(t *testing.T) { t.Run("elephants.jpg", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg") mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, false, mediaFile.IsHEIF()) assert.Equal(t, false, mediaFile.IsHEIC())
}) })
} }
@ -1220,8 +1222,8 @@ func TestMediaFile_IsTiff(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, fs.JsonFile, mediaFile.FileType()) assert.Equal(t, fs.SidecarJSON, mediaFile.FileType())
assert.Equal(t, "", mediaFile.MimeType()) assert.Equal(t, fs.MimeTypeJSON, mediaFile.MimeType())
assert.Equal(t, false, mediaFile.IsTiff()) assert.Equal(t, false, mediaFile.IsTiff())
}) })
t.Run("purple.tiff", func(t *testing.T) { t.Run("purple.tiff", func(t *testing.T) {

View file

@ -11,7 +11,7 @@ func AccountUploads(a entity.Account, limit int) (results entity.Files, err erro
Where("files.id NOT IN (SELECT file_id FROM files_sync WHERE file_id > 0 AND account_id = ?)", a.ID) Where("files.id NOT IN (SELECT file_id FROM files_sync WHERE file_id > 0 AND account_id = ?)", a.ID)
if !a.SyncRaw { if !a.SyncRaw {
s = s.Where("files.file_type <> ? OR files.file_type IS NULL", fs.RawImage) s = s.Where("files.file_type <> ? OR files.file_type IS NULL", fs.ImageRaw)
} }
s = s.Order("files.file_name ASC") s = s.Order("files.file_name ASC")