acowley / ffmpeg-light

Minimal Haskell bindings to the FFmpeg library
BSD 3-Clause "New" or "Revised" License
67 stars 29 forks source link

imageReader reads all but two frames of a video #58

Open burkaman opened 4 years ago

burkaman commented 4 years ago

imageReader reads all but two frames when attempting to read all frames of a video, regardless of the video length. I'm not yet sure which frames are not being read.

OS: NixOS 20.03 ffmpeg version: 3.4.7

Things I would like to try but haven't yet: other OSes, other ffmpeg versions, other video types, and making a short video with numbered frames so I can save the images and see which frames are dropped.

Steps to Reproduce

Make sure you have the following packages installed: ffmpeg-light, JuicyPixels, monad-loops.

  1. Make DroppedFrames.hs and add the following:
    
    import Codec.FFmpeg
    import Codec.Picture
    import Control.Monad.Loops
    import Data.Maybe

readAllFrames :: IO Int readAllFrames = do initFFmpeg setLogLevel avLogTrace (reader, cleanup) <- imageReader (File "pulse.mov") :: IO (IO (Maybe (Image PixelRGB8)), IO ()) frames <- unfoldM reader return $ length frames


2. Generate `pulse.mov`, either by building the demo application and running with no arguments, or loading /demo/Main.hs in ghci and running `testEncode`.
3. Run `ffmpeg -i pulse.mov -map 0:v:0 -c copy -f null -`, and in the second to last line of output you'll see `frame= 600`, indicating that this video has 600 frames.
4. Load DroppedFrames.hs in ghci and run `readAllFrames`. The output is 598, indicating `imageReader` only got 598 frames out of this video.

Here's a gist with the output of `ffmpeg` and `readAllFrames` with log level set to trace: https://gist.github.com/burkaman/0bb1e18b6769eed13cdf521c240222c4
burkaman commented 4 years ago

pulse.mov is actually a great test case for this. The default behavior of testEncode writes a 600 frame video of uniform vectors, starting with a frame full of 255s, decreasing by 1 each frame down to 100, increasing back up to 255, then back down again, etc.

When I read back the frames and print out an element of each vector, I see that the 2nd frame is dropped, but the 5th is duplicated, the 9th is dropped, the 12th is duplicated, 16th dropped, 19th duplicated, and it repeats like that every 3 or 4 frames for the entire video. Finally at the end, the 596th is duplicated, and frames 598-600 are dropped. Here's the first 20 frames:

255
253
252
251
251
250
249
248
246
245
244
244
243
242
241
239
238
237
237
236

Here's an updated DroppedFrames.hs to try this yourself:

import Codec.FFmpeg
import Codec.Picture
import Control.Monad.Loops
import Data.Maybe
import qualified Data.Vector.Storable as V

readAllFrames :: IO Int
readAllFrames = do
  initFFmpeg
  (reader, cleanup) <- imageReader (File "pulse.mov") :: IO (IO (Maybe (Image PixelRGB8)), IO ())
  frames <- unfoldM reader
  mapM (putStrLn . show . V.head . imageData) frames
  return $ length frames

Not sure yet why this is happening.

acowley commented 4 years ago

The frame timing things I mentioned that I'm suspicious of are here and a lot of things starting here.

It may be worth instrumenting that code to see what frame times are being used for the pulse example. I can imagine things going wrong in various ways, such as mixing use of rational and floating point times. I don't know exactly what we're doing wrong, but this has always struck me as a real Danger Zone.

acowley commented 4 years ago

Hey @burkaman, did you ever have a chance to dig into this any further?

burkaman commented 4 years ago

Not a lot yet, unfortunately. I did a little bit of light logging around the lines you mentioned and I didn't see anything suspicious yet, but I didn't look very deeply.

I've somewhat convinced myself the problem is in decoding though, not encoding, which is where your suspicious sections are. If I ask ffmpeg to extract all the frames with ffmpeg -i pulse.mov image-%04.png, it gets all 600, and there don't appear to be any deletions or duplicates. I'm wondering if I should try to follow through exactly what ffmpeg does with that command and what ffmpeg-light is doing differently, but maybe that's a bad idea.

This is still on my list, but I haven't managed to devote very much time to it yet.

gelisam commented 4 years ago

