diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml new file mode 100644 index 0000000..7a5f64e --- /dev/null +++ b/.github/workflows/builder.yml @@ -0,0 +1,74 @@ +name: Build and Push Builder Image + +on: + pull_request: + paths: + - 'Dockerfile.builder' + - '.github/workflows/builder.yml' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}-builder + GOLANG_VERSION: '1.25.1' + FFMPEG_VERSION: '7.1.1' + VIPS_VERSION: '8.17.2' + +jobs: + build-builder: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set build arguments + id: args + run: | + echo "ffmpeg_version=${{ env.FFMPEG_VERSION }}" >> $GITHUB_OUTPUT + echo "vips_version=${{ env.VIPS_VERSION }}" >> $GITHUB_OUTPUT + echo "golang_version=${{ env.GOLANG_VERSION }}" >> $GITHUB_OUTPUT + + # Create version tag + VERSION_TAG="ffmpeg-${{ env.FFMPEG_VERSION }}-vips-${{ env.VIPS_VERSION }}-go-${{ env.GOLANG_VERSION }}" + echo "version_tag=${VERSION_TAG}" >> $GITHUB_OUTPUT + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest + type=raw,value=${{ steps.args.outputs.version_tag }} + + - name: Build and push builder image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.builder + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + GOLANG_VERSION=${{ steps.args.outputs.golang_version }} + FFMPEG_VERSION=${{ steps.args.outputs.ffmpeg_version }} + VIPS_VERSION=${{ steps.args.outputs.vips_version }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Image digest + run: echo ${{ steps.build.outputs.digest }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8ea71c0..f164dfc 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,13 +1,9 @@ name: docker -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - on: push: - branches: [ master ] + branches: + - master tags: [ 'v*.*.*' ] jobs: @@ -17,32 +13,21 @@ jobs: permissions: contents: read packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - # Setup qemu for Docker buildx - name: Set up QEMU - id: qemu - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: - image: tonistiigi/binfmt:latest platforms: arm64 - # Workaround: https://github.com/docker/build-push-action/issues/461 - name: Setup Docker buildx - uses: docker/setup-buildx-action@v2 - with: - version: latest + uses: docker/setup-buildx-action@v3 - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -50,44 +35,28 @@ jobs: - name: Login to Docker Hub if: github.repository == 'cshum/imagorvideo' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: | - name=${{ format('ghcr.io/{0}', github.repository) }},enable=true + name=ghcr.io/${{ github.repository }},enable=true name=shumc/imagorvideo,enable=${{ github.repository == 'cshum/imagorvideo' }} tags: | type=ref,event=branch type=semver,pattern={{version}} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image tag - if: startsWith(github.ref, 'refs/tags/v') - id: build-and-push-tag - uses: docker/build-push-action@v3 + - name: Build and push Docker image + uses: docker/build-push-action@v5 with: context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Build and push Docker image branch - if: startsWith(github.ref, 'refs/heads') - id: build-and-push-branch - uses: docker/build-push-action@v3 - with: - context: . - platforms: linux/amd64 + platforms: ${{ startsWith(github.ref, 'refs/tags/v') && 'linux/amd64,linux/arm64' || 'linux/amd64' }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4092b0..3942133 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,126 +1,57 @@ name: test -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: + branches: + - master jobs: - build: - name: Test - runs-on: ubuntu-22.04 - env: - CGO_CFLAGS_ALLOW: -Xpreprocessor - VIPS_VERSION: 8.15.2 - FFMPEG_VERSION: 5.1.2 - V: 4 - + test: + name: Docker Test + runs-on: ubuntu-24.04 steps: - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: ^1.22 - - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Install linux dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - meson ninja-build \ - libglib2.0-dev libexpat-dev librsvg2-dev libpng-dev \ - libjpeg-turbo8-dev libimagequant-dev libfftw3-dev \ - libpoppler-glib-dev libxml2-dev \ - libopenslide-dev libcfitsio-dev liborc-0.4-dev libpango1.0-dev \ - libtiff5-dev libgsf-1-dev giflib-tools libwebp-dev libheif-dev \ - yasm libx264-dev libx265-dev libnuma-dev libvpx-dev libtheora-dev \ - librtmp-dev libvorbis-dev \ - libopenjp2-7-dev libcgif-dev + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Cache libvips - uses: actions/cache@v3 + - name: Build test image + uses: docker/build-push-action@v5 with: - path: vips-${{ env.VIPS_VERSION }} - key: ${{ runner.os }}-vips-${{ env.V }}-${{ env.VIPS_VERSION }} - restore-keys: | - ${{ runner.os }}-vips-${{ env.V }}- + context: . + target: builder + tags: imagorvideo-test + load: true + cache-from: type=gha + cache-to: type=gha,mode=max - - name: Build libvips from source + - name: Run tests in Docker run: | - if [ ! -d "vips-${{ env.VIPS_VERSION }}" ] - then - wget https://github.com/libvips/libvips/releases/download/v${{ env.VIPS_VERSION }}/vips-${{ env.VIPS_VERSION }}.tar.xz - tar xf vips-${{ env.VIPS_VERSION }}.tar.xz - fi - cd vips-${{ env.VIPS_VERSION }} - meson setup _build \ - --buildtype=release \ - --strip \ - --prefix=/usr/local \ - --libdir=lib \ - -Dgtk_doc=false \ - -Dmagick=disabled \ - -Dintrospection=disabled - ninja -C _build - sudo ninja -C _build install - sudo ldconfig - - - name: Cache ffmpeg - uses: actions/cache@v3 - with: - path: ffmpeg-${{ env.FFMPEG_VERSION }} - key: ${{ runner.os }}-ffmpeg-${{ env.V }}-${{ env.FFMPEG_VERSION }} - restore-keys: | - ${{ runner.os }}-ffmpeg-${{ env.V }}- - - - - name: Build ffmpeg from source - run: | - if [ ! -d "ffmpeg-${{ env.FFMPEG_VERSION }}" ] - then - wget https://ffmpeg.org/releases/ffmpeg-${{ env.FFMPEG_VERSION }}.tar.bz2 - tar jvxf ffmpeg-${{ env.FFMPEG_VERSION }}.tar.bz2 - fi - cd ffmpeg-${{ env.FFMPEG_VERSION }} - ./configure --prefix=/usr/local \ - --disable-debug \ - --disable-doc \ - --disable-ffplay \ - --disable-static \ - --enable-shared \ - --enable-version3 \ - --enable-gpl \ - --enable-libtheora \ - --enable-libvorbis \ - --enable-librtmp \ - --enable-libwebp \ - --enable-libvpx \ - --enable-libx265 \ - --enable-libx264 - make && sudo make install - sudo ldconfig - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Get dependencies - run: make get - - - name: Test - run: make test + docker run --rm \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + imagorvideo-test \ + go test ./... -v - name: Commit golden files - if: github.event_name != 'pull_request' - uses: stefanzweifel/git-auto-commit-action@v4 + if: github.event_name == 'pull_request' + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "test: update golden files" file_pattern: "testdata/golden" + - name: Run coverage in Docker + run: | + docker run --rm \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + imagorvideo-test \ + sh -c "rm -rf examples cmd && go test -coverprofile=profile.cov ./..." + - name: Coveralls uses: shogo82148/actions-goveralls@v1 with: diff --git a/Dockerfile b/Dockerfile index eddff2a..cff4e04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,66 +1,9 @@ -ARG GOLANG_VERSION=1.22.5 -FROM golang:${GOLANG_VERSION}-bookworm as builder +ARG BUILDER_IMAGE_TAG=ffmpeg-7.1.1-vips-8.17.2-go-1.25.1 -ARG FFMPEG_VERSION=5.1.2 -ARG VIPS_VERSION=8.15.2 -ARG TARGETARCH +# Stage 1: Build application using builder image with go + libvips + FFmpeg +FROM ghcr.io/cshum/imagorvideo-builder:${BUILDER_IMAGE_TAG} AS builder ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig -ENV MAKEFLAGS="-j8" - - -# Installs libvips + required libraries -RUN DEBIAN_FRONTEND=noninteractive \ - apt-get update && \ - apt-get install --no-install-recommends -y \ - ca-certificates \ - automake build-essential curl \ - meson ninja-build pkg-config \ - gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg62-turbo-dev libpng-dev \ - libwebp-dev libtiff-dev libexif-dev libxml2-dev libpoppler-glib-dev \ - swig libpango1.0-dev libmatio-dev libopenslide-dev libcfitsio-dev libopenjp2-7-dev \ - libgsf-1-dev libfftw3-dev liborc-0.4-dev librsvg2-dev libimagequant-dev libaom-dev libheif-dev \ - yasm libx264-dev libx265-dev libnuma-dev libvpx-dev libtheora-dev \ - libspng-dev libcgif-dev librtmp-dev libvorbis-dev && \ - cd /tmp && \ - curl -fsSLO https://github.com/libvips/libvips/releases/download/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.xz && \ - tar xf vips-${VIPS_VERSION}.tar.xz && \ - cd vips-${VIPS_VERSION} && \ - meson setup _build \ - --buildtype=release \ - --strip \ - --prefix=/usr/local \ - --libdir=lib \ - -Dgtk_doc=false \ - -Dmagick=disabled \ - -Dintrospection=disabled && \ - ninja -C _build && \ - ninja -C _build install && \ - cd /tmp && \ - curl -fsSLO https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \ - tar jvxf ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \ - cd /tmp/ffmpeg-${FFMPEG_VERSION} && \ - ./configure --prefix=/usr/local \ - --disable-debug \ - --disable-doc \ - --disable-ffplay \ - --disable-static \ - --enable-shared \ - --enable-version3 \ - --enable-gpl \ - --enable-libtheora \ - --enable-libvorbis \ - --enable-librtmp \ - --enable-libwebp \ - --enable-libvpx \ - --enable-libx265 \ - --enable-libx264 && \ - make && make install && \ - ldconfig && \ - rm -rf /usr/local/lib/python* && \ - rm -rf /usr/local/lib/libvips-cpp.* && \ - rm -rf /usr/local/lib/*.a && \ - rm -rf /usr/local/lib/*.la WORKDIR ${GOPATH}/src/github.com/cshum/imagorvideo @@ -73,22 +16,23 @@ COPY . . RUN go build -o ${GOPATH}/bin/imagorvideo ./cmd/imagorvideo/main.go -FROM debian:bookworm-slim +# Stage 2: Runtime image +FROM debian:trixie-slim as runtime LABEL maintainer="adrian@cshum.com" COPY --from=builder /usr/local/lib /usr/local/lib COPY --from=builder /etc/ssl/certs /etc/ssl/certs -# Install runtime dependencies RUN DEBIAN_FRONTEND=noninteractive \ apt-get update && \ apt-get install --no-install-recommends -y \ - procps libglib2.0-0 libjpeg62-turbo libpng16-16 libopenexr-3-1-30 \ - libwebp7 libwebpmux3 libwebpdemux2 libtiff6 libexif12 libxml2 libpoppler-glib8 \ - libpango1.0-0 libmatio11 libopenslide0 libopenjp2-7 libjemalloc2 \ - libgsf-1-114 libfftw3-bin liborc-0.4-0 librsvg2-2 libcfitsio10 libimagequant0 libaom3 libheif1 \ - libx264-dev libx265-dev libnuma-dev libvpx7 libtheora0 libvorbis-dev \ - libspng0 libcgif0 && \ + procps curl libglib2.0-0 libjpeg62-turbo libpng16-16 libopenexr-3-1-30 \ + libwebp7 libwebpmux3 libwebpdemux2 libtiff6 libexif12 libxml2 libpoppler-glib8t64 \ + libpango-1.0-0 libmatio13 libopenslide0 libopenjp2-7 libjemalloc2 \ + libgsf-1-114 libfftw3-bin liborc-0.4-0 librsvg2-2 libcfitsio10t64 libimagequant0 libaom3 \ + libspng0 libcgif0 libheif1 libheif-plugin-x265 libheif-plugin-aomenc libjxl0.11 libavif-dev \ + libmagickwand-7.q16-10 \ + libdav1d7 libx264-dev libx265-dev libnuma-dev libvpx9 libtheora0 libvorbis-dev && \ ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \ apt-get autoremove -y && \ apt-get autoclean && \ diff --git a/Dockerfile.builder b/Dockerfile.builder new file mode 100644 index 0000000..feaa666 --- /dev/null +++ b/Dockerfile.builder @@ -0,0 +1,73 @@ +ARG GOLANG_VERSION=1.25.1 +ARG FFMPEG_VERSION=7.1.1 +ARG VIPS_VERSION=8.17.2 + +FROM golang:${GOLANG_VERSION}-trixie AS cache-builder + +ARG FFMPEG_VERSION +ARG VIPS_VERSION +ARG TARGETARCH + +ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig +ENV MAKEFLAGS="-j8" + +# Install libvips + FFmpeg + required libraries +RUN DEBIAN_FRONTEND=noninteractive \ + apt-get update && \ + apt-get install --no-install-recommends -y \ + ca-certificates \ + automake build-essential curl \ + meson ninja-build pkg-config \ + gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg62-turbo-dev libpng-dev \ + libwebp-dev libtiff-dev libexif-dev libxml2-dev libpoppler-glib-dev \ + swig libpango1.0-dev libmatio-dev libopenslide-dev libcfitsio-dev libopenjp2-7-dev liblcms2-dev \ + libgsf-1-dev libfftw3-dev liborc-0.4-dev librsvg2-dev libimagequant-dev libaom-dev \ + libspng-dev libcgif-dev libheif-dev libheif-plugin-x265 libheif-plugin-aomenc libjxl-dev libavif-dev \ + libmagickwand-dev \ + yasm libx264-dev libx265-dev libnuma-dev libvpx-dev libtheora-dev \ + librtmp-dev libvorbis-dev libdav1d-dev && \ + cd /tmp && \ + curl -fsSLO https://github.com/libvips/libvips/releases/download/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.xz && \ + tar xf vips-${VIPS_VERSION}.tar.xz && \ + cd vips-${VIPS_VERSION} && \ + meson setup _build \ + --buildtype=release \ + --strip \ + --prefix=/usr/local \ + --libdir=lib \ + -Dmagick=enabled \ + -Djpeg-xl=enabled \ + -Dintrospection=disabled && \ + ninja -C _build && \ + ninja -C _build install && \ + cd /tmp && \ + curl -fsSLO https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz && \ + tar xf ffmpeg-${FFMPEG_VERSION}.tar.xz && \ + cd /tmp/ffmpeg-${FFMPEG_VERSION} && \ + ./configure --prefix=/usr/local \ + --disable-debug \ + --disable-doc \ + --disable-ffplay \ + --disable-static \ + --enable-shared \ + --enable-version3 \ + --enable-gpl \ + --enable-libtheora \ + --enable-libvorbis \ + --enable-librtmp \ + --enable-libwebp \ + --enable-libvpx \ + --enable-libx265 \ + --enable-libx264 \ + --enable-libdav1d \ + --enable-libaom && \ + make && make install && \ + ldconfig && \ + rm -rf /usr/local/lib/python* && \ + rm -rf /usr/local/lib/libvips-cpp.* && \ + rm -rf /usr/local/lib/*.a && \ + rm -rf /usr/local/lib/*.la && \ + rm -rf /tmp/* + +# This cache image provides Go + compiled libvips + FFmpeg libraries +LABEL maintainer="imagorvideo" diff --git a/README.md b/README.md index 32363c3..c3b21fb 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Test Status](https://github.com/cshum/imagorvideo/workflows/test/badge.svg)](https://github.com/cshum/imagorvideo/actions/workflows/test.yml) [![Coverage Status](https://img.shields.io/coverallsCoverage/github/cshum/imagorvideo)](https://coveralls.io/github/cshum/imagorvideo?branch=master) [![Docker Hub](https://img.shields.io/badge/docker-shumc/imagorvideo-blue.svg)](https://hub.docker.com/r/shumc/imagorvideo/) -[![GitHub Container Registry](https://ghcr-badge.deta.dev/cshum/imagorvideo/latest_tag?trim=major&label=ghcr.io&ignore=next,master&color=%23007ec6)](https://github.com/cshum/imagorvideo/pkgs/container/imagorvideo) imagorvideo is a new initiative that brings video thumbnail capability through ffmpeg, built on the foundations of [imagor](https://github.com/cshum/imagor) - a fast, secure image processing server and Go library, using libvips. diff --git a/cmd/imagorvideo/main.go b/cmd/imagorvideo/main.go index 0c0f6cd..ab9cedf 100644 --- a/cmd/imagorvideo/main.go +++ b/cmd/imagorvideo/main.go @@ -1,26 +1,25 @@ package main import ( - imagorvideoextended "git.cef.icu/CEF/imagorextended" - "github.com/cshum/imagor/config" - "github.com/cshum/imagor/config/awsconfig" - "github.com/cshum/imagor/config/gcloudconfig" - "github.com/cshum/imagor/config/vipsconfig" - "github.com/cshum/imagorvideo" - "os" + // imagorvideoextended "git.cef.icu/CEF/imagorextended" + "github.com/cshum/imagor/config" + "github.com/cshum/imagor/config/awsconfig" + "github.com/cshum/imagor/config/gcloudconfig" + "github.com/cshum/imagor/config/vipsconfig" + "github.com/cshum/imagorvideo" + "os" ) func main() { - var server = config.CreateServer( - os.Args[1:], - //imagorvideoextended.LoaderConfig, - imagorvideoextended.Config, - imagorvideo.Config, - vipsconfig.WithVips, - awsconfig.WithAWS, - gcloudconfig.WithGCloud, - ) - if server != nil { - server.Run() - } + var server = config.CreateServer( + os.Args[1:], + // imagorvideoextended.Config, + imagorvideo.Config, + vipsconfig.WithVips, + awsconfig.WithAWS, + gcloudconfig.WithGCloud, + ) + if server != nil { + server.Run() + } } diff --git a/ffmpeg/callback.go b/ffmpeg/callback.go new file mode 100644 index 0000000..7924535 --- /dev/null +++ b/ffmpeg/callback.go @@ -0,0 +1,56 @@ +package ffmpeg + +// #include "ffmpeg.h" +import "C" +import ( + "io" + "reflect" + "unsafe" + + "github.com/cshum/vipsgen/pointer" +) + +//export goPacketRead +func goPacketRead(opaque unsafe.Pointer, buffer *C.uint8_t, bufSize C.int) C.int { + ctx, ok := pointer.Restore(opaque).(*AVContext) + if !ok || ctx.reader == nil { + return C.int(ErrUnknown) + } + size := int(bufSize) + sh := &reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(buffer)), + Len: size, + Cap: size, + } + buf := *(*[]byte)(unsafe.Pointer(sh)) + n, err := ctx.reader.Read(buf) + if err == io.EOF { + if n == 0 { + return C.int(ErrEOF) + } + } else if err != nil { + return C.int(ErrUnknown) + } + return C.int(n) +} + +//export goPacketSeek +func goPacketSeek(opaque unsafe.Pointer, offset C.int64_t, whence C.int) C.int64_t { + ctx, ok := pointer.Restore(opaque).(*AVContext) + if !ok || ctx.seeker == nil { + return C.int64_t(ErrUnknown) + } + if whence == C.AVSEEK_SIZE { + return C.int64_t(ctx.size) + } + n, err := ctx.seeker.Seek(int64(offset), int(whence)) + if err != nil { + return C.int64_t(ErrUnknown) + } + return C.int64_t(n) +} + +//export goAVLoggingHandler +func goAVLoggingHandler(level C.int, cstr *C.char) { + log(AVLogLevel(level), C.GoString(cstr)) +} diff --git a/ffmpeg/errors.go b/ffmpeg/errors.go new file mode 100644 index 0000000..0ffd823 --- /dev/null +++ b/ffmpeg/errors.go @@ -0,0 +1,38 @@ +package ffmpeg + +// #include "ffmpeg.h" +import "C" + +type avError int + +// AV Error enum +const ( + ErrNoMem = avError(-C.ENOMEM) + ErrEOF = avError(C.AVERROR_EOF) + ErrUnknown = avError(C.AVERROR_UNKNOWN) + ErrDecoderNotFound = avError(C.AVERROR_DECODER_NOT_FOUND) + ErrInvalidData = avError(C.AVERROR_INVALIDDATA) + ErrTooBig = avError(C.ERR_TOO_BIG) +) + +func (e avError) errorString() string { + switch e { + case ErrNoMem: + return "cannot allocate memory" + case ErrTooBig: + return "video or cover art size exceeds maximum allowed dimensions" + case ErrEOF: + return "end of file" + case ErrDecoderNotFound: + return "decoder not found" + case ErrInvalidData: + return "invalid data found when processing input" + default: + return "unknown error occurred" + } +} + +// Error implements error interface +func (e avError) Error() string { + return "ffmpeg: " + e.errorString() +} diff --git a/ffmpeg/errors_test.go b/ffmpeg/errors_test.go new file mode 100644 index 0000000..3b0d483 --- /dev/null +++ b/ffmpeg/errors_test.go @@ -0,0 +1,15 @@ +package ffmpeg + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestErrors(t *testing.T) { + assert.Equal(t, "ffmpeg: cannot allocate memory", ErrNoMem.Error()) + assert.Equal(t, "ffmpeg: end of file", ErrEOF.Error()) + assert.Equal(t, "ffmpeg: unknown error occurred", ErrUnknown.Error()) + assert.Equal(t, "ffmpeg: decoder not found", ErrDecoderNotFound.Error()) + assert.Equal(t, "ffmpeg: invalid data found when processing input", ErrInvalidData.Error()) + assert.Equal(t, "ffmpeg: video or cover art size exceeds maximum allowed dimensions", ErrTooBig.Error()) +} diff --git a/ffmpeg/ffmpeg.c b/ffmpeg/ffmpeg.c new file mode 100644 index 0000000..2225eef --- /dev/null +++ b/ffmpeg/ffmpeg.c @@ -0,0 +1,425 @@ +#include "ffmpeg.h" + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void free_format_context(AVFormatContext *fmt_ctx) { + if (!fmt_ctx) { + return; + } + av_free(fmt_ctx->pb->buffer); + avio_context_free(&fmt_ctx->pb); + avformat_close_input(&fmt_ctx); +} + +int allocate_format_context(AVFormatContext **fmt_ctx) { + AVFormatContext *ctx = NULL; + if (!(ctx = avformat_alloc_context())) { + return AVERROR(ENOMEM); + } + *fmt_ctx = ctx; + return 0; +} + +int create_format_context(AVFormatContext *fmt_ctx, void* opaque, int flags) { + int err = 0; + uint8_t *avio_buffer = NULL; + AVIOContext *avio_ctx = NULL; + if (!(avio_buffer = av_malloc(BUFFER_SIZE))) { + avformat_free_context(fmt_ctx); + return AVERROR(ENOMEM); + } + void *reader = NULL; + void *seeker = NULL; + int write_flag = 0; + int seekable = 0; + if (flags & READ_PACKET_FLAG) { + reader = goPacketRead; + } + if (flags & SEEK_PACKET_FLAG) { + seeker = goPacketSeek; + seekable = 1; + } + if (!(avio_ctx = avio_alloc_context(avio_buffer, BUFFER_SIZE, write_flag, opaque, reader, NULL, seeker))) { + av_free(avio_buffer); + avformat_free_context(fmt_ctx); + return AVERROR(ENOMEM); + } + fmt_ctx->pb = avio_ctx; + fmt_ctx->pb->seekable = seekable; + err = avformat_open_input(&fmt_ctx, NULL, NULL, NULL); + if (err < 0) { + av_free(avio_ctx->buffer); + avio_context_free(&avio_ctx); + free_format_context(fmt_ctx); + return err; + } + err = pthread_mutex_lock(&mutex); + if (err < 0) { + free_format_context(fmt_ctx); + return err; + } + err = avformat_find_stream_info(fmt_ctx, NULL); + int muErr = pthread_mutex_unlock(&mutex); + if (err < 0 || muErr < 0) { + free_format_context(fmt_ctx); + if (muErr < 0) { + return muErr; + } + } + return err; +} + +static int get_orientation(AVStream *video_stream) { + const AVPacketSideData *side_data = NULL; + double theta = 0; + + // Use the new API to get side data from codecpar + side_data = av_packet_side_data_get(video_stream->codecpar->coded_side_data, + video_stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_DISPLAYMATRIX); + + if (side_data && side_data->size >= 9 * sizeof(int32_t)) { + theta = -av_display_rotation_get((int32_t *) side_data->data); + } + + theta -= 360 * floor(theta / 360 + 0.9 / 360); + + int rot = (int) (90 * round(theta / 90)) % 360; + + switch (rot) { + case 90: + return 6; + case 180: + return 3; + case 270: + return 8; + default: + return 1; + }; +} + +void get_metadata(AVFormatContext *fmt_ctx, char **artist, char **title) { + AVDictionaryEntry *tag = NULL; + if ((tag = av_dict_get(fmt_ctx->metadata, "artist", NULL, 0))) { + *artist = tag->value; + } + if ((tag = av_dict_get(fmt_ctx->metadata, "title", NULL, 0))) { + *title = tag->value; + } +} + +int find_streams(AVFormatContext *fmt_ctx, AVStream **video_stream, int *orientation) { + int video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + int audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); + int video_audio = 0; + if (audio_stream_index >= 0) { + video_audio |= HAS_AUDIO_STREAM; + } + if (video_stream_index >= 0) { + video_audio |= HAS_VIDEO_STREAM; + } else { + if (video_audio) { + return video_audio; + } + return AVERROR_STREAM_NOT_FOUND; + } + *video_stream = fmt_ctx->streams[video_stream_index]; + *orientation = get_orientation(*video_stream); + return video_audio; +} + +static int open_codec(AVCodecContext *codec_ctx, const AVCodec *codec) { + int err = pthread_mutex_lock(&mutex); + if (err < 0) { + return err; + } + err = avcodec_open2(codec_ctx, codec, NULL); + int muErr = pthread_mutex_unlock(&mutex); + if (muErr < 0) { + return muErr; + } + return err; +} + +int create_codec_context(AVStream *video_stream, AVCodecContext **dec_ctx) { + const AVCodec *dec = NULL; + AVCodecParameters *par = video_stream->codecpar; + if (par->codec_id == AV_CODEC_ID_VP8) { + dec = avcodec_find_decoder_by_name("libvpx"); + } else if (par->codec_id == AV_CODEC_ID_VP9) { + dec = avcodec_find_decoder_by_name("libvpx-vp9"); + } + if (!dec) { + dec = avcodec_find_decoder(par->codec_id); + } + if (dec == NULL) { + return AVERROR_DECODER_NOT_FOUND; + } + if (par->format == -1) { + return AVERROR_INVALIDDATA; + } + if (av_get_bits_per_pixel(av_pix_fmt_desc_get(par->format)) * par->height * par->width > 1 << 30) { + return ERR_TOO_BIG; + } + if (!(*dec_ctx = avcodec_alloc_context3(dec))) { + return AVERROR(ENOMEM); + } + int err = avcodec_parameters_to_context(*dec_ctx, par); + if (err < 0) { + avcodec_free_context(dec_ctx); + return err; + } + err = open_codec(*dec_ctx, dec); + if (err < 0) { + avcodec_free_context(dec_ctx); + } + return err; +} + +AVFrame *convert_frame_to_rgb(AVFrame *frame, int alpha) { + int output_fmt = alpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24; + struct SwsContext *sws_ctx = NULL; + AVFrame *output_frame = av_frame_alloc(); + if (!output_frame) { + return output_frame; + } + output_frame->height = frame->height; + output_frame->width = frame->width; + output_frame->format = output_fmt; + if (av_frame_get_buffer(output_frame, 1) < 0) { + goto free; + } + if (output_fmt == frame->format) { + if (av_frame_copy(output_frame, frame) < 0) { + goto free; + } + goto done; + } + sws_ctx = sws_getContext(frame->width, frame->height, frame->format, + output_frame->width, output_frame->height, output_fmt, + SWS_LANCZOS | SWS_ACCURATE_RND, NULL, NULL, NULL); + if (!sws_ctx) { + goto free; + } + if (sws_scale(sws_ctx, (const uint8_t *const *) frame->data, frame->linesize, 0, frame->height, output_frame->data, + output_frame->linesize) != output_frame->height) { + goto free; + } else { + goto done; + } + free: + av_frame_free(&output_frame); + done: + if (sws_ctx) { + sws_freeContext(sws_ctx); + } + return output_frame; +} + +AVPacket *create_packet() { + AVPacket *pkt = av_packet_alloc(); + if (!pkt) { + return NULL; + } + pkt->data = NULL; + pkt->size = 0; + return pkt; +} + +int obtain_next_frame(AVFormatContext *fmt_ctx, AVCodecContext *dec_ctx, int stream_index, AVPacket *pkt, AVFrame **frame) { + int err = 0, retry = 0; + if (!(*frame) && !(*frame = av_frame_alloc())) { + err = AVERROR(ENOMEM); + return err; + } + + // Try to receive a frame from the decoder first + if ((err = avcodec_receive_frame(dec_ctx, *frame)) != AVERROR(EAGAIN)) { + return err; + } + + while (1) { + if ((err = av_read_frame(fmt_ctx, pkt)) < 0) { + break; + } + if (pkt->stream_index != stream_index) { + av_packet_unref(pkt); + continue; + } + if ((err = avcodec_send_packet(dec_ctx, pkt)) < 0) { + if (retry++ >= 10) { + break; + } + av_packet_unref(pkt); + continue; + } + if (!(*frame) && !(*frame = av_frame_alloc())) { + err = AVERROR(ENOMEM); + break; + } + err = avcodec_receive_frame(dec_ctx, *frame); + if (err >= 0 || err != AVERROR(EAGAIN)) { + break; + } + av_packet_unref(pkt); + } + if (pkt->data) { + av_packet_unref(pkt); + } + return err; +} + +ThumbContext *create_thumb_context(AVStream *stream, AVFrame *frame) { + ThumbContext *thumb_ctx = av_mallocz(sizeof *thumb_ctx); + if (!thumb_ctx) { + return thumb_ctx; + } + thumb_ctx->desc = av_pix_fmt_desc_get(frame->format); + int nb_frames = 100; + if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) { + nb_frames = 1; + } else if (stream->nb_frames && stream->nb_frames < 400) { + nb_frames = (int) (stream->nb_frames >> 2) + 1; + } + int frames_in_128mb = (1 << 30) / (av_get_bits_per_pixel(thumb_ctx->desc) * frame->height * frame->width); + thumb_ctx->max_frames = FFMIN(nb_frames, frames_in_128mb); + int i; + for (i = 0; i < thumb_ctx->desc->nb_components; i++) { + thumb_ctx->hist_size += 1 << thumb_ctx->desc->comp[i].depth; + } + thumb_ctx->median = av_calloc(thumb_ctx->hist_size, sizeof(double)); + if (!thumb_ctx->median) { + av_free(thumb_ctx); + return NULL; + } + thumb_ctx->frames = av_malloc_array((size_t) thumb_ctx->max_frames, sizeof *thumb_ctx->frames); + if (!thumb_ctx->frames) { + av_free(thumb_ctx->median); + av_free(thumb_ctx); + return NULL; + } + for (i = 0; i < thumb_ctx->max_frames; i++) { + thumb_ctx->frames[i].frame = NULL; + thumb_ctx->frames[i].hist = av_calloc(thumb_ctx->hist_size, sizeof(int)); + if (!thumb_ctx->frames[i].hist) { + for (i--; i >= 0; i--) { + av_free(thumb_ctx->frames[i].hist); + } + av_free(thumb_ctx->median); + av_free(thumb_ctx); + return NULL; + } + } + return thumb_ctx; +} + +void free_thumb_context(ThumbContext *thumb_ctx) { + if (!thumb_ctx) { + return; + } + int i; + for (i = 0; i < thumb_ctx->n; i++) { + av_frame_free(&thumb_ctx->frames[i].frame); + av_free(thumb_ctx->frames[i].hist); + } + for (i = thumb_ctx->n; i < thumb_ctx->max_frames; i++) { + av_free(thumb_ctx->frames[i].hist); + } + av_free(thumb_ctx->median); + av_free(thumb_ctx->frames); + av_free(thumb_ctx); +} + +static double root_mean_square_error(const int *hist, const double *median, size_t hist_size) { + int i; + double err, sum_sq_err = 0; + for (i = 0; i < hist_size; i++) { + err = median[i] - (double) hist[i]; + sum_sq_err += err * err; + } + return sum_sq_err; +} + +void populate_frame(ThumbContext *thumb_ctx, int n, AVFrame *frame) { + thumb_ctx->frames[n].frame = frame; +} + +void populate_histogram(ThumbContext *thumb_ctx, int n, AVFrame *frame) { + const AVPixFmtDescriptor *desc = thumb_ctx->desc; + thumb_ctx->frames[n].frame = frame; + int *hist = thumb_ctx->frames[n].hist; + AVComponentDescriptor comp; + int w, h, plane, depth, mask, shift, step, height, width; + uint64_t flags; + uint8_t **data = frame->data; + int *linesize = frame->linesize; + for (int c = 0; c < desc->nb_components; c++) { + comp = desc->comp[c]; + plane = comp.plane; + depth = comp.depth; + mask = (1 << depth) - 1; + shift = comp.shift; + step = comp.step; + flags = desc->flags; + width = !(desc->log2_chroma_w) || (c != 1 && c != 2) ? frame->width : AV_CEIL_RSHIFT(frame->width, + desc->log2_chroma_w); + height = !(desc->log2_chroma_h) || (c != 1 && c != 2) ? frame->height : AV_CEIL_RSHIFT(frame->height, + desc->log2_chroma_h); + for (h = 0; h < height; h++) { + w = width; + if (flags & AV_PIX_FMT_FLAG_BITSTREAM) { + const uint8_t *p = data[plane] + h * linesize[plane] + (comp.offset >> 3); + shift = 8 - depth - (comp.offset & 7); + + while (w--) { + int val = (*p >> shift) & mask; + shift -= step; + p -= shift >> 3; + shift &= 7; + (*(hist + val))++; + } + } else { + const uint8_t *p = data[plane] + h * linesize[plane] + comp.offset; + int is_8bit = shift + depth <= 8; + + if (is_8bit) + p += (flags & AV_PIX_FMT_FLAG_BE) != 0; + + while (w--) { + int val = is_8bit ? *p : + flags & AV_PIX_FMT_FLAG_BE ? AV_RB16(p) : AV_RL16(p); + val = (val >> shift) & mask; + p += step; + (*(hist + val))++; + } + } + } + hist += 1 << depth; + } +} + +int find_best_frame_index(ThumbContext *thumb_ctx) { + int i, j, n = 0, m = thumb_ctx->n, *hist = NULL; + double *median = thumb_ctx->median; + for (j = 0; j < m; j++) { + hist = thumb_ctx->frames[j].hist; + for (i = 0; i < thumb_ctx->hist_size; i++) { + median[i] += (double) hist[i] / m; + } + } + struct thumb_frame *t_frame = NULL; + double min_sum_sq_err = DBL_MAX, sum_sq_err = 0; + for (i = 0; i < thumb_ctx->n; i++) { + t_frame = thumb_ctx->frames + i; + sum_sq_err = root_mean_square_error(t_frame->hist, thumb_ctx->median, thumb_ctx->hist_size); + if (sum_sq_err < min_sum_sq_err) { + min_sum_sq_err = sum_sq_err; + n = i; + } + } + return n; +} + +AVFrame *select_frame(ThumbContext *thumb_ctx, int n) { + return thumb_ctx->frames[n].frame; +} diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go new file mode 100644 index 0000000..ba657b9 --- /dev/null +++ b/ffmpeg/ffmpeg.go @@ -0,0 +1,402 @@ +package ffmpeg + +// #cgo pkg-config: libavformat libavutil libavcodec libswscale +// #cgo CFLAGS: -std=c11 +// #cgo LDFLAGS: -lm +// #include "ffmpeg.h" +import "C" +import ( + "github.com/cshum/vipsgen/pointer" + "io" + "math" + "time" + "unsafe" +) + +const ( + readPacketFlag = 1 + seekPacketFlag = 2 + hasVideo = 1 + hasAudio = 2 +) + +// Metadata AV metadata +type Metadata struct { + Orientation int `json:"orientation"` + Duration int `json:"duration,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + Title string `json:"title,omitempty"` + Artist string `json:"artist,omitempty"` + FPS float64 `json:"fps,omitempty"` + HasVideo bool `json:"has_video"` + HasAudio bool `json:"has_audio"` +} + +// AVContext manages lifecycle of AV contexts and reader stream +type AVContext struct { + opaque unsafe.Pointer + reader io.Reader + seeker io.Seeker + formatContext *C.AVFormatContext + stream *C.AVStream + codecContext *C.AVCodecContext + thumbContext *C.ThumbContext + selectedIndex C.int + selectedDuration time.Duration + frame *C.AVFrame + durationInFormat bool + orientation int + size int64 + duration time.Duration + availableIndex C.int + availableDuration time.Duration + width, height int + title, artist string + hasVideo, hasAudio bool + closed bool +} + +// LoadAVContext load and create AVContext from reader stream +func LoadAVContext(reader io.Reader, size int64) (*AVContext, error) { + av := &AVContext{ + reader: reader, + size: size, + selectedIndex: -1, + } + if seeker, ok := reader.(io.Seeker); ok { + av.seeker = seeker + } + flags := C.int(readPacketFlag) + if av.seeker != nil { + flags |= seekPacketFlag + } + if err := createFormatContext(av, flags); err != nil { + return nil, err + } + if !av.hasVideo { + return av, nil + } + return av, createDecoder(av) +} + +// ProcessFrames triggers frame processing +// limit under max num of frames if maxFrames > 0 +func (av *AVContext) ProcessFrames(maxFrames int) (err error) { + if av.formatContext == nil || av.codecContext == nil { + return ErrDecoderNotFound + } + if av.thumbContext == nil { + return createThumbContext(av, C.int(maxFrames)) + } + return +} + +// SelectFrame triggers frame processing and select specific frame index +func (av *AVContext) SelectFrame(n int) (err error) { + nn := C.int(n - 1) + if av.thumbContext != nil && nn > av.availableIndex { + nn = av.availableIndex + } + av.selectedIndex = nn + return av.ProcessFrames(-1) +} + +func (av *AVContext) positionToDuration(f float64) time.Duration { + return time.Duration(float64(av.duration) * math.Max(math.Min(f, 1), 0)) +} + +func (av *AVContext) SelectPosition(f float64) (err error) { + return av.SelectDuration(av.positionToDuration(f)) +} + +// SelectDuration seeks to keyframe before the specified duration +// then process frames to find precise duration +func (av *AVContext) SelectDuration(ts time.Duration) (err error) { + if ts > 0 { + av.selectedDuration = ts + if err = av.SeekDuration(ts); err != nil { + return + } + return av.ProcessFrames(-1) + } else { + return av.SelectFrame(1) + } +} + +// SeekPosition seeks to keyframe before specified position percentage between 0 and 1 +// then process frames to find precise position +func (av *AVContext) SeekPosition(f float64) error { + return av.SeekDuration(av.positionToDuration(f)) +} + +// SeekDuration seeks to keyframe before the specified duration +func (av *AVContext) SeekDuration(ts time.Duration) error { + if av.formatContext == nil || av.codecContext == nil { + return ErrDecoderNotFound + } + return seekDuration(av, ts) +} + +// Export frame to RGB or RGBA buffer +func (av *AVContext) Export(bands int) (buf []byte, err error) { + if err = av.ProcessFrames(-1); err != nil { + return + } + if bands < 3 || bands > 4 { + bands = 4 + } + if err = convertFrameToRGB(av, bands); err != nil { + return + } + return exportBuffer(av, bands) +} + +// Close AVContext objects +func (av *AVContext) Close() { + closeAVContext(av) +} + +// Metadata AV metadata +func (av *AVContext) Metadata() *Metadata { + var fps float64 + if av.stream != nil { + fps = float64(av.stream.r_frame_rate.num) / float64(av.stream.r_frame_rate.den) + } + return &Metadata{ + Orientation: av.orientation, + Duration: int(av.duration / time.Millisecond), + Width: av.width, + Height: av.height, + Title: av.title, + Artist: av.artist, + FPS: fps, + HasVideo: av.hasVideo, + HasAudio: av.hasAudio, + } +} + +func closeAVContext(av *AVContext) { + if !av.closed { + if av.frame != nil { + C.av_frame_free(&av.frame) + } + if av.thumbContext != nil { + C.free_thumb_context(av.thumbContext) + } + if av.codecContext != nil { + C.avcodec_free_context(&av.codecContext) + } + if av.formatContext != nil { + C.free_format_context(av.formatContext) + } + pointer.Unref(av.opaque) + av.closed = true + } +} + +func createFormatContext(av *AVContext, callbackFlags C.int) error { + intErr := C.allocate_format_context(&av.formatContext) + if intErr < 0 { + return avError(intErr) + } + av.opaque = pointer.Save(av) + intErr = C.create_format_context(av.formatContext, av.opaque, callbackFlags) + if intErr < 0 { + pointer.Unref(av.opaque) + return avError(intErr) + } + metadata(av) + duration(av) + err := findStreams(av) + if err != nil { + C.free_format_context(av.formatContext) + pointer.Unref(av.opaque) + } + return err +} + +func metadata(av *AVContext) { + var artist, title *C.char + C.get_metadata(av.formatContext, &artist, &title) + av.artist = C.GoString(artist) + av.title = C.GoString(title) +} + +func duration(av *AVContext) { + if av.formatContext.duration > 0 { + av.durationInFormat = true + av.duration = time.Duration(1000 * av.formatContext.duration) + } +} + +func findStreams(av *AVContext) error { + var orientation C.int + err := C.find_streams(av.formatContext, &av.stream, &orientation) + if err < 0 { + return avError(err) + } + av.hasVideo = err&hasVideo != 0 + av.hasAudio = err&hasAudio != 0 + if av.hasVideo { + av.width = int(av.stream.codecpar.width) + av.height = int(av.stream.codecpar.height) + av.orientation = int(orientation) + } + return nil +} + +func createDecoder(av *AVContext) error { + err := C.create_codec_context(av.stream, &av.codecContext) + if err < 0 { + return avError(err) + } + return nil +} + +func seekDuration(av *AVContext, ts time.Duration) error { + tts := C.int64_t(ts.Milliseconds()) * C.AV_TIME_BASE / 1000 + err := C.av_seek_frame(av.formatContext, C.int(-1), tts, C.AVSEEK_FLAG_BACKWARD) + C.avcodec_flush_buffers(av.codecContext) + if err < 0 { + return avError(err) + } + return nil +} + +func incrementDuration(av *AVContext, frame *C.AVFrame, i C.int) { + av.availableIndex = i + if frame.pts != C.AV_NOPTS_VALUE { + ptsToNano := C.int64_t(1000000000 * av.stream.time_base.num / av.stream.time_base.den) + newDuration := time.Duration(frame.pts * ptsToNano) + av.availableDuration = newDuration + + if !av.durationInFormat && newDuration > av.duration { + av.duration = newDuration + } + } +} + +func populateFrames(av *AVContext, frames <-chan *C.AVFrame) <-chan struct{} { + done := make(chan struct{}) + var isSelected = av.selectedIndex > -1 + go func() { + var n C.int + if !isSelected { + for frame := range frames { + C.populate_histogram(av.thumbContext, n, frame) + n++ + } + } else { + for frame := range frames { + C.populate_frame(av.thumbContext, n, frame) + n++ + } + } + av.thumbContext.n = n + close(done) + }() + return done +} + +func createThumbContext(av *AVContext, maxFrames C.int) error { + pkt := C.create_packet() + if pkt == nil { + return avError(C.int(ErrNoMem)) + } + defer C.av_packet_free(&pkt) + + var frame *C.AVFrame + err := C.obtain_next_frame(av.formatContext, av.codecContext, av.stream.index, pkt, &frame) + if err >= 0 { + incrementDuration(av, frame, 0) + av.thumbContext = C.create_thumb_context(av.stream, frame) + if av.thumbContext == nil { + err = C.int(ErrNoMem) + } + } + if err < 0 { + if frame != nil { + C.av_frame_free(&frame) + } + return avError(err) + } + n := av.thumbContext.max_frames + if maxFrames > 0 && n > maxFrames { + n = maxFrames + } + if av.selectedIndex > -1 && n > av.selectedIndex+1 { + n = av.selectedIndex + 1 + } + if av.selectedDuration > 0 && av.selectedIndex < 0 { + av.selectedIndex = 0 + } + frames := make(chan *C.AVFrame, n) + done := populateFrames(av, frames) + frames <- frame + return populateThumbContext(av, frames, n, done) +} + +func populateThumbContext(av *AVContext, frames chan *C.AVFrame, n C.int, done <-chan struct{}) error { + pkt := C.create_packet() + if pkt == nil { + return avError(C.int(ErrNoMem)) + } + defer C.av_packet_free(&pkt) + + var frame *C.AVFrame + var err C.int + for i := C.int(1); i < n; i++ { + err = C.obtain_next_frame(av.formatContext, av.codecContext, av.stream.index, pkt, &frame) + if err < 0 { + break + } + incrementDuration(av, frame, i) + frames <- frame + frame = nil + if av.selectedDuration > 0 { + if av.availableDuration <= av.selectedDuration { + av.selectedIndex = i + } else { + break + } + } + } + if av.selectedIndex > av.availableIndex { + av.selectedIndex = av.availableIndex + } + close(frames) + if frame != nil { + C.av_frame_free(&frame) + } + <-done + if err != 0 && err != C.int(ErrEOF) { + return avError(err) + } + if av.selectedIndex < 0 { + av.selectedIndex = C.find_best_frame_index(av.thumbContext) + } + return nil +} + +func convertFrameToRGB(av *AVContext, bands int) error { + var alpha int + if bands == 4 { + alpha = 1 + } + av.frame = C.convert_frame_to_rgb( + C.select_frame(av.thumbContext, av.selectedIndex), C.int(alpha)) + if av.frame == nil { + return ErrNoMem + } + return nil +} + +func exportBuffer(av *AVContext, bands int) ([]byte, error) { + if av.frame == nil { + return nil, ErrInvalidData + } + size := av.height * av.width * bands + buf := C.GoBytes(unsafe.Pointer(av.frame.data[0]), C.int(size)) + return buf, nil +} diff --git a/ffmpeg/ffmpeg.h b/ffmpeg/ffmpeg.h new file mode 100644 index 0000000..d04b83a --- /dev/null +++ b/ffmpeg/ffmpeg.h @@ -0,0 +1,68 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 1 << 12 +#define READ_PACKET_FLAG 1 +#define SEEK_PACKET_FLAG 2 +#define HAS_VIDEO_STREAM 1 +#define HAS_AUDIO_STREAM 2 +#define ERR_TOO_BIG FFERRTAG('H','M','M','M') + +struct thumb_frame { + AVFrame *frame; + int *hist; +}; + +typedef struct ThumbContext { + int n, max_frames; + struct thumb_frame *frames; + double *median; + const AVPixFmtDescriptor *desc; + size_t hist_size; +} ThumbContext; + +int allocate_format_context(AVFormatContext **fmt_ctx); + +int create_format_context(AVFormatContext *fmt_ctx, void* opaque, int callbacks); + +void free_format_context(AVFormatContext *fmt_ctx); + +void get_metadata(AVFormatContext *fmt_ctx, char **artist, char **title); + +int find_streams(AVFormatContext *fmt_ctx, AVStream **video_stream, int *orientation); + +int create_codec_context(AVStream *video_stream, AVCodecContext **dec_ctx); + +AVFrame *convert_frame_to_rgb(AVFrame *frame, int alpha); + +AVPacket *create_packet(); + +int obtain_next_frame(AVFormatContext *fmt_ctx, AVCodecContext *dec_ctx, int stream_index, AVPacket *pkt, AVFrame **frame); + +ThumbContext *create_thumb_context(AVStream *stream, AVFrame *frame); + +void free_thumb_context(ThumbContext *thumb_ctx); + +int find_best_frame_index(ThumbContext *thumb_ctx); + +AVFrame *select_frame(ThumbContext *thumb_ctx, int i); + +void populate_frame(ThumbContext *thumb_ctx, int n, AVFrame *frame); + +void populate_histogram(ThumbContext *thumb_ctx, int n, AVFrame *frame); + +extern int goPacketRead(void *opaque, uint8_t *buf, int buf_size); + +extern int64_t goPacketSeek(void *opaque, int64_t seek, int whence); diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go new file mode 100644 index 0000000..516b677 --- /dev/null +++ b/ffmpeg/ffmpeg_test.go @@ -0,0 +1,188 @@ +package ffmpeg + +import ( + "encoding/json" + "fmt" + "github.com/cshum/vipsgen/pointer" + "github.com/cshum/vipsgen/vips" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "io" + "os" + "reflect" + "strings" + "testing" + "time" +) + +var files = []string{ + "everybody-betray-me.mkv", + "alpha-webm.webm", + "schizo.flv", + "macabre.mp4", + "schizo_0.mp4", + "schizo_90.mp4", + "schizo_180.mp4", + "schizo_270.mp4", + "with_cover.mp3", +} + +var noVideo = []string{ + "no_cover.mp3", +} + +var baseDir = "../testdata/" + +func TestAVContext(t *testing.T) { + vips.Startup(nil) + SetFFmpegLogLevel(AVLogDebug) + logger := zap.NewExample() + SetLogging(nil) + log(AVLogDebug, "nop logging") + SetLogging(func(level AVLogLevel, message string) { + message = strings.TrimSuffix(message, "\n") + switch level { + case AVLogTrace, AVLogDebug, AVLogVerbose: + logger.Debug("ffmpeg", zap.String("log", message)) + case AVLogInfo: + logger.Info("ffmpeg", zap.String("log", message)) + case AVLogWarning, AVLogError, AVLogFatal, AVLogPanic: + logger.Warn("ffmpeg", zap.String("log", message)) + } + }) + require.NoError(t, os.MkdirAll(baseDir+"golden/meta", 0755)) + require.NoError(t, os.MkdirAll(baseDir+"golden/export", 0755)) + t.Parallel() + for _, filename := range files { + for _, n := range []int{-1, 1, 5, 10, 9999, 99999} { + name := filename + if n > -1 { + name = fmt.Sprintf("%s-%d", filename, n) + } + t.Run(name, func(t *testing.T) { + path := baseDir + filename + reader, err := os.Open(path) + require.NoError(t, err) + stats, err := os.Stat(path) + require.NoError(t, err) + av, err := LoadAVContext(reader, stats.Size()) + meta := av.Metadata() + metaBuf, err := json.Marshal(meta) + require.NoError(t, err) + goldenFile := baseDir + "golden/meta/" + name + ".meta.json" + if curr, err := os.ReadFile(goldenFile); err == nil { + assert.Equal(t, string(curr), string(metaBuf)) + } else { + require.NoError(t, os.WriteFile(goldenFile, metaBuf, 0666)) + } + require.NoError(t, err) + defer av.Close() + if n == 10 { + require.NoError(t, av.ProcessFrames(n)) + } else if n == 99999 { + require.NoError(t, av.SelectDuration(time.Second)) + } else if n == 9999 { + require.NoError(t, av.SelectPosition(0.7)) + } else if n == 1 { + require.NoError(t, av.SelectDuration(0)) + } else if n == 5 { + require.NoError(t, av.SelectFrame(n)) + } else { + require.NoError(t, av.SeekPosition(0.7)) + } + bands := 4 + if n == 99999 { + bands = 999 + } + buf, err := av.Export(bands) + require.NoError(t, err) + if bands > 4 { + bands = 4 + } + img, err := vips.NewImageFromMemory(buf, meta.Width, meta.Height, bands) + require.NoError(t, err) + buf, err = img.JpegsaveBuffer(nil) + require.NoError(t, err) + goldenFile = baseDir + "golden/export/" + name + ".jpg" + if curr, err := os.ReadFile(goldenFile); err == nil { + assert.True(t, reflect.DeepEqual(curr, buf)) + } else { + require.NoError(t, os.WriteFile(goldenFile, buf, 0666)) + } + }) + } + } +} + +func TestNoVideo(t *testing.T) { + require.NoError(t, os.MkdirAll(baseDir+"golden/meta", 0755)) + require.NoError(t, os.MkdirAll(baseDir+"golden/export", 0755)) + for _, filename := range noVideo { + for i := 0; i < 2; i++ { + t.Run(fmt.Sprintf("%s-%d", filename, i), func(t *testing.T) { + path := baseDir + filename + reader, err := os.Open(path) + require.NoError(t, err) + stats, err := os.Stat(path) + require.NoError(t, err) + av, err := LoadAVContext(reader, stats.Size()) + require.NoError(t, err) + defer av.Close() + require.Equal(t, ErrDecoderNotFound, av.ProcessFrames(-1)) + meta := av.Metadata() + metaBuf, err := json.Marshal(meta) + require.NoError(t, err) + goldenFile := baseDir + "golden/meta/" + filename + ".meta.json" + if curr, err := os.ReadFile(goldenFile); err == nil { + assert.Equal(t, string(curr), string(metaBuf)) + } else { + require.NoError(t, os.WriteFile(goldenFile, metaBuf, 0666)) + } + if i == 0 { + buf, err := av.Export(3) + require.Empty(t, buf) + assert.Equal(t, ErrDecoderNotFound, err) + } else { + assert.Equal(t, ErrDecoderNotFound, av.SelectFrame(1)) + } + }) + } + } +} + +func TestCorrupted(t *testing.T) { + filename := "macabre.mp4" + path := baseDir + filename + file, err := os.Open(path) + require.NoError(t, err) + reader := &readCloser{ + Reader: io.LimitReader(file, 1024), + Closer: file, + } + stats, err := os.Stat(path) + require.NoError(t, err) + av, err := LoadAVContext(reader, stats.Size()) + require.Equal(t, ErrInvalidData, err) + require.Empty(t, av) +} + +func TestCorruptedOpaque(t *testing.T) { + filename := "macabre.mp4" + path := baseDir + filename + reader, err := os.Open(path) + require.NoError(t, err) + stats, err := os.Stat(path) + require.NoError(t, err) + av, err := LoadAVContext(reader, stats.Size()) + require.NoError(t, err) + defer av.Close() + pointer.Unref(av.opaque) + err = av.ProcessFrames(-1) + assert.Equal(t, ErrUnknown, err) +} + +type readCloser struct { + io.Reader + io.Closer +} diff --git a/ffmpeg/logging.c b/ffmpeg/logging.c new file mode 100644 index 0000000..45865e8 --- /dev/null +++ b/ffmpeg/logging.c @@ -0,0 +1,12 @@ +#include "logging.h" + +void goavLogCallback(void *class_ptr, int level, const char *fmt, va_list vl) { + char line[LINE_SZ]; + int print_prefix = 1; + av_log_format_line(class_ptr, level, fmt, vl, line, LINE_SZ, &print_prefix); + goAVLoggingHandler(level, line); +} + +void goavLogSetup() { + av_log_set_callback(goavLogCallback); +} diff --git a/ffmpeg/logging.go b/ffmpeg/logging.go new file mode 100644 index 0000000..2ac0528 --- /dev/null +++ b/ffmpeg/logging.go @@ -0,0 +1,55 @@ +package ffmpeg + +// #include "ffmpeg.h" +// #include "logging.h" +import "C" +import "sync" + +// AVLogLevel defines the ffmpeg threshold for dumping information to stderr. +type AVLogLevel int + +// AVLogLevel enum +const ( + AVLogQuiet AVLogLevel = (iota - 1) * 8 + AVLogPanic + AVLogFatal + AVLogError + AVLogWarning + AVLogInfo + AVLogVerbose + AVLogDebug + AVLogTrace +) + +var ( + currentLoggingHandlerFunction = noopLoggingHandler + currentLoggingVerbosity AVLogLevel + onceLogging sync.Once +) + +// SetFFmpegLogLevel allows you to change the log level from the default (AVLogInfo). +func SetFFmpegLogLevel(logLevel AVLogLevel) { + C.av_log_set_level(C.int(logLevel)) + currentLoggingVerbosity = logLevel +} + +type LoggingHandlerFunction func(messageLevel AVLogLevel, message string) + +// SetLogging set AV logging handler +func SetLogging(handler LoggingHandlerFunction) { + onceLogging.Do(func() { + C.goavLogSetup() + }) + if handler != nil { + currentLoggingHandlerFunction = handler + } +} + +func noopLoggingHandler(_ AVLogLevel, _ string) { +} + +func log(level AVLogLevel, message string) { + if level <= currentLoggingVerbosity { + currentLoggingHandlerFunction(level, message) + } +} diff --git a/ffmpeg/logging.h b/ffmpeg/logging.h new file mode 100644 index 0000000..ec6d4c9 --- /dev/null +++ b/ffmpeg/logging.h @@ -0,0 +1,11 @@ +#include + +#define LINE_SZ 1024 + +extern void goAVLoggingHandler(int level, char *str); +extern void av_log_set_callback(void (*callback)(void *, int, const char *, va_list)); +extern void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl, char *line, int line_size, int *print_prefix); + +void goavLogCallback(void *class_ptr, int level, const char *fmt, va_list vl); + +void goavLogSetup(); diff --git a/go.mod b/go.mod index 04286d3..a11e181 100644 --- a/go.mod +++ b/go.mod @@ -1,68 +1,95 @@ -module git.cef.icu/CEF/imagorextended +module github.com/cshum/imagorvideo -go 1.21 - -toolchain go1.21.1 +go 1.24.2 require ( - github.com/cshum/imagor v1.4.13 - github.com/cshum/imagorvideo v0.4.13 - github.com/gabriel-vasile/mimetype v1.4.4 - github.com/stretchr/testify v1.9.0 + github.com/cshum/imagor v1.6.6 + github.com/cshum/vipsgen v1.2.1 + github.com/gabriel-vasile/mimetype v1.4.11 + github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.27.0 ) require ( - cloud.google.com/go v0.115.0 // indirect - cloud.google.com/go/auth v0.7.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect - cloud.google.com/go/compute/metadata v0.5.0 // indirect - cloud.google.com/go/iam v1.1.11 // indirect - cloud.google.com/go/storage v1.43.0 // indirect - github.com/antchfx/htmlquery v1.3.2 // indirect - github.com/antchfx/xpath v1.3.1 // indirect - github.com/aws/aws-sdk-go v1.54.20 // indirect + cel.dev/expr v0.25.1 // indirect + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/iam v1.5.3 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect + cloud.google.com/go/storage v1.57.2 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect + github.com/TheZeroSlave/zapsentry v1.23.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect + github.com/aws/aws-sdk-go-v2/config v1.31.20 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.24 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 // indirect + github.com/aws/smithy-go v1.23.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/getsentry/sentry-go v0.38.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.5 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/rs/cors v1.11.0 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/procfs v0.19.2 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/image v0.18.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.188.0 // indirect - google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/crypto v0.44.0 // indirect + golang.org/x/image v0.33.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/api v0.256.0 // indirect + google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/grpc v1.77.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9fed035..40a1f10 100644 --- a/go.sum +++ b/go.sum @@ -1,266 +1,240 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= -cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= -cloud.google.com/go/auth v0.7.1 h1:Iv1bbpzJ2OIg16m94XI9/tlzZZl3cdeR3nGVGj78N7s= -cloud.google.com/go/auth v0.7.1/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= -cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= -cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= -cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw= -cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ= -cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k= -cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c= -cloud.google.com/go/pubsub v1.40.0 h1:0LdP+zj5XaPAGtWr2V6r88VXJlmtaB/+fde1q3TU8M0= -cloud.google.com/go/pubsub v1.40.0/go.mod h1:BVJI4sI2FyXp36KFKvFwcfDRDfR8MiLT8mMhmIhdAeA= -cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= -cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/antchfx/htmlquery v1.3.2 h1:85YdttVkR1rAY+Oiv/nKI4FCimID+NXhDn82kz3mEvs= -github.com/antchfx/htmlquery v1.3.2/go.mod h1:1mbkcEgEarAokJiWhTfr4hR06w/q2ZZjnYLrDt6CTUk= -github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk= -github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/aws/aws-sdk-go v1.54.20 h1:FZ2UcXya7bUkvkpf7TaPmiL7EubK0go1nlXGLRwEsoo= -github.com/aws/aws-sdk-go v1.54.20/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute v1.49.1 h1:KYKIG0+pfpAWaAYayFkE/KPrAVCge0Hu82bPraAmsCk= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= +cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= +cloud.google.com/go/pubsub v1.50.1 h1:fzbXpPyJnSGvWXF1jabhQeXyxdbCIkXTpjXHy7xviBM= +cloud.google.com/go/pubsub/v2 v2.3.0 h1:DgAN907x+sP0nScYfBzneRiIhWoXcpCD8ZAut8WX9vs= +cloud.google.com/go/pubsub/v2 v2.3.0/go.mod h1:O5f0KHG9zDheZAd3z5rlCRhxt2JQtB+t/IYLKK3Bpvw= +cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5Mb4= +cloud.google.com/go/storage v1.57.2/go.mod h1:n5ijg4yiRXXpCu0sJTD6k+eMf7GRrJmPyr9YxLXGHOk= +cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= +cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= +github.com/TheZeroSlave/zapsentry v1.23.0 h1:TKyzfEL7LRlRr+7AvkukVLZ+jZPC++ebCUv7ZJHl1AU= +github.com/TheZeroSlave/zapsentry v1.23.0/go.mod h1:3DRFLu4gIpnCTD4V9HMCBSaqYP8gYU7mZickrs2/rIY= +github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk= +github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y= +github.com/aws/aws-sdk-go-v2/config v1.31.20 h1:/jWF4Wu90EhKCgjTdy1DGxcbcbNrjfBHvksEL79tfQc= +github.com/aws/aws-sdk-go-v2/config v1.31.20/go.mod h1:95Hh1Tc5VYKL9NJ7tAkDcqeKt+MCXQB1hQZaRdJIZE0= +github.com/aws/aws-sdk-go-v2/credentials v1.18.24 h1:iJ2FmPT35EaIB0+kMa6TnQ+PwG5A1prEdAw+PsMzfHg= +github.com/aws/aws-sdk-go-v2/credentials v1.18.24/go.mod h1:U91+DrfjAiXPDEGYhh/x29o4p0qHX5HDqG7y5VViv64= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13/go.mod h1:/FDdxWhz1486obGrKKC1HONd7krpk38LBt+dutLcN9k= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4/go.mod h1:455WPHSwaGj2waRSpQp7TsnpOnBfw8iDfPfbwl7KPJE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 h1:zhBJXdhWIFZ1acfDYIhu4+LCzdUS2Vbcum7D01dXlHQ= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13/go.mod h1:JaaOeCE368qn2Hzi3sEzY6FgAZVCIYcC2nwbro2QCh8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2 h1:DhdbtDl4FdNlj31+xiRXANxEE+eC7n8JQz+/ilwQ8Uc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 h1:NjShtS1t8r5LUfFVtFeI8xLAHQNTa7UI0VawXlrBMFQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.3/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 h1:gTsnx0xXNQ6SBbymoDvcoRHL+q4l/dAFsQuKfDWSaGc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= +github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 h1:HK5ON3KmQV2HcAunnx4sKLB9aPf3gKGwVAf7xnx0QT0= +github.com/aws/aws-sdk-go-v2/service/sts v1.40.2/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= +github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= +github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cshum/imagor v1.4.13 h1:BFcSpsTUOJj+Wv5SzDeXa8bhsT/Ehw7EcrFD0UTdpmU= -github.com/cshum/imagor v1.4.13/go.mod h1:LHxXgks6Y06GzEHitnlO8vcD5gznxIHWPdvGsnlGpMo= -github.com/cshum/imagorvideo v0.4.13 h1:tn+TmdPMvS00XndPX8j/BUMyebErkxXgpJ9CLxNvROE= -github.com/cshum/imagorvideo v0.4.13/go.mod h1:y2g2GQst6b1X+jvAcxhDusTcYYGJqp+fSRcUo3l+PxU= -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= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e h1:gt7U1Igw0xbJdyaCM5H2CnlAlPSkzrhsebQB6WQWjLA= +github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cshum/imagor v1.6.6 h1:w+pf6hy1qFUkz8/Np8sDrtAs7pgzqZuDjaJX4xn6ams= +github.com/cshum/imagor v1.6.6/go.mod h1:Dt7yj7TLE3VFmGqc+gxfXWtvoHaXHIOeog4zlsHXZiE= +github.com/cshum/vipsgen v1.2.1 h1:Es305Zf7C9T+8QbsiWn3BtQ+2/uHz6sp/SFnvwnO/kU= +github.com/cshum/vipsgen v1.2.1/go.mod h1:1GboZQcNmo4NwuNnGogM24m3O+1i6UpnvurqMcsFItE= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsouza/fake-gcs-server v1.49.2 h1:fukDqzEQM50QkA0jAbl6cLqeDu3maQjwZBuys759TR4= -github.com/fsouza/fake-gcs-server v1.49.2/go.mod h1:17SYzJEXRcaAA5ATwwvgBkSIqIy7r1icnGM0y/y4foY= -github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= -github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/fsouza/fake-gcs-server v1.52.3 h1:hXddOPMGDKq5ENmttw6xkodVJy0uVhf7HhWvQgAOH6g= +github.com/fsouza/fake-gcs-server v1.52.3/go.mod h1:A0XtSRX+zz5pLRAt88j9+Of0omQQW+RMqipFbvdNclQ= +github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= +github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/getsentry/sentry-go v0.38.0 h1:S8Xui7gLeAvXINVLMOaX94HnsDf1GexnfXGSNC4+KQs= +github.com/getsentry/sentry-go v0.38.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/renameio/v2 v2.0.1 h1:HyOM6qd9gF9sf15AvhbptGHUnaLTpEI9akAFFU3VyW0= +github.com/google/renameio/v2 v2.0.1/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= -github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/johannesboyne/gofakes3 v0.0.0-20240701191259-edd0227ffc37 h1:w/TiKkLc+oLH7mUCpP5DUn8+a0CjhK9yWQLKBA0Iv1w= -github.com/johannesboyne/gofakes3 v0.0.0-20240701191259-edd0227ffc37/go.mod h1:AxgWC4DDX54O2WDoQO1Ceabtn6IbktjU/7bigor+66g= +github.com/johannesboyne/gofakes3 v0.0.0-20250916175020-ebf3e50324d3 h1:2713fQZ560HxoNVgfJH41GKzjMjIG+DW4hH6nYXfXW8= +github.com/johannesboyne/gofakes3 v0.0.0-20250916175020-ebf3e50324d3/go.mod h1:S4S9jGBVlLri0OeqrSSbCGG5vsI6he06UJyuz1WT1EE= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= -github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= -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/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= -github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM= +github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= +github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= -github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIGMa7oSwp8SQPp7Hb4ZiI38K3RNBKD2LLeEM= -github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d h1:Ns9kd1Rwzw7t0BR8XMphenji4SmIoNZPn8zhYmaVKP8= +go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d/go.mod h1:92Uoe3l++MlthCm+koNi0tcUCX3anayogF0Pa/sp24k= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -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/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= -google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= -google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= +golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= +google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba h1:Ze6qXW0j37YCqZdCD2LkzVSxgEWez0cO4NUyd44DiDY= +google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:4FLPzLA8eGAktPOTemJGDgDYRpLYwrNu4u2JtWINhnI= +google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= +google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/processor_test.go b/processor_test.go new file mode 100644 index 0000000..04f88d2 --- /dev/null +++ b/processor_test.go @@ -0,0 +1,166 @@ +package imagorvideo + +import ( + "context" + "fmt" + "github.com/cshum/imagor" + "github.com/cshum/imagor/imagorpath" + "github.com/cshum/imagor/processor/vipsprocessor" + "github.com/cshum/imagor/storage/filestorage" + "github.com/cshum/vipsgen/vips" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" +) + +var testDataDir string + +func init() { + _, b, _, _ := runtime.Caller(0) + testDataDir = filepath.Join(filepath.Dir(b), "./testdata") +} + +type test struct { + name string + path string + expectCode int +} + +func TestProcessor(t *testing.T) { + v := vipsprocessor.NewProcessor(vipsprocessor.WithDebug(true)) + require.NoError(t, v.Startup(context.Background())) + t.Cleanup(func() { + require.NoError(t, v.Shutdown(context.Background())) + }) + doGoldenTests(t, filepath.Join(testDataDir, "golden/result"), []test{ + {name: "mkv", path: "fit-in/100x100/everybody-betray-me.mkv"}, + {name: "mkv specific frame", path: "fit-in/100x100/filters:frame(3)/everybody-betray-me.mkv"}, + {name: "mkv specific max_frames", path: "fit-in/100x100/filters:max_frames(6)/everybody-betray-me.mkv"}, + {name: "mkv specific frame exceeded", path: "fit-in/100x100/filters:frame(99999)/everybody-betray-me.mkv"}, + {name: "mkv meta max_frames", path: "meta/filters:max_frames()/everybody-betray-me.mkv"}, + {name: "mkv meta max_frames 6", path: "meta/filters:max_frames(6)/everybody-betray-me.mkv"}, + {name: "mkv meta", path: "meta/everybody-betray-me.mkv"}, + {name: "mp4", path: "200x100/schizo_0.mp4"}, + {name: "mp4 orient 90", path: "220x100/schizo_90.mp4"}, + {name: "mp4 orient 180", path: "200x100/schizo_180.mp4"}, + {name: "mp4 orient 270", path: "200x100/schizo_270.mp4"}, + {name: "image", path: "fit-in/100x100/demo.png"}, + {name: "alpha", path: "fit-in/filters:format(png)/alpha-webm.webm"}, + {name: "alpha frame duration", path: "500x/filters:frame(5s):format(png)/alpha-webm.webm"}, + {name: "alpha frame position", path: "500x/filters:frame(0.5):format(png)/alpha-webm.webm"}, + {name: "alpha seek duration", path: "500x/filters:seek(5s):format(png)/alpha-webm.webm"}, + {name: "alpha seek position", path: "500x/filters:seek(0.5):format(png)/alpha-webm.webm"}, + {name: "corrupted", path: "fit-in/100x100/corrupt/everybody-betray-me.mkv", expectCode: 406}, + {name: "no cover meta", path: "meta/no_cover.mp3"}, + {name: "no cover 406", path: "fit-in/100x100/no_cover.mp3", expectCode: 406}, + }, WithDebug(true), WithLogger(zap.NewExample())) + doGoldenTests(t, filepath.Join(testDataDir, "golden/result-fallback-image"), []test{ + {name: "corrupted with fallback image", path: "fit-in/100x100/corrupt/everybody-betray-me.mkv", expectCode: 406}, + {name: "corrupted with fallback image", path: "filters:seek(0.1)/no_cover.mp3", expectCode: 406}, + }, WithDebug(false), WithLogger(zap.NewExample()), WithFallbackImage("demo.png")) +} + +func doGoldenTests(t *testing.T, resultDir string, tests []test, opts ...Option) { + resStorage := filestorage.New(resultDir, + filestorage.WithSaveErrIfExists(true)) + fileLoader := filestorage.New(testDataDir) + loaders := []imagor.Loader{ + fileLoader, + loaderFunc(func(r *http.Request, image string) (blob *imagor.Blob, err error) { + image, _ = fileLoader.Path(image) + return imagor.NewBlob(func() (reader io.ReadCloser, size int64, err error) { + // force read full file by 0 size + reader, err = os.Open(image) + return + }), nil + }), + } + for i, loader := range loaders { + app := imagor.New( + imagor.WithLoaders(loaderFunc(func(r *http.Request, image string) (blob *imagor.Blob, err error) { + if strings.HasPrefix(image, "corrupt/") { + image, _ = fileLoader.Path(strings.TrimPrefix(image, "corrupt/")) + return imagor.NewBlob(func() (reader io.ReadCloser, size int64, err error) { + file, err := os.Open(image) + // truncate so it corrupt + reader = &readCloser{ + Reader: io.LimitReader(file, 1024), + Closer: file, + } + return + }), nil + } + return nil, imagor.ErrNotFound + }), loader), + imagor.WithUnsafe(true), + imagor.WithDebug(true), + imagor.WithLogger(zap.NewExample()), + imagor.WithProcessors(NewProcessor(opts...), vipsprocessor.NewProcessor()), + ) + require.NoError(t, app.Startup(context.Background())) + t.Cleanup(func() { + assert.NoError(t, app.Shutdown(context.Background())) + }) + for _, tt := range tests { + t.Run(fmt.Sprintf("%s-%d", tt.name, i+1), func(t *testing.T) { + w := httptest.NewRecorder() + ctx, cancel := context.WithCancel(context.Background()) + req := httptest.NewRequest( + http.MethodGet, fmt.Sprintf("/unsafe/%s", tt.path), nil).WithContext(ctx) + app.ServeHTTP(w, req) + cancel() + if tt.expectCode == 0 { + assert.Equal(t, 200, w.Code) + } else { + assert.Equal(t, tt.expectCode, w.Code) + } + b := imagor.NewBlobFromBytes(w.Body.Bytes()) + path := tt.path + if strings.HasPrefix(path, "meta/") { + path += ".json" + } + _ = resStorage.Put(context.Background(), path, b) + path = filepath.Join(resultDir, imagorpath.Normalize(path, nil)) + bc := imagor.NewBlobFromFile(path) + buf, err := bc.ReadAll() + require.NoError(t, err) + if reflect.DeepEqual(buf, w.Body.Bytes()) { + return + } + img1, err := vips.NewImageFromBuffer(buf, nil) + require.NoError(t, err) + img2, err := vips.NewImageFromBuffer(w.Body.Bytes(), nil) + require.NoError(t, err) + require.Equal(t, img1.Width(), img2.Width(), "width mismatch") + require.Equal(t, img1.Height(), img2.Height(), "height mismatch") + buf1, err := img1.WebpsaveBuffer(nil) + require.NoError(t, err) + buf2, err := img2.WebpsaveBuffer(nil) + require.NoError(t, err) + require.True(t, reflect.DeepEqual(buf1, buf2), "image mismatch") + }) + } + + } + +} + +type loaderFunc func(r *http.Request, image string) (blob *imagor.Blob, err error) + +func (f loaderFunc) Get(r *http.Request, image string) (*imagor.Blob, error) { + return f(r, image) +} + +type readCloser struct { + io.Reader + io.Closer +} diff --git a/testdata/alpha-webm.webm b/testdata/alpha-webm.webm new file mode 100644 index 0000000..add19f2 Binary files /dev/null and b/testdata/alpha-webm.webm differ diff --git a/testdata/black.jpg b/testdata/black.jpg new file mode 100644 index 0000000..3bc9832 Binary files /dev/null and b/testdata/black.jpg differ diff --git a/testdata/demo.jpg b/testdata/demo.jpg new file mode 100644 index 0000000..9340745 Binary files /dev/null and b/testdata/demo.jpg differ diff --git a/testdata/demo.png b/testdata/demo.png new file mode 100644 index 0000000..24b35b4 Binary files /dev/null and b/testdata/demo.png differ diff --git a/testdata/demo2.jpg b/testdata/demo2.jpg new file mode 100644 index 0000000..247add8 Binary files /dev/null and b/testdata/demo2.jpg differ diff --git a/testdata/demo3.jpg b/testdata/demo3.jpg new file mode 100644 index 0000000..0f31c87 Binary files /dev/null and b/testdata/demo3.jpg differ diff --git a/testdata/everybody-betray-me.mkv b/testdata/everybody-betray-me.mkv new file mode 100644 index 0000000..3e58df8 Binary files /dev/null and b/testdata/everybody-betray-me.mkv differ diff --git a/testdata/golden/export/alpha-webm.webm-1.jpg b/testdata/golden/export/alpha-webm.webm-1.jpg new file mode 100644 index 0000000..81432c2 Binary files /dev/null and b/testdata/golden/export/alpha-webm.webm-1.jpg differ diff --git a/testdata/golden/export/alpha-webm.webm-10.jpg b/testdata/golden/export/alpha-webm.webm-10.jpg new file mode 100644 index 0000000..de68f94 Binary files /dev/null and b/testdata/golden/export/alpha-webm.webm-10.jpg differ diff --git a/testdata/golden/export/alpha-webm.webm-5.jpg b/testdata/golden/export/alpha-webm.webm-5.jpg new file mode 100644 index 0000000..de68f94 Binary files /dev/null and b/testdata/golden/export/alpha-webm.webm-5.jpg differ diff --git a/testdata/golden/export/alpha-webm.webm-9999.jpg b/testdata/golden/export/alpha-webm.webm-9999.jpg new file mode 100644 index 0000000..331f304 Binary files /dev/null and b/testdata/golden/export/alpha-webm.webm-9999.jpg differ diff --git a/testdata/golden/export/alpha-webm.webm-99999.jpg b/testdata/golden/export/alpha-webm.webm-99999.jpg new file mode 100644 index 0000000..f217275 Binary files /dev/null and b/testdata/golden/export/alpha-webm.webm-99999.jpg differ diff --git a/testdata/golden/export/alpha-webm.webm.jpg b/testdata/golden/export/alpha-webm.webm.jpg new file mode 100644 index 0000000..e6b849b Binary files /dev/null and b/testdata/golden/export/alpha-webm.webm.jpg differ diff --git a/testdata/golden/export/everybody-betray-me.mkv-1.jpg b/testdata/golden/export/everybody-betray-me.mkv-1.jpg new file mode 100644 index 0000000..a1c5eb4 Binary files /dev/null and b/testdata/golden/export/everybody-betray-me.mkv-1.jpg differ diff --git a/testdata/golden/export/everybody-betray-me.mkv-10.jpg b/testdata/golden/export/everybody-betray-me.mkv-10.jpg new file mode 100644 index 0000000..fb87c5f Binary files /dev/null and b/testdata/golden/export/everybody-betray-me.mkv-10.jpg differ diff --git a/testdata/golden/export/everybody-betray-me.mkv-5.jpg b/testdata/golden/export/everybody-betray-me.mkv-5.jpg new file mode 100644 index 0000000..6e47b22 Binary files /dev/null and b/testdata/golden/export/everybody-betray-me.mkv-5.jpg differ diff --git a/testdata/golden/export/everybody-betray-me.mkv-9999.jpg b/testdata/golden/export/everybody-betray-me.mkv-9999.jpg new file mode 100644 index 0000000..7ae12b0 Binary files /dev/null and b/testdata/golden/export/everybody-betray-me.mkv-9999.jpg differ diff --git a/testdata/golden/export/everybody-betray-me.mkv-99999.jpg b/testdata/golden/export/everybody-betray-me.mkv-99999.jpg new file mode 100644 index 0000000..027bee7 Binary files /dev/null and b/testdata/golden/export/everybody-betray-me.mkv-99999.jpg differ diff --git a/testdata/golden/export/everybody-betray-me.mkv.jpg b/testdata/golden/export/everybody-betray-me.mkv.jpg new file mode 100644 index 0000000..9ac708e Binary files /dev/null and b/testdata/golden/export/everybody-betray-me.mkv.jpg differ diff --git a/testdata/golden/export/macabre.mp4-1.jpg b/testdata/golden/export/macabre.mp4-1.jpg new file mode 100644 index 0000000..482e8db Binary files /dev/null and b/testdata/golden/export/macabre.mp4-1.jpg differ diff --git a/testdata/golden/export/macabre.mp4-10.jpg b/testdata/golden/export/macabre.mp4-10.jpg new file mode 100644 index 0000000..5a08c7d Binary files /dev/null and b/testdata/golden/export/macabre.mp4-10.jpg differ diff --git a/testdata/golden/export/macabre.mp4-5.jpg b/testdata/golden/export/macabre.mp4-5.jpg new file mode 100644 index 0000000..5a08c7d Binary files /dev/null and b/testdata/golden/export/macabre.mp4-5.jpg differ diff --git a/testdata/golden/export/macabre.mp4-9999.jpg b/testdata/golden/export/macabre.mp4-9999.jpg new file mode 100644 index 0000000..b3b3057 Binary files /dev/null and b/testdata/golden/export/macabre.mp4-9999.jpg differ diff --git a/testdata/golden/export/macabre.mp4-99999.jpg b/testdata/golden/export/macabre.mp4-99999.jpg new file mode 100644 index 0000000..b3b3057 Binary files /dev/null and b/testdata/golden/export/macabre.mp4-99999.jpg differ diff --git a/testdata/golden/export/macabre.mp4.jpg b/testdata/golden/export/macabre.mp4.jpg new file mode 100644 index 0000000..3e42111 Binary files /dev/null and b/testdata/golden/export/macabre.mp4.jpg differ diff --git a/testdata/golden/export/schizo.flv-1.jpg b/testdata/golden/export/schizo.flv-1.jpg new file mode 100644 index 0000000..499c7f0 Binary files /dev/null and b/testdata/golden/export/schizo.flv-1.jpg differ diff --git a/testdata/golden/export/schizo.flv-10.jpg b/testdata/golden/export/schizo.flv-10.jpg new file mode 100644 index 0000000..47c79fb Binary files /dev/null and b/testdata/golden/export/schizo.flv-10.jpg differ diff --git a/testdata/golden/export/schizo.flv-5.jpg b/testdata/golden/export/schizo.flv-5.jpg new file mode 100644 index 0000000..a9b389d Binary files /dev/null and b/testdata/golden/export/schizo.flv-5.jpg differ diff --git a/testdata/golden/export/schizo.flv-9999.jpg b/testdata/golden/export/schizo.flv-9999.jpg new file mode 100644 index 0000000..e35a669 Binary files /dev/null and b/testdata/golden/export/schizo.flv-9999.jpg differ diff --git a/testdata/golden/export/schizo.flv-99999.jpg b/testdata/golden/export/schizo.flv-99999.jpg new file mode 100644 index 0000000..bbac6a3 Binary files /dev/null and b/testdata/golden/export/schizo.flv-99999.jpg differ diff --git a/testdata/golden/export/schizo.flv.jpg b/testdata/golden/export/schizo.flv.jpg new file mode 100644 index 0000000..77f9e32 Binary files /dev/null and b/testdata/golden/export/schizo.flv.jpg differ diff --git a/testdata/golden/export/schizo_0.mp4-1.jpg b/testdata/golden/export/schizo_0.mp4-1.jpg new file mode 100644 index 0000000..5119177 Binary files /dev/null and b/testdata/golden/export/schizo_0.mp4-1.jpg differ diff --git a/testdata/golden/export/schizo_0.mp4-10.jpg b/testdata/golden/export/schizo_0.mp4-10.jpg new file mode 100644 index 0000000..dc14ec8 Binary files /dev/null and b/testdata/golden/export/schizo_0.mp4-10.jpg differ diff --git a/testdata/golden/export/schizo_0.mp4-5.jpg b/testdata/golden/export/schizo_0.mp4-5.jpg new file mode 100644 index 0000000..a2f81c5 Binary files /dev/null and b/testdata/golden/export/schizo_0.mp4-5.jpg differ diff --git a/testdata/golden/export/schizo_0.mp4-9999.jpg b/testdata/golden/export/schizo_0.mp4-9999.jpg new file mode 100644 index 0000000..8e57c76 Binary files /dev/null and b/testdata/golden/export/schizo_0.mp4-9999.jpg differ diff --git a/testdata/golden/export/schizo_0.mp4-99999.jpg b/testdata/golden/export/schizo_0.mp4-99999.jpg new file mode 100644 index 0000000..8e57c76 Binary files /dev/null and b/testdata/golden/export/schizo_0.mp4-99999.jpg differ diff --git a/testdata/golden/export/schizo_0.mp4.jpg b/testdata/golden/export/schizo_0.mp4.jpg new file mode 100644 index 0000000..e9f9a36 Binary files /dev/null and b/testdata/golden/export/schizo_0.mp4.jpg differ diff --git a/testdata/golden/export/schizo_180.mp4-1.jpg b/testdata/golden/export/schizo_180.mp4-1.jpg new file mode 100644 index 0000000..b526af2 Binary files /dev/null and b/testdata/golden/export/schizo_180.mp4-1.jpg differ diff --git a/testdata/golden/export/schizo_180.mp4-10.jpg b/testdata/golden/export/schizo_180.mp4-10.jpg new file mode 100644 index 0000000..1cfd0ae Binary files /dev/null and b/testdata/golden/export/schizo_180.mp4-10.jpg differ diff --git a/testdata/golden/export/schizo_180.mp4-5.jpg b/testdata/golden/export/schizo_180.mp4-5.jpg new file mode 100644 index 0000000..1cfd0ae Binary files /dev/null and b/testdata/golden/export/schizo_180.mp4-5.jpg differ diff --git a/testdata/golden/export/schizo_180.mp4-9999.jpg b/testdata/golden/export/schizo_180.mp4-9999.jpg new file mode 100644 index 0000000..f67161f Binary files /dev/null and b/testdata/golden/export/schizo_180.mp4-9999.jpg differ diff --git a/testdata/golden/export/schizo_180.mp4-99999.jpg b/testdata/golden/export/schizo_180.mp4-99999.jpg new file mode 100644 index 0000000..f67161f Binary files /dev/null and b/testdata/golden/export/schizo_180.mp4-99999.jpg differ diff --git a/testdata/golden/export/schizo_180.mp4.jpg b/testdata/golden/export/schizo_180.mp4.jpg new file mode 100644 index 0000000..fe7bf4c Binary files /dev/null and b/testdata/golden/export/schizo_180.mp4.jpg differ diff --git a/testdata/golden/export/schizo_270.mp4-1.jpg b/testdata/golden/export/schizo_270.mp4-1.jpg new file mode 100644 index 0000000..3ef301b Binary files /dev/null and b/testdata/golden/export/schizo_270.mp4-1.jpg differ diff --git a/testdata/golden/export/schizo_270.mp4-10.jpg b/testdata/golden/export/schizo_270.mp4-10.jpg new file mode 100644 index 0000000..ae84607 Binary files /dev/null and b/testdata/golden/export/schizo_270.mp4-10.jpg differ diff --git a/testdata/golden/export/schizo_270.mp4-5.jpg b/testdata/golden/export/schizo_270.mp4-5.jpg new file mode 100644 index 0000000..e6a9639 Binary files /dev/null and b/testdata/golden/export/schizo_270.mp4-5.jpg differ diff --git a/testdata/golden/export/schizo_270.mp4-9999.jpg b/testdata/golden/export/schizo_270.mp4-9999.jpg new file mode 100644 index 0000000..97b47ae Binary files /dev/null and b/testdata/golden/export/schizo_270.mp4-9999.jpg differ diff --git a/testdata/golden/export/schizo_270.mp4-99999.jpg b/testdata/golden/export/schizo_270.mp4-99999.jpg new file mode 100644 index 0000000..97b47ae Binary files /dev/null and b/testdata/golden/export/schizo_270.mp4-99999.jpg differ diff --git a/testdata/golden/export/schizo_270.mp4.jpg b/testdata/golden/export/schizo_270.mp4.jpg new file mode 100644 index 0000000..bf83cad Binary files /dev/null and b/testdata/golden/export/schizo_270.mp4.jpg differ diff --git a/testdata/golden/export/schizo_90.mp4-1.jpg b/testdata/golden/export/schizo_90.mp4-1.jpg new file mode 100644 index 0000000..240328d Binary files /dev/null and b/testdata/golden/export/schizo_90.mp4-1.jpg differ diff --git a/testdata/golden/export/schizo_90.mp4-10.jpg b/testdata/golden/export/schizo_90.mp4-10.jpg new file mode 100644 index 0000000..1befb22 Binary files /dev/null and b/testdata/golden/export/schizo_90.mp4-10.jpg differ diff --git a/testdata/golden/export/schizo_90.mp4-5.jpg b/testdata/golden/export/schizo_90.mp4-5.jpg new file mode 100644 index 0000000..03d5aae Binary files /dev/null and b/testdata/golden/export/schizo_90.mp4-5.jpg differ diff --git a/testdata/golden/export/schizo_90.mp4-9999.jpg b/testdata/golden/export/schizo_90.mp4-9999.jpg new file mode 100644 index 0000000..f6c7b31 Binary files /dev/null and b/testdata/golden/export/schizo_90.mp4-9999.jpg differ diff --git a/testdata/golden/export/schizo_90.mp4-99999.jpg b/testdata/golden/export/schizo_90.mp4-99999.jpg new file mode 100644 index 0000000..f6c7b31 Binary files /dev/null and b/testdata/golden/export/schizo_90.mp4-99999.jpg differ diff --git a/testdata/golden/export/schizo_90.mp4.jpg b/testdata/golden/export/schizo_90.mp4.jpg new file mode 100644 index 0000000..3e342ad Binary files /dev/null and b/testdata/golden/export/schizo_90.mp4.jpg differ diff --git a/testdata/golden/export/with_cover.mp3-1.jpg b/testdata/golden/export/with_cover.mp3-1.jpg new file mode 100644 index 0000000..0de0d94 Binary files /dev/null and b/testdata/golden/export/with_cover.mp3-1.jpg differ diff --git a/testdata/golden/export/with_cover.mp3-10.jpg b/testdata/golden/export/with_cover.mp3-10.jpg new file mode 100644 index 0000000..0de0d94 Binary files /dev/null and b/testdata/golden/export/with_cover.mp3-10.jpg differ diff --git a/testdata/golden/export/with_cover.mp3-5.jpg b/testdata/golden/export/with_cover.mp3-5.jpg new file mode 100644 index 0000000..0de0d94 Binary files /dev/null and b/testdata/golden/export/with_cover.mp3-5.jpg differ diff --git a/testdata/golden/export/with_cover.mp3-9999.jpg b/testdata/golden/export/with_cover.mp3-9999.jpg new file mode 100644 index 0000000..0de0d94 Binary files /dev/null and b/testdata/golden/export/with_cover.mp3-9999.jpg differ diff --git a/testdata/golden/export/with_cover.mp3-99999.jpg b/testdata/golden/export/with_cover.mp3-99999.jpg new file mode 100644 index 0000000..0de0d94 Binary files /dev/null and b/testdata/golden/export/with_cover.mp3-99999.jpg differ diff --git a/testdata/golden/export/with_cover.mp3.jpg b/testdata/golden/export/with_cover.mp3.jpg new file mode 100644 index 0000000..0de0d94 Binary files /dev/null and b/testdata/golden/export/with_cover.mp3.jpg differ diff --git a/testdata/golden/meta/alpha-webm.webm-1.meta.json b/testdata/golden/meta/alpha-webm.webm-1.meta.json new file mode 100644 index 0000000..e45c027 --- /dev/null +++ b/testdata/golden/meta/alpha-webm.webm-1.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false} \ No newline at end of file diff --git a/testdata/golden/meta/alpha-webm.webm-10.meta.json b/testdata/golden/meta/alpha-webm.webm-10.meta.json new file mode 100644 index 0000000..e45c027 --- /dev/null +++ b/testdata/golden/meta/alpha-webm.webm-10.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false} \ No newline at end of file diff --git a/testdata/golden/meta/alpha-webm.webm-5.meta.json b/testdata/golden/meta/alpha-webm.webm-5.meta.json new file mode 100644 index 0000000..e45c027 --- /dev/null +++ b/testdata/golden/meta/alpha-webm.webm-5.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false} \ No newline at end of file diff --git a/testdata/golden/meta/alpha-webm.webm-9999.meta.json b/testdata/golden/meta/alpha-webm.webm-9999.meta.json new file mode 100644 index 0000000..e45c027 --- /dev/null +++ b/testdata/golden/meta/alpha-webm.webm-9999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false} \ No newline at end of file diff --git a/testdata/golden/meta/alpha-webm.webm-99999.meta.json b/testdata/golden/meta/alpha-webm.webm-99999.meta.json new file mode 100644 index 0000000..e45c027 --- /dev/null +++ b/testdata/golden/meta/alpha-webm.webm-99999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false} \ No newline at end of file diff --git a/testdata/golden/meta/alpha-webm.webm.meta.json b/testdata/golden/meta/alpha-webm.webm.meta.json new file mode 100644 index 0000000..e45c027 --- /dev/null +++ b/testdata/golden/meta/alpha-webm.webm.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false} \ No newline at end of file diff --git a/testdata/golden/meta/everybody-betray-me.mkv-1.meta.json b/testdata/golden/meta/everybody-betray-me.mkv-1.meta.json new file mode 100644 index 0000000..1444d9e --- /dev/null +++ b/testdata/golden/meta/everybody-betray-me.mkv-1.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/everybody-betray-me.mkv-10.meta.json b/testdata/golden/meta/everybody-betray-me.mkv-10.meta.json new file mode 100644 index 0000000..1444d9e --- /dev/null +++ b/testdata/golden/meta/everybody-betray-me.mkv-10.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/everybody-betray-me.mkv-5.meta.json b/testdata/golden/meta/everybody-betray-me.mkv-5.meta.json new file mode 100644 index 0000000..1444d9e --- /dev/null +++ b/testdata/golden/meta/everybody-betray-me.mkv-5.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/everybody-betray-me.mkv-9999.meta.json b/testdata/golden/meta/everybody-betray-me.mkv-9999.meta.json new file mode 100644 index 0000000..1444d9e --- /dev/null +++ b/testdata/golden/meta/everybody-betray-me.mkv-9999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/everybody-betray-me.mkv-99999.meta.json b/testdata/golden/meta/everybody-betray-me.mkv-99999.meta.json new file mode 100644 index 0000000..1444d9e --- /dev/null +++ b/testdata/golden/meta/everybody-betray-me.mkv-99999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/everybody-betray-me.mkv.meta.json b/testdata/golden/meta/everybody-betray-me.mkv.meta.json new file mode 100644 index 0000000..1444d9e --- /dev/null +++ b/testdata/golden/meta/everybody-betray-me.mkv.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/macabre.mp4-1.meta.json b/testdata/golden/meta/macabre.mp4-1.meta.json new file mode 100644 index 0000000..e298110 --- /dev/null +++ b/testdata/golden/meta/macabre.mp4-1.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/macabre.mp4-10.meta.json b/testdata/golden/meta/macabre.mp4-10.meta.json new file mode 100644 index 0000000..e298110 --- /dev/null +++ b/testdata/golden/meta/macabre.mp4-10.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/macabre.mp4-5.meta.json b/testdata/golden/meta/macabre.mp4-5.meta.json new file mode 100644 index 0000000..e298110 --- /dev/null +++ b/testdata/golden/meta/macabre.mp4-5.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/macabre.mp4-9999.meta.json b/testdata/golden/meta/macabre.mp4-9999.meta.json new file mode 100644 index 0000000..e298110 --- /dev/null +++ b/testdata/golden/meta/macabre.mp4-9999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/macabre.mp4-99999.meta.json b/testdata/golden/meta/macabre.mp4-99999.meta.json new file mode 100644 index 0000000..e298110 --- /dev/null +++ b/testdata/golden/meta/macabre.mp4-99999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/macabre.mp4.meta.json b/testdata/golden/meta/macabre.mp4.meta.json new file mode 100644 index 0000000..e298110 --- /dev/null +++ b/testdata/golden/meta/macabre.mp4.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/no_cover.mp3.meta.json b/testdata/golden/meta/no_cover.mp3.meta.json new file mode 100644 index 0000000..8526068 --- /dev/null +++ b/testdata/golden/meta/no_cover.mp3.meta.json @@ -0,0 +1 @@ +{"orientation":0,"duration":13536,"has_video":false,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo.flv-1.meta.json b/testdata/golden/meta/schizo.flv-1.meta.json new file mode 100644 index 0000000..eaa82ba --- /dev/null +++ b/testdata/golden/meta/schizo.flv-1.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo.flv-10.meta.json b/testdata/golden/meta/schizo.flv-10.meta.json new file mode 100644 index 0000000..eaa82ba --- /dev/null +++ b/testdata/golden/meta/schizo.flv-10.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo.flv-5.meta.json b/testdata/golden/meta/schizo.flv-5.meta.json new file mode 100644 index 0000000..eaa82ba --- /dev/null +++ b/testdata/golden/meta/schizo.flv-5.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo.flv-9999.meta.json b/testdata/golden/meta/schizo.flv-9999.meta.json new file mode 100644 index 0000000..eaa82ba --- /dev/null +++ b/testdata/golden/meta/schizo.flv-9999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo.flv-99999.meta.json b/testdata/golden/meta/schizo.flv-99999.meta.json new file mode 100644 index 0000000..eaa82ba --- /dev/null +++ b/testdata/golden/meta/schizo.flv-99999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo.flv.meta.json b/testdata/golden/meta/schizo.flv.meta.json new file mode 100644 index 0000000..eaa82ba --- /dev/null +++ b/testdata/golden/meta/schizo.flv.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_0.mp4-1.meta.json b/testdata/golden/meta/schizo_0.mp4-1.meta.json new file mode 100644 index 0000000..deb4d31 --- /dev/null +++ b/testdata/golden/meta/schizo_0.mp4-1.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_0.mp4-10.meta.json b/testdata/golden/meta/schizo_0.mp4-10.meta.json new file mode 100644 index 0000000..deb4d31 --- /dev/null +++ b/testdata/golden/meta/schizo_0.mp4-10.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_0.mp4-5.meta.json b/testdata/golden/meta/schizo_0.mp4-5.meta.json new file mode 100644 index 0000000..deb4d31 --- /dev/null +++ b/testdata/golden/meta/schizo_0.mp4-5.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_0.mp4-9999.meta.json b/testdata/golden/meta/schizo_0.mp4-9999.meta.json new file mode 100644 index 0000000..deb4d31 --- /dev/null +++ b/testdata/golden/meta/schizo_0.mp4-9999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_0.mp4-99999.meta.json b/testdata/golden/meta/schizo_0.mp4-99999.meta.json new file mode 100644 index 0000000..deb4d31 --- /dev/null +++ b/testdata/golden/meta/schizo_0.mp4-99999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_0.mp4.meta.json b/testdata/golden/meta/schizo_0.mp4.meta.json new file mode 100644 index 0000000..deb4d31 --- /dev/null +++ b/testdata/golden/meta/schizo_0.mp4.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_180.mp4-1.meta.json b/testdata/golden/meta/schizo_180.mp4-1.meta.json new file mode 100644 index 0000000..9dfd3c4 --- /dev/null +++ b/testdata/golden/meta/schizo_180.mp4-1.meta.json @@ -0,0 +1 @@ +{"orientation":3,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_180.mp4-10.meta.json b/testdata/golden/meta/schizo_180.mp4-10.meta.json new file mode 100644 index 0000000..9dfd3c4 --- /dev/null +++ b/testdata/golden/meta/schizo_180.mp4-10.meta.json @@ -0,0 +1 @@ +{"orientation":3,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_180.mp4-5.meta.json b/testdata/golden/meta/schizo_180.mp4-5.meta.json new file mode 100644 index 0000000..9dfd3c4 --- /dev/null +++ b/testdata/golden/meta/schizo_180.mp4-5.meta.json @@ -0,0 +1 @@ +{"orientation":3,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_180.mp4-9999.meta.json b/testdata/golden/meta/schizo_180.mp4-9999.meta.json new file mode 100644 index 0000000..9dfd3c4 --- /dev/null +++ b/testdata/golden/meta/schizo_180.mp4-9999.meta.json @@ -0,0 +1 @@ +{"orientation":3,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_180.mp4-99999.meta.json b/testdata/golden/meta/schizo_180.mp4-99999.meta.json new file mode 100644 index 0000000..9dfd3c4 --- /dev/null +++ b/testdata/golden/meta/schizo_180.mp4-99999.meta.json @@ -0,0 +1 @@ +{"orientation":3,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_180.mp4.meta.json b/testdata/golden/meta/schizo_180.mp4.meta.json new file mode 100644 index 0000000..9dfd3c4 --- /dev/null +++ b/testdata/golden/meta/schizo_180.mp4.meta.json @@ -0,0 +1 @@ +{"orientation":3,"duration":2535,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_270.mp4-1.meta.json b/testdata/golden/meta/schizo_270.mp4-1.meta.json new file mode 100644 index 0000000..4111717 --- /dev/null +++ b/testdata/golden/meta/schizo_270.mp4-1.meta.json @@ -0,0 +1 @@ +{"orientation":6,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_270.mp4-10.meta.json b/testdata/golden/meta/schizo_270.mp4-10.meta.json new file mode 100644 index 0000000..4111717 --- /dev/null +++ b/testdata/golden/meta/schizo_270.mp4-10.meta.json @@ -0,0 +1 @@ +{"orientation":6,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_270.mp4-5.meta.json b/testdata/golden/meta/schizo_270.mp4-5.meta.json new file mode 100644 index 0000000..4111717 --- /dev/null +++ b/testdata/golden/meta/schizo_270.mp4-5.meta.json @@ -0,0 +1 @@ +{"orientation":6,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_270.mp4-9999.meta.json b/testdata/golden/meta/schizo_270.mp4-9999.meta.json new file mode 100644 index 0000000..4111717 --- /dev/null +++ b/testdata/golden/meta/schizo_270.mp4-9999.meta.json @@ -0,0 +1 @@ +{"orientation":6,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_270.mp4-99999.meta.json b/testdata/golden/meta/schizo_270.mp4-99999.meta.json new file mode 100644 index 0000000..4111717 --- /dev/null +++ b/testdata/golden/meta/schizo_270.mp4-99999.meta.json @@ -0,0 +1 @@ +{"orientation":6,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_270.mp4.meta.json b/testdata/golden/meta/schizo_270.mp4.meta.json new file mode 100644 index 0000000..4111717 --- /dev/null +++ b/testdata/golden/meta/schizo_270.mp4.meta.json @@ -0,0 +1 @@ +{"orientation":6,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_90.mp4-1.meta.json b/testdata/golden/meta/schizo_90.mp4-1.meta.json new file mode 100644 index 0000000..57bb871 --- /dev/null +++ b/testdata/golden/meta/schizo_90.mp4-1.meta.json @@ -0,0 +1 @@ +{"orientation":8,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_90.mp4-10.meta.json b/testdata/golden/meta/schizo_90.mp4-10.meta.json new file mode 100644 index 0000000..57bb871 --- /dev/null +++ b/testdata/golden/meta/schizo_90.mp4-10.meta.json @@ -0,0 +1 @@ +{"orientation":8,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_90.mp4-5.meta.json b/testdata/golden/meta/schizo_90.mp4-5.meta.json new file mode 100644 index 0000000..57bb871 --- /dev/null +++ b/testdata/golden/meta/schizo_90.mp4-5.meta.json @@ -0,0 +1 @@ +{"orientation":8,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_90.mp4-9999.meta.json b/testdata/golden/meta/schizo_90.mp4-9999.meta.json new file mode 100644 index 0000000..57bb871 --- /dev/null +++ b/testdata/golden/meta/schizo_90.mp4-9999.meta.json @@ -0,0 +1 @@ +{"orientation":8,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_90.mp4-99999.meta.json b/testdata/golden/meta/schizo_90.mp4-99999.meta.json new file mode 100644 index 0000000..57bb871 --- /dev/null +++ b/testdata/golden/meta/schizo_90.mp4-99999.meta.json @@ -0,0 +1 @@ +{"orientation":8,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/schizo_90.mp4.meta.json b/testdata/golden/meta/schizo_90.mp4.meta.json new file mode 100644 index 0000000..57bb871 --- /dev/null +++ b/testdata/golden/meta/schizo_90.mp4.meta.json @@ -0,0 +1 @@ +{"orientation":8,"duration":2535,"width":360,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/with_cover.mp3-1.meta.json b/testdata/golden/meta/with_cover.mp3-1.meta.json new file mode 100644 index 0000000..00ce5c9 --- /dev/null +++ b/testdata/golden/meta/with_cover.mp3-1.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":1906,"width":1280,"height":720,"fps":90000,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/with_cover.mp3-10.meta.json b/testdata/golden/meta/with_cover.mp3-10.meta.json new file mode 100644 index 0000000..00ce5c9 --- /dev/null +++ b/testdata/golden/meta/with_cover.mp3-10.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":1906,"width":1280,"height":720,"fps":90000,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/with_cover.mp3-5.meta.json b/testdata/golden/meta/with_cover.mp3-5.meta.json new file mode 100644 index 0000000..00ce5c9 --- /dev/null +++ b/testdata/golden/meta/with_cover.mp3-5.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":1906,"width":1280,"height":720,"fps":90000,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/with_cover.mp3-9999.meta.json b/testdata/golden/meta/with_cover.mp3-9999.meta.json new file mode 100644 index 0000000..00ce5c9 --- /dev/null +++ b/testdata/golden/meta/with_cover.mp3-9999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":1906,"width":1280,"height":720,"fps":90000,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/with_cover.mp3-99999.meta.json b/testdata/golden/meta/with_cover.mp3-99999.meta.json new file mode 100644 index 0000000..00ce5c9 --- /dev/null +++ b/testdata/golden/meta/with_cover.mp3-99999.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":1906,"width":1280,"height":720,"fps":90000,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/meta/with_cover.mp3.meta.json b/testdata/golden/meta/with_cover.mp3.meta.json new file mode 100644 index 0000000..00ce5c9 --- /dev/null +++ b/testdata/golden/meta/with_cover.mp3.meta.json @@ -0,0 +1 @@ +{"orientation":1,"duration":1906,"width":1280,"height":720,"fps":90000,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/result-fallback-image/filters%3Aseek%280.1%29/no_cover.mp3 b/testdata/golden/result-fallback-image/filters%3Aseek%280.1%29/no_cover.mp3 new file mode 100644 index 0000000..8c74ba6 --- /dev/null +++ b/testdata/golden/result-fallback-image/filters%3Aseek%280.1%29/no_cover.mp3 @@ -0,0 +1 @@ +{"message":"ffmpeg: decoder not found","status":406} \ No newline at end of file diff --git a/testdata/golden/result-fallback-image/fit-in/100x100/corrupt/everybody-betray-me.mkv b/testdata/golden/result-fallback-image/fit-in/100x100/corrupt/everybody-betray-me.mkv new file mode 100644 index 0000000..57dc1fc --- /dev/null +++ b/testdata/golden/result-fallback-image/fit-in/100x100/corrupt/everybody-betray-me.mkv @@ -0,0 +1 @@ +{"message":"ffmpeg: invalid data found when processing input","status":406} \ No newline at end of file diff --git a/testdata/golden/result/200x100/schizo_0.mp4 b/testdata/golden/result/200x100/schizo_0.mp4 new file mode 100644 index 0000000..8a84a32 Binary files /dev/null and b/testdata/golden/result/200x100/schizo_0.mp4 differ diff --git a/testdata/golden/result/200x100/schizo_180.mp4 b/testdata/golden/result/200x100/schizo_180.mp4 new file mode 100644 index 0000000..49c8356 Binary files /dev/null and b/testdata/golden/result/200x100/schizo_180.mp4 differ diff --git a/testdata/golden/result/200x100/schizo_270.mp4 b/testdata/golden/result/200x100/schizo_270.mp4 new file mode 100644 index 0000000..4b90c98 Binary files /dev/null and b/testdata/golden/result/200x100/schizo_270.mp4 differ diff --git a/testdata/golden/result/220x100/schizo_90.mp4 b/testdata/golden/result/220x100/schizo_90.mp4 new file mode 100644 index 0000000..c1cdec5 Binary files /dev/null and b/testdata/golden/result/220x100/schizo_90.mp4 differ diff --git a/testdata/golden/result/500x/filters%3Aframe%280.5%29%3Aformat%28png%29/alpha-webm.webm b/testdata/golden/result/500x/filters%3Aframe%280.5%29%3Aformat%28png%29/alpha-webm.webm new file mode 100644 index 0000000..adb5b6c Binary files /dev/null and b/testdata/golden/result/500x/filters%3Aframe%280.5%29%3Aformat%28png%29/alpha-webm.webm differ diff --git a/testdata/golden/result/500x/filters%3Aframe%285s%29%3Aformat%28png%29/alpha-webm.webm b/testdata/golden/result/500x/filters%3Aframe%285s%29%3Aformat%28png%29/alpha-webm.webm new file mode 100644 index 0000000..288b08e Binary files /dev/null and b/testdata/golden/result/500x/filters%3Aframe%285s%29%3Aformat%28png%29/alpha-webm.webm differ diff --git a/testdata/golden/result/500x/filters%3Aseek%280.5%29%3Aformat%28png%29/alpha-webm.webm b/testdata/golden/result/500x/filters%3Aseek%280.5%29%3Aformat%28png%29/alpha-webm.webm new file mode 100644 index 0000000..805e1fa Binary files /dev/null and b/testdata/golden/result/500x/filters%3Aseek%280.5%29%3Aformat%28png%29/alpha-webm.webm differ diff --git a/testdata/golden/result/500x/filters%3Aseek%285s%29%3Aformat%28png%29/alpha-webm.webm b/testdata/golden/result/500x/filters%3Aseek%285s%29%3Aformat%28png%29/alpha-webm.webm new file mode 100644 index 0000000..85890e1 Binary files /dev/null and b/testdata/golden/result/500x/filters%3Aseek%285s%29%3Aformat%28png%29/alpha-webm.webm differ diff --git a/testdata/golden/result/fit-in/100x100/corrupt/everybody-betray-me.mkv b/testdata/golden/result/fit-in/100x100/corrupt/everybody-betray-me.mkv new file mode 100644 index 0000000..57dc1fc --- /dev/null +++ b/testdata/golden/result/fit-in/100x100/corrupt/everybody-betray-me.mkv @@ -0,0 +1 @@ +{"message":"ffmpeg: invalid data found when processing input","status":406} \ No newline at end of file diff --git a/testdata/golden/result/fit-in/100x100/demo.png b/testdata/golden/result/fit-in/100x100/demo.png new file mode 100644 index 0000000..960fc72 Binary files /dev/null and b/testdata/golden/result/fit-in/100x100/demo.png differ diff --git a/testdata/golden/result/fit-in/100x100/everybody-betray-me.mkv b/testdata/golden/result/fit-in/100x100/everybody-betray-me.mkv new file mode 100644 index 0000000..3a4b600 Binary files /dev/null and b/testdata/golden/result/fit-in/100x100/everybody-betray-me.mkv differ diff --git a/testdata/golden/result/fit-in/100x100/filters%3Aframe%283%29/everybody-betray-me.mkv b/testdata/golden/result/fit-in/100x100/filters%3Aframe%283%29/everybody-betray-me.mkv new file mode 100644 index 0000000..728547c Binary files /dev/null and b/testdata/golden/result/fit-in/100x100/filters%3Aframe%283%29/everybody-betray-me.mkv differ diff --git a/testdata/golden/result/fit-in/100x100/filters%3Aframe%2899999%29/everybody-betray-me.mkv b/testdata/golden/result/fit-in/100x100/filters%3Aframe%2899999%29/everybody-betray-me.mkv new file mode 100644 index 0000000..893b561 Binary files /dev/null and b/testdata/golden/result/fit-in/100x100/filters%3Aframe%2899999%29/everybody-betray-me.mkv differ diff --git a/testdata/golden/result/fit-in/100x100/filters%3Amax_frames%286%29/everybody-betray-me.mkv b/testdata/golden/result/fit-in/100x100/filters%3Amax_frames%286%29/everybody-betray-me.mkv new file mode 100644 index 0000000..2270ad6 Binary files /dev/null and b/testdata/golden/result/fit-in/100x100/filters%3Amax_frames%286%29/everybody-betray-me.mkv differ diff --git a/testdata/golden/result/fit-in/100x100/no_cover.mp3 b/testdata/golden/result/fit-in/100x100/no_cover.mp3 new file mode 100644 index 0000000..8c74ba6 --- /dev/null +++ b/testdata/golden/result/fit-in/100x100/no_cover.mp3 @@ -0,0 +1 @@ +{"message":"ffmpeg: decoder not found","status":406} \ No newline at end of file diff --git a/testdata/golden/result/fit-in/filters%3Aformat%28png%29/alpha-webm.webm b/testdata/golden/result/fit-in/filters%3Aformat%28png%29/alpha-webm.webm new file mode 100644 index 0000000..3d86d86 Binary files /dev/null and b/testdata/golden/result/fit-in/filters%3Aformat%28png%29/alpha-webm.webm differ diff --git a/testdata/golden/result/meta/everybody-betray-me.mkv.json b/testdata/golden/result/meta/everybody-betray-me.mkv.json new file mode 100644 index 0000000..586d7e6 --- /dev/null +++ b/testdata/golden/result/meta/everybody-betray-me.mkv.json @@ -0,0 +1 @@ +{"format":"mkv","content_type":"video/x-matroska","orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/result/meta/filters%3Amax_frames%28%29/everybody-betray-me.mkv.json b/testdata/golden/result/meta/filters%3Amax_frames%28%29/everybody-betray-me.mkv.json new file mode 100644 index 0000000..586d7e6 --- /dev/null +++ b/testdata/golden/result/meta/filters%3Amax_frames%28%29/everybody-betray-me.mkv.json @@ -0,0 +1 @@ +{"format":"mkv","content_type":"video/x-matroska","orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/result/meta/filters%3Amax_frames%286%29/everybody-betray-me.mkv.json b/testdata/golden/result/meta/filters%3Amax_frames%286%29/everybody-betray-me.mkv.json new file mode 100644 index 0000000..586d7e6 --- /dev/null +++ b/testdata/golden/result/meta/filters%3Amax_frames%286%29/everybody-betray-me.mkv.json @@ -0,0 +1 @@ +{"format":"mkv","content_type":"video/x-matroska","orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true} \ No newline at end of file diff --git a/testdata/golden/result/meta/no_cover.mp3.json b/testdata/golden/result/meta/no_cover.mp3.json new file mode 100644 index 0000000..6f0994b --- /dev/null +++ b/testdata/golden/result/meta/no_cover.mp3.json @@ -0,0 +1 @@ +{"format":"mp3","content_type":"audio/mpeg","orientation":0,"duration":13536,"has_video":false,"has_audio":true} \ No newline at end of file diff --git a/testdata/macabre.mp4 b/testdata/macabre.mp4 new file mode 100644 index 0000000..0177b9c Binary files /dev/null and b/testdata/macabre.mp4 differ diff --git a/testdata/no_cover.mp3 b/testdata/no_cover.mp3 new file mode 100644 index 0000000..9c6f3d9 Binary files /dev/null and b/testdata/no_cover.mp3 differ diff --git a/testdata/schizo.flv b/testdata/schizo.flv new file mode 100644 index 0000000..2e3efa1 Binary files /dev/null and b/testdata/schizo.flv differ diff --git a/testdata/schizo_0.mp4 b/testdata/schizo_0.mp4 new file mode 100644 index 0000000..2047fc8 Binary files /dev/null and b/testdata/schizo_0.mp4 differ diff --git a/testdata/schizo_180.mp4 b/testdata/schizo_180.mp4 new file mode 100644 index 0000000..7ae8a74 Binary files /dev/null and b/testdata/schizo_180.mp4 differ diff --git a/testdata/schizo_270.mp4 b/testdata/schizo_270.mp4 new file mode 100644 index 0000000..1b6450e Binary files /dev/null and b/testdata/schizo_270.mp4 differ diff --git a/testdata/schizo_90.mp4 b/testdata/schizo_90.mp4 new file mode 100644 index 0000000..b110972 Binary files /dev/null and b/testdata/schizo_90.mp4 differ diff --git a/testdata/seek5m.jpg b/testdata/seek5m.jpg new file mode 100644 index 0000000..d800142 Binary files /dev/null and b/testdata/seek5m.jpg differ diff --git a/testdata/with_cover.mp3 b/testdata/with_cover.mp3 new file mode 100644 index 0000000..b5995f9 Binary files /dev/null and b/testdata/with_cover.mp3 differ