Merge branch 'github-master'
74
.github/workflows/builder.yml
vendored
Normal file
|
|
@ -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 }}
|
||||||
57
.github/workflows/docker.yml
vendored
|
|
@ -1,13 +1,9 @@
|
||||||
name: docker
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches:
|
||||||
|
- master
|
||||||
tags: [ 'v*.*.*' ]
|
tags: [ 'v*.*.*' ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -17,32 +13,21 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
# This is used to complete the identity challenge
|
|
||||||
# with sigstore/fulcio when running outside of PRs.
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Setup qemu for Docker buildx
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
id: qemu
|
uses: docker/setup-qemu-action@v3
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
with:
|
with:
|
||||||
image: tonistiigi/binfmt:latest
|
|
||||||
platforms: arm64
|
platforms: arm64
|
||||||
|
|
||||||
# Workaround: https://github.com/docker/build-push-action/issues/461
|
|
||||||
- name: Setup Docker buildx
|
- name: Setup Docker buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
# Login against a Docker registry except on PR
|
|
||||||
# https://github.com/docker/login-action
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|
@ -50,44 +35,28 @@ jobs:
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
if: github.repository == 'cshum/imagorvideo'
|
if: github.repository == 'cshum/imagorvideo'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
# Extract metadata (tags, labels) for Docker
|
|
||||||
# https://github.com/docker/metadata-action
|
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: |
|
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' }}
|
name=shumc/imagorvideo,enable=${{ github.repository == 'cshum/imagorvideo' }}
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=semver,pattern={{version}}
|
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)
|
- name: Build and push Docker image
|
||||||
# https://github.com/docker/build-push-action
|
uses: docker/build-push-action@v5
|
||||||
- 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
|
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
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 }}
|
|
||||||
|
|
||||||
- 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
|
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
|
||||||
143
.github/workflows/test.yml
vendored
|
|
@ -1,126 +1,57 @@
|
||||||
name: test
|
name: test
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test:
|
||||||
name: Test
|
name: Docker Test
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
env:
|
|
||||||
CGO_CFLAGS_ALLOW: -Xpreprocessor
|
|
||||||
VIPS_VERSION: 8.15.2
|
|
||||||
FFMPEG_VERSION: 5.1.2
|
|
||||||
V: 4
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.x
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ^1.22
|
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install linux dependencies
|
- name: Set up Docker Buildx
|
||||||
run: |
|
uses: docker/setup-buildx-action@v3
|
||||||
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: Cache libvips
|
- name: Build test image
|
||||||
uses: actions/cache@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
path: vips-${{ env.VIPS_VERSION }}
|
context: .
|
||||||
key: ${{ runner.os }}-vips-${{ env.V }}-${{ env.VIPS_VERSION }}
|
target: builder
|
||||||
restore-keys: |
|
tags: imagorvideo-test
|
||||||
${{ runner.os }}-vips-${{ env.V }}-
|
load: true
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Build libvips from source
|
- name: Run tests in Docker
|
||||||
run: |
|
run: |
|
||||||
if [ ! -d "vips-${{ env.VIPS_VERSION }}" ]
|
docker run --rm \
|
||||||
then
|
-v ${{ github.workspace }}:/workspace \
|
||||||
wget https://github.com/libvips/libvips/releases/download/v${{ env.VIPS_VERSION }}/vips-${{ env.VIPS_VERSION }}.tar.xz
|
-w /workspace \
|
||||||
tar xf vips-${{ env.VIPS_VERSION }}.tar.xz
|
imagorvideo-test \
|
||||||
fi
|
go test ./... -v
|
||||||
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
|
|
||||||
|
|
||||||
- name: Commit golden files
|
- name: Commit golden files
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
uses: stefanzweifel/git-auto-commit-action@v4
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
with:
|
with:
|
||||||
commit_message: "test: update golden files"
|
commit_message: "test: update golden files"
|
||||||
file_pattern: "testdata/golden"
|
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
|
- name: Coveralls
|
||||||
uses: shogo82148/actions-goveralls@v1
|
uses: shogo82148/actions-goveralls@v1
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
80
Dockerfile
|
|
@ -1,66 +1,9 @@
|
||||||
ARG GOLANG_VERSION=1.22.5
|
ARG BUILDER_IMAGE_TAG=ffmpeg-7.1.1-vips-8.17.2-go-1.25.1
|
||||||
FROM golang:${GOLANG_VERSION}-bookworm as builder
|
|
||||||
|
|
||||||
ARG FFMPEG_VERSION=5.1.2
|
# Stage 1: Build application using builder image with go + libvips + FFmpeg
|
||||||
ARG VIPS_VERSION=8.15.2
|
FROM ghcr.io/cshum/imagorvideo-builder:${BUILDER_IMAGE_TAG} AS builder
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
|
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
|
WORKDIR ${GOPATH}/src/github.com/cshum/imagorvideo
|
||||||
|
|
||||||
|
|
@ -73,22 +16,23 @@ COPY . .
|
||||||
|
|
||||||
RUN go build -o ${GOPATH}/bin/imagorvideo ./cmd/imagorvideo/main.go
|
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"
|
LABEL maintainer="adrian@cshum.com"
|
||||||
|
|
||||||
COPY --from=builder /usr/local/lib /usr/local/lib
|
COPY --from=builder /usr/local/lib /usr/local/lib
|
||||||
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
|
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
|
||||||
|
|
||||||
# Install runtime dependencies
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive \
|
RUN DEBIAN_FRONTEND=noninteractive \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install --no-install-recommends -y \
|
apt-get install --no-install-recommends -y \
|
||||||
procps libglib2.0-0 libjpeg62-turbo libpng16-16 libopenexr-3-1-30 \
|
procps curl libglib2.0-0 libjpeg62-turbo libpng16-16 libopenexr-3-1-30 \
|
||||||
libwebp7 libwebpmux3 libwebpdemux2 libtiff6 libexif12 libxml2 libpoppler-glib8 \
|
libwebp7 libwebpmux3 libwebpdemux2 libtiff6 libexif12 libxml2 libpoppler-glib8t64 \
|
||||||
libpango1.0-0 libmatio11 libopenslide0 libopenjp2-7 libjemalloc2 \
|
libpango-1.0-0 libmatio13 libopenslide0 libopenjp2-7 libjemalloc2 \
|
||||||
libgsf-1-114 libfftw3-bin liborc-0.4-0 librsvg2-2 libcfitsio10 libimagequant0 libaom3 libheif1 \
|
libgsf-1-114 libfftw3-bin liborc-0.4-0 librsvg2-2 libcfitsio10t64 libimagequant0 libaom3 \
|
||||||
libx264-dev libx265-dev libnuma-dev libvpx7 libtheora0 libvorbis-dev \
|
libspng0 libcgif0 libheif1 libheif-plugin-x265 libheif-plugin-aomenc libjxl0.11 libavif-dev \
|
||||||
libspng0 libcgif0 && \
|
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 && \
|
ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
apt-get autoclean && \
|
apt-get autoclean && \
|
||||||
|
|
|
||||||
73
Dockerfile.builder
Normal file
|
|
@ -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"
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
[](https://github.com/cshum/imagorvideo/actions/workflows/test.yml)
|
[](https://github.com/cshum/imagorvideo/actions/workflows/test.yml)
|
||||||
[](https://coveralls.io/github/cshum/imagorvideo?branch=master)
|
[](https://coveralls.io/github/cshum/imagorvideo?branch=master)
|
||||||
[](https://hub.docker.com/r/shumc/imagorvideo/)
|
[](https://hub.docker.com/r/shumc/imagorvideo/)
|
||||||
[](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.
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,25 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
imagorvideoextended "git.cef.icu/CEF/imagorextended"
|
// imagorvideoextended "git.cef.icu/CEF/imagorextended"
|
||||||
"github.com/cshum/imagor/config"
|
"github.com/cshum/imagor/config"
|
||||||
"github.com/cshum/imagor/config/awsconfig"
|
"github.com/cshum/imagor/config/awsconfig"
|
||||||
"github.com/cshum/imagor/config/gcloudconfig"
|
"github.com/cshum/imagor/config/gcloudconfig"
|
||||||
"github.com/cshum/imagor/config/vipsconfig"
|
"github.com/cshum/imagor/config/vipsconfig"
|
||||||
"github.com/cshum/imagorvideo"
|
"github.com/cshum/imagorvideo"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var server = config.CreateServer(
|
var server = config.CreateServer(
|
||||||
os.Args[1:],
|
os.Args[1:],
|
||||||
//imagorvideoextended.LoaderConfig,
|
// imagorvideoextended.Config,
|
||||||
imagorvideoextended.Config,
|
imagorvideo.Config,
|
||||||
imagorvideo.Config,
|
vipsconfig.WithVips,
|
||||||
vipsconfig.WithVips,
|
awsconfig.WithAWS,
|
||||||
awsconfig.WithAWS,
|
gcloudconfig.WithGCloud,
|
||||||
gcloudconfig.WithGCloud,
|
)
|
||||||
)
|
if server != nil {
|
||||||
if server != nil {
|
server.Run()
|
||||||
server.Run()
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
ffmpeg/callback.go
Normal file
|
|
@ -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))
|
||||||
|
}
|
||||||
38
ffmpeg/errors.go
Normal file
|
|
@ -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()
|
||||||
|
}
|
||||||
15
ffmpeg/errors_test.go
Normal file
|
|
@ -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())
|
||||||
|
}
|
||||||
425
ffmpeg/ffmpeg.c
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
402
ffmpeg/ffmpeg.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
68
ffmpeg/ffmpeg.h
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#include <math.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#include <libavutil/file.h>
|
||||||
|
#include <libavutil/pixfmt.h>
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
#include <libavutil/pixdesc.h>
|
||||||
|
#include <libavutil/intreadwrite.h>
|
||||||
|
#include <libavutil/imgutils.h>
|
||||||
|
#include <libavutil/display.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
188
ffmpeg/ffmpeg_test.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
12
ffmpeg/logging.c
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
55
ffmpeg/logging.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
11
ffmpeg/logging.h
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#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();
|
||||||
129
go.mod
|
|
@ -1,68 +1,95 @@
|
||||||
module git.cef.icu/CEF/imagorextended
|
module github.com/cshum/imagorvideo
|
||||||
|
|
||||||
go 1.21
|
go 1.24.2
|
||||||
|
|
||||||
toolchain go1.21.1
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cshum/imagor v1.4.13
|
github.com/cshum/imagor v1.6.6
|
||||||
github.com/cshum/imagorvideo v0.4.13
|
github.com/cshum/vipsgen v1.2.1
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4
|
github.com/gabriel-vasile/mimetype v1.4.11
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.11.1
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.115.0 // indirect
|
cel.dev/expr v0.25.1 // indirect
|
||||||
cloud.google.com/go/auth v0.7.1 // indirect
|
cloud.google.com/go v0.123.0 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
|
cloud.google.com/go/auth v0.17.0 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
cloud.google.com/go/iam v1.1.11 // indirect
|
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||||
cloud.google.com/go/storage v1.43.0 // indirect
|
cloud.google.com/go/iam v1.5.3 // indirect
|
||||||
github.com/antchfx/htmlquery v1.3.2 // indirect
|
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||||
github.com/antchfx/xpath v1.3.1 // indirect
|
cloud.google.com/go/storage v1.57.2 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.54.20 // 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/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // 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/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/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/peterbourgon/ff/v3 v3.4.0 // indirect
|
github.com/peterbourgon/ff/v3 v3.4.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||||
github.com/prometheus/common v0.55.0 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/common v0.67.3 // indirect
|
||||||
github.com/rs/cors v1.11.0 // indirect
|
github.com/prometheus/procfs v0.19.2 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
|
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.28.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
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/image v0.18.0 // indirect
|
golang.org/x/crypto v0.44.0 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/image v0.33.0 // indirect
|
||||||
golang.org/x/oauth2 v0.21.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/oauth2 v0.33.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/text v0.31.0 // indirect
|
||||||
google.golang.org/api v0.188.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect
|
google.golang.org/api v0.256.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
|
google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||||
google.golang.org/grpc v1.65.0 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // 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
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
422
go.sum
|
|
@ -1,266 +1,240 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||||
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||||
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||||
cloud.google.com/go/auth v0.7.1 h1:Iv1bbpzJ2OIg16m94XI9/tlzZZl3cdeR3nGVGj78N7s=
|
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||||
cloud.google.com/go/auth v0.7.1/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs=
|
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
|
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
cloud.google.com/go/compute v1.49.1 h1:KYKIG0+pfpAWaAYayFkE/KPrAVCge0Hu82bPraAmsCk=
|
||||||
cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw=
|
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||||
cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ=
|
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||||
cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k=
|
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||||
cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c=
|
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||||
cloud.google.com/go/pubsub v1.40.0 h1:0LdP+zj5XaPAGtWr2V6r88VXJlmtaB/+fde1q3TU8M0=
|
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
|
||||||
cloud.google.com/go/pubsub v1.40.0/go.mod h1:BVJI4sI2FyXp36KFKvFwcfDRDfR8MiLT8mMhmIhdAeA=
|
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
|
||||||
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
|
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
|
||||||
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
|
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||||
github.com/antchfx/htmlquery v1.3.2 h1:85YdttVkR1rAY+Oiv/nKI4FCimID+NXhDn82kz3mEvs=
|
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||||
github.com/antchfx/htmlquery v1.3.2/go.mod h1:1mbkcEgEarAokJiWhTfr4hR06w/q2ZZjnYLrDt6CTUk=
|
cloud.google.com/go/pubsub v1.50.1 h1:fzbXpPyJnSGvWXF1jabhQeXyxdbCIkXTpjXHy7xviBM=
|
||||||
github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk=
|
cloud.google.com/go/pubsub/v2 v2.3.0 h1:DgAN907x+sP0nScYfBzneRiIhWoXcpCD8ZAut8WX9vs=
|
||||||
github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
cloud.google.com/go/pubsub/v2 v2.3.0/go.mod h1:O5f0KHG9zDheZAd3z5rlCRhxt2JQtB+t/IYLKK3Bpvw=
|
||||||
github.com/aws/aws-sdk-go v1.54.20 h1:FZ2UcXya7bUkvkpf7TaPmiL7EubK0go1nlXGLRwEsoo=
|
cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5Mb4=
|
||||||
github.com/aws/aws-sdk-go v1.54.20/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
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/xds/go v0.0.0-20251110193048-8bfbf64dc13e h1:gt7U1Igw0xbJdyaCM5H2CnlAlPSkzrhsebQB6WQWjLA=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||||
github.com/cshum/imagor v1.4.13 h1:BFcSpsTUOJj+Wv5SzDeXa8bhsT/Ehw7EcrFD0UTdpmU=
|
github.com/cshum/imagor v1.6.6 h1:w+pf6hy1qFUkz8/Np8sDrtAs7pgzqZuDjaJX4xn6ams=
|
||||||
github.com/cshum/imagor v1.4.13/go.mod h1:LHxXgks6Y06GzEHitnlO8vcD5gznxIHWPdvGsnlGpMo=
|
github.com/cshum/imagor v1.6.6/go.mod h1:Dt7yj7TLE3VFmGqc+gxfXWtvoHaXHIOeog4zlsHXZiE=
|
||||||
github.com/cshum/imagorvideo v0.4.13 h1:tn+TmdPMvS00XndPX8j/BUMyebErkxXgpJ9CLxNvROE=
|
github.com/cshum/vipsgen v1.2.1 h1:Es305Zf7C9T+8QbsiWn3BtQ+2/uHz6sp/SFnvwnO/kU=
|
||||||
github.com/cshum/imagorvideo v0.4.13/go.mod h1:y2g2GQst6b1X+jvAcxhDusTcYYGJqp+fSRcUo3l+PxU=
|
github.com/cshum/vipsgen v1.2.1/go.mod h1:1GboZQcNmo4NwuNnGogM24m3O+1i6UpnvurqMcsFItE=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/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.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
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.52.3 h1:hXddOPMGDKq5ENmttw6xkodVJy0uVhf7HhWvQgAOH6g=
|
||||||
github.com/fsouza/fake-gcs-server v1.49.2/go.mod h1:17SYzJEXRcaAA5ATwwvgBkSIqIy7r1icnGM0y/y4foY=
|
github.com/fsouza/fake-gcs-server v1.52.3/go.mod h1:A0XtSRX+zz5pLRAt88j9+Of0omQQW+RMqipFbvdNclQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
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.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.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
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 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
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/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
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/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.1 h1:HyOM6qd9gF9sf15AvhbptGHUnaLTpEI9akAFFU3VyW0=
|
||||||
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
|
github.com/google/renameio/v2 v2.0.1/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
|
||||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/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.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
|
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
|
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 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
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 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
github.com/johannesboyne/gofakes3 v0.0.0-20250916175020-ebf3e50324d3 h1:2713fQZ560HxoNVgfJH41GKzjMjIG+DW4hH6nYXfXW8=
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/johannesboyne/gofakes3 v0.0.0-20250916175020-ebf3e50324d3/go.mod h1:S4S9jGBVlLri0OeqrSSbCGG5vsI6he06UJyuz1WT1EE=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
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 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
|
||||||
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
|
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/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=
|
||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
|
||||||
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||||
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
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 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
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/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||||
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc=
|
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
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=
|
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
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/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
|
||||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
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 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
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=
|
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
|
||||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
|
||||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba h1:Ze6qXW0j37YCqZdCD2LkzVSxgEWez0cO4NUyd44DiDY=
|
||||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:4FLPzLA8eGAktPOTemJGDgDYRpLYwrNu4u2JtWINhnI=
|
||||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
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=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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=
|
|
||||||
|
|
|
||||||
166
processor_test.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
BIN
testdata/alpha-webm.webm
vendored
Normal file
BIN
testdata/black.jpg
vendored
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
testdata/demo.jpg
vendored
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
testdata/demo.png
vendored
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
testdata/demo2.jpg
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
testdata/demo3.jpg
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
testdata/everybody-betray-me.mkv
vendored
Normal file
BIN
testdata/golden/export/alpha-webm.webm-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
testdata/golden/export/alpha-webm.webm-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
testdata/golden/export/alpha-webm.webm-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
testdata/golden/export/alpha-webm.webm-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
testdata/golden/export/alpha-webm.webm-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
testdata/golden/export/alpha-webm.webm.jpg
vendored
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
testdata/golden/export/everybody-betray-me.mkv-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
testdata/golden/export/everybody-betray-me.mkv-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
testdata/golden/export/everybody-betray-me.mkv-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
testdata/golden/export/everybody-betray-me.mkv-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
testdata/golden/export/everybody-betray-me.mkv-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
testdata/golden/export/everybody-betray-me.mkv.jpg
vendored
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
testdata/golden/export/macabre.mp4-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
testdata/golden/export/macabre.mp4-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
testdata/golden/export/macabre.mp4-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
testdata/golden/export/macabre.mp4-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
testdata/golden/export/macabre.mp4-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
testdata/golden/export/macabre.mp4.jpg
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
testdata/golden/export/schizo.flv-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo.flv-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo.flv-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo.flv-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo.flv-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo.flv.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_0.mp4-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
testdata/golden/export/schizo_0.mp4-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
testdata/golden/export/schizo_0.mp4-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
testdata/golden/export/schizo_0.mp4-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_0.mp4-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_0.mp4.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_180.mp4-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_180.mp4-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_180.mp4-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_180.mp4-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_180.mp4-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_180.mp4.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_270.mp4-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
testdata/golden/export/schizo_270.mp4-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_270.mp4-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_270.mp4-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_270.mp4-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_270.mp4.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_90.mp4-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_90.mp4-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_90.mp4-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_90.mp4-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_90.mp4-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/schizo_90.mp4.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
testdata/golden/export/with_cover.mp3-1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
testdata/golden/export/with_cover.mp3-10.jpg
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
testdata/golden/export/with_cover.mp3-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
testdata/golden/export/with_cover.mp3-9999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
testdata/golden/export/with_cover.mp3-99999.jpg
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
testdata/golden/export/with_cover.mp3.jpg
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
1
testdata/golden/meta/alpha-webm.webm-1.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}
|
||||||
1
testdata/golden/meta/alpha-webm.webm-10.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}
|
||||||
1
testdata/golden/meta/alpha-webm.webm-5.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}
|
||||||
1
testdata/golden/meta/alpha-webm.webm-9999.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}
|
||||||
1
testdata/golden/meta/alpha-webm.webm-99999.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}
|
||||||
1
testdata/golden/meta/alpha-webm.webm.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}
|
||||||
1
testdata/golden/meta/everybody-betray-me.mkv-1.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/everybody-betray-me.mkv-10.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/everybody-betray-me.mkv-5.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/everybody-betray-me.mkv-9999.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/everybody-betray-me.mkv-99999.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/everybody-betray-me.mkv.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/macabre.mp4-1.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/macabre.mp4-10.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/macabre.mp4-5.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/macabre.mp4-9999.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/macabre.mp4-99999.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/macabre.mp4.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":1,"duration":3924,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}
|
||||||
1
testdata/golden/meta/no_cover.mp3.meta.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"orientation":0,"duration":13536,"has_video":false,"has_audio":true}
|
||||||