I have encountered a similar issue in my gloss-to-flv project, while testing that writing and reading back a video with ffmpeg-light gives back what I started with. Since the encoding is lossy, I don't expect to get exactly the same thing I started with, but I did notice some duplicated frames. Using a tolerance check in order to detect such roundtrip errors automatically, I noticed that roundtrip errors get more common when the fps is increased.

Here is my data. I am encoding a 1 second video at 2 fps up to 300 fps, and I am printing the frame numbers which don't round-trip properly. So the line 2 fps: means that both frames are correct, 35 fps: 35 means that the last frame is incorrect, while 60 fps: 27..60 means that all frames from 27 up are incorrect; possibly because of a single duplicate frame at frame 27, causing all later frames to be off by one.

my data

``` 2 fps: 3 fps: 4 fps: 5 fps: 6 fps: 7 fps: 8 fps: 9 fps: 10 fps: 11 fps: 12 fps: 13 fps: 14 fps: 15 fps: 16 fps: 17 fps: 18 fps: 19 fps: 20 fps: 21 fps: 22 fps: 23 fps: 24 fps: 25 fps: 26 fps: 27 fps: 28 fps: 29 fps: 30 fps: 31 fps: 32 fps: 33 fps: 34 fps: 35 fps: 35 36 fps: 37 fps: 38 fps: 39 fps: 37..39 40 fps: 41 fps: 32..40 42 fps: 43 fps: 44 fps: 43..44 45 fps: 46 fps: 43..46 47 fps: 39..46 48 fps: 49 fps: 25..29,31..48 50 fps: 51 fps: 27..51 52 fps: 42..44,46..51 53 fps: 54 fps: 21..40,42..54 55 fps: 51..54 56 fps: 57 fps: 21..57 58 fps: 36..57 59 fps: 60 fps: 27..60 61 fps: 21..30,32..60 62 fps: 63 fps: 64 fps: 22..64 65 fps: 20..45,47..58,60..63 66 fps: 51..65 67 fps: 68 fps: 27..68 69 fps: 15..67 70 fps: 25..30,32..37,39..44,46..58,60..65,67..69 71 fps: 72 fps: 64..67,69..72 73 fps: 24..73 74 fps: 15..19,21..50,52..54,57,59..65,67..74 75 fps: 21..37,39..52,54..71,73 76 fps: 42..75 77 fps: 78 fps: 37..58,60..78 79 fps: 20..79 80 fps: 17,41..42,50,57,65,73..75 81 fps: 18..40,42..79 82 fps: 32..61,63..81 83 fps: 84 fps: 64..84 85 fps: 28..43,45..60,62..85 86 fps: 17..86 87 fps: 12..83 88 fps: 16..85 89 fps: 24..87 90 fps: 51..58,60..67,69..76,78..85,87..89 91 fps: 92 fps: 43..92 93 fps: 23..47,49..93 94 fps: 16..23,25..94 95 fps: 13..95 96 fps: 13..73,75..82,84..92 97 fps: 17..71,73..94 98 fps: 25..61,64..96 99 fps: 51..56,58..61,63..71,73..91,93..96,98 100 fps: 101 fps: 53..101 102 fps: 27..102 103 fps: 18..103 104 fps: 14..104 105 fps: 11..55,57..99,101..105 106 fps: 11..101 107 fps: 14..103 108 fps: 18..105 109 fps: 27..54,56..107 110 fps: 51..60,62..109 111 fps: 112 fps: 64..112 113 fps: 31..113 114 fps: 21..114 115 fps: 16..59,61..83,85..115 116 fps: 13..116 117 fps: 11..61,63..117 118 fps: 9..83,85..88,90..111 119 fps: 11..113 120 fps: 13..61,63..64,66..67,69..70,72..73,75..76,78..79,81..82,84..115 121 fps: 16..117 122 fps: 21..30,32..119 123 fps: 32..61,63..121 124 fps: 63..123 125 fps: 126 fps: 64..119,121..126 127 fps: 33..64,66..127 128 fps: 22..128 129 fps: 17..129 130 fps: 14..33,35..130 131 fps: 12..131 132 fps: 10..83,85..132 133 fps: 9..133 134 fps: 9..126 135 fps: 10..128 136 fps: 11..129 137 fps: 13..30,32..131 138 fps: 15..99,101..133 139 fps: 19..135 140 fps: 25..73,75..78,80..99,101..106,108..113,115..126,128..137 141 fps: 39..139 142 fps: 84..106,108..141 143 fps: 144 fps: 64..144 145 fps: 35..103,105..132,134..145 146 fps: 24..146 147 fps: 19..147 148 fps: 15..129,131..148 149 fps: 13..149 150 fps: 11..53,55,57..58,60..61,63..64,66..67,70,73,75..115,118,120..121,123..124,126..127,129..130,133,135..150 151 fps: 10..151 152 fps: 9..152 153 fps: 9..153 154 fps: 7..142 155 fps: 8..108,110..139,141..144 156 fps: 8..127,129..146 157 fps: 9..148 158 fps: 10..150 159 fps: 11..152 160 fps: 13..14,16,18..19,21..22,24,27,29..30,32,34,37,39,42,44,46..47,50,52..53,55,59..64,66..80,82..84,87..88,90,93..96,98..100,103..104,106,108..115,117..121,124..131,133..136,139..152 161 fps: 15..156 162 fps: 18..118,120..157 163 fps: 23..159 164 fps: 32..102,104..161 165 fps: 50..51,53..56,58..61,63..84,86..89,91..94,96..109,111..117,119..122,124..127,129..135,137,139..163 166 fps: 126..146,148..154,156,158..165 167 fps: 168 fps: 64..168 169 fps: 37..169 170 fps: 27..95,97..130,132..164,166..170 171 fps: 21..171 172 fps: 17..172 173 fps: 15..89,91..173 174 fps: 13..174 175 fps: 12..91,93..128,131..136,138..143,145..150,153..165,167..175 176 fps: 10..176 177 fps: 10..177 178 fps: 9..178 179 fps: 8..179 180 fps: 8..24,26..33,35..117,119..121,125..150,152..180 181 fps: 7..181 182 fps: 6..166 183 fps: 6..167 184 fps: 7..169 185 fps: 7..171 186 fps: 8..173 187 fps: 8..175 188 fps: 9..66,68..177 189 fps: 10..179 190 fps: 11..117,119..135,137..180 191 fps: 12..95,97..182 192 fps: 13..184 193 fps: 15..186 194 fps: 17..188 195 fps: 21..190 196 fps: 25..51,53..61,63..192 197 fps: 34..98,100..194 198 fps: 50..51,53,55..57,59..61,63..67,69..89,91,93,95..97,99,101,103,105,107,109,111,113,115..117,119..123,125,127,129..148,150..162,164..170,172,175..192,194..196 199 fps: 100..115,117..121,123,125..129,131..133,135..179,181..189,191..198 200 fps: 201 fps: 102..142,144..190,192..201 202 fps: 53..92,94,96..100,102,104,106,108,110,112..153,155..202 203 fps: 35..203 204 fps: 27..204 205 fps: 21..147,149..205 206 fps: 18..206 207 fps: 16..207 208 fps: 14..208 209 fps: 13..209 210 fps: 11..210 211 fps: 11..111,113..211 212 fps: 10..79,81..212 213 fps: 9..106,108..213 214 fps: 9..214 215 fps: 8..161,163..215 216 fps: 8..40,42..148,150..216 217 fps: 7..213,215..217 218 fps: 7..218 219 fps: 7..219 220 fps: 6..115,117..121,124..125,128..220 221 fps: 6..221 222 fps: 6..222 223 fps: 5..199 224 fps: 5..59,61..178,180..201 225 fps: 6..20,22..80,82..85,87..101,103..139,141..148,150..157,159,161..163,165..182,184..203 226 fps: 6..204 227 fps: 6..206 228 fps: 6..208 229 fps: 6..210 230 fps: 7..95,97..212 231 fps: 7..115,117..122,124..213 232 fps: 7..94,96..215 233 fps: 8..217 234 fps: 8..164,166..219 235 fps: 9..221 236 fps: 9..223 237 fps: 10..225 238 fps: 11..227 239 fps: 12..228 240 fps: 13..61,63..64,66..67,69..70,72..73,75..76,78..79,81..82,84..133,135..136,138..139,141..145,147..148,150..151,153..154,156..205,207..217,219..220,222..223,225..226,228..230 241 fps: 14..37,39..232 242 fps: 16..234 243 fps: 18..236 244 fps: 21..183,185..238 245 fps: 26..240 246 fps: 32..184,186..242 247 fps: 42..244 248 fps: 63..134,136..159,161..164,166..174,176..184,186..246 249 fps: 125..139,141..172,174..202,204..237,239..248 250 fps: 251 fps: 127..177,179..251 252 fps: 64..140,142..252 253 fps: 43..253 254 fps: 33..64,67..190,192..193,195..254 255 fps: 26..130,132..255 256 fps: 22..256 257 fps: 19..257 258 fps: 17..258 259 fps: 15..134,136,138..259 260 fps: 14..260 261 fps: 13..261 262 fps: 12..118,120..262 263 fps: 11..138,140..257,259..263 264 fps: 10..148,150..156,158..264 265 fps: 10..265 266 fps: 9..266 267 fps: 9..267 268 fps: 8..268 269 fps: 8..144,146..269 270 fps: 8..43,45..270 271 fps: 7..271 272 fps: 7..209,211..212,214..215,217..272 273 fps: 7..273 274 fps: 7..274 275 fps: 6..242,244,246..275 276 fps: 6..276 277 fps: 6..277 278 fps: 6..278 279 fps: 6..279 280 fps: 6..66,68..126,129,131..132,136..192,195..199,201..262,264..280 281 fps: 6..281 282 fps: 5..79,81..282 283 fps: 5..283 284 fps: 5..284 285 fps: 5..162,164..285 286 fps: 4..245 287 fps: 4..247 288 fps: 4..249 289 fps: 4..251 290 fps: 4..252 291 fps: 4..254 292 fps: 5..256 293 fps: 5..258 294 fps: 5..259 295 fps: 5..261 296 fps: 5..263 297 fps: 5..76,78..265 298 fps: 5..266 299 fps: 5..268 300 fps: 5..22,25,27..28,30..52,54..55,57..58,60..77,82,85..103,105..106,108..109,111..130,133,135..136,138..139,141..158,160,162..163,165..166,168..184,186..187,189..190,192..193,195..211,214,216,219..241,243..244,246..268,270 ```

