1mb movie

A useless but radical compression project: getting films down to 1mb and back again with language models

A 4×3 grid of mid-clip frames from twelve films reconstructed by the lossy pipeline.

How could we compress a film to less than 1 MB while preserving the most quality?

Normally when we want a smaller video we reduce things like

But we’ll see why that’s not enough for sub-1 MB (5 MB is needed just for audio) in the next section.

In this article I’ll explore how we can summarise and compress films & TV using models to get any film down to sub-1 MB, then re-generate the film from its compressed form.

I’ll be optimising for the best quality for the least $.

Where we’re headed: the original film (top) vs. the reconstruction generated from the sub-1 MB file (bottom). The Fellowship of the Ring, Moria. Unmute for the generated audio.

The code for everything below is at github.com/willhs/lossy.

How we normally compress films

Let’s see a video get progressively crappier with lossy compression:

Source: the opening shot of Star Wars IV, 12 sec, ~455 kbps H.264, 673 KB. The full film file is 632 MB.
100 kbps AV1: 244 KB for 12s. 124.7 min ≈ 147 MB.
30 kbps AV1, 320×136, 12fps: 76 KB. 124.7 min ≈ 46 MB.
10 kbps AV1, 160×68, 8fps: 39 KB. The codec floor. 124.7 min ≈ 23 MB. Still 23× over budget.
6 kbps Opus speech-only: Opus’s hard floor. 124.7 min ≈ 5 MB.

So even the smallest recognisable video and audio is still over 20× larger than 1 MB. We need a different way to store video & audio.

Compress: film -> sub-1 MB text file

A reconstructed Star Wars IV garbage-masher frame: Luke, Leia, Han and Chewbacca trapped as two grimy metal walls grind inward, squeezing them and floating debris into a narrowing gap.
Prompt fed to Seedance: “…Luke, Leia, Han and Chewbacca trapped as two massive metal walls grind inward, squeezing them into an ever-narrowing gap…”, the Star Wars IV garbage masher, compression made literal.

Shot by shot

Since video models generate short videos of around 5-10 seconds and shots in films tend to be a similar length, it would seem that video models could be used to generate a film as a sequence of shot videos.

It turns out there’s a Python library for finding shot boundaries in films! PySceneDetect.

Star Wars, for example, has around 2,000 shots which are detected pretty well by this library. There are a few exceptions, like the full-screen laser flashes in the first scene causing a few erroneous extra shots to be detected.

ffmpeg is a popular toolset for working with videos. You can use it to extract individual frames, audio, and clips. We can use it to split a film into shots, separate out audio, and extract key frames.

Describe each shot

We can generate a summary of each shot in the film, given a few frames sampled from the shot. Google’s Gemini 2.5 Flash-Lite is cheap at $0.10 per million input tokens and $0.40 per million output tokens. Scanning a full 2-hour film costs around $0.67.

Going through security, The Matrix (8.8 seconds):

Close on a black coat and duffel bag as the camera begins to pull back.The camera pulls back further in the lobby.Trinity begins to come into frame.Trinity revealed in a black vinyl coat and sunglasses.Trinity and Neo walking forward together through the lobby.Neo reaches into his coat for his holstered guns.The pair continue walking toward the camera.Wide view of Neo and Trinity striding through the lobby.
The 8 frames sampled from this 8.8s shot were the only input the vision model saw.
{
  "shot_type": "medium",
  "camera_movement": "slow zoom out",
  "subjects": "A woman in a black vinyl coat and a man in a black trench coat, both wearing dark sunglasses.",
  "action": "The two characters walk forward together through a lobby. The man draws two handguns from his coat holsters simultaneously as they continue walking.",
  "lighting": "High-contrast, cool-toned interior lighting with bright highlights reflecting off the vinyl fabric.",
  "color_palette": "Black, dark grey, muted green, and white.",
  "mood": "Intense, cool, determined, and action-oriented.",
  "setting": "A modern, sterile office building lobby with stone walls and a green 'EXIT' sign in the background.",
  "sound": "A mix of intense, rhythmic electronic music, ambient room tone, and the sharp, metallic sound of guns being drawn."
}

