The Swiss-army knife for embeddable media on Android.
Convert videos and GIFs to platform-ready animated WebP, compress images to WebP/JPEG/PNG/AVIF, inspect Open Graph and social embed metadata, and upload files to anonymous hosts — all from one app with a clean Material 3 dark-mode UI.
Every push to main triggers a CI build that produces split-ABI APKs:
| ABI | Best for |
|---|---|
arm64-v8a |
Modern phones (Pixel, Samsung Galaxy S/A series, OnePlus) |
armeabi-v7a |
Older 32-bit ARM devices |
x86_64 |
Emulators, Chromebooks |
universal |
Any device (larger download) |
- Adaptive quality algorithm — starts at target quality, steps down until file fits the size limit
- Platform presets with tuned parameters:
- Discord — 10 MB / 720p / 12 fps
- Telegram — 256 KB / 512px / 30 fps (sticker-ready)
- Slack — 5 MB / 640px / 12 fps
- Custom — full manual control
- Animated WebP input — frames are decoded in-app by splitting the RIFF container, because the bundled FFmpeg cannot demux the format
- Trim to a range, or stitch several segments together
- Lanczos downscaling, optional text sharpening (
unsharpfilter), denoise, dithering - Exact output dimensions with centre-crop, or scale to a maximum
- 3-second preview clip before committing to a full encode
- Real-time progress with quality-step feedback
- Share/Save output directly from the result card
- Accepts shared media via Android intent filters (video, GIF, animated WebP)
- Fetches and parses Open Graph, Twitter Card, and general
<meta>tags - Discord/Slack-style live embed preview card
- Expandable tag sections with one-tap copy
- Extracts favicon, canonical URL, charset, theme-color, robots, generator
- Powered by Jsoup for robust HTML parsing
- Upload to 0x0.st (512 MB) or catbox.moe (200 MB)
- EXIF stripping — removes GPS, camera info, artist, serial numbers before upload
- Determinate progress bar with chunked streaming (no full-file memory buffering)
- Copy URL or share result instantly
- Output formats: WebP, JPEG, PNG, AVIF
- AVIF encoding via avif-coder (libaom, speed 6 — Squoosh.app equivalent)
- Quality slider (1–100) for lossy formats
- Lossless mode for WebP and AVIF
- Max dimension scaling with efficient
inSampleSizesubsampling - Before/after size comparison with savings percentage
- Image preview of compressed output
app.embeddy
├── conversion/ # FFmpeg-kit animated WebP pipeline
│ ├── ConversionEngine # Adaptive quality loop with callbackFlow
│ ├── ConversionConfig # Mutable settings from presets
│ └── Preset # Discord, Telegram, Slack, Custom
├── inspect/
│ └── MetadataEngine # Jsoup-based OG/Twitter/meta parser
├── squoosh/
│ └── SquooshEngine # Bitmap.compress + HeifCoder AVIF
├── upload/
│ └── UploadEngine # Multipart POST with ProgressOutputStream
├── viewmodel/ # AndroidViewModel per tab
├── navigation/
│ ├── EmbeddyTab # Tab enum with Material icons
│ └── AppScaffold # Scaffold + NavigationBar + AnimatedContent
└── ui/
├── screens/ # ConvertScreen, InspectScreen, UploadScreen, SquooshScreen
├── components/ # MediaPicker, SettingsPanel, ConversionProgress, OutputPreview
└── theme/ # Material 3 dark theme
| Component | Library |
|---|---|
| UI | Jetpack Compose + Material 3 |
| Video → WebP | FFmpeg-kit (libwebp_anim) |
| AVIF encoding | avif-coder (libaom via JNI) |
| Image loading | Coil (compose, gif, video) |
| HTML parsing | Jsoup |
| EXIF handling | AndroidX ExifInterface |
| Navigation | Tab-based with AnimatedContent |
| Async | Kotlin Coroutines + StateFlow |
- JDK 17+
- Android SDK with
compileSdk 35
./gradlew assembleDebugA helper script handles the aapt2 override needed for on-device builds:
bash build-and-install.sh # build debug
bash build-and-install.sh --install # build + install via adb
bash build-and-install.sh --release # build releaseThe build produces per-ABI APKs (arm64-v8a, armeabi-v7a, x86_64) plus a universal APK. Configured in app/build.gradle.kts:
splits {
abi {
isEnable = true
include("arm64-v8a", "armeabi-v7a", "x86_64")
isUniversalApk = true
}
}Three workflows run on push to main:
Tests (test.yml) — gates everything else. Runs the Android unit suite, the
Worker's SSRF tests plus typecheck, and the site's unit tests plus astro check.
Also runs on pull requests.
Deploy Site (pages.yml) — runs the site tests, then builds and deploys to
GitHub Pages. A failing suite blocks the deploy.
Build & Release (release.yml):
- Run the Android unit tests — a failure blocks the release
- Pin the resolved version into
version.propertiesand commit it, with a skip-CI marker so the push does not re-trigger the workflow - Build release APKs for the three configured ABIs plus universal
- Tag the version-pinned commit and publish a GitHub Release with grouped
release notes, a download table with sizes and SHA-256 prefixes, and
SHA256SUMS.txt
Version is version.properties (major/minor) + github.run_number (patch).
Step 2 matters for anyone rebuilding from a tag — without it the version exists
only in the CI environment and a tag build produces a stale version number.
./gradlew :app:testDebugUnitTest # Android
cd worker && bun test # Worker (SSRF)
cd site && bun test src # Site (WebP parsing, upload retry)Store metadata lives in fastlane/metadata/android/en-US/. A draft build recipe
and a submission guide — including the dependency-provenance questions a
reviewer will raise — are in fdroid/.
Build a single all-ABI APK the way F-Droid does:
./gradlew assembleRelease -PsingleApkGPL-3.0-only — see LICENSE file for details.