Skip to main content

Convert animated GIFs into animated Valve Texture Format (.vtf) files.

Project description

gif2vtf

Convert animated GIFs into animated Valve Texture Format (.vtf) files from the command line.

This automates the manual workflow described in the Steam "animated sprays" guide: split a GIF into frames, normalise their size, and pack them into a single multi-frame VTF. Instead of GIMP plus VTFEdit, you run one command.

How it works

GIF -> decode + coalesce frames -> resize/clamp -> detect alpha
    -> pick image format -> write multi-frame VTF (srctools)

Frames are decoded with Pillow and fully composited (this undoes GIF "optimization", equivalent to the guide's GIMP Unoptimize step). They are resized to a power-of-two size, then written as a single animated VTF using srctools.

Installation

pip install gif2vtf

This installs the gif2vtf command. Dependencies are srctools and Pillow.

To install from a source checkout instead:

pip install .

To run the tests as well:

pip install .[dev]
pytest

Compressed formats need the compiled srctools build. DXT1/DXT3/DXT5 and ATI2N compression is implemented in srctools' optional Cython extension (libsquish). The standard pip install srctools wheel includes it. If your build lacks it, those formats raise an error on save; use an uncompressed format such as RGBA8888 or BGRA8888 instead.

Usage

gif2vtf input.gif                 # writes input.vtf with the "general" preset
gif2vtf input.gif -o out.vtf
gif2vtf input.gif --preset spray --width 128 --height 128

Presets

A preset only supplies defaults; any explicit flag overrides it.

Preset Defaults
general (default) mipmaps on, resize to nearest power of two, clamp 4096, DXT1 / DXT5 for alpha
spray mipmaps off, clamp 512, point sample + clamp S/T + no LOD flags, warns if over 512 KB

Options

--format FORMAT          Format for frames without alpha (default DXT1)
--alpha-format FORMAT    Format for frames with alpha (default DXT5)
--alpha-threshold N      One-bit-alpha cutoff (0-255)

--resize / --no-resize   Resize frames to a valid VTF size
--resize-method METHOD   nearest-power2 | biggest-power2 | smallest-power2 | set
--width W --height H     Explicit target size (implies the 'set' method)
--clamp / --no-clamp     Cap dimensions to the clamp size
--clamp-width W          Maximum width when clamping
--clamp-height H         Maximum height when clamping
--resize-filter FILTER   nearest | bilinear | bicubic | lanczos

--mipmaps / --no-mipmaps Generate a mipmap chain
--mip-filter FILTER      nearest | bilinear

--flag NAME              Add a VTF flag, e.g. CLAMP_S (repeatable)
--no-flag NAME           Remove a preset flag (repeatable)

--max-frames N           Keep at most N frames
--skip N                 Skip the first N frames
--decimate N             Drop every Nth frame (1-based); N must be >= 2
--optimize-frames        EXPERIMENTAL: remove duplicate and zero-delay frames
--optimize-fuzz N        Max per-channel RGBA difference for duplicate detection
--version 7.5            VTF version (7.2-7.5)
--strict-size            Fail instead of warning when over the spray limit
-v / --verbose           Print conversion details

Reducing frame count

Sprays have a tight size budget, so trimming frames is often necessary.

  • --decimate N drops every Nth frame using 1-based counting. --decimate 2 on a 10-frame GIF removes frames 2, 4, 6, 8, 10 and keeps 5; --decimate 4 removes every 4th frame and keeps roughly three quarters.
  • --optimize-frames is experimental. It removes whole frames that add nothing to the visible animation: zero-delay intermediate frames and runs of consecutive identical frames (the spirit of ImageMagick's RemoveZero and RemoveDups layer methods). Duplicate detection is exact by default; raise the tolerance with --optimize-fuzz N, where N is the maximum allowed per-channel difference. This does not perform GIF sub-frame or disposal optimization, which is irrelevant to VTF because every VTF frame stores a full image.

Optimization runs before decimation, and both run after --skip / --max-frames.

Supported format names: DXT1, DXT3, DXT5, DXT1_ONEBITALPHA, BGR888, RGB888, BGRA8888, RGBA8888, BGR565, RGB565, ATI2N.

Notes and limitations

  • Dimensions must be powers of two. srctools only writes power-of-two VTFs, so all resize methods converge on power-of-two sizes. With --no-resize, every frame must already be a valid power-of-two size.
  • Frame rate is not stored in the VTF. Source controls animation timing through the material (.vmt) and engine, not the texture. In-game sprays play at roughly 5 FPS regardless of the source GIF.
  • Disabling mipmaps writes only the base image (and sets the NO_MIP flag), which keeps sprays under the 512 KB limit as the guide recommends.
  • Mipmap filters are limited to nearest and bilinear (all srctools supports).
  • This tool outputs .vtf only; it does not generate a companion .vmt material file.

Releasing (maintainers)

One-time PyPI trusted publishing setup

GitHub Actions publishes to PyPI using trusted publishing (OIDC). No API token is stored in GitHub secrets.

  1. PyPI — open Account settings → Publishing (or the project page after the first upload) and add a trusted publisher:
    • PyPI project name: gif2vtf
    • Owner: Zisomerism
    • Repository name: gif2vtf
    • Workflow name: publish.yml
    • Environment name: pypi
  2. GitHub — in this repository go to Settings → Environments, create an environment named pypi (optionally restrict it to the main branch and require approval for production releases).

Publish a release

  1. Bump the version in pyproject.toml, src/gif2vtf/__init__.py, and add a CHANGELOG.md entry.
  2. Commit, push, and tag (e.g. v1.0.0).
  3. Create a GitHub Release from that tag. Publishing the release triggers .github/workflows/publish.yml, which builds the sdist/wheel and uploads them to PyPI.

Manual upload (fallback)

pip install build twine
python -m build
twine check dist/*
twine upload dist/* --username __token__ --password pypi-...

License

This project is licensed under the GNU General Public License, version 3 or later (GPL-3.0-or-later). See LICENSE for the full text.

Copyright (C) 2026 Zisomerism.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gif2vtf-1.0.0.tar.gz (31.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gif2vtf-1.0.0-py3-none-any.whl (29.0 kB view details)

Uploaded Python 3

File details

Details for the file gif2vtf-1.0.0.tar.gz.

File metadata

  • Download URL: gif2vtf-1.0.0.tar.gz
  • Upload date:
  • Size: 31.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gif2vtf-1.0.0.tar.gz
Algorithm Hash digest
SHA256 d606a704a74444a154ab7f042a1ddce9e77a97d21c99a330a42ccfe21254921c
MD5 7648a28fa666b3bdcf6a394767aa7c36
BLAKE2b-256 0d9c17cea5d9cc82aeed88f91c6ddf85ffac13d6208419b26e4171c1e9842dea

See more details on using hashes here.

Provenance

The following attestation bundles were made for gif2vtf-1.0.0.tar.gz:

Publisher: publish.yml on Zisomerism/gif2vtf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gif2vtf-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: gif2vtf-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 29.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gif2vtf-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8267529a793d2cc7f95e12d8c79cd68c33a43de6eda4febd6cf159b2d31d7620
MD5 ccde61b33a2b27b95b8069a11057c59d
BLAKE2b-256 64983b499f1f72788d60bad61bd44bdf793f2414da012261817971d66d642b5d

See more details on using hashes here.

Provenance

The following attestation bundles were made for gif2vtf-1.0.0-py3-none-any.whl:

Publisher: publish.yml on Zisomerism/gif2vtf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page