Arduino cannot decode MP4 on a tiny SSD1306 in real time. A practical
video to OLED workflow is: convert the clip offline (or in the browser) into a short
loop of 1-bit frames, store them in PROGMEM, and play them with drawBitmap.
That is exactly what this free video to OLED converter does — plus threshold, dithering,
crop, FPS sync, and a full sketch export.
How to convert video to OLED Arduino code
- Open the Import tab on oledanimationmaker.com.
- Drop your MP4 / WebM / MOV (or click to browse). Short clips work best for Uno flash limits.
- Crop the video to match your display aspect (e.g. 128×64).
- Set video frames — the tool samples across the whole clip (keeps the editor and code usable).
- Tune threshold & dithering with a live OLED preview.
- Optional: Sync to video so playback FPS matches clip duration.
- Get the Code — Adafruit SSD1306, U8g2, or MicroPython export.
Video to OLED vs gif2cpp / GIF to CPP
Many makers search gif2cpp or gif to cpp when they want GIF → C++ arrays. This tool covers that and video:
- GIF — classic gif2cpp / gif to cpp style conversion in the browser
- Video — MP4/WebM frame sampling → same PROGMEM export path
- Edit after import — timeline, FPS, pixel editor, templates
Full GIF walkthrough: GIF to OLED Arduino (gif2cpp alternative).
Arduino oled display animation code tips
- Uno / Nano: keep frame counts low (each 128×64 frame ≈ 1KB flash).
- ESP32 / ESP8266: more room for longer video-derived loops.
- High contrast source: faces, logos, and simple motion convert cleaner than dark footage.
- Test on hardware: WebSerial or WiFi live preview before flashing final code.
Frequently asked questions
Is this a real video to OLED converter?
Yes. It imports video in the browser, extracts frames, converts them to monochrome OLED bitmaps, and exports Arduino-ready animation code — not just a GIF-only tool.
Can I convert MP4 to SSD1306 code?
Yes. Use MP4 (or WebM/MOV), pick 128×64 SSD1306 (or SH1106), sample frames, then click Get the Code.
Does it replace gif2cpp?
For most hobby workflows, yes as a free online gif2cpp / gif to cpp alternative — plus video import and a full animation editor.
Convert video to OLED code now
Free video to OLED converter — MP4/WebM/GIF → Arduino C++ or MicroPython. No install.
Try Video Import →Popular searches: video to oled converter · video to oled arduino · mp4 to oled · mp4 to ssd1306 · convert video to oled animation · video to arduino oled · gif2cpp · gif to cpp · arduino oled display animation code · oled animation maker
Why the board cannot decode MP4 while it draws
An MP4 is a compressed movie, not a stack of pictures. The file stores motion as groups of predicted frames, and a player has to unpack those groups before a single pixel exists. A 0.96" SSD1306 does none of that work. It only accepts a bitmap you already built: 128 by 64 pixels, one bit each, packed eight pixels to a byte. The Arduino (or ESP) in this project is a frame player. The browser on your computer is the decoder.
That split is the whole video to OLED method. You drop an MP4, WebM, or MOV
on the Import tab. The page samples stills from the clip, turns each still into a 1-bit
frame, and exports those frames as PROGMEM arrays plus a playback loop.
After upload, drawBitmap copies one array into the Adafruit buffer and
display() pushes that buffer over I2C. No codec, no audio, no file system.
Trying to decode the MP4 on the microcontroller fails for three practical reasons, even before you look at libraries. First, the compressed file is usually larger than Uno flash and awkward to keep on a small ESP sketch. Second, a decoder needs a work buffer measured in tens of kilobytes, and an Uno has 2 KB of SRAM, already half used by one 128×64 display buffer. Third, I2C to the panel is the slow part you can actually see: one full frame is 1024 bytes. At the default 100 kHz bus that transfer is on the order of 90 ms before the next frame can start. A live decoder would still have to finish, then wait on the wire. Pre-baked frames skip the decoder and leave you only the wire time.
Frame budget: 1024 bytes, 15 fps sampling, 200 frame cap
Count bytes before you fall in love with a long clip. One 128×64 frame is 128 × 64 ÷ 8 = 1024 bytes. Ten frames are about 10 KB. Fifty frames are about 50 KB. One hundred frames are about 100 KB. The Import path caps a video at 200 frames (about 200 KB of bitmaps) so the editor stays usable and the generated sketch stays something a 4MB board can hold.
Sampling is automatic. Short clips are taken at about 15 frames per second. The frame count is the clip length times 15, rounded, and then clamped between 2 and 200. A longer clip is not rejected. It is sampled more sparsely so the total never passes 200. You still get a picture from the start, the middle, and the end. You just do not get every source frame.
- 4 seconds × 15 ≈ 60 frames (about 60 KB). Comfortable on ESP8266/ESP32. Still too big for a classic Uno once libraries are included.
- 8 seconds × 15 ≈ 120 frames (about 120 KB). This is a normal “song hook” length for an ESP board.
- 13.3 seconds × 15 = 200 frames, the cap. Past this point the tool keeps 200 samples and stretches the gap between them.
- 30 seconds still exports 200 frames. The gap between samples is 30 / 200 = 0.15 s, so you are keeping about 6.7 pictures per second of the original, not 15.
Sync to video is the checkbox that makes playback last as long as the source. With it checked (the default), frames per second becomes frame count divided by clip length, limited to the range 1–30. An 8 second clip with 120 frames plays at 15 fps. A 30 second clip with 200 frames plays at about 6.7 fps, and the loop lasts 30 seconds. Uncheck it when you want to pick the speed yourself. With the box off, a multi-frame import starts at 15 fps, which will run a long, sparsely sampled clip faster than the original.
There is also a Video frames field if you want fewer samples than the automatic count. Dropping from 120 frames to 40 on an 8 second clip is the right move when you are testing wiring, or when you plan to finish on an Uno. Fewer samples means a jerkier loop and a much smaller sketch. The cap does not go the other way: asking for more than 200 frames still stops at 200.
Crop and fit before you trust the preview
A phone video is tall or wide. A 128×64 panel is a short strip, exactly 2:1. If you stretch a 9:16 portrait clip into that strip, faces become pancakes and captions become streaks. Crop, or use the fit control, before you judge threshold.
The importer shows a frame from the middle of the clip so you can frame the crop on a representative picture, not on a black fade at time zero. Then pick how that crop lands on the panel:
- Keep aspect (no squash) fits the whole crop inside 128×64 and leaves unused pixels black. Use this for logos and faces you cannot afford to cut.
- Fill screen (center crop) covers the whole OLED and trims whatever falls outside. Use this when the action is already in the center and you want no empty bars.
- Stretch to fit maps every source pixel into the panel and ignores aspect. Use it only when the source is already close to 2:1.
Leave a little margin around thin details. A one-pixel eyebrow in the source often disappears after the resize. If the subject is small in a 1080p frame, crop tight first, then convert. Resizing a tiny face down again will not invent detail the panel cannot show.
Threshold, dither, and which one to keep
The panel has no gray. Every source pixel becomes on or off. Threshold is the cutoff: with the default global value of 128, a pixel darker than mid-gray becomes a lit OLED pixel in the usual “white on black” export, and a lighter pixel stays off (you can invert if your preview looks like a negative). Drag the slider while the live preview is up. The Auto button picks a cutoff aimed at keeping text and thin lines readable, which is a better start than 128 on footage that is mostly dark or mostly bright.
Global threshold is the right default for logos, titles, and high-contrast animation. Two adaptive choices exist when the lighting is uneven. Bradley looks at a local window and is the one to try on line art. Sauvola also uses local contrast and is the one to try when one side of the frame is in shadow. Adaptive modes cost you a clean silhouette: a flat background can fill with speckle. If the preview looks noisy, go back to global and move the slider instead.
Dithering is Floyd–Steinberg. It spreads the rounding error of the 1-bit choice onto neighboring pixels, so a smooth cheek or a gradient sky becomes a pepper of dots instead of a hard poster edge. Turn it on for photos and soft motion. Leave it off for text, icons, and drawings with hard outlines. Dithered frames also look worse if you later scale the recording up on a phone: the dots are real data, not a broken panel. Optional Sharpen runs before the cutoff and helps soft phone footage. Skip it on art that is already black and white, or you will thicken every edge twice.
Contrast habits that survive 1-bit
- Prefer a bright subject on a dark background, or the reverse. Mid-gray on mid-gray becomes mud at any threshold.
- Turn on a lamp in the original if you control the shoot. You cannot recover a face that was underexposed once it is one bit.
- Avoid thin light-gray captions. They vanish first. If the clip is a lyric video, crop to the picture and accept that small type will not read at 128 pixels wide.
- Check three moments: the first sample, the middle sample the crop screen uses, and a frame near the end. A threshold that looks perfect on a title card can wipe out a darker shot later in the same clip.
- If only part of the clip looks good, cut a shorter source file and import that. Two hundred frames of a bad middle is worse than sixty frames of a clean hook.
Uno versus ESP32 after you click Get the Code
Export targets include Adafruit SSD1306, U8g2, and MicroPython. The wiring does not
change with the exporter. On Uno and Nano the I2C pins are SDA on A4 and SCL on A5, and
the usual 0.96" address is 0x3C. Many SSD1306 modules accept 5V because
of an onboard regulator. ESP32 boards want 3.3V, with SDA on GPIO21 and SCL on GPIO22.
An ESP8266 NodeMCU wants 3.3V as well, with SDA on D2 (GPIO4) and SCL on D1 (GPIO5).
Flash is the real split. A classic Uno has about 32 KB. A typical Adafruit sketch already uses roughly 10–14 KB, so you might fit a dozen full frames, not a video sample. Anything in the 150–200 frame range is an ESP32 job, or a 4MB ESP8266. Do not “try Uno anyway” on a 120-frame export. The IDE will stop with a sketch-too-large error, or you will spend an evening deleting frames by hand. If the only board on the desk is an Uno, set a much smaller video frame count, or switch the source to a short GIF and follow the gif2cpp path on this site.
On ESP8266, open Tools and set the flash size to the chip you actually have, commonly 4MB. A 1MB module can hold a short test. It will not hold a 200 KB bitmap block plus the libraries. If the compile complains about IRAM or a segment that does not fit, you are out of room for this clip, not out of USB cables.
A playback loop that matches the export
The generated sketch stores each sample as a 1024-byte PROGMEM array and
walks them with millis(). The delay is the number you get from the frame
rate after Sync to video (or from the FPS you typed). For a 15 fps loop the step is
about 67 ms. For a 10 fps loop it is 100 ms. This is the same shape the song sketches on
this site use:
void loop() {
static uint8_t frameIdx = 0;
static unsigned long lastMs = 0;
const uint8_t* frames[] = { frame0, frame1, /* ... through the last sample */ };
const uint8_t numFrames = 120; // your exported count, max 200
if (millis() - lastMs >= 67) { // 1000 / fps, here 15 fps
lastMs = millis();
display.clearDisplay();
display.drawBitmap(0, 0, frames[frameIdx], 128, 64, SSD1306_WHITE);
display.display();
frameIdx = (frameIdx + 1) % numFrames;
}
}
Change only the delay when the motion feels wrong. A smaller number speeds the loop up
and will drift away from the original clip length. It will not create frames you did not
sample. If display() itself takes longer than the delay, which happens on a
100 kHz bus, the picture still advances, just not on the clock you typed. Raising the
I2C clock to 400 kHz, with Wire.setClock(400000) after Wire.begin(),
is reasonable on these modules when you really want a 15 fps full-frame loop.
frameIdx as a uint8_t is fine through 200 frames. Do not put the
bitmap arrays in ordinary RAM. On Uno that overflows SRAM immediately. On ESP32 the
arrays can live in flash without the AVR pgm_read_* dance; the exported
Adafruit code already accounts for that. If a frame looks like static, you are usually
reading a PROGMEM pointer as if it were a RAM pointer on AVR, or the width and height
passed to drawBitmap do not match 128 and 64.
What a useful recording of the panel looks like
Film the glass, not a screen recording of the browser preview. The preview is there to set threshold. The recording proves the upload. Prop the module facing the phone, fill the frame with the 0.96" window, and turn off the room lights behind the camera so you are not shooting a reflection of a window. OLED pixels are sharp. A 1080p phone frame of the whole desk makes the animation look like noise.
Record at least one full pass. If Sync to video left you with an 8 second loop, film 10 seconds so the cut does not hide a stalled last frame. You want to see the picture change, return to the first sample, and keep going. Black bars from “keep aspect” are normal. A panel that stays black for the whole take is a wiring or address problem, not a shy video file. A panel that flickers one correct frame and resets is usually power: use a short USB cable and, on ESP boards, power the module from 3.3V.
Questions after the first video import
Why does a 60 fps MP4 become a slow OLED loop?
The converter does not keep the camera’s frame rate. It samples the clip at about 15 fps and never stores more than 200 frames. A long file is thinned out so the sketch stays small. Sync to video then stretches those samples across the original duration. The motion is a summary of the clip, played as bitmaps.
The preview looks fine and the Uno says the sketch is too big. What now?
The preview lives in the browser. Uno flash is about 32 KB, and each 128×64 frame is 1 KB. Drop the video frame count into the low teens, or move the same sketch to an ESP32 or a 4MB ESP8266. A 150 to 200 frame export will not fit on Uno.
Dithering made the picture speckled. Should I leave it on?
Leave it on for photos and gradients. Turn it off for titles, line art, and flat cartoons, and use global threshold or Bradley instead. Speckle is the dither doing its job. If you wanted hard edges, that checkbox is the wrong tool.
The OLED stays black after upload. Is the MP4 unsupported?
If the browser imported the file, the MP4 was fine. A black panel is almost always power, pins, or address. Uno uses A4 and A5. ESP32 uses GPIO21 and GPIO22. ESP8266 NodeMCU uses D2 and D1, and those boards need 3.3V on VCC. The usual address is 0x3C. If begin() fails, try 0x3D. Also confirm the IDE board menu matches the chip you plugged in. A sketch compiled for Uno and sent to an ESP32 will not bring the display up.