Here are the things I have noticed in that data.

  1. no errors until 35 fps, then 30% of the frame rates have errors until 45 fps, then 60% until 65 fps, 80% until 85 fps, then 90%+ beyond that. So the roundtrip errors increase as the fps increases.
  2. no errors for 30 fps, which is surprising because pulse.mov is encoded at 30 fps. I see two important differences between our two experiments: I am using flv instead of mov, and I am rendering the current time as a floating point as black-on-white text instead of varying shades of solid grey. So perhaps the codec makes a difference. For example, maybe pulse.mov is stored in a way which does not store every frame, but instead describes the fact that the color is getting brighter over time. If a codec can save a lot of space at the cost of getting those small off-by-one-shade-of-gray errors @burkaman noticed, that sounds like a worthwhile tradeoff in most situations. Maybe the mov codec can take advantage of this optimization while flv cannot, or maybe the optimization can only be applied to smoothly-animated shades but not rapidly-changing text.
  3. no errors at 50 fps, 100 fps, and 200 fps; nor at 125 fps and 250 fps. These round numbers remind me of a similar problem I've just fixed in a similar project, gloss-to-gif, in which roundtrip tests were also failing at some frame rates but succeeding when the frame rate was a round number. The problem there was that the gif format uses an integer number of centiseconds to express the delay between each frame; not every frame rate can be expressed as an exact number of centiseconds per frame, but round frame rates can. I thus solved the problem by converting the frame rate to centiseconds, rounding to an integer number of centiseconds, and using that approximate frame rate instead of the exact one requested by the user. Perhaps the mov and flv formats use a similar encoding under the hood?
  4. While in the 60 fps: 27..60 example a single duplicated frame causes all later frames to also be incorrect, not all frame rates lead to such cascading errors. For example, 75 fps: 21..37,39..52,54..71,73 has a few correct frames in the middle of a bunch of incorrect frames. This tells me that ffmpeg-light's time tracking is not getting more and more out of sync with the real time, e.g. as it accumulates more and more rounding errors, instead it is successfully staying in sync, but it is often one frame off. That reminds me of a different problem I was having in gloss-to-gif. After loading the frames from disk, I was representing the animation using a function from Double to Frame. If the frame rate is 10 fps, that function would return the first frame from 0.0 to 0.1, then the second frame from 0.1 to 0.2, etc. When writing the frames back to disk, I was sampling that function at the requested frame rate. For example, at 10 fps I would sample 0.0, 0.1, 0.2, 0.3, etc. But those are exactly the discontinuity points! Since some of those fractional numbers cannot be expressed exactly as a Double, in practice I was sometimes sampling exactly at 0.1, sometimes at 0.1 minus epsilon, and sometimes at 0.1 plus epsilon, thus resulting in being off by one frame quite often. Is ffmpeg-light using a similar setup? My solution was to sample at the middle of the frames, so at 0.05, 0.15, etc.
