commit bec393cde779e1489cc97b2cfc6410dd92607515 Author: Marcus Noble Date: Sun Apr 11 14:04:56 2021 +0100 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..aa2f1cc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +node_modules +jspm_packages +.env +Makefile +.git +.DS_Store +*.out +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.npm +.node_repl_history +.vscode +*.code-workspace +.history/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad062f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,170 @@ +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +### Go Patch ### +/vendor/ +/Godeps/ + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# rollup.js default build output +dist/ + +# Storybook build outputs +.out +.storybook-out + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Temporary folders +tmp/ +temp/ + +# VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace +.history/ + +# MacOS +# General +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eca32f0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.16-alpine AS builder +RUN apk update && apk add --no-cache git && apk add -U --no-cache ca-certificates +WORKDIR /app/ +ADD go.mod go.sum ./ +RUN go mod download +ADD . . +RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o photo-go . + +FROM scratch +WORKDIR /app/ +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /app/photo-go /app/photo-go +ENTRYPOINT ["/app/photo-go"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2955198 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +MIT License Copyright (c) 2020 - present Marcus Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..559e1a4 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +.DEFAULT_GOAL := default + +IMAGE ?= docker.cluster.fun/averagemarcus/photo-go:latest + +.PHONY: test # Run all tests, linting and format checks +test: lint check-format run-tests + +.PHONY: lint # Perform lint checks against code +lint: + @go vet && golint -set_exit_status ./... + +.PHONY: check-format # Checks code formatting and returns a non-zero exit code if formatting errors found +check-format: + @gofmt -e -l . + +.PHONY: format # Performs automatic format fixes on all code +format: + @gofmt -s -w . + +.PHONY: run-tests # Runs all tests +run-tests: + @go test + +.PHONY: fetch-deps # Fetch all project dependencies +fetch-deps: + @go mod tidy + +.PHONY: build # Build the project +build: lint check-format fetch-deps + @go build -o ${PROJECT_NAME} main.go + +.PHONY: docker-build # Build the docker image +docker-build: + @docker build -t $(IMAGE) . + +.PHONY: docker-publish # Push the docker image to the remote registry +docker-publish: + @docker push $(IMAGE) + +.PHONY: run # Run the application +run: + @go run main.go + +.PHONY: ci # Perform CI specific tasks to perform on a pull request +ci: + @echo "⚠️ 'ci' unimplemented" + +.PHONY: release # Release the latest version of the application +release: + @echo "⚠️ 'release' unimplemented" + +.PHONY: help # Show this list of commands +help: + @echo "photo-go" + @echo "Usage: make [target]" + @echo "" + @echo "target description" | expand -t20 + @echo "-----------------------------------" + @grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1 \2/' | expand -t20 + +default: test build diff --git a/README.md b/README.md new file mode 100644 index 0000000..a01a789 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# photo-go + +A basic photo display for use as a digital photo frame + +## Features + +* Transition between photos +* Random photo ordering +* Automatic cropping of image to size of browser + +## Building from source + +With Docker: + +```sh +make docker-build +``` + +Standalone: + +```sh +make build +``` + +## Resources + +* [artyom/smartcrop](https://github.com/artyom/smartcrop) +* [edwvee/exiffix](https://github.com/edwvee/exiffix) +* [gofiber/fiber](https://github.com/gofiber/fiber) + +## Contributing + +If you find a bug or have an idea for a new feature please [raise an issue](issues/new) to discuss it. + +Pull requests are welcomed but please try and follow similar code style as the rest of the project and ensure all tests and code checkers are passing. + +Thank you 💛 + +## License + +See [LICENSE](LICENSE) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..027699a --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module photo-go + +go 1.16 + +require ( + github.com/artyom/smartcrop v0.0.0-20151228104656-7a9cbb970c13 + github.com/bamiaux/rez v0.0.0-20170731184118-29f4463c688b // indirect + github.com/disintegration/imaging v1.6.2 // indirect + github.com/edwvee/exiffix v0.0.0-20190810152521-16aac9658f23 + github.com/gofiber/fiber/v2 v2.7.1 + github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect + golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..40edc29 --- /dev/null +++ b/go.sum @@ -0,0 +1,37 @@ +github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/artyom/smartcrop v0.0.0-20151228104656-7a9cbb970c13 h1:ifG8HbKnkohE3TaQtAIQ5K9qZ5VbIcodWX/Ay4rRNJM= +github.com/artyom/smartcrop v0.0.0-20151228104656-7a9cbb970c13/go.mod h1:bl6XEUJ4zdB8sT4TgmXIbRHGw4hIcsErGaJjJwyc2oE= +github.com/bamiaux/rez v0.0.0-20170731184118-29f4463c688b h1:5Ci5wpOL75rYF6RQGRoqhEAU6xLJ6n/D4SckXX1yB74= +github.com/bamiaux/rez v0.0.0-20170731184118-29f4463c688b/go.mod h1:obBQGGIFbbv9KWg92Qu9UHeD94JXmHD1jovY/z6I3O8= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/edwvee/exiffix v0.0.0-20190810152521-16aac9658f23 h1:cHT1oZQVzPQzq3Iz3CCISUA4X94kHdoOFJ/xcbwEtqs= +github.com/edwvee/exiffix v0.0.0-20190810152521-16aac9658f23/go.mod h1:KoE3Ti1qbQXCb3s/XGj0yApHnbnNnn1bXTtB5Auq/Vc= +github.com/gofiber/fiber/v2 v2.7.1 h1:CpzGXD+7VhmptQ6McU5qYyRFxZdQkP2Y32ySIid+BXQ= +github.com/gofiber/fiber/v2 v2.7.1/go.mod h1:f8BRRIMjMdRyt2qmJ/0Sea3j3rwwfufPrh9WNBRiVZ0= +github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.18.0 h1:IV0DdMlatq9QO1Cr6wGJPVW1sV1Q8HvZXAIcjorylyM= +github.com/valyala/fasthttp v1.18.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk= +golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201210223839-7e3030f88018 h1:XKi8B/gRBuTZN1vU9gFsLMm6zVz5FSCDzm8JYACnjy8= +golang.org/x/sys v0.0.0-20201210223839-7e3030f88018/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/index.html b/index.html new file mode 100644 index 0000000..6777d79 --- /dev/null +++ b/index.html @@ -0,0 +1,51 @@ + + + + Photos + + + +
+
+ + + + diff --git a/main.go b/main.go new file mode 100644 index 0000000..4108d4a --- /dev/null +++ b/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "bytes" + "embed" + "image" + "image/jpeg" + "log" + "math/rand" + "os" + "path/filepath" + "strconv" + + "github.com/artyom/smartcrop" + "github.com/edwvee/exiffix" + "github.com/gofiber/fiber/v2" +) + +//go:embed index.html + +var content embed.FS + +var imageDirectory = os.Getenv("IMAGE_DIRECTORY") + +func main() { + app := fiber.New() + + app.Get("/", func(c *fiber.Ctx) error { + body, _ := content.ReadFile("index.html") + c.Type("html") + return c.Send(body) + }) + + app.Get("/image", func(c *fiber.Ctx) error { + photos, _ := filepath.Glob(filepath.Join(imageDirectory, "*.jpg")) + width, _ := strconv.Atoi(c.Query("width", "1280")) + height, _ := strconv.Atoi(c.Query("height", "800")) + + img := cropImage(photos[rand.Intn(len(photos))], width, height) + + buf := new(bytes.Buffer) + jpeg.Encode(buf, img, &jpeg.Options{Quality: 100}) + + c.Type("jpg") + return c.SendStream(buf, buf.Len()) + }) + + app.Listen(":3000") +} + +func cropImage(imgSrc string, width, height int) image.Image { + f, err := os.Open(imgSrc) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + img, _, err := exiffix.Decode(f) + if err != nil { + log.Fatal(err) + } + + topCrop, err := smartcrop.Crop(img, width, height) + if err != nil { + log.Fatal(err) + } + + type subImager interface { + SubImage(image.Rectangle) image.Image + } + if si, ok := img.(subImager); ok { + return si.SubImage(topCrop) + } + return nil +}