What about audio?

Movies have three layers of audio: (1) dialogue, (2) music, and (3) sound effects. We’ll record those layers separately for each shot.

I’ll cheat a bit with dialogue by directly using the subtitles for the film, which are sometimes embedded in the film itself.

Listening for sound effects

We can use an audio classification model like YAMNet to detect which of 521 everyday sound classes are present at which times.

YAMNet · one reading every 0.48s · 521 class scores
+0.0s Music 0.15 · Arrow 0.09
+1.4s Music 0.87 · Timpani 0.01
+3.3s Domestic animals 0.19 · Dog 0.16
+5.2s Outside, urban 0.12 · Vehicle 0.08
+7.1s Inside, small room 0.17 · Bicycle 0.16
average the 20 frames in this shot, keep the top 5, pick a bucket
per-shot audio summary
effectsMusic 0.14 · Inside, small room 0.06 · Typing 0.05 · Explosion 0.05 · Gunshot 0.05
hand to the vision model with the sampled frames + subtitle lines
written into the shot entry
”sound”: “A mix of intense, rhythmic electronic music, ambient room tone, and the sharp, metallic sound of guns being drawn.”
The Matrix lobby shot (8.8s) down the audio side of the encoder. Noisy per-frame guesses: a dog, a bicycle, wash out once the 20 frames are averaged, leaving a stable bucket and labels to seed the final sound description.

Describing the music

YAMNet also tells us which shots are dominated by music. For those, Gemini listens to the shot’s audio and writes a one-line description of the score:

“A slow, ethereal, and slightly ominous orchestral score begins to swell. It features sustained strings and a subtle, rising synth pad, creating a sense of anticipation and foreboding. The tempo is very slow, around ~60bpm, and the music builds tension.”

That sentence is all we keep of the score — the melody itself doesn’t survive.

Putting it together: the lossy compression encode pipeline

Film 632 MB · ~2 hr · 1080p
↓ detect shot boundaries · PySceneDetect
~2,000 shots ~3.6 s each
↓ analyse each shot
vision
2–8 frames
Gemini VLMscene caption
motion
optical flow
camera movepan / zoom / static
sound
audio
YAMNeteffects + music cue
speech
subtitles
dialoguelines + timings
↓ fuse into one JSON entry per shot
The summarised film shots.json + characters.json · ~3 MB ≈ 200× smaller
↓ compress · xz -9e
Zipped up ~300 KB the whole film as text, smaller than a phone photo ≈ 2,000× smaller
Star Wars IV, summarisedsize
Source film: 124.7 min, 1080p H.264632 MB
2,012 shot descriptions + dialogue (shots.json)3.0 MB
Character descriptions (characters.json)38 KB
Compressed (xz)≈320 KB
The whole film in ≈320 KB, about 2,000× smaller than the source, and ~330 bits per second of film: twenty times below even the 6 kbps Opus speech floor.

By summarising the film in English we’re still able to preserve a lot of the key details, but we’ll need video and audio models to re-generate the film as a video.

File compression tools like gzip or xz squeeze the summary another ~10×. If you run the same compressor on the original .mp4 it shaves under 1%, since H.264 has already wrung almost all of the redundancy out of the pixels.

You can download a real encode: the Matrix lobby scene as the-matrix-lobby.shots.json (99 shots, 155 KB, or 21 KB xz’d).

Decompress: 1 MB file -> film

Making the least crap film from the smallest amount of information for the lowest cost.

A reconstructed frame from The Matrix, generated by the lossy pipeline: two figures in a stark white void flanked by endless racks of weapons receding into the distance.
Prompt fed to Wan 2.1: “a man and a woman in black trench coats standing in a stark white, infinite void… a long, narrow rack of weapons stretching into the distance, high-key shadowless lighting…”