gelisam commented 4 years ago

A roundtrip failure indicates that the encoding and the decoding don't match, but it doesn't say which of the two directions has the bug.

One advantage of rendering the timestamp over rendering a solid shade of grey is that is is easy to distinguish two frames visually. I took one of the videos exhibiting the glitch, and I compared the frames produced by ffmpeg-light's imageReaderTime to frames displayed by mplayer -fps 1. I did not see any difference, so I conclude that the problem must be somewhere in imageWriter.

gelisam commented 4 years ago

I did not see any difference, so I conclude that the problem must be somewhere in imageWriter.

Wait, that doesn't sound right: @burkaman tried the opposite, using ffmpeg to observe the frame count and then observing that imageReader reads a different number of frames. I'm starting to think there might be more than one bug here, which together explain the results we have observed.

gelisam commented 4 years ago

Turns out the file format matters a lot. If I change nothing except the name of the output file, from "pulse.mov" to "pulse.flv", then imageReader now reads back exactly 600 frames as expected.

gelisam commented 4 years ago

"pulse.gif" also yields 600 frames. I think I will restrict my attention to the gif format for now, since that's the one I am now the most familiar with thanks to my gloss-to-gif project. I re-did my experiment with gif instead of flv, and here is the resulting data. This time I am also including the value of 100/fps in parentheses, in order to check my hypothesis that it is only possible to roundtrip via gif when 100/fps is an integer.

