On the fly image conversion

This commit is contained in:
Evan Reichard 2021-02-21 12:31:03 -05:00
parent 5212d7bf70
commit 901a69bb91
13 changed files with 296 additions and 53 deletions

View File

@ -2,6 +2,10 @@
A self hosted photo library with user management & authentication. Cross platform Client supporting Android, iOS, and Web. A self hosted photo library with user management & authentication. Cross platform Client supporting Android, iOS, and Web.
## Server ## Server
### Dependencies
- libvips 8.3+ (8.8+ recommended)
### Running ### Running
CONFIG_PATH=$(pwd) DATA_PATH=$(pwd) go run cmd/main.go serve CONFIG_PATH=$(pwd) DATA_PATH=$(pwd) go run cmd/main.go serve

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/davidbyttow/govips/v2/vips"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"reichard.io/imagini/internal/api" "reichard.io/imagini/internal/api"
@ -44,6 +45,9 @@ func (s *Server) StartServer() {
} }
go func() { go func() {
vips.Startup(nil)
defer vips.Shutdown()
err := s.httpServer.ListenAndServe() err := s.httpServer.ListenAndServe()
if err != nil { if err != nil {
log.Error("Error starting server ", err) log.Error("Error starting server ", err)

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.15
require ( require (
github.com/99designs/gqlgen v0.13.0 github.com/99designs/gqlgen v0.13.0
github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 // indirect github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 // indirect
github.com/davidbyttow/govips/v2 v2.5.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/disintegration/imaging v1.6.2 // indirect github.com/disintegration/imaging v1.6.2 // indirect
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483 github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483

16
go.sum
View File

@ -26,6 +26,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidbyttow/govips v0.0.0-20201026223743-b1b72c7305d9 h1:nXNjG6pGALu++DSxtrdO79AZhtU+jZnyMlq3FTkFH74=
github.com/davidbyttow/govips/v2 v2.5.0 h1:CLSVkXwZYfF7bOR5bZwlUFL1TIWXwyuNF33UrlKscM4=
github.com/davidbyttow/govips/v2 v2.5.0/go.mod h1:goq38QD8XEMz2aWEeucEZqRxAWsemIN40vbUqfPfTAw=
github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -92,6 +95,8 @@ github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/h2non/bimg v1.1.5 h1:o3xsUBxM8s7+e7PmpiWIkEYdeYayJ94eh4cJLx67m1k=
github.com/h2non/bimg v1.1.5/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
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 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
@ -145,6 +150,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -213,12 +219,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
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=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -244,6 +252,8 @@ golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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=
@ -266,10 +276,15 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -311,6 +326,7 @@ gopkg.in/Acconut/lockfile.v1 v1.1.0/go.mod h1:6UCz3wJ8tSFUsPR6uP/j8uegEtDuEEqFxl
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag= gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

5
init_pkg_config_path Executable file
View File

@ -0,0 +1,5 @@
# nix-env -i vips
PKG1=$(find /nix/store/*/lib/pkgconfig/* -name 'vips.pc')
PKG2=$(find /nix/store/*/lib/pkgconfig/* -name 'gobject-2.0.pc')
export PKG_CONFIG_PATH=$PKG1:$PKG2

View File

@ -1,10 +1,17 @@
package api package api
import ( import (
"bytes"
"errors"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path" "path"
"strconv"
"time"
"github.com/davidbyttow/govips/v2/vips"
log "github.com/sirupsen/logrus"
"reichard.io/imagini/graph/model" "reichard.io/imagini/graph/model"
) )
@ -23,21 +30,14 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) {
} }
// Acquire Width & Height Parameters // Acquire Width & Height Parameters
query := r.URL.Query() width, _ := strconv.Atoi(r.URL.Query().Get("width"))
width := query["width"]
height := query["height"]
_ = width
_ = height
// TODO: Caching & Resizing
// - If both, force resize with new scale
// - If one, scale resize proportionally
// Pull out userID // Pull out userID
authContext := r.Context().Value("auth").(*model.AuthContext) authContext := r.Context().Value("auth").(*model.AuthContext)
rawUserID, _ := (*authContext.AccessToken).Get("sub") rawUserID, _ := (*authContext.AccessToken).Get("sub")
userID := rawUserID.(string) userID := rawUserID.(string)
// TODO: Derive Cache & Width Location
// Derive Path // Derive Path
fileName := path.Base(r.URL.Path) fileName := path.Base(r.URL.Path)
folderPath := path.Join("/" + api.Config.DataPath + "/media/" + userID) folderPath := path.Join("/" + api.Config.DataPath + "/media/" + userID)
@ -46,10 +46,54 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) {
// Check if File Exists // Check if File Exists
_, err := os.Stat(mediaPath) _, err := os.Stat(mediaPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
// TODO: Different HTTP Response Code? log.Warn("[media] Image does not exist on disk")
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusInternalServerError)
return return
} }
http.ServeFile(w, r, mediaPath) mediaFile, err := resizeAndConvertImage(mediaPath, width)
if err != nil {
log.Warn("[media] Image conversion failed:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// TODO: Cache mediaFile
http.ServeContent(w, r, fileName, time.Time{}, bytes.NewReader(mediaFile))
}
func resizeAndConvertImage(path string, desiredWidth int) ([]byte, error) {
inputImage, err := vips.NewImageFromFile(path)
if err != nil {
return nil, errors.New("[media] Unable to read image")
}
// If we're viewing full image, we want full quality
desiredQuality := 100
// Do we need to scale?
if desiredWidth != 0 && inputImage.Width() > desiredWidth {
desiredQuality = 50
desiredScale := float64(desiredWidth) / float64(inputImage.Width())
err := inputImage.Resize(desiredScale, vips.KernelLanczos3)
if err != nil {
return nil, errors.New("[media] Unable to resize")
}
} else if inputImage.Format() == vips.ImageTypeJPEG {
// Return raw file
return ioutil.ReadFile(path)
}
// Return non-converted but scaled image
if inputImage.Format() == vips.ImageTypeJPEG {
imageBytes, _, err := inputImage.ExportNative()
return imageBytes, err
}
// Convert
ep := vips.NewJpegExportParams()
ep.Quality = desiredQuality
imageBytes, _, err := inputImage.ExportJpeg(ep)
return imageBytes, err
} }

View File

@ -1 +0,0 @@
package api

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart';
import 'package:imagini/api/cookie_client/cookie_client.dart' import 'package:imagini/api/cookie_client/cookie_client.dart'
if (dart.library.html) 'package:imagini/api/cookie_client/browser_cookie_client.dart' if (dart.library.html) 'package:imagini/api/cookie_client/browser_cookie_client.dart'
@ -58,6 +59,16 @@ class APIProvider{
return response; return response;
} }
Image getImage(String fileName) {
// TODO: Get headers & Server
String fullURL = fileName;
return new Image.network(
fullURL,
headers: {},
fit: BoxFit.contain,
);
}
Future<QueryResult> me() async { Future<QueryResult> me() async {
QueryResult response = await _client.query( QueryResult response = await _client.query(
QueryOptions( QueryOptions(
@ -73,8 +84,6 @@ class APIProvider{
document: MediaItemsQuery().document, document: MediaItemsQuery().document,
) )
); );
print(response);
return response; return response;
} }

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:imagini/api/api_provider.dart'; import 'package:imagini/api/api_provider.dart';
import 'package:imagini/graphql/imagini_graphql.dart'; import 'package:imagini/graphql/imagini_graphql.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:flutter/material.dart';
class ImaginiAPIRepository { class ImaginiAPIRepository {
APIProvider _apiProvider; APIProvider _apiProvider;
@ -25,6 +26,13 @@ class ImaginiAPIRepository {
return Stream.fromFuture(_apiProvider.me()); return Stream.fromFuture(_apiProvider.me());
} }
Stream<MediaItems$Query> mediaItems() {
return Stream.fromFuture(_apiProvider.mediaItems().then((QueryResult resp) {
final mediaItemsResponse = MediaItems$Query.fromJson(resp.data);
return mediaItemsResponse;
}));
}
Stream<bool> isAuthenticated() { Stream<bool> isAuthenticated() {
return Stream.fromFuture(_apiProvider.me().then((QueryResult resp) { return Stream.fromFuture(_apiProvider.me().then((QueryResult resp) {
if (resp.exception != null) if (resp.exception != null)
@ -32,4 +40,8 @@ class ImaginiAPIRepository {
return true; return true;
})); }));
} }
Image getImage(String fileName) {
return _apiProvider.getImage(fileName);
}
} }

View File

@ -0,0 +1,52 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:imagini/core/imagini_application.dart';
import 'package:imagini/api/imagini_api_repository.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
class HomeBloc{
final ImaginiApplication _application;
ImaginiAPIRepository _imaginiAPI;
final _mediaItemsController = StreamController<MediaItems$Query>.broadcast();
Stream<MediaItems$Query> get mediaItemsResult => _mediaItemsController.stream;
final _loginController = StreamController<bool>.broadcast();
Stream<bool> get loginResult => _loginController.stream;
final _authenticatedController = StreamController<bool>.broadcast();
Stream<bool> get authenticatedResult => _authenticatedController.stream;
HomeBloc(this._application){
_init();
}
void _init(){
_imaginiAPI = _application.imaginiAPI;
getMediaItems();
}
void dispose(){
_loginController.close();
_authenticatedController.close();
_mediaItemsController.close();
}
Image getImage(String fileName){
return _imaginiAPI.getImage(fileName);
}
getMediaItems(){
_mediaItemsController.addStream(_imaginiAPI.mediaItems());
}
checkAuthentication(){
_authenticatedController.addStream(_imaginiAPI.isAuthenticated());
}
attemptHome(String username, password, server){
_loginController.addStream(_imaginiAPI.login(username, password, server));
}
}

View File

@ -3,6 +3,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:imagini/blocs/home_bloc.dart';
import 'package:imagini/core/app_provider.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
static const String PATH = '/Home'; static const String PATH = '/Home';
@ -14,19 +18,7 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> {
// HomeBloc bloc; HomeBloc bloc;
void _init(){
// if(null == bloc){
// bloc = HomeBloc(AppProvider.getApplication(context));
// }
}
@override
void dispose() {
super.dispose();
// bloc.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -38,6 +30,21 @@ class _HomeScreenState extends State<HomeScreen> {
); );
} }
void _init(){
if(bloc != null)
return;
bloc = HomeBloc(AppProvider.getApplication(context));
// bloc.mediaItemsResult.listen((bool status) {
// });
}
@override
void dispose() {
super.dispose();
// bloc.dispose();
}
Widget _buildNavBar() { Widget _buildNavBar() {
return PlatformNavBar( return PlatformNavBar(
currentIndex: 0, currentIndex: 0,
@ -76,18 +83,41 @@ class _HomeScreenState extends State<HomeScreen> {
// ); // );
// return new StaggeredGridView.countBuilder(
// crossAxisCount: 4,
// itemCount: 80,
// itemBuilder: (BuildContext context, int index) => _buildCard("https://i.imgur.com/CgSGqUz.jpeg"),
// // itemBuilder: (BuildContext context, int index) => new Container(
// // color: Colors.green,
// // child: new Center(
// // child: new CircleAvatar(
// // backgroundColor: Colors.white,
// // child: new Text('$index'),
// // ),
// // )),
// staggeredTileBuilder: (int index) =>
// // new StaggeredTile.count(2, index.isEven ? 2 : 1),
// new StaggeredTile.fit(2),
// mainAxisSpacing: 4.0,
// crossAxisSpacing: 4.0,
// );
return StreamBuilder<MediaItems$Query>(
stream: bloc.mediaItemsResult,
builder: (context, snapshot) {
if (snapshot.data == null)
return _appLoading();
List<MediaItems$Query$MediaItemResponse$MediaItem> allItems = snapshot.data.mediaItems.data;
return new StaggeredGridView.countBuilder( return new StaggeredGridView.countBuilder(
crossAxisCount: 4, crossAxisCount: 4,
itemCount: 80, itemCount: allItems.length,
itemBuilder: (BuildContext context, int index) => _buildCard("https://i.imgur.com/CgSGqUz.jpeg"), itemBuilder: (BuildContext context, int index) {
// itemBuilder: (BuildContext context, int index) => new Container( String fileName = allItems[index].fileName;
// color: Colors.green, print(fileName);
// child: new Center( return _buildCard("https://i.imgur.com/CgSGqUz.jpeg");
// child: new CircleAvatar( },
// backgroundColor: Colors.white,
// child: new Text('$index'),
// ),
// )),
staggeredTileBuilder: (int index) => staggeredTileBuilder: (int index) =>
// new StaggeredTile.count(2, index.isEven ? 2 : 1), // new StaggeredTile.count(2, index.isEven ? 2 : 1),
new StaggeredTile.fit(2), new StaggeredTile.fit(2),
@ -95,10 +125,38 @@ class _HomeScreenState extends State<HomeScreen> {
crossAxisSpacing: 4.0, crossAxisSpacing: 4.0,
); );
} }
);
}
Widget _appLoading(){
return Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 270,
child: Column(
children: <Widget>[
Container(
child: FittedBox(
fit: BoxFit.contain,
child: const FlutterLogo(),
),
width: 175,
margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
),
PlatformCircularProgressIndicator()
],
),
),
),
);
}
Widget _buildCard(charImageUrl) { Widget _buildCard(charImageUrl) {
return new Image.network( return new Image.network(
charImageUrl, charImageUrl,
headers: {},
fit: BoxFit.contain, fit: BoxFit.contain,
); );
} }

View File

@ -19,12 +19,6 @@ class _LoginScreenState extends State<LoginScreen> {
LoginBloc bloc; LoginBloc bloc;
@override
void dispose() {
super.dispose();
// bloc.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_init(); _init();
@ -42,9 +36,8 @@ class _LoginScreenState extends State<LoginScreen> {
} }
void _init(){ void _init(){
if(bloc != null){ if(bloc != null)
return; return;
}
bloc = LoginBloc(AppProvider.getApplication(context)); bloc = LoginBloc(AppProvider.getApplication(context));
bloc.authenticatedResult.listen((bool status) { bloc.authenticatedResult.listen((bool status) {
@ -60,6 +53,12 @@ class _LoginScreenState extends State<LoginScreen> {
}); });
} }
@override
void dispose() {
super.dispose();
// bloc.dispose();
}
Widget _appLoading(){ Widget _appLoading(){
return Center( return Center(
child: ConstrainedBox( child: ConstrainedBox(

View File

@ -0,0 +1,40 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class ImaginiImageProvider extends ImageProvider<ImaginiImageProvider> {
@override
final String url;
@override
final double scale;
@override
final Map<String, String> headers;
ImaginiImageProvider(this.url, {this.scale = 1.0, this.headers})
: assert(url != null),
assert(scale != null);
NetworkImage _netImg;
@override
ImageStreamCompleter load(ImaginiImageProvider key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(decode),
scale: 1.0,
debugLabel: fileId,
informationCollector: () sync* {
yield ErrorDescription('Path: $fileId');
},
);
}
Future<Codec> _loadAsync(DecoderCallback decode) async {
_netImg._loadAsync(decode);
}
@override
Future<ImaginiImageProvider> obtainKey(ImageConfiguration configuration) {
}
}