Fanbase

webp/webp-container

version 1.0

WebP RIFF container in all five file shapes; image data is filler, so files do not decode

Format
WebP (webp)
Authors
The Fandango Fuzzer Team
License
Apache-2.0
Fandango
>=1.3
Extensions
webp
Media type
image/webp
Specification
https://developers.google.com/speed/webp/docs/riff_container
Its files should decode
always
In the registry
specs/webp/webp-container/webp-container.fan

Use it

fanbase install webp-container
fandango fuzz -F webp-container -n 10

How it does

accepts 100%, covers 29.2%, 35/s. 1000 inputs, seed 1.

ParserAcceptsVersion
ffmpeg8%ffmpeg version 7.1.5-0+deb13u1
imagemagick0%Version: ImageMagick 7.1.1-43 Q16 x86_64 22550
libwebp-cov0%libwebp 1.6.0
pillow0%12.3.0
webpinfo100%WebP Decoder version: 1.5.0

The spec says its files decode always: as expected.

libwebp-cov

lines reached
29.2% of 6775
real files reach
45.4%
only the generated files
182 lines
only the real files
1276 lines
6.5% after 1 inputs, 29.2% after 1000real files: 45.4%

Cite it

@software{fanbase_webp_webp_container_1_0,
  author  = {{The Fandango Fuzzer Team}},
  title   = {WebP (webp/webp-container), a Fandango input specification},
  version = {1.0},
  url     = {https://github.com/fandango-fuzzer/fanbase},
  note    = {Fanbase registry, specs/webp/webp-container/webp-container.fan, sha256 5ad64bdc1ca3},
}

Source

Source (135 lines)
# WebP as a RIFF container, all five file shapes: simple lossy, simple
# lossless, extended lossy with ICCP, ALPH, EXIF and XMP, extended lossless,
# and animated with ANIM and ANMF. The canvas is 64x64 everywhere and chunk
# sizes are tied to their payload with where clauses. The VP8 and VP8L data
# are filler bytes, so the files do not decode.
#
# TODO:
# * Check for redundant defs (say, *_size fields or to_byte() calls)
# * Merge with the other `webp.fan` specifications.

<start> ::= <webp>

# primitive value fields
<byte>  ::= rb'[\x00-\xff]'
<u16>   ::= <byte> <byte>
<u32>   ::= <byte> <byte> <byte> <byte>
<bpair> ::= <byte> <byte>

# dedicated 4-byte size fields (one per referencing production)
<riff_size> ::= <byte> <byte> <byte> <byte>
<iccp_size> ::= <byte> <byte> <byte> <byte>
<alph_size> ::= <byte> <byte> <byte> <byte>
<vp8_size>  ::= <byte> <byte> <byte> <byte>
<vp8l_size> ::= <byte> <byte> <byte> <byte>
<exif_size> ::= <byte> <byte> <byte> <byte>
<xmp_size>  ::= <byte> <byte> <byte> <byte>
<anmf_size> ::= <byte> <byte> <byte> <byte>

# Top-level RIFF/WEBP container.
# size = len('WEBP' + chunks) = everything after the 4-byte size field.
<webp> ::= b'RIFF' <riff_size> <riff_content>
where <riff_size> == len(bytes(<riff_content>)).to_bytes(4, 'little')

<riff_content> ::= b'WEBP' <chunks>

# The five canonical WebP file shapes.
<chunks> ::= <lossy> \
           | <lossless> \
           | <ext_lossy> \
           | <ext_lossless> \
           | <animated>

# Simple file format - lossy:  a single 'VP8 ' chunk.
<lossy> ::= <vp8_chunk>

# Simple file format - lossless:  a single 'VP8L' chunk.
<lossless> ::= <vp8l_chunk>

# Extended file format - still image, lossy with an alpha channel.
# VP8X flags = ICC(0x20) | Alpha(0x10) | Exif(0x08) | XMP(0x04) = 0x3C.
<ext_lossy> ::= <vp8x_lossy> <iccp_chunk> <alph_chunk> <vp8_chunk> \
                <exif_chunk> <xmp_chunk>

# VP8X: 'VP8X' + size(10) + flags(1) + reserved(3) + canvasW-1(3) + canvasH-1(3)
<vp8x_lossy> ::= b'VP8X' b'\x0a\x00\x00\x00' \
                 b'\x3c\x00\x00\x00\x3f\x00\x00\x3f\x00\x00'

# Extended file format - still image, lossless.
# VP8X flags = ICC(0x20) | Exif(0x08) | XMP(0x04) = 0x2C.
<ext_lossless> ::= <vp8x_lossless> <iccp_chunk> <vp8l_chunk> \
                   <exif_chunk> <xmp_chunk>

<vp8x_lossless> ::= b'VP8X' b'\x0a\x00\x00\x00' \
                    b'\x2c\x00\x00\x00\x3f\x00\x00\x3f\x00\x00'

# Extended file format - animated.
# VP8X flags = ICC(0x20) | Exif(0x08) | XMP(0x04) | Animation(0x02) = 0x2E.
# 1..3 animation frames, each carrying an alpha chunk + a lossy image.
<animated> ::= <vp8x_anim> <iccp_chunk> <anim_chunk> <anmf_chunk>{1,3} \
               <exif_chunk> <xmp_chunk>

<vp8x_anim> ::= b'VP8X' b'\x0a\x00\x00\x00' \
                b'\x2e\x00\x00\x00\x3f\x00\x00\x3f\x00\x00'

# 'ANIM' global animation parameters (fixed 6-byte payload)
# Background colour (BGRA, 4 bytes) + loop count (u16 LE).
<anim_chunk> ::= b'ANIM' b'\x06\x00\x00\x00' <u32> <u16>

# 'ANMF' animation frame
# 16-byte header (X, Y, W-1, H-1, duration : 24-bit LE each; flags byte) then
# an embedded image chunk. size ties to the whole payload length.
<anmf_chunk> ::= b'ANMF' <anmf_size> <anmf_payload>
where <anmf_size> == len(bytes(<anmf_payload>)).to_bytes(4, 'little')

<anmf_payload> ::= <anmf_header> <anmf_framedata>

<anmf_header> ::= b'\x00\x00\x00\x00\x00\x00\x3f\x00\x00\x3f\x00\x00\x64\x00\x00\x00'

<anmf_framedata> ::= <alph_chunk> <vp8_chunk> \
                   | <vp8l_chunk>

# 'VP8 ' - lossy VP8 key-frame chunk.
# 10-byte header: frame-tag(3) + start-code 9d 01 2a + width(2) + height(2),
# encoding a 64x64 key-frame with first_partition_size = 8; then bitstream
# body (kept even, >= first_partition_size bytes).
<vp8_chunk> ::= b'VP8 ' <vp8_size> <vp8_payload>
where <vp8_size> == len(bytes(<vp8_payload>)).to_bytes(4, 'little')

<vp8_payload> ::= <vp8_header> <vp8_data>
<vp8_header>  ::= b'\x10\x01\x00\x9d\x01\x2a\x40\x00\x40\x00'
<vp8_data>    ::= <bpair>{4,16}

# 'VP8L' - lossless VP8L chunk.
# 5-byte header: signature 0x2f + 32 bits packing width-1(14) | height-1(14)
# | alpha(1) | version(3)  =>  64x64, no alpha, version 0.
# Body kept so the total payload length stays even.
<vp8l_chunk> ::= b'VP8L' <vp8l_size> <vp8l_payload>
where <vp8l_size> == len(bytes(<vp8l_payload>)).to_bytes(4, 'little')

<vp8l_payload> ::= <vp8l_header> <vp8l_data>
<vp8l_header>  ::= b'\x2f\x3f\xc0\x0f\x00'
<vp8l_data>    ::= <byte> <bpair>{1,8}

# 'ALPH' - alpha channel chunk.
# 1 flags byte (Rsv 2 | pre-processing 2 | filtering 2 | compression 2 = 0x00,
# uncompressed) followed by raw alpha samples; length kept even.
<alph_chunk> ::= b'ALPH' <alph_size> <alph_payload>
where <alph_size> == len(bytes(<alph_payload>)).to_bytes(4, 'little')

<alph_payload> ::= b'\x00' <byte> <bpair>{1,8}

# 'ICCP' / 'EXIF' / 'XMP ' - variable-length metadata chunks.
# Each length field equals the byte length of its payload; payloads are
# emitted in 2-byte pairs so every chunk length is even.
<iccp_chunk> ::= b'ICCP' <iccp_size> <iccp_data>
where <iccp_size> == len(bytes(<iccp_data>)).to_bytes(4, 'little')
<iccp_data> ::= <bpair>{2,16}

<exif_chunk> ::= b'EXIF' <exif_size> <exif_data>
where <exif_size> == len(bytes(<exif_data>)).to_bytes(4, 'little')
<exif_data> ::= <bpair>{2,16}

<xmp_chunk> ::= b'XMP ' <xmp_size> <xmp_data>
where <xmp_size> == len(bytes(<xmp_data>)).to_bytes(4, 'little')
<xmp_data> ::= <bpair>{2,16}