So now we have a file with a set of shot descriptions, one for each shot in the movie:

0010:00–0:09A black coat and duffel bag; the camera pulls back to reveal two figures in dark sunglasses walking through a sterile office lobby…
0020:09–0:13A woman in a black vinyl coat strides forward as the man beside her draws two handguns from his coat holsters…
0030:13–0:18Wide shot of the pair advancing toward the camera, high-contrast cool lighting, a green EXIT sign behind them…
⋮  

Reminder: each description includes shot_type, camera_movement, subjects, action, lighting, color_palette, mood, and sound, plus the shot’s dialogue lines, pulled from the subtitles.

Now we’re ready to generate a video for each of these shots with a video generation model, before stitching them together to create the movie, whole again!

Selecting a video model

I’m going for the lowest cost for best quality movie in 480p+.

The best-quality video model as of writing is ByteDance’s Seedance 2, but it’s too expensive at around $2,200 for 2 hours of output. It’s also not open-weight so we’d have less control of model variants like quantised models.

Alibaba’s Wan 2.1 & 2.2 are older model families from early/mid 2025. The quality is noticeably worse but they’re much cheaper to run, and open-weight. The smaller 1.3B model can run slowly on a MacBook and the 5B model can fit on rented hardware like an Nvidia RTX 4090 GPU.

Wan 2.1 reconstruction of the opening Star Wars space shot: a blocky ship firing a red laser, surrounded by a busy field of planets and lens flares.Wan 2.1
Seedance 1.0 Pro reconstruction of the same shot: a cleaner, more cinematic ship firing a red laser over a single planet against a starfield.Seedance 1.0 Pro
Star Wars IV shot 011 “a spacecraft moves across the frame… a red laser beam fires from the rear of the ship… deep space, orbiting a large brownish-orange planet”. Wan 2.1 (left) clutters the frame and looks like a painting; Seedance 1.0 Pro (right) holds a cleaner, more intentional composition.
ModelWherecost / secwhole film (~2 hr)
Wan 2.1 T2V 1.3Bself-hosted (RunPod, ≈$0.34/hr GPU)$0.003≈$22
Wan 2.2 T2V Fastpaid API (Replicate)≈$0.01≈$74
Seedance 1.0 Propaid API (fal)$0.05≈$374
Seedance 2.0paid API (fal, standard)$0.30≈$2,200
Measured from our own runs on Star Wars IV (124.7 min); Seedance 2.0 is fal.ai list price, not run (retries add cost). The $22-vs-$74 gap is mostly a small model on a rented GPU, not self-host vs API: the cheap Wan 2.2 API is a distilled “Fast” variant.

Self hosting

If you run models on your own hardware, even if rented, you can sometimes save on costs relative to model service providers like fal.ai and Replicate. I used runpod.io to provision GPUs for me, sometimes with their community hardware which is discounted for its low availability.

Self-hosting models means significant effort spent researching, building and maintaining a system to run workflows on rented hardware.

I tried to outsource this to Claude Code, which worked well once established but I did spend time and extra credits debugging and re-running failed jobs.

Wan 2.2 5B, same scene (11 clips, 33.8s)Billed bycost / secscene total
fal.ai, hostedper clip ($0.15)≈$0.05$1.65
RunPod A6000, self-hostedGPU time ($0.33 / hr)≈$0.01$0.33
The same Wan 2.2 5B run both ways; the only variable is who owns the GPU. Fal.ai charges $0.15 per clip however short; self-hosting pays A6000 uptime at $0.33/hr. ~5× cheaper.

Some models like Wan 2.1 1.3B can be run on consumer hardware like a MacBook.

Long shots

Wan 2.1/2 models support output lengths of ~3–6 seconds. Not enough for longer shots from film and tv, e.g. the opening shot of The Big Lebowski where The Dude browses for milk.

There’s a good solution to this: we can use the last frame of an output clip as the first frame of the next clip with the first_image parameter, then join the clips into one shot:

