Decoded picture types are forced onto the encoder, so an image sequence encodes as all keyframes

The engine passes the decoder's pict_type straight through to the encoder, and libx264 honours it: a frame arriving as AV_PICTURE_TYPE_I is forced to an IDR. Nothing in src/ ever resets it.

For an image-sequence input every decoded frame is an I-picture by construction, so every output frame becomes a keyframe and the encoder never gets to choose a P or a B.

Measured

100 frames, 128×96, a white block sliding one pixel per frame across a static gradient — about as compressible as video gets. Same encoder, same options (bf=3, g=25):

                bytes     picture types
engine          34727     99 I, 1 I
ffmpeg CLI       7287     57 B, 39 P, 3 I, 1 I

4.8× the size, from identical input frames.

It is invisible on a normal video input because the source's own types are reproduced: transcoding a file with 65 B / 31 P / 3 I through the engine yields exactly 65 B / 31 P / 3 I, while the CLI re-decides and yields 64 B / 32 P. The output looks plausible, so nothing has ever flagged it.

Who this bites

keryx renders reels from PNG stills, which is precisely the image-sequence case. Every reel it has produced is roughly five times larger than it needs to be, and carries no B-frames at all.

The fix

fftools/ffmpeg_enc.c clears it before encoding, and the engine should do the same: set pict_type to AV_PICTURE_TYPE_NONE on the frame handed to avcodec_send_frame. The decoder's picture type describes how the INPUT was coded and says nothing about how the output should be.

Whether a caller should be able to force keyframes deliberately is a separate question — today they cannot ask for it and get it anyway.

Why this is filed rather than fixed inside #55 (closed)

It blocks #55 (closed)'s regression test. That test needs a fixture with real B-frames, and the engine cannot currently produce one from any fixture this suite can build: internal/fixture writes PNGs, PNGs decode as all-I, and the forced types mean the encoder emits all-I no matter what x264 options are set. Every attempt reports frame I:100.

So #55 (closed) cannot be tested red until this is fixed, and the two want separate commits: this one is a quality defect with its own measurement, and #55 (closed) is a correctness defect about which axis a cutoff uses.