diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 754d18bd..30f417c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,22 +1,31 @@ name: Docker builds -env: - TARGET_PLATFORMS: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 - on: - schedule: - - cron: "0 10 * * *" # everyday at 10am pull_request: branches: master push: branches: master tags: - v* + +env: + DOCKER_USERNAME: viktorstrate + DOCKER_IMAGE: viktorstrate/photoview + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} jobs: build: - name: Build and deploy docker images + name: Build Docker Image runs-on: ubuntu-20.04 + + strategy: + matrix: + target_platform: + - "linux/amd64" + - "linux/arm64" + - "linux/arm/v7" + - "linux/arm/v6" + steps: - name: Checkout uses: actions/checkout@v2 @@ -26,14 +35,14 @@ jobs: id: cache with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} + key: ${{ runner.os }}-buildx-${{ secrets.CACHE_KEY }}-${{ matrix.target_platform }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-buildx- + ${{ runner.os }}-buildx-${{ secrets.CACHE_KEY }}-${{ matrix.target_platform }}- - name: Set up QEMU uses: docker/setup-qemu-action@v1 with: - platforms: ${{ env.TARGET_PLATFORMS }} + platforms: ${{ matrix.target_platform }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -41,24 +50,17 @@ jobs: - name: Prepare id: prepare run: | - DOCKER_USERNAME=viktorstrate - DOCKER_IMAGE=viktorstrate/photoview - DOCKER_PLATFORMS=${TARGET_PLATFORMS} + DOCKER_PLATFORMS=${{ matrix.target_platform }} + VERSION=edge - if [[ $GITHUB_REF == refs/tags/* ]]; then VERSION=${GITHUB_REF#refs/tags/v} fi - if [ "${{ github.event_name }}" = "schedule" ]; then - VERSION=nightly - fi - - TAGS="--tag ${DOCKER_IMAGE}:${VERSION}" - if [[ $VERSION =~ ^(([0-9]{1,3})\.[0-9]{1,3})\.[0-9]{1,3}$ ]]; then - VERSION_MINOR=${BASH_REMATCH[1]} - VERSION_MAJOR=${BASH_REMATCH[2]} - TAGS="$TAGS --tag ${DOCKER_IMAGE}:latest --tag ${DOCKER_IMAGE}:${VERSION_MINOR} --tag ${DOCKER_IMAGE}:${VERSION_MAJOR}" - fi + + OUTPUT_PLATFORM=$(echo ${{ matrix.target_platform }} | sed 's/\//-/g') + echo ::set-output name=output_platform::${OUTPUT_PLATFORM} + + TAG="--tag ${DOCKER_IMAGE}:${OUTPUT_PLATFORM}-${GITHUB_SHA::8}" echo ::set-output name=docker_username::${DOCKER_USERNAME} echo ::set-output name=docker_image::${DOCKER_IMAGE} @@ -70,30 +72,58 @@ jobs: --build-arg VERSION=${VERSION} \ --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ --build-arg VCS_REF=${GITHUB_SHA::8} \ - ${TAGS} --file Dockerfile . + ${TAG} --file Dockerfile . - - name: Build Docker images + - name: Docker Build run: | docker buildx build --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args }} - + - name: Docker Login - if: success() && github.event_name != 'pull_request' && github.repository == 'photoview/photoview' - env: - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} run: | echo "${DOCKER_PASSWORD}" | docker login --username "${{ steps.prepare.outputs.docker_username }}" --password-stdin - name: Push to Docker Hub - if: success() && github.event_name != 'pull_request' && github.repository == 'photoview/photoview' run: | docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} - - # - name: Docker Check Manifest - # if: always() && github.event_name != 'pull_request' && github.repository == 'photoview/photoview' - # run: | - # docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }} - + - name: Clear - if: always() && github.event_name != 'pull_request' run: | rm -f ${HOME}/.docker/config.json + + docker-manifest: + name: Combine Docker Images + runs-on: ubuntu-20.04 + if: github.event_name != 'pull_request' && github.repository == 'photoview/photoview' + + needs: [build] + + steps: + - name: Docker Login + run: | + echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin + + - name: Create Manifests + run: | + DOCKER_IMAGES="${DOCKER_IMAGE}:linux-amd64-${GITHUB_SHA::8}" + DOCKER_IMAGES="${DOCKER_IMAGES} ${DOCKER_IMAGE}:linux-arm64-${GITHUB_SHA::8}" + DOCKER_IMAGES="${DOCKER_IMAGES} ${DOCKER_IMAGE}:linux-arm-v7-${GITHUB_SHA::8}" + DOCKER_IMAGES="${DOCKER_IMAGES} ${DOCKER_IMAGE}:linux-arm-v6-${GITHUB_SHA::8}" + + VERSION=edge + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/v} + fi + + TAGS=("${VERSION}") + + if [[ $VERSION =~ ^(([0-9]{1,3})\.[0-9]{1,3})\.[0-9]{1,3}$ ]]; then + VERSION_MINOR=${BASH_REMATCH[1]} + VERSION_MAJOR=${BASH_REMATCH[2]} + TAGS+=("${VERSION_MAJOR}", "${VERSION_MINOR}") + fi + + for TAG in ${TAGS[*]}; do + echo "Creating tag: ${TAG}" + docker manifest create ${DOCKER_IMAGE}:${TAG} ${DOCKER_IMAGES} + docker manifest push ${DOCKER_IMAGE}:${TAG} + done diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 295cf405..2d199d3d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,8 +27,10 @@ jobs: - name: Get C dependencies run: | + sudo add-apt-repository ppa:strukturag/libheif + sudo add-apt-repository ppa:strukturag/libde265 sudo apt-get update - sudo apt-get install -y libdlib-dev libblas-dev liblapack-dev libjpeg-turbo8-dev + sudo apt-get install -y libdlib-dev libblas-dev liblapack-dev libjpeg-turbo8-dev libheif-dev - name: Get GO dependencies run: | diff --git a/Dockerfile b/Dockerfile index 2d6c3f85..9b06d826 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,29 +20,17 @@ COPY ui /app RUN npm run build -- --public-url $UI_PUBLIC_URL ### Build API ### -FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.15-buster AS api +FROM --platform=${BUILDPLATFORM:-linux/amd64} debian:bullseye-slim AS api +ARG TARGETPLATFORM -# Install G++/GCC cross compilers -RUN dpkg --add-architecture arm64 && dpkg --add-architecture armhf -RUN apt-get update && apt-get install -y \ - g++-aarch64-linux-gnu \ - libc6-dev-arm64-cross \ - g++-arm-linux-gnueabihf \ - libc6-dev-armhf-cross \ - # Install go-face dependencies - libdlib-dev libdlib-dev:arm64 libdlib-dev:armhf \ - libblas-dev libblas-dev:arm64 libblas-dev:armhf \ - liblapack-dev liblapack-dev:arm64 liblapack-dev:armhf \ - libjpeg62-turbo-dev libjpeg62-turbo-dev:arm64 libjpeg62-turbo-dev:armhf \ - # Cleanup - && apt-get clean && rm -rf /var/lib/apt/lists/* +COPY docker/install_build_dependencies.sh /tmp/ +RUN chmod +x /tmp/install_build_dependencies.sh && /tmp/install_build_dependencies.sh COPY docker/go_wrapper.sh /go/bin/go RUN chmod +x /go/bin/go ENV CGO_ENABLED 1 -ARG TARGETPLATFORM RUN go env RUN mkdir -p /app @@ -53,7 +41,7 @@ COPY api/go.mod api/go.sum /app/ RUN go mod download # Patch go-face -RUN sed -i 's/-march=native//g' /go/pkg/mod/github.com/!kagami/go-face*/face.go +RUN sed -i 's/-march=native//g' /root/go/pkg/mod/github.com/!kagami/go-face*/face.go # Build dependencies that use CGO RUN go install \ @@ -65,7 +53,7 @@ COPY api /app RUN go build -v -o photoview . ### Copy api and ui to production environment ### -FROM debian:buster +FROM debian:bullseye-slim ARG TARGETPLATFORM WORKDIR /app @@ -73,15 +61,11 @@ COPY api/data /app/data RUN apt-get update \ # Required dependencies - && apt-get install -y curl gpg libdlib19 ffmpeg exiftool + && apt-get install -y curl gpg libdlib19 ffmpeg exiftool libheif1 # Install Darktable if building for a supported architecture RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ] || [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \ - echo 'deb http://download.opensuse.org/repositories/graphics:/darktable/Debian_10/ /' | tee /etc/apt/sources.list.d/graphics:darktable.list \ - && curl -fsSL https://download.opensuse.org/repositories/graphics:darktable/Debian_10/Release.key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/graphics_darktable.gpg > /dev/null \ - && apt-get update \ - && apt-get install -y darktable; \ - fi + apt-get install -y darktable; fi # Remove build dependencies and cleanup RUN apt-get purge -y curl gpg \ diff --git a/README.md b/README.md index 83cfcf15..1c6a5451 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Password: **demo** - [Why yet another self-hosted photo gallery](#why-yet-another-self-hosted-photo-gallery) - [Getting started - Setup with Docker](#getting-started---setup-with-docker) - [Setup development environment](#setup-development-environment) +- [Sponsors](#sponsors) ## Main features @@ -139,3 +140,10 @@ cd ./ui && npm start The site can now be accessed at [localhost:1234](http://localhost:1234). And the graphql playground at [localhost:4001](http://localhost:4001) + +## Sponsors + + +
+ @ericerkz +
diff --git a/api/go.mod b/api/go.mod index 7d5ebbe2..3cc631d7 100644 --- a/api/go.mod +++ b/api/go.mod @@ -16,6 +16,7 @@ require ( github.com/kjeldgaard/go-exiftool v1.3.3 github.com/pkg/errors v0.9.1 github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f + github.com/strukturag/libheif v1.11.0 github.com/vektah/gqlparser/v2 v2.1.0 github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 github.com/xor-gate/goexif2 v1.1.0 diff --git a/api/go.sum b/api/go.sum index f17d24d2..03d6dc5f 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,6 +1,5 @@ github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA= github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Kagami/go-face v0.0.0-20200825065730-3dd2d74dccfb h1:DXwA1Te9paM+nsdTGc7uve37lq7WEbQO+gwGBPVwQuQ= github.com/Kagami/go-face v0.0.0-20200825065730-3dd2d74dccfb/go.mod h1:9wdDJkRgo3SGTcFwbQ7elVIQhIr2bbBjecuY7VoqmPU= @@ -16,11 +15,8 @@ github.com/barasher/go-exiftool v1.3.2/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9n github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -31,19 +27,14 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ= github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f h1:9oNbS1z4rVpbnkHBdPZU4jo9bSmrLpII768arSyMFgk= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -107,7 +98,6 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= @@ -115,85 +105,63 @@ github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kjeldgaard/go-exiftool v1.3.3 h1:tDlkcHtTuduu7UgopqGS/ooh1tmEgtIhlBjmTXzjxbg= github.com/kjeldgaard/go-exiftool v1.3.3/go.mod h1:Nmi1VkXBbr48Kq5ZJjo29RD+a3PdWVGlL+8bSssxFbU= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg= github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8= github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f h1:8P2MkG70G76gnZBOPGwmMIgwBb/rESQuwsJ7K8ds4NE= github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0 h1:JJV9CsgM9EC9w2iVkwuz+sMx8yRFe89PJRUrv6hPCIA= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -202,9 +170,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/strukturag/libheif v1.3.2 h1:NXft6czPSJiBeGc6hchNgczIY9aRPxsIcNIlVlDm2IY= +github.com/strukturag/libheif v1.3.2/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks= +github.com/strukturag/libheif v1.11.0 h1:HaWu5re98INSXNq7C8o5AwLcv2qD8+U7a+jVCpGWemI= +github.com/strukturag/libheif v1.11.0/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns= github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= @@ -212,19 +182,14 @@ github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlV github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/xor-gate/goexif2 v1.1.0 h1:OvTZ5iEvsDhRWFjV5xY3wT7uHFna28nSSP7ucau+cXQ= github.com/xor-gate/goexif2 v1.1.0/go.mod h1:eRjn3VSkAwpNpxEx/CGmd0zg0JFGL3akrSMxnJ581AY= -github.com/zenazn/goji v0.9.0 h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -239,17 +204,13 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP 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/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -261,9 +222,7 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -278,7 +237,6 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM= golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -288,11 +246,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/vansante/go-ffprobe.v2 v2.0.2 h1:DdxSfFnlqeawPIVbIQEI6LR6OQHQNR7tNgWb2mWuC4w= gopkg.in/vansante/go-ffprobe.v2 v2.0.2/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE= @@ -310,9 +265,6 @@ gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9D gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.20.12 h1:ebZ5KrSHzet+sqOCVdH9mTjW91L298nX3v5lVxAzSUY= gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755 h1:d2maSb13hr/ArmfK3rW+wNUKKfytCol7W1/vDHxMPiE= sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= -sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 h1:e1sMhtVq9AfcEy8AXNb8eSg6gbzfdpYhoNqnPJa+GzI= sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= diff --git a/api/graphql/resolvers/share_token.go b/api/graphql/resolvers/share_token.go index 6817d010..524f7b26 100644 --- a/api/graphql/resolvers/share_token.go +++ b/api/graphql/resolvers/share_token.go @@ -147,8 +147,15 @@ func (r *mutationResolver) ShareMedia(ctx context.Context, mediaID int, expire * var media models.Media + var query string + if r.Database.Dialector.Name() == "postgres" { + query = "EXISTS (SELECT * FROM user_albums WHERE user_albums.album_id = \"Album\".id AND user_albums.user_id = ?)" + } else { + query = "EXISTS (SELECT * FROM user_albums WHERE user_albums.album_id = Album.id AND user_albums.user_id = ?)" + } + err := r.Database.Joins("Album"). - Where("EXISTS (SELECT * FROM user_albums WHERE user_albums.album_id = Album.id AND user_albums.user_id = ?)", user.ID). + Where(query, user.ID). First(&media, mediaID). Error diff --git a/api/graphql/resolvers/user.go b/api/graphql/resolvers/user.go index 11e6bc85..a48bbb83 100644 --- a/api/graphql/resolvers/user.go +++ b/api/graphql/resolvers/user.go @@ -321,7 +321,7 @@ func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int, var deletedAlbumIDs []int = nil - err := r.Database.Transaction(func(tx *gorm.DB) error { + transactionError := r.Database.Transaction(func(tx *gorm.DB) error { if err := tx.Raw("DELETE FROM user_albums WHERE user_id = ? AND album_id = ?", userID, albumID).Error; err != nil { return err } @@ -346,12 +346,12 @@ func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int, } // Cleanup if no user owns the album anymore - var count int - if err := tx.Raw("SELECT COUNT(user_id) FROM user_albums WHERE album_id = ?", albumID).Scan(&count).Error; err != nil { + var userAlbumCount int + if err := tx.Raw("SELECT COUNT(user_id) FROM user_albums WHERE album_id = ?", albumID).Scan(&userAlbumCount).Error; err != nil { return err } - if count == 0 { + if userAlbumCount == 0 { deletedAlbumIDs = append(childAlbumIDs, albumID) childAlbumIDs = nil @@ -360,17 +360,13 @@ func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int, deletedAlbumIDs = nil return err } - - if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(); err != nil { - return err - } } return nil }) - if err != nil { - return nil, err + if transactionError != nil { + return nil, transactionError } if deletedAlbumIDs != nil { @@ -382,6 +378,11 @@ func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int, return nil, err } } + + // Reload faces as media might have been deleted + if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(r.Database); err != nil { + return nil, err + } } return &album, nil diff --git a/api/scanner/cleanup_media.go b/api/scanner/cleanup_media.go index 068891a3..90311221 100644 --- a/api/scanner/cleanup_media.go +++ b/api/scanner/cleanup_media.go @@ -52,7 +52,7 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error } // Reload faces after deleting media - if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(); err != nil { + if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(db); err != nil { deleteErrors = append(deleteErrors, errors.Wrap(err, "reload faces from database")) } } @@ -123,7 +123,7 @@ func deleteOldUserAlbums(db *gorm.DB, scannedAlbums []*models.Album, user *model } // Reload faces after deleting albums - if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(); err != nil { + if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(db); err != nil { deleteErrors = append(deleteErrors, err) } diff --git a/api/scanner/encode_photo.go b/api/scanner/encode_photo.go index faf9e0bc..6b1f9458 100644 --- a/api/scanner/encode_photo.go +++ b/api/scanner/encode_photo.go @@ -11,34 +11,29 @@ import ( "github.com/photoview/photoview/api/utils" "github.com/pkg/errors" "gopkg.in/vansante/go-ffprobe.v2" - "gorm.io/gorm" + + _ "github.com/strukturag/libheif/go/heif" ) -func DecodeImage(imagePath string) (image.Image, error) { - file, err := os.Open(imagePath) - if err != nil { - return nil, errors.Wrapf(err, "failed to open file to decode image (%s)", imagePath) - } - defer file.Close() +func EncodeThumbnail(inputPath string, outputPath string) (*image_helpers.PhotoDimensions, error) { - image, err := imaging.Decode(file, imaging.AutoOrientation(true)) + inputImage, err := imaging.Open(inputPath, imaging.AutoOrientation(true)) if err != nil { - return nil, errors.Wrapf(err, "failed to decode image (%s)", imagePath) + return nil, err } - return image, nil + dimensions := image_helpers.PhotoDimensionsFromRect(inputImage.Bounds()) + dimensions = dimensions.ThumbnailScale() + + thumbImage := imaging.Resize(inputImage, dimensions.Width, dimensions.Height, imaging.NearestNeighbor) + if err = encodeImageJPEG(thumbImage, outputPath, 60); err != nil { + return nil, err + } + + return &dimensions, nil } -// EncodeMediaData is used to easily decode media data, with a cache so expensive operations are not repeated -type EncodeMediaData struct { - media *models.Media - _photoImage image.Image - _thumbnailImage image.Image - _contentType *MediaType - _videoMetadata *ffprobe.ProbeData -} - -func EncodeImageJPEG(image image.Image, outputPath string, jpegQuality int) error { +func encodeImageJPEG(image image.Image, outputPath string, jpegQuality int) error { photo_file, err := os.Create(outputPath) if err != nil { return errors.Wrapf(err, "could not create file: %s", outputPath) @@ -53,6 +48,14 @@ func EncodeImageJPEG(image image.Image, outputPath string, jpegQuality int) erro return nil } +// EncodeMediaData is used to easily decode media data, with a cache so expensive operations are not repeated +type EncodeMediaData struct { + media *models.Media + _photoImage image.Image + _contentType *MediaType + _videoMetadata *ffprobe.ProbeData +} + // ContentType reads the image to determine its content type func (img *EncodeMediaData) ContentType() (*MediaType, error) { if img._contentType != nil { @@ -68,7 +71,7 @@ func (img *EncodeMediaData) ContentType() (*MediaType, error) { return imgType, nil } -func (img *EncodeMediaData) EncodeHighRes(tx *gorm.DB, outputPath string) error { +func (img *EncodeMediaData) EncodeHighRes(outputPath string) error { contentType, err := img.ContentType() if err != nil { return err @@ -89,36 +92,19 @@ func (img *EncodeMediaData) EncodeHighRes(tx *gorm.DB, outputPath string) error return errors.New("could not convert photo as no RAW converter was found") } } else { - image, err := img.photoImage(tx) + image, err := img.photoImage() if err != nil { return err } - EncodeImageJPEG(image, outputPath, 70) + encodeImageJPEG(image, outputPath, 70) } return nil } -func EncodeThumbnail(inputPath string, outputPath string) (*image_helpers.PhotoDimensions, error) { - inputImage, err := DecodeImage(inputPath) - if err != nil { - return nil, err - } - - dimensions := image_helpers.PhotoDimensionsFromRect(inputImage.Bounds()) - dimensions = dimensions.ThumbnailScale() - - thumbImage := imaging.Resize(inputImage, dimensions.Width, dimensions.Height, imaging.NearestNeighbor) - if err = EncodeImageJPEG(thumbImage, outputPath, 60); err != nil { - return nil, err - } - - return &dimensions, nil -} - -// PhotoImage reads and decodes the image file and saves it in a cache so the photo in only decoded once -func (img *EncodeMediaData) photoImage(tx *gorm.DB) (image.Image, error) { +// photoImage reads and decodes the image file and saves it in a cache so the photo in only decoded once +func (img *EncodeMediaData) photoImage() (image.Image, error) { if img._photoImage != nil { return img._photoImage, nil } @@ -130,7 +116,7 @@ func (img *EncodeMediaData) photoImage(tx *gorm.DB) (image.Image, error) { photoPath = img.media.Path } - photoImg, err := DecodeImage(photoPath) + photoImg, err := img.decodeImage(photoPath) if err != nil { return nil, utils.HandleError("image decoding", err) } @@ -138,3 +124,32 @@ func (img *EncodeMediaData) photoImage(tx *gorm.DB) (image.Image, error) { img._photoImage = photoImg return img._photoImage, nil } + +func (img *EncodeMediaData) decodeImage(imagePath string) (image.Image, error) { + file, err := os.Open(imagePath) + if err != nil { + return nil, errors.Wrapf(err, "failed to open file to decode image (%s)", imagePath) + } + defer file.Close() + + mediaType, err := img.ContentType() + if err != nil { + return nil, errors.Wrapf(err, "failed to get media content type needed to decode it (%s)", imagePath) + } + + var decodedImage image.Image + + if *mediaType == TypeHeic { + decodedImage, _, err = image.Decode(file) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode HEIF image (%s)", imagePath) + } + } else { + decodedImage, err = imaging.Decode(file, imaging.AutoOrientation(true)) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode image (%s)", imagePath) + } + } + + return decodedImage, nil +} diff --git a/api/scanner/face_detection/face_detector.go b/api/scanner/face_detection/face_detector.go index c9ee45a5..09e44a1c 100644 --- a/api/scanner/face_detection/face_detector.go +++ b/api/scanner/face_detection/face_detector.go @@ -13,7 +13,6 @@ import ( type FaceDetector struct { mutex sync.Mutex - db *gorm.DB rec *face.Recognizer faceDescriptors []face.Descriptor faceGroupIDs []int32 @@ -37,7 +36,6 @@ func InitializeFaceDetector(db *gorm.DB) error { } GlobalFaceDetector = FaceDetector{ - db: db, rec: rec, faceDescriptors: faceDescriptors, faceGroupIDs: faceGroupIDs, @@ -69,8 +67,8 @@ func getSamplesFromDatabase(db *gorm.DB) (samples []face.Descriptor, faceGroupID } // ReloadFacesFromDatabase replaces the in-memory face descriptors with the ones in the database -func (fd *FaceDetector) ReloadFacesFromDatabase() error { - faceDescriptors, faceGroupIDs, imageFaceIDs, err := getSamplesFromDatabase(fd.db) +func (fd *FaceDetector) ReloadFacesFromDatabase(db *gorm.DB) error { + faceDescriptors, faceGroupIDs, imageFaceIDs, err := getSamplesFromDatabase(db) if err != nil { return err } @@ -86,8 +84,8 @@ func (fd *FaceDetector) ReloadFacesFromDatabase() error { } // DetectFaces finds the faces in the given image and saves them to the database -func (fd *FaceDetector) DetectFaces(media *models.Media) error { - if err := fd.db.Model(media).Preload("MediaURL").First(&media).Error; err != nil { +func (fd *FaceDetector) DetectFaces(db *gorm.DB, media *models.Media) error { + if err := db.Model(media).Preload("MediaURL").First(&media).Error; err != nil { return err } @@ -118,7 +116,7 @@ func (fd *FaceDetector) DetectFaces(media *models.Media) error { } for _, face := range faces { - fd.classifyFace(&face, media, thumbnailPath) + fd.classifyFace(db, &face, media, thumbnailPath) } return nil @@ -128,7 +126,7 @@ func (fd *FaceDetector) classifyDescriptor(descriptor face.Descriptor) int32 { return int32(fd.rec.ClassifyThreshold(descriptor, 0.2)) } -func (fd *FaceDetector) classifyFace(face *face.Face, media *models.Media, imagePath string) error { +func (fd *FaceDetector) classifyFace(db *gorm.DB, face *face.Face, media *models.Media, imagePath string) error { fd.mutex.Lock() defer fd.mutex.Unlock() @@ -155,18 +153,18 @@ func (fd *FaceDetector) classifyFace(face *face.Face, media *models.Media, image ImageFaces: []models.ImageFace{imageFace}, } - if err := fd.db.Create(&faceGroup).Error; err != nil { + if err := db.Create(&faceGroup).Error; err != nil { return err } } else { log.Println("Found match") - if err := fd.db.First(&faceGroup, int(match)).Error; err != nil { + if err := db.First(&faceGroup, int(match)).Error; err != nil { return err } - if err := fd.db.Model(&faceGroup).Association("ImageFaces").Append(&imageFace); err != nil { + if err := db.Model(&faceGroup).Association("ImageFaces").Append(&imageFace); err != nil { return err } } diff --git a/api/scanner/media_type.go b/api/scanner/media_type.go index a1dfce6a..60d0e4eb 100644 --- a/api/scanner/media_type.go +++ b/api/scanner/media_type.go @@ -20,6 +20,7 @@ const ( TypeTiff MediaType = "image/tiff" TypeWebp MediaType = "image/webp" TypeBmp MediaType = "image/bmp" + TypeHeic MediaType = "image/heic" // Raw formats TypeDNG MediaType = "image/x-adobe-dng" @@ -73,6 +74,7 @@ var SupportedMimetypes = [...]MediaType{ TypeTiff, TypeWebp, TypeBmp, + TypeHeic, } var WebMimetypes = [...]MediaType{ @@ -144,6 +146,7 @@ var fileExtensions = map[string]MediaType{ ".tif": TypeTiff, ".tiff": TypeTiff, ".bmp": TypeBmp, + ".heic": TypeHeic, // RAW formats ".dng": TypeDNG, diff --git a/api/scanner/process_photo.go b/api/scanner/process_photo.go index 389d25cf..47baa032 100644 --- a/api/scanner/process_photo.go +++ b/api/scanner/process_photo.go @@ -149,7 +149,7 @@ func processPhoto(tx *gorm.DB, imageData *EncodeMediaData, photoCachePath *strin fmt.Printf("High-res photo found in database but not in cache, re-encoding photo to cache: %s\n", highResURL.MediaName) didProcess = true - err = imageData.EncodeHighRes(tx, baseImagePath) + err = imageData.EncodeHighRes(baseImagePath) if err != nil { return false, errors.Wrap(err, "creating high-res cached image") } @@ -275,7 +275,7 @@ func saveOriginalPhotoToDB(tx *gorm.DB, photo *models.Media, imageData *EncodeMe func generateSaveHighResJPEG(tx *gorm.DB, media *models.Media, imageData *EncodeMediaData, highres_name string, imagePath string, mediaURL *models.MediaURL) (*models.MediaURL, error) { - err := imageData.EncodeHighRes(tx, imagePath) + err := imageData.EncodeHighRes(imagePath) if err != nil { return nil, errors.Wrap(err, "creating high-res cached image") } diff --git a/api/scanner/scanner_album.go b/api/scanner/scanner_album.go index aa1fd647..9e89b3a3 100644 --- a/api/scanner/scanner_album.go +++ b/api/scanner/scanner_album.go @@ -12,7 +12,7 @@ import ( "github.com/photoview/photoview/api/scanner/face_detection" "github.com/photoview/photoview/api/utils" "github.com/pkg/errors" - "github.com/sabhiram/go-gitignore" + ignore "github.com/sabhiram/go-gitignore" "gorm.io/gorm" ) @@ -75,10 +75,11 @@ func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) { album_has_changes := false for count, media := range albumMedia { - // tx, err := db.Begin() + processing_was_needed := false transactionError := db.Transaction(func(tx *gorm.DB) error { - processing_was_needed, err := ProcessMedia(tx, media) + log.Printf("Process media transaction enter (%s)", media.Path) + processing_was_needed, err = ProcessMedia(tx, media) if err != nil { return errors.Wrapf(err, "failed to process photo (%s)", media.Path) } @@ -93,22 +94,23 @@ func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) { Content: fmt.Sprintf("Processed media at %s", media.Path), Progress: &progress, }) - - if media.Type == models.MediaTypePhoto { - go func() { - if err := face_detection.GlobalFaceDetector.DetectFaces(media); err != nil { - ScannerError("Error detecting faces in image (%s): %s", media.Path, err) - } - }() - } } + log.Printf("Process media transaction exit (%s)", media.Path) return nil }) if transactionError != nil { ScannerError("Failed to begin database transaction: %s", transactionError) } + + if processing_was_needed && media.Type == models.MediaTypePhoto { + go func(media *models.Media) { + if err := face_detection.GlobalFaceDetector.DetectFaces(db, media); err != nil { + ScannerError("Error detecting faces in image (%s): %s", media.Path, err) + } + }(media) + } } cleanup_errors := CleanupMedia(db, album.ID, albumMedia) @@ -139,14 +141,14 @@ func findMediaForAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.D } // Get ignore data - albumIgnore := ignore.CompileIgnoreLines(*cache.GetAlbumIgnore(album.Path)...) + albumIgnore := ignore.CompileIgnoreLines(*cache.GetAlbumIgnore(album.Path)...) for _, item := range dirContent { photoPath := path.Join(album.Path, item.Name()) if !item.IsDir() && isPathMedia(photoPath, cache) { // Match file against ignore data - if (albumIgnore.MatchesPath(item.Name())) { + if albumIgnore.MatchesPath(item.Name()) { log.Printf("File %s ignored\n", item.Name()) continue } diff --git a/docker/go_wrapper.sh b/docker/go_wrapper.sh index 4526eccb..e3887553 100644 --- a/docker/go_wrapper.sh +++ b/docker/go_wrapper.sh @@ -104,4 +104,4 @@ if [ -z "$GOBIN" ] && [ -n "$GOPATH" ] && [ -n "$GOARCH" ] && [ -n "$GOOS" ]; th export PATH=${GOPATH}/bin/${GOOS}_${GOARCH}:${PATH} fi -exec /usr/local/go/bin/go "$@" +exec /usr/bin/go "$@" diff --git a/docker/install_build_dependencies.sh b/docker/install_build_dependencies.sh new file mode 100644 index 00000000..0b71d39d --- /dev/null +++ b/docker/install_build_dependencies.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +if [ "$TARGETPLATFORM" == "linux/arm64" ]; then + dpkg --add-architecture arm64 + DEBIAN_ARCH='arm64' +elif [ "$TARGETPLATFORM" == "linux/arm/v6" ] || [ "$TARGETPLATFORM" == "linux/arm/v7" ]; then + dpkg --add-architecture armhf + DEBIAN_ARCH='armhf' +else + DEBIAN_ARCH='amd64' +fi + +apt-get update + +# Install Golang +apt-get install -y ca-certificates golang + +# Install G++/GCC cross compilers +apt-get install -y \ + g++-aarch64-linux-gnu \ + libc6-dev-arm64-cross \ + g++-arm-linux-gnueabihf \ + libc6-dev-armhf-cross + +# Install go-face dependencies +apt-get install -y \ + libdlib-dev:$DEBIAN_ARCH \ + libblas-dev:$DEBIAN_ARCH \ + liblapack-dev:$DEBIAN_ARCH \ + libjpeg62-turbo-dev:$DEBIAN_ARCH \ + +# Install libheif for HEIF media decoding +apt-get install -y \ + libheif-dev:$DEBIAN_ARCH + +# Cleanup +apt-get clean +rm -rf /var/lib/apt/lists/*