diff --git a/.github/workflows/dependency-build.yml b/.github/workflows/dependency-build.yml index 600019bf573f..a8bf190ff385 100644 --- a/.github/workflows/dependency-build.yml +++ b/.github/workflows/dependency-build.yml @@ -84,7 +84,22 @@ jobs: - name: Install native build dependencies run: | sudo apt-get update - sudo apt-get install --yes automake autoconf build-essential file git libgmp-dev libtool make python3 + sudo apt-get install --yes \ + automake \ + autoconf \ + build-essential \ + ffmpeg \ + file \ + git \ + libavcodec-dev \ + libavdevice-dev \ + libavformat-dev \ + libavutil-dev \ + libgmp-dev \ + libswscale-dev \ + libtool \ + make \ + python3 - name: Compile production application run: | @@ -125,6 +140,42 @@ jobs: end) ' + - name: Smoke test NFT video processing + run: | + video_dir="$(mktemp --directory)" + video_path="$video_dir/smoke.mp4" + trap 'rm -rf "$video_dir"' EXIT + + ffmpeg \ + -hide_banner \ + -loglevel error \ + -f lavfi \ + -i color=c=red:s=600x600:d=1 \ + -c:v mpeg4 \ + -pix_fmt yuv420p \ + "$video_path" + + VIDEO_PATH="$video_path" mix run --no-start -e ' + video_path = System.fetch_env!("VIDEO_PATH") + + {:ok, image} = + Image.Video.with_video(video_path, fn video -> + Image.Video.image_from_video(video, frame: 0) + end) + + 600 = Image.width(image) + 600 = Image.height(image) + + thumbnails = + NFTMediaHandler.Image.Resizer.resize( + image, + "https://example.com/nft/smoke.mp4", + ".jpg" + ) + + [60, 250, 500] = Enum.map(thumbnails, &elem(&1, 0)) + ' + production-docker: name: Build published production Docker image needs: changes @@ -138,16 +189,59 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + - name: Generate NFT video fixture + run: | + sudo apt-get update + sudo apt-get install --yes ffmpeg + mkdir -p "$RUNNER_TEMP/nft-video-smoke" + ffmpeg \ + -hide_banner \ + -loglevel error \ + -f lavfi \ + -i color=c=red:s=600x600:d=1 \ + -c:v mpeg4 \ + -pix_fmt yuv420p \ + "$RUNNER_TEMP/nft-video-smoke/smoke.mp4" + - name: Build production image uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: ./docker/Dockerfile + load: true push: false platforms: linux/amd64 + tags: doscan-dependency-build:local cache-from: type=gha,scope=dependency-build-production cache-to: type=gha,mode=max,scope=dependency-build-production + - name: Smoke test production image NFT video processing + run: | + docker run --rm \ + --volume "$RUNNER_TEMP/nft-video-smoke:/tmp/smoke:ro" \ + doscan-dependency-build:local \ + elixir -pa '/app/lib/*/ebin' -e ' + video_path = "/tmp/smoke/smoke.mp4" + + {:ok, image} = + Image.Video.with_video(video_path, fn video -> + Image.Video.image_from_video(video, frame: 0) + end) + + 600 = Image.width(image) + 600 = Image.height(image) + + [60, 250, 500] = + image + |> NFTMediaHandler.Image.Resizer.resize( + "https://example.com/nft/smoke.mp4", + ".jpg" + ) + |> Enum.map(&elem(&1, 0)) + + IO.puts("NFT_VIDEO_SMOKE_OK") + ' + old-ui-docker: name: Build old UI Docker image needs: changes diff --git a/apps/nft_media_handler/mix.exs b/apps/nft_media_handler/mix.exs index d2027b0ae3a8..d042c9f54142 100644 --- a/apps/nft_media_handler/mix.exs +++ b/apps/nft_media_handler/mix.exs @@ -32,6 +32,7 @@ defmodule NFTMediaHandler.MixProject do {:image, "~> 0.54"}, {:httpoison, "~> 2.0"}, {:evision, "~> 0.2"}, + {:xav, "~> 0.11.0"}, {:mime, "~> 2.0"}, {:utils, in_umbrella: true} ] diff --git a/docker/Dockerfile b/docker/Dockerfile index 1f3c715cddf2..c980e0b0fe54 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,7 +3,7 @@ FROM hexpm/elixir:1.19.4-erlang-27.3.4.6-alpine-3.22.2 AS builder-deps WORKDIR /app RUN apk --no-cache --update add \ - alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file gcompat libstdc++ curl ca-certificates git make bash + alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file gcompat libstdc++ curl ca-certificates git make bash ffmpeg-dev # Cache elixir deps COPY mix.exs mix.lock ./ @@ -60,7 +60,7 @@ ARG BLOCKSCOUT_GROUP=blockscout ARG BLOCKSCOUT_UID=10001 ARG BLOCKSCOUT_GID=10001 -RUN apk --no-cache --update add jq curl && \ +RUN apk --no-cache --update add jq curl ffmpeg-libs && \ addgroup --system --gid ${BLOCKSCOUT_GID} ${BLOCKSCOUT_GROUP} && \ adduser --system --uid ${BLOCKSCOUT_UID} --ingroup ${BLOCKSCOUT_GROUP} --disabled-password --home /app ${BLOCKSCOUT_USER} diff --git a/docker/oldUI.Dockerfile b/docker/oldUI.Dockerfile index 29b7f7e083b4..3e8bfbab9854 100644 --- a/docker/oldUI.Dockerfile +++ b/docker/oldUI.Dockerfile @@ -3,7 +3,7 @@ FROM hexpm/elixir:1.19.4-erlang-27.3.4.6-alpine-3.22.2 AS builder-deps WORKDIR /app RUN apk --no-cache --update add \ - alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file gcompat libstdc++ curl ca-certificates git make bash + alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file gcompat libstdc++ curl ca-certificates git make bash ffmpeg-dev # Cache elixir deps COPY mix.exs mix.lock ./ @@ -76,7 +76,7 @@ ARG BLOCKSCOUT_GROUP=blockscout ARG BLOCKSCOUT_UID=10001 ARG BLOCKSCOUT_GID=10001 -RUN apk --no-cache --update add jq curl && \ +RUN apk --no-cache --update add jq curl ffmpeg-libs && \ addgroup --system --gid ${BLOCKSCOUT_GID} ${BLOCKSCOUT_GROUP} && \ adduser --system --uid ${BLOCKSCOUT_UID} --ingroup ${BLOCKSCOUT_GROUP} --disabled-password ${BLOCKSCOUT_USER} diff --git a/mix.lock b/mix.lock index 6aecccf4db6e..350fa9dcd6d1 100644 --- a/mix.lock +++ b/mix.lock @@ -188,5 +188,6 @@ "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, "websockex": {:hex, :websockex, "0.5.1", "9de28d37bbe34f371eb46e29b79c94c94fff79f93c960d842fbf447253558eb4", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8ef39576ed56bc3804c9cd8626f8b5d6b5721848d2726c0ccd4f05385a3c9f14"}, + "xav": {:hex, :xav, "0.11.0", "95c0a71aa1d1e2ded9d84c63659878c2bf836a1212d413271458447f310c7b93", [:make, :mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nx, "~> 0.7", [hex: :nx, repo: "hexpm", optional: true]}], "hexpm", "de754c6047dc28f7492dfc2bfe5f77d0a3e2c386f471da1ccfca8b0ffb47c4d7"}, "ymlr": {:hex, :ymlr, "5.1.5", "0b9207c7940be3f2bc29b77cd55109d5aa2f4dcde6575942017335769e6f5628", [:mix], [], "hexpm", "7030cb240c46850caeb3b01be745307632be319b15f03083136f6251f49b516d"}, }