Split any animated GIF into perfectly sized tiles for your Elgato Stream Deck — including Stream Deck MK.2, Stream Deck XL, Stream Deck Mini, Stream Deck +, and Stream Deck Neo. Create seamless animated backgrounds and wallpapers that span your entire device, with gap-aware cutoff mode, custom crop, loop trimming, and one-click .streamDeckProfile export. Runs 100% in your browser — no uploads, no servers, no account needed.
- 🖱️ Drag & drop — Upload any GIF and see an instant cropped preview
- 🔍 GIPHY search — Browse trending GIFs or search GIPHY directly from the app — no need to leave the page
- 🎚️ 5 device presets — Stream Deck MK.2, XL, Mini, +, and Neo
- ✂️ Cutoff mode — Accounts for the physical gap between buttons so animations appear seamless
- 🎯 Custom crop position — Drag the crop region to choose exactly which part of the GIF to keep, instead of always center-cropping
- 🔁 Custom loop trim — Trim the animation loop length with a timeline scrubber to keep only the portion you want
- 📐 Custom grid size — Use a smaller sub-area of your device (e.g. 6×3 on an 8×4 XL) and position it anywhere on the button layout
- 🎨 High-quality encoding — Two-pass palette generation with Floyd-Steinberg dithering
- 📦 ZIP download — Get all tiles in a numbered, ready-to-assign archive
- 🗂️
.streamDeckProfileexport — One-click installable profile with all tiles pre-assigned - 📡 Fully offline — FFmpeg is cached after first load; works without internet on repeat visits
- 🔒 Privacy-first — Your files never leave your browser
| Device | Grid | Tile Size | Button Gap |
|---|---|---|---|
| Stream Deck MK.2 | 5 × 3 | 72 × 72 px | 16 px |
| Stream Deck XL | 8 × 4 | 144 × 144 px | 40 px |
| Stream Deck Mini | 3 × 2 | 72 × 72 px | 16 px |
| Stream Deck + | 4 × 2 | 72 × 72 px | 16 px |
| Stream Deck Neo | 4 × 2 | 72 × 72 px | 16 px |
- Node.js 18+ (LTS recommended)
- npm, yarn, or pnpm
git clone https://github.com/SaschaWebDev/animated-stream-deck-background-gif-converter.git
cd animated-stream-deck-background-gif-converter
npm installnpm run devOpen http://localhost:5173 in your browser.
npm test # run all tests once
npm run test:watch # run tests in watch mode
npm run test:coverage # run tests with coverage reportnpm run build
npm run preview # preview the production build locally| Layer | Technology |
|---|---|
| ⚛️ Framework | React 19 with React Compiler |
| 🟦 Language | TypeScript 5.9 |
| ⚡ Bundler | Vite 7 |
| 🎬 Video Processing | FFmpeg.wasm 0.12 |
| 🗜️ Archive Generation | JSZip |
| 🔍 GIF Search | GIPHY API |
| 🧹 Linting | ESLint 9 with TypeScript & React plugins |
- 📤 Upload — Drop a GIF, click to browse, or search GIPHY for the perfect animation
- ⚙️ Configure — Select your device model. Optionally enable Custom Grid to target a smaller sub-area (e.g. 6×3 on an XL) and use the arrow controls to position it on the device
- ✂️ Crop — The GIF is automatically cropped and scaled to match the target grid area (including optional gap compensation). Enable Custom Crop to drag the crop region and choose which area to keep
- 🔁 Trim — Enable Custom Loop to shorten the animation loop. A filmstrip timeline shows snapshot frames at evenly-spaced intervals; drag the left/right handles to select the portion you want. Trimmed-out regions are shown with a striped overlay
- 🔪 Split — FFmpeg slices the cropped GIF into individual tile animations using two-pass encoding for optimal quality
- 💾 Export — Download as a ZIP of numbered tiles or as a ready-to-install
.streamDeckProfile. With Custom Grid, the profile places tiles at the correct offset positions and the mockup shows the full device with unused slots blacked out
src/
├── main.tsx # Entry point
├── App.tsx # Root application component
├── index.css # Global styles & CSS variables
├── App.css # App-level styles
├── components/
│ ├── CropPreview.tsx # Cropped GIF preview with split controls & custom crop editor
│ ├── DeviceConfig.tsx # Device preset, cutoff mode & custom crop selector
│ ├── FileDropZone.tsx # Drag-and-drop file upload area
│ ├── GifSourceTabs.tsx # Upload / GIPHY toggle switcher
│ ├── GiphyPicker.tsx # GIPHY search, grid & GIF selection
│ ├── HeroSection.tsx # Landing hero banner
│ ├── ResultsPanel.tsx # Tile grid results & download buttons
│ └── UserManual.tsx # Inline usage instructions
├── constants/
│ └── presets.ts # Device preset configurations
├── hooks/
│ ├── useAutoScroll.ts # Auto-scroll to results
│ ├── useDeviceConfig.ts # Device preset state management
│ ├── useDownload.ts # ZIP & profile download logic
│ ├── useFileUpload.ts # File input & drag-and-drop handling
│ ├── useGifProcessor.ts # Crop & split orchestration
│ ├── useGifSync.ts # Synchronized GIF playback
│ └── useGiphySearch.ts # GIPHY search state & debouncing
├── services/
│ ├── ffmpeg.ts # FFmpeg.wasm hook (crop, split, progress)
│ ├── giphy.ts # GIPHY API service & GIF-to-File fetcher
│ └── streamDeckProfile.ts # .streamDeckProfile ZIP generator
├── types/
│ └── index.ts # Shared TypeScript interfaces
└── utils/
├── crop.ts # Crop filter, trim args & coordinate calculations
├── device.ts # Device dimension calculations
├── filename.ts # Download filename generation
├── format.ts # File size formatting
├── gifDuration.ts # GIF duration parser with outlier detection
└── progress.ts # Progress label formatting
Contributions are welcome! Feel free to open an issue or submit a pull request.
- 🍴 Fork the repository
- 🌿 Create your feature branch (
git checkout -b feature/amazing-feature) - 💾 Commit your changes (
git commit -m 'Add amazing feature') - 📤 Push to the branch (
git push origin feature/amazing-feature) - 🎉 Open a Pull Request
Distributed under the MIT License. See LICENSE for details.
Made with ❤️ by Sascha Majewsky