chained: last frame → first frame
independent clips
Two ~5s Wan 2.2 clips of a landspeeder joined into one ~10s shot, seam at the midpoint.

Short shots

Some movie shots are under a second — shorter than the ~2s floor that Wan will generate. Every model I tried bottoms out around there (Seedance too).

We can either squeeze the 2-second clip to fit or cut it shorter:

The Lord of the Rings, Moria. Original (left) vs. reconstruction (right): a normal shot, three sub-second cuts, then another shot. The short cuts are trimmed to length, not sped up.

The continuity problem: every shot, a different actor

Four reconstructed frames of C-3PO from the same Lars homestead scene in Star Wars IV. He is a different gold droid in each, a different head and face every time.
C-3PO across four shots of one scene (Star Wars IV, 279–285), same Wan 2.1 model. A different gold droid every shot — new head, new face — because the model only sees a new ~100-word description for each prompt.

Since we’re stitching together clips that are generated independently, continuity becomes the major challenge in making something coherent.

Something that surprised me about video models was how small the input prompt is. The sweet spot for models generally is on the order of 50 - 150 words:

Star Wars IV · shot 011 · fed to Wan 2.1 · 109 words

A large, blocky spacecraft with a grid of bright glowing thrusters. The spacecraft moves steadily across the frame against the backdrop of a planet and a moon, while a red laser beam fires from the rear of the ship, dissipating as it travels through space. Extreme Wide shot, static. Deep space, orbiting a large, textured, brownish-orange planet with a smaller moon visible in the distance. High contrast space lighting; the ship is illuminated by its own thrusters and the distant light of a star, while the planet below is lit by a sun off-screen. Deep black, vibrant orange, muted grey, and bright white. Epic, cinematic, and adventurous.

That’s the budget we have for describing a shot in our compressed movie. So if we want Luke Skywalker to look the same across the clips we’re generating we may only have 100 words to describe Mark Hamill’s face, expression, pose, costume, etc. Other characters might be in the shot too.

Character clustering: an attempt

Something I tried to mitigate the continuity problem is to include something extra in the encoded output: descriptions of characters, characters.json.

We can use characters.json in two ways depending on the model:

(1) Primarily: to enrich shot prompts with more, and more accurate, details about the character.

before · raw shot description · 121 words

An elderly man with gray hair and a weathered face, wearing a light-colored shirt and a vest. The man slowly brings his hand up to his face, rubbing his forehead and eyes in a gesture of weariness or distress, while looking slightly off-camera. Medium close-up, slow dolly in. A dimly lit, rustic interior, likely a study or home, with a view of a dark, forested area through a window in the background. Low-key, dramatic lighting; a strong, cool-toned light source from the background creates a silhouette effect, while a soft, warm light illuminates the side of his face. Deep shadows, dark browns, and muted earth tones, contrasted with a vibrant, cool teal light coming from the window. Melancholic, weary, and somber.

blend in the canonical description from characters.json
after · enriched with the character description · 151 words

A medium close-up shot, with a slow dolly in, captures Gandalf, the elderly wizard, in a dimly lit, rustic interior, his long white beard and hair framing a weathered face. He wears his iconic dark, weathered cloak, the fabric hinting at countless journeys. The air is thick with a melancholic, weary, and somber mood, amplified by the low-key, dramatic lighting. A strong, cool-toned teal light spills from a window in the background, offering a glimpse of a dark, forested area and creating a striking silhouette effect. This vibrant, cool light contrasts with the soft, warm illumination on the side of Gandalf’s face, highlighting the deep shadows and muted earth tones of the room. Slowly, he brings a hand, gnarled with age and wisdom, up to his forehead, rubbing his eyes in a gesture of profound weariness or distress, his gaze fixed slightly off-camera, lost in thought or burdened by unseen knowledge.

Here’s a particularly good example of the same shot generated from each of those two prompts:

