The Bad Apple!! music video, played frame by frame inside a Google Sheets spreadsheet,
where every cell is a pixel.
🎬 Demo GIF coming soon
Bad Apple!! is the black and white music video that people love to run on anything with a screen: oscilloscopes, calculators, terminals, Minecraft...
This is the Google Sheets version. The video is converted into frames of 0s and 1s, and an Apps Script player paints the cell backgrounds black or white fast enough to play the animation inside the spreadsheet.
-
Create a Google Sheets spreadsheet and open Extensions → Apps Script.
-
Add the project files:
Código.gswith the contents ofapps-script/Código.gs- an HTML file named
playerwith the contents ofapps-script/player.html
The HTML file must be named exactly
player, because the script loads it withcreateHtmlOutputFromFile("player"). -
Save and reload the spreadsheet. A Bad Apple menu will appear.
-
In the Apps Script editor, run
setupScreenonce. It resizes the cells and hides the gridlines. -
Click Bad Apple → Player → ▶ Play. 🎶
On the first run, Google asks for permission to edit the spreadsheet and to make external requests (UrlFetchApp), which are used to download the frames from this repo.
badApple.mp4 ──► notebooks/convertVideoIntoJson.ipynb ──► data/video_W_H_FPSfps.json
│
(GitHub raw URL)│
▼
Sheet ◄───── setBackgrounds ◄── Código.gs ◄── google.script.run ◄── player.html
- Conversion: a Colab notebook downscales the video to
W × H, turns it black and white and samples it at the chosen FPS. - Hosting: the frames are saved as JSON in
data/, and Apps Script downloads them fromraw.githubusercontent.com. - Playback: the sidebar (
player.html) works out the current frame from the elapsed time, so it stays in sync even when a frame takes longer to draw.- The first frame is painted all at once with
setBackgrounds(renderFrame). - After that, only the cells that changed since the previous frame are repainted, using
getRangeList(...).setBackground(renderChanges). That's what makes it fast enough to look like a video.
- The first frame is painted all at once with
Each JSON file is an array of frames. Each frame is an array of H strings with W characters, where "1" is black and "0" is white:
[
["1111111111", "1100000011", "..."],
["..."]
]| File | Resolution | FPS | Size |
|---|---|---|---|
data/video_40_30_10fps.json |
40 × 30 | 10 | ~2.9 MB |
data/video_80_60_10fps.json |
80 × 60 | 10 | ~11 MB |
data/video_80_60_15fps.json |
80 × 60 | 15 | ~16.6 MB |
Warning
VIDEO_URL, FPS and the rows/cols in setupScreen must all match the chosen JSON. Right now VIDEO_URL points to the 80×60 file, but setupScreen only sets up a 40×30 screen.
VIDEO_URL points to the main branch, so a new or renamed JSON only works after you git push it.
- Open
notebooks/convertVideoIntoJson.ipynbin Google Colab. - Set
WIDTH,HEIGHT,TARGET_FPSandTHRESHOLDin the config cell. - Run all cells and upload your
.mp4when asked. - Rename the downloaded
frames.jsontovideo_<W>_<H>_<FPS>fps.json, put it indata/and push.
Any black and white video works. Bad Apple!! just happens to be the classic one.
bad-apple-google-sheets/
├── apps-script/
│ ├── Código.gs # Menu, screen setup and rendering
│ └── player.html # Sidebar with the Play button and the animation loop
├── notebooks/
│ └── convertVideoIntoJson.ipynb # Converts the .mp4 into JSON frames (Colab)
├── data/ # Pre-generated frames
├── assets/
│ └── badApple.mp3 # Soundtrack
└── README.md
- Bad Apple!!: original song from Touhou Project by ZUN, arranged by Alstroemeria Records feat. nomico. The shadow art music video was made by Anira.
- Inspired by every other "Bad Apple on X" project out there.
If this made you smile, consider leaving a ⭐
