hvac-transcoder 5.2.2

GPU-accelerated media transcoder (HEVC/h265 via NVENC, VAAPI, VideoToolbox). Single-binary Tdarr alternative.
name: Release

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to release (e.g. v5.2.1)"
        required: true

permissions:
  contents: write

jobs:
  build:
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
            name: hvac-linux-x86_64
            deb: true
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
            name: hvac-linux-aarch64
            deb: true
          - target: x86_64-apple-darwin
            os: macos-latest
            name: hvac-macos-x86_64
            deb: false
          - target: aarch64-apple-darwin
            os: macos-latest
            name: hvac-macos-aarch64
            deb: false
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

      - name: Install cross-compilation tools
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu
          echo '[target.aarch64-unknown-linux-gnu]' >> ~/.cargo/config.toml
          echo 'linker = "aarch64-linux-gnu-gcc"' >> ~/.cargo/config.toml

      - name: Build
        run: cargo build --release --target ${{ matrix.target }}

      - name: Package tarball
        run: |
          # Copy the binary alongside config.yaml so Homebrew's etc.install
          # finds it; having config.yaml in the tarball is harmless for other
          # install paths.
          cp target/${{ matrix.target }}/release/hvac .
          tar czf ${{ matrix.name }}.tar.gz hvac config.yaml
          rm hvac
          sha256sum ${{ matrix.name }}.tar.gz > ${{ matrix.name }}.tar.gz.sha256

      - name: Install cargo-deb
        if: matrix.deb
        run: cargo install cargo-deb --locked

      - name: Build .deb package
        if: matrix.deb
        run: |
          cargo deb --no-build --target ${{ matrix.target }}
          DEB_PATH=$(ls target/${{ matrix.target }}/debian/hvac_*.deb | head -n1)
          # Normalise filename to a predictable shape we can advertise in the README
          ARCH=$([ "${{ matrix.target }}" = "x86_64-unknown-linux-gnu" ] && echo amd64 || echo arm64)
          VERSION="${GITHUB_REF_NAME#v}"
          NEW_NAME="hvac_${VERSION}_${ARCH}.deb"
          cp "$DEB_PATH" "$NEW_NAME"
          sha256sum "$NEW_NAME" > "$NEW_NAME.sha256"

      - name: Upload tarball artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.name }}
          path: |
            ${{ matrix.name }}.tar.gz
            ${{ matrix.name }}.tar.gz.sha256

      - name: Upload .deb artifact
        if: matrix.deb
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.name }}-deb
          path: |
            hvac_*.deb
            hvac_*.deb.sha256

  publish:
    needs: build
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          merge-multiple: true

      - name: Compute version
        id: meta
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          generate_release_notes: true
          files: |
            hvac-linux-x86_64.tar.gz
            hvac-linux-x86_64.tar.gz.sha256
            hvac-linux-aarch64.tar.gz
            hvac-linux-aarch64.tar.gz.sha256
            hvac-macos-x86_64.tar.gz
            hvac-macos-x86_64.tar.gz.sha256
            hvac-macos-aarch64.tar.gz
            hvac-macos-aarch64.tar.gz.sha256
            hvac_${{ steps.meta.outputs.version }}_amd64.deb
            hvac_${{ steps.meta.outputs.version }}_amd64.deb.sha256
            hvac_${{ steps.meta.outputs.version }}_arm64.deb
            hvac_${{ steps.meta.outputs.version }}_arm64.deb.sha256

  crates-io:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - name: Publish to crates.io
        run: cargo publish --allow-dirty
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

  homebrew:
    needs: publish
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          merge-multiple: true

      - name: Compute SHA256 checksums
        id: sha
        run: |
          echo "linux_x86_64=$(sha256sum hvac-linux-x86_64.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
          echo "linux_aarch64=$(sha256sum hvac-linux-aarch64.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
          echo "macos_x86_64=$(sha256sum hvac-macos-x86_64.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
          echo "macos_aarch64=$(sha256sum hvac-macos-aarch64.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"

      - name: Update Homebrew formula
        env:
          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          TAG="${GITHUB_REF_NAME}"

          cat > hvac.rb << 'FORMULA'
          class Hvac < Formula
            desc "GPU-accelerated media transcoder (HEVC/h265 via NVENC, VAAPI, VideoToolbox)"
            homepage "https://github.com/JackDanger/hvac"
            version "VERSION_PLACEHOLDER"
            license "MIT"

            on_macos do
              if Hardware::CPU.arm?
                url "MACOS_AARCH64_URL"
                sha256 "MACOS_AARCH64_SHA"
              else
                url "MACOS_X86_64_URL"
                sha256 "MACOS_X86_64_SHA"
              end
            end

            on_linux do
              if Hardware::CPU.arm?
                url "LINUX_AARCH64_URL"
                sha256 "LINUX_AARCH64_SHA"
              else
                url "LINUX_X86_64_URL"
                sha256 "LINUX_X86_64_SHA"
              end
            end

            depends_on "ffmpeg"

            def install
              bin.install "hvac"
              etc.install "config.yaml" => "hvac/config.yaml"
            end

            def caveats
              <<~EOS
                hvac requires a GPU with hardware HEVC encoding:
                  macOS:  Apple VideoToolbox (built into all Apple Silicon and recent Intel Macs)
                  Linux:  NVIDIA NVENC (hevc_nvenc) or Intel/AMD VAAPI (hevc_vaapi)

                A starter config was installed to:
                  #{etc}/hvac/config.yaml

                Run `hvac --dry-run /path/to/videos` to preview what would be transcoded.
              EOS
            end

            test do
              assert_match version.to_s, shell_output("#{bin}/hvac --version")
            end
          end
          FORMULA

          # Remove leading whitespace from heredoc, then substitute real values.
          sed -i 's/^          //' hvac.rb
          sed -i "s/VERSION_PLACEHOLDER/${VERSION}/" hvac.rb
          sed -i "s|MACOS_AARCH64_URL|https://github.com/JackDanger/hvac/releases/download/${TAG}/hvac-macos-aarch64.tar.gz|" hvac.rb
          sed -i "s/MACOS_AARCH64_SHA/${{ steps.sha.outputs.macos_aarch64 }}/" hvac.rb
          sed -i "s|MACOS_X86_64_URL|https://github.com/JackDanger/hvac/releases/download/${TAG}/hvac-macos-x86_64.tar.gz|" hvac.rb
          sed -i "s/MACOS_X86_64_SHA/${{ steps.sha.outputs.macos_x86_64 }}/" hvac.rb
          sed -i "s|LINUX_AARCH64_URL|https://github.com/JackDanger/hvac/releases/download/${TAG}/hvac-linux-aarch64.tar.gz|" hvac.rb
          sed -i "s/LINUX_AARCH64_SHA/${{ steps.sha.outputs.linux_aarch64 }}/" hvac.rb
          sed -i "s|LINUX_X86_64_URL|https://github.com/JackDanger/hvac/releases/download/${TAG}/hvac-linux-x86_64.tar.gz|" hvac.rb
          sed -i "s/LINUX_X86_64_SHA/${{ steps.sha.outputs.linux_x86_64 }}/" hvac.rb

          # Push to homebrew-tap repo
          git clone "https://x-access-token:${GH_TOKEN}@github.com/JackDanger/homebrew-tap.git"
          cp hvac.rb homebrew-tap/Formula/hvac.rb
          cd homebrew-tap
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add Formula/hvac.rb
          git commit -m "Update hvac to ${VERSION}"
          git push

  aur:
    needs: publish
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Bump pkgver in PKGBUILD
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          sed -i "s/^pkgver=.*/pkgver=${VERSION}/" packaging/aur/PKGBUILD
          sed -i "s/^pkgrel=.*/pkgrel=1/" packaging/aur/PKGBUILD

      - name: Publish to AUR
        uses: KSXGitHub/github-actions-deploy-aur@v4.1.3
        with:
          pkgname: hvac
          pkgbuild: packaging/aur/PKGBUILD
          commit_username: jackdanger
          commit_email: jack@jackdanger.com
          ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
          commit_message: "Release v${{ needs.publish.outputs.version }}"
          ssh_keyscan_types: rsa,ecdsa,ed25519
          updpkgsums: true

  # ── apt repository on GitHub Pages ───────────────────────────────────────
  #
  # One-time setup required before the first release:
  #   1. Generate a signing key:
  #        gpg --batch --gen-key <<EOF
  #        Key-Type: RSA
  #        Key-Length: 4096
  #        Name-Real: hvac apt repository
  #        Name-Email: jack@jackdanger.com
  #        Expire-Date: 0
  #        %no-protection
  #        EOF
  #   2. Export and add to repo secrets:
  #        gpg --armor --export-secret-keys <KEY_ID> → secret APT_SIGNING_KEY
  #   3. In repo Settings → Pages, set Source to "Deploy from a branch"
  #      and select the gh-pages branch (created automatically on first run).
  apt-repo:
    needs: build
    runs-on: ubuntu-latest
    # Skip on forks (they won't have the signing key secret).
    if: github.repository_owner == 'JackDanger'
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/download-artifact@v4
        with:
          pattern: "*-deb"
          merge-multiple: true
          path: incoming/

      - name: Install apt-utils
        run: sudo apt-get install -y --no-install-recommends apt-utils

      - name: Import GPG signing key
        run: |
          echo "${{ secrets.APT_SIGNING_KEY }}" | gpg --batch --import
          KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ { print $5; exit }')
          gpg --armor --export "$KEY_ID" > key.gpg

      - name: Set up gh-pages worktree
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          if git show-ref --verify --quiet refs/remotes/origin/gh-pages; then
            git worktree add -B gh-pages apt-repo origin/gh-pages
          else
            git worktree add --orphan -b gh-pages apt-repo
            touch apt-repo/.nojekyll
          fi
          cp key.gpg apt-repo/key.gpg

      - name: Update apt repository
        run: bash scripts/update-apt-repo.sh incoming/ apt-repo/

      - name: Commit and push
        run: |
          cd apt-repo
          git add -A
          git diff --staged --quiet && exit 0
          git commit -m "apt: add hvac ${{ github.ref_name }}"
          git push origin gh-pages