before: raw shot description
after: enriched with the character description
The Fellowship of the Ring, shot 002, Wan 2.1. A generic old man becomes recognisably Gandalf.

(2) Some models like Wan 2.1 VACE accept reference image inputs, so we can generate an image for each character:

A 3×2 grid of character headshots generated from the pipeline's clustered descriptions: Luke Skywalker, Gandalf, Harry Potter, C-3PO, Larry David, and Jimmy McGill.
Luke Skywalker: “A young male human with fair skin, light brown hair, and blue eyes… a bright orange Rebel Alliance flight suit over a white thermal undershirt… sitting in the cockpit of an X-wing starfighter.”

Feeding that headshot in as a VACE reference does buy back some continuity: three C-3PO shots from the scene above, regenerated with the droid’s portrait attached:

A 3×2 grid of reconstructed C-3PO frames from Star Wars IV. The top row, generated with no reference image, shows three different gold droids. The bottom row, generated with a C-3PO reference image fed in, shows a consistent gold humanoid droid with yellow eyes across all three shots.
Three shots from the same scene (Star Wars IV, 279–285), no reference (top) vs the C-3PO headshot fed in as a VACE reference (bottom). With the reference, C-3PO is a little more consistent and less R2D2-like.

Generating the audio

For dialogue we’ll just use the cheapest (dullest) ElevenLabs voice to keep costs low. We can’t attribute dialogue to characters from the subtitles alone anyway. It’s a harder problem to infer that, for another day…

“I find your lack of faith disturbing”

Sound effects

We use MMAudio to generate sound effects for the film from the sound part of each shot summary, e.g.:

“The ambient sounds of a bowling alley, including the low rumble of a ball rolling on wood, the distant clatter of pins, and background chatter, accompanied by classic soul or blues music.”

The bowling-alley shot from The Big Lebowski. Wan 2.1 video with an MMAudio track generated from the shot descriptions alone, no original audio. The alley noise, the clatter of pins and ball returns, is entirely the model’s invention.

MMAudio co-hosts on the video pod for next to nothing, but its output is often noisy, so for the final reconstructions I ended up leaning more on ElevenLabs’ sound-effects model instead: cleaner, but hosted-only and a little pricier.

Music

The one-line score description captured during encode is fed to Meta’s 2023 text-to-music model MusicGen, which invents a fresh score to match. Only the vibes are preserved, not the score.

The Star Wars binary sunset: Seedance video, MusicGen score. The original is the swelling Force theme; all that survived was Gemini hearing it as “ethereal, ominous, foreboding”, so MusicGen invents a tense ambient swell.

Audio: summarised

So, audio generation costs are:

AudioModelratewhole film (~2 hr)
Ambience + SFXMMAudio, on the video podco-hosted≈$1
orMMAudio, fal.ai hosted$0.002 / sec≈$2.60
Extra SFXElevenLabs SFX$0.001 / sec≈$0.50
DialogueElevenLabs Speech$0.05 / 1K chars≈$2.70
Music / scoreMusicGen, on the video podco-hosted≈$0.50
orMusicGen, Replicate hosted$0.002 / sec≈$5
Total: co-hosted (pod where possible)≈$4.70
Total: fully hosted≈$10.80
Near-zero marginal cost when MMAudio and MusicGen reuse the GPU already rented for video, running in series once the video model unloads, a few extra GPU-hours (≈$1–2 a film) rather than a separate card. Dialogue and extra SFX are hosted-only, so they hit both totals.

The result: decompressed 1 MB films

Scenes generated from 1 MB films:

Reflections

What astonishing film compression! We took a 632 MB film down to ≈320 KB, that’s about 2,000× smaller.