my data

``` 2 fps (50.0): 3 fps (33.333333333333336): 4 fps (25.0): 5 fps (20.0): 6 fps (16.666666666666668): 7 fps (14.285714285714286): 8 fps (12.5): 9 fps (11.11111111111111): 10 fps (10.0): 11 fps (9.090909090909092): 12 fps (8.333333333333334): 13 fps (7.6923076923076925): 14 fps (7.142857142857143): 15 fps (6.666666666666667):12..13,15 16 fps (6.25):13..14 17 fps (5.882352941176471): 18 fps (5.555555555555555):8..10,12..18 19 fps (5.2631578947368425):11..18 20 fps (5.0): 21 fps (4.761904761904762):13..18,20..21 22 fps (4.545454545454546):8..16,18..22 23 fps (4.3478260869565215):7..11,13..21 24 fps (4.166666666666667):14..22 25 fps (4.0): 26 fps (3.8461538461538463):14..19,22..26 27 fps (3.7037037037037037):8..14,17..27 28 fps (3.5714285714285716):6..10,13..17,19,21..28 29 fps (3.4482758620689653):4..14,16..25 30 fps (3.3333333333333335):6..7,9..10,12..13,15..22,25,27 31 fps (3.225806451612903):8..15,17..29 32 fps (3.125):16..17,22,26,29 33 fps (3.0303030303030303): 34 fps (2.9411764705882355):28,30..33 35 fps (2.857142857142857):13..18,20..25,27..31,33,35 36 fps (2.7777777777777777):8..13,15..16,18..24,26..27,29..31,33..36 37 fps (2.7027027027027026):6..9,11..20,22..37 38 fps (2.6315789473684212):5..9,12..32,34..38 39 fps (2.5641025641025643):4..22,24..39 40 fps (2.5):5 41 fps (2.4390243902439024):3..20,22..34 42 fps (2.380952380952381):4..10,12..35 43 fps (2.3255813953488373):4..21,23..37 44 fps (2.272727272727273):7..14,16..39 45 fps (2.2222222222222223):6..10,12,15..27,30..36,38..41 46 fps (2.1739130434782608):7..11,13..34,36..42 47 fps (2.127659574468085):9..22,25..44 48 fps (2.0833333333333335):13..25,29..30,33..40,42..46 49 fps (2.0408163265306123):26..29,31..45,47..48 50 fps (2.0): 51 fps (1.9607843137254901):28..44,46..51 52 fps (1.9230769230769231):14..19,21..32,34..52 53 fps (1.8867924528301887):10..26,28,30..46,48..52 54 fps (1.8518518518518519):8..13,16..26,28..29,31..54 55 fps (1.8181818181818181):6,8..18,20..30,32..36,38,40..42,44..54 56 fps (1.7857142857142858):6..19,21..27,29..31,33..38,40..56 57 fps (1.7543859649122806):5..32,34..57 58 fps (1.7241379310344827):5..14,16,18..58 59 fps (1.694915254237288):4..34,36..59 60 fps (1.6666666666666667):4..9,11..15,18,21..22,24..33,36..37,39..51,54..55,57..60 61 fps (1.639344262295082):4..37,39..61 62 fps (1.6129032258064515):4..15,17..62 63 fps (1.5873015873015872):3..31,33..63 64 fps (1.5625):3..18,20,22..25,27,29..37,39..58,60..62,64 65 fps (1.5384615384615385):3..8,10..25,27..32,34..58,61..65 66 fps (1.5151515151515151):3..29,31..52,54..66 67 fps (1.492537313432836):2..15,17..67 68 fps (1.4705882352941178):2..59,61..68 69 fps (1.4492753623188406):2..69 70 fps (1.4285714285714286):3..24,26..70 71 fps (1.408450704225352):2..71 72 fps (1.3888888888888888):2..72 73 fps (1.36986301369863):2..73 74 fps (1.3513513513513513):2..74 75 fps (1.3333333333333333):2..10,12..13,15..17,19..31,33..34,36..41,43..52,57..58,60..75 76 fps (1.3157894736842106):2..75 77 fps (1.2987012987012987):2..77 78 fps (1.2820512820512822):2..78 79 fps (1.2658227848101267):2..79 80 fps (1.25):2..5,7..65,68..70,72..73,77..78 81 fps (1.2345679012345678):2..81 82 fps (1.2195121951219512):2..55,57..82 83 fps (1.2048192771084338):2..83 84 fps (1.1904761904761905):2..84 85 fps (1.1764705882352942):2..76,78..85 86 fps (1.1627906976744187):2..86 87 fps (1.1494252873563218):2..87 88 fps (1.1363636363636365):2..49,51..88 89 fps (1.1235955056179776):2..89 90 fps (1.1111111111111112):2..40,42..90 91 fps (1.098901098901099):2..22,24..67,69..91 92 fps (1.0869565217391304):2..92 93 fps (1.075268817204301):2..93 94 fps (1.0638297872340425):2..94 95 fps (1.0526315789473684):2..41,43..75,77..90,93..95 96 fps (1.0416666666666667):2..96 97 fps (1.0309278350515463):2..97 98 fps (1.0204081632653061):2..31,33..98 99 fps (1.0101010101010102):2..99 100 fps (1.0):43,45,49..50 101 fps (0.9900990099009901):2..101 102 fps (0.9803921568627451):2..63,65..102 103 fps (0.970873786407767):2..80,82..103 104 fps (0.9615384615384616):2..71,73..104 105 fps (0.9523809523809523):2..29,31..99,101..105 106 fps (0.9433962264150944):2..106 107 fps (0.9345794392523364):2..107 108 fps (0.9259259259259259):2..94,96..108 109 fps (0.9174311926605505):2..59,61..109 110 fps (0.9090909090909091):2..24,26..60,62..108,110 111 fps (0.9009009009009009):2..111 112 fps (0.8928571428571429):2..112 113 fps (0.8849557522123894):2..113 114 fps (0.8771929824561403):2..114 115 fps (0.8695652173913043):2..39,41..102,104..115 116 fps (0.8620689655172413):2..116 117 fps (0.8547008547008547):2..117 118 fps (0.847457627118644):2..118 119 fps (0.8403361344537815):2..119 120 fps (0.8333333333333334):2..16,18..52,54..85,87..88,90..91,93..94,96..120 121 fps (0.8264462809917356):2..121 122 fps (0.819672131147541):2..122 123 fps (0.8130081300813008):2..123 124 fps (0.8064516129032258):2..124 125 fps (0.8):64..66,69,71,74..75 126 fps (0.7936507936507936):2..126 127 fps (0.7874015748031497):2..127 128 fps (0.78125):2..128 129 fps (0.7751937984496124):2..129 130 fps (0.7692307692307693):2..84,86..130 131 fps (0.7633587786259542):2..131 132 fps (0.7575757575757576):2..132 133 fps (0.7518796992481203):2..133 134 fps (0.746268656716418):2..134 135 fps (0.7407407407407407):2..135 136 fps (0.7352941176470589):2..136 137 fps (0.7299270072992701):2..137 138 fps (0.7246376811594203):2..138 139 fps (0.7194244604316546):2..139 140 fps (0.7142857142857143):2..45,47..52,54..140 141 fps (0.7092198581560284):2..141 142 fps (0.704225352112676):2..142 143 fps (0.6993006993006993):2..143 144 fps (0.6944444444444444):2..144 145 fps (0.6896551724137931):2..19,21..145 146 fps (0.684931506849315):2..146 147 fps (0.6802721088435374):2..147 148 fps (0.6756756756756757):2..148 149 fps (0.6711409395973155):2..149 150 fps (0.6666666666666666):2..3,5..12,14..16,18..19,21..22,24..25,27..28,30..45,47..48,50..57,59..61,63..64,66..67,69..70,72..73,75..106,108..109,111..112,114..115,117..118,120..135,137..150 151 fps (0.6622516556291391):2..151 152 fps (0.6578947368421053):2..142,144..152 153 fps (0.6535947712418301):2..153 154 fps (0.6493506493506493):2..154 155 fps (0.6451612903225806):2..155 156 fps (0.6410256410256411):2..156 157 fps (0.6369426751592356):2..157 158 fps (0.6329113924050633):2..158 159 fps (0.6289308176100629):2..134,136..159 160 fps (0.625):3..160 161 fps (0.6211180124223602):2..161 162 fps (0.6172839506172839):2..162 163 fps (0.6134969325153374):2..163 164 fps (0.6097560975609756):2..164 165 fps (0.6060606060606061):2..165 166 fps (0.6024096385542169):2..166 167 fps (0.5988023952095808):2..167 168 fps (0.5952380952380952):2..168 169 fps (0.591715976331361):2..169 170 fps (0.5882352941176471):2..144,146..170 171 fps (0.5847953216374269):2..171 172 fps (0.5813953488372093):2..172 173 fps (0.5780346820809249):2..173 174 fps (0.5747126436781609):2..174 175 fps (0.5714285714285714):2..52,54..59,61..66,68..175 176 fps (0.5681818181818182):2..176 177 fps (0.5649717514124294):2..177 178 fps (0.5617977528089888):2..178 179 fps (0.5586592178770949):2..102,104..179 180 fps (0.5555555555555556):2..76,78..85,87..180 181 fps (0.5524861878453039):2..97,99..181 182 fps (0.5494505494505495):2..182 183 fps (0.546448087431694):2..92,94..183 184 fps (0.5434782608695652):2..184 185 fps (0.5405405405405406):2..185 186 fps (0.5376344086021505):2..186 187 fps (0.5347593582887701):2..187 188 fps (0.5319148936170213):2..188 189 fps (0.5291005291005291):2..189 190 fps (0.5263157894736842):2..14,16..180,182..190 191 fps (0.5235602094240838):2..191 192 fps (0.5208333333333334):2..192 193 fps (0.5181347150259067):2..193 194 fps (0.5154639175257731):2..113,115..194 195 fps (0.5128205128205128):2..101,103..195 196 fps (0.5102040816326531):2..196 197 fps (0.5076142131979695):2..197 198 fps (0.5050505050505051):2..104,106..198 199 fps (0.5025125628140703):2..199 200 fps (0.5):3..4,7,9..11,13..15,17,19,29,31,33,37,39,41..90,93..106,109..110,113..116,119..140,161..200 201 fps (0.4975124378109453):2..201 202 fps (0.49504950495049505):3..202 203 fps (0.49261083743842365):2..203 204 fps (0.49019607843137253):2..204 205 fps (0.4878048780487805):2..205 206 fps (0.4854368932038835):2..206 207 fps (0.4830917874396135):2..207 208 fps (0.4807692307692308):2..208 209 fps (0.4784688995215311):2..209 210 fps (0.47619047619047616):2..151,153..210 211 fps (0.47393364928909953):2..211 212 fps (0.4716981132075472):2..212 213 fps (0.4694835680751174):2..213 214 fps (0.4672897196261682):2..214 215 fps (0.46511627906976744):2..215 216 fps (0.46296296296296297):2..216 217 fps (0.4608294930875576):2..217 218 fps (0.45871559633027525):2..218 219 fps (0.45662100456621):2..219 220 fps (0.45454545454545453):2..10,12..115,117..126,128..220 221 fps (0.45248868778280543):2..221 222 fps (0.45045045045045046):2..222 223 fps (0.4484304932735426):2..223 224 fps (0.44642857142857145):2..224 225 fps (0.4444444444444444):2..18,20..94,96..103,105..169,171..188,190..197,199..225 226 fps (0.4424778761061947):2..226 227 fps (0.44052863436123346):2..227 228 fps (0.43859649122807015):2..228 229 fps (0.4366812227074236):2..177,179..229 230 fps (0.43478260869565216):2..16,18..230 231 fps (0.4329004329004329):2..172,174..231 232 fps (0.43103448275862066):2..232 233 fps (0.4291845493562232):2..167,169..233 234 fps (0.42735042735042733):2..234 235 fps (0.425531914893617):2..235 236 fps (0.423728813559322):2..236 237 fps (0.4219409282700422):2..237 238 fps (0.42016806722689076):2..238 239 fps (0.41841004184100417):2..239 240 fps (0.4166666666666667):2..6,8..18,20..240 241 fps (0.4149377593360996):2..241 242 fps (0.4132231404958678):2..224,226..242 243 fps (0.411522633744856):2..243 244 fps (0.4098360655737705):2..244 245 fps (0.40816326530612246):2..133,135..186,188..245 246 fps (0.4065040650406504):2..246 247 fps (0.4048582995951417):2..247 248 fps (0.4032258064516129):2..248 249 fps (0.40160642570281124):2..249 250 fps (0.4):65,72,75,77,92,97,100..102,107,109..112,115..117,120..122,124..125 251 fps (0.398406374501992):2..222,224..251 252 fps (0.3968253968253968):2..252 253 fps (0.3952569169960474):2..253 254 fps (0.3937007874015748):2..254 255 fps (0.39215686274509803):2..212,214..255 256 fps (0.390625):2..256 257 fps (0.38910505836575876):2..207,209..257 258 fps (0.3875968992248062):2..258 259 fps (0.3861003861003861):2..259 260 fps (0.38461538461538464):2..162,164..175,177..260 261 fps (0.3831417624521073):2..261 262 fps (0.3816793893129771):2..262 263 fps (0.38022813688212925):2..155,157..263 264 fps (0.3787878787878788):2..264 265 fps (0.37735849056603776):2..152,154..265 266 fps (0.37593984962406013):2..266 267 fps (0.37453183520599254):2..149,151..267 268 fps (0.373134328358209):2..268 269 fps (0.37174721189591076):2..146,148..269 270 fps (0.37037037037037035):2..158,160..196,198..270 271 fps (0.36900369003690037):2..143,145..271 272 fps (0.36764705882352944):2..211,213..272 273 fps (0.3663003663003663):2..140,142..273 274 fps (0.36496350364963503):2..274 275 fps (0.36363636363636365):2..137,139..148,150..159,161..171,173..194,196..216,218..275 276 fps (0.36231884057971014):2..276 277 fps (0.36101083032490977):2..277 278 fps (0.3597122302158273):2..278 279 fps (0.35842293906810035):2..279 280 fps (0.35714285714285715):2..280 281 fps (0.35587188612099646):2..281 282 fps (0.3546099290780142):2..282 283 fps (0.35335689045936397):2..283 284 fps (0.352112676056338):2..284 285 fps (0.3508771929824561):2..285 286 fps (0.34965034965034963):2..286 287 fps (0.34843205574912894):2..60,62..287 288 fps (0.3472222222222222):2..288 289 fps (0.3460207612456747):2..289 290 fps (0.3448275862068966):2..290 291 fps (0.3436426116838488):2..291 292 fps (0.3424657534246575):2..292 293 fps (0.3412969283276451):2..293 294 fps (0.3401360544217687):2..294 295 fps (0.3389830508474576):2..295 296 fps (0.33783783783783783):2..296 297 fps (0.3367003367003367):2..297 298 fps (0.33557046979865773):2..298 299 fps (0.33444816053511706):2..299 300 fps (0.3333333333333333):2..18,20..31,33..34,36..37,39..40,42..43,45..46,48..49,51..52,54..55,57..58,60..124,126..127,129..142,144..211,213..214,216..217,219..220,222..223,225..226,228..229,231..232,234..235,237..238,240..251,253..300 ```

My hypothesis looked good for a while, but then the 100 fps case falsified it! I think that means there are at least two bugs. Assuming that the fact that some frame rates are unrepresentable in the target format and that the 100 fps case can be represented in a gif, that 100 fps case should only have one bug (or at least one fewer bug), so I plan to focus on that case next.