StageModel / servicewhole film (~2 hr)
EncodeGemini 2.5 Flash-Lite$0.67
VideoWan 2.1 1.3B, self-hosted, RunPod ≈$0.34/hr GPU≈$22
orWan 2.2 5B, self-hosted, RunPod A6000≈$75
orSeedance 1.0 Pro, fal.ai≈$374
orSeedance 2.0, fal.ai (list price)≈$2,200
Ambience + SFXMMAudio, co-hosted on video pod≈$1
Music / scoreMusicGen, co-hosted on video pod≈$0.50
DialogueElevenLabs Speech≈$2.70
Total: budget (Wan 2.1 + MMAudio)≈$27
Total: quality (Wan 2.2 5B + MMAudio)≈$80
Figures from actual runs on Star Wars IV (124.7 min); Seedance 2.0 is fal.ai list price, not run. Only the score’s mood survives, not the melody.

Now we can carry over 1000 films on a 1 GB USB drive, and to watch one of them we’ll only need to, best-case, pay ≈$30 USD and wait a minimum of 12 hours for processing. People are used to paying for compression, like with WinRAR.

Our decompressed films are lossy. But the output is indeed a full-length 480p (or up to 1080p for more $) video which retains core aspects of the film including all dialogue, and a basic visual and audio reconstruction (incl. some of the music & sound effects) of every shot in the input film.

Full scenes, original vs. reconstruction, synced:

The bad: the continuity issues we talked about earlier make the output film chaotic and disjointed, like every shot is from a different universe where the movie was made with a different crew, at a different location, etc. Our character prompt enrichment can only mitigate that as each clip has been generated in almost total isolation.

Most of the artistic detail of the film is wiped away in the compression process and made up by video and audio models. Setting aside the chaos, what comes out the other side is often rather banal, lacking artistic taste; it goes to the middle. However, some moments are intriguing and make me curious about the alternate universe.

Further improvements

Our compression process is quite good already considering the 1 MB limitation. It’s the generation phase that can improve.

To make our output movies better we really need to work on the consistency/continuity between shots. Our character clustering gets some of the way there but there’s still a long way to go. Newer models may provide more input parameters, like the VACE Wan variant which allows input image references.

Better visuals: generate a first_image with an image model: a known technique to improve the quality of video output, so the video starts from a solid foundation. Image models like Google’s “Nano Banana” or OpenAI’s gpt-image-x are high quality and relatively cheap, and we could feed our character images into them for better continuity. Generating ~2,000 images does add up though, so it’s more cost to add.

The first frame of a Wan 2.2 clip of Gollum in a cell: a dark, murky, barely-legible creature behind bars.Wan 2.2: first frame of the clip
Nano Banana 2 generation from the same prompt: a sharp, fully-formed gaunt creature gripping rusted prison bars, gnarled skin and pale eyes clearly rendered.Nano Banana 2: same prompt
Same prompt to both (the Lord of the Rings Moria shot): “An extreme close-up shot, static, focuses on Gollum, a gaunt and spectral creature with large, pale, wide eyes and dark, gnarled skin, peering through what appear to be prison bars. His long, thin fingers twitch and reach out from the darkness, their gnarled texture emphasized by the harsh, directional lighting… The setting is implied to be a dark, confined cell, with the bars creating a sense of claustrophobia…”

Dialogue: the default ElevenLabs voice gets old pretty quickly. With clever analysis of the script and visual clues from the frames we ingest we could guess which lines are said by each character, then pick voices for each of them. Harder when many characters are on-screen at once.

Sound effects: the audio generated by MMAudio with our prompts can sometimes sound pretty noisy/garbled, so there’s room there to explore better prompting or other models. ElevenLabs is better but expensive through an API.

Music is currently generated per-shot so it suffers the same continuity issues as video: music will cut off or change suddenly mid-scene, so we’d want a better way to detect music transitions, separately to shots.

Hagrid and Hedwig the snowy owl, reconstructed by the lossy pipeline (character-identity-enriched generation).
Wan 2.1 (character-enriched): “Rubeus Hagrid: A large, imposing man with a massive frame, characterized by his long, wild, unkempt dark hair and a thick, bushy beard…”

← all posts