High-performance desktop media downloader & transcoder
Still Under Development
Built with GPUI-kit (Rust native UI framework), yt-dlp (media extraction engine), and FFmpeg (merging, remuxing, conversion).
A Rust + GPUI-kit rewrite of the original Video-Downloader (Python + Flet). The core download, conversion, history, and settings workflows are implemented natively, with the remaining parity work tracked in plan.md.
| Mode | Description |
|---|---|
| Video + Audio | Best video stream merged with audio via FFmpeg |
| Audio Only | Extract to MP3, M4A, AAC, FLAC, Opus, or WAV |
| Video Only | Silent video track, no audio stream |
| Preset | Max Height |
|---|---|
| Best | Unlimited (highest available) |
| 4K | 2160p |
| 1080p | 1080p |
| 720p | 720p |
| 480p | 480p |
| 360p | 360p |
| Container | Notes |
|---|---|
| MKV | Best multi-audio and subtitle support — auto-selected when needed |
| MP4 | Universal compatibility |
| WebM | Open web format |
Auto-switch: MKV is automatically selected whenever multiple audio tracks or subtitles are enabled.
--audio-multistreams is automatically enabled for multi-track downloadsHeader-Name: value, one per line)--extractor-args youtube:player_client=all to expose all dubbed language tracks| Tool | Version | Purpose |
|---|---|---|
| yt-dlp | Latest recommended | URL analysis, format extraction, download |
| FFmpeg | 6.0+ recommended | Stream merge, remux, subtitle embed, conversion |
Both must be on your PATH.
# Windows (winget)
winget install yt-dlp.yt-dlp
winget install Gyan.FFmpeg
# macOS (Homebrew)
brew install yt-dlp ffmpeg
# Debian / Ubuntu
sudo apt install yt-dlp ffmpeg
# Arch Linux
sudo pacman -S yt-dlp ffmpeg
.github/workflows/ for exact packages)git clone https://github.com/fazi-gondal/downloader.git
cd downloader
# Verify runtime dependencies
yt-dlp --version
ffmpeg -version
# Run in development mode
cargo run
Release build:
cargo build --release
# Output: target/release/downloader (Linux / macOS)
# target/release/downloader.exe (Windows)
User pastes URL → Dashboard analyzes via yt-dlp --dump-json
↓
MediaInfo parsed (formats, audio tracks, subtitles, thumbnail)
↓
Configure view: mode / resolution / container / audio tracks / subs
↓
FormatBuilder compiles DownloadOptions → yt-dlp CLI args
↓
DownloadManager spawns yt-dlp in a worker thread, streams progress
↓
FFmpeg post-processes (merge / embed subs / embed thumbnail)
↓
Final file saved to the download directory
Merges the best separate video and audio streams using FFmpeg. Container is determined by the selected output format (MP4, MKV, WebM).
Extracts and re-encodes (or losslessly copies) audio to the selected format.
Downloads video stream only — no audio. Useful for silent footage or when audio is handled separately.
FormatBuilder (src/services/format_builder.rs) translates UI options into a yt-dlp format selector string:
| Scenario | Format String |
|---|---|
| Default best | bv*+ba/b |
| 1080p best | bv*[height<=?1080]+ba/b[height<=?1080]/b |
| 1080p 30fps cap | bv*[height<=?1080][fps<=?30]+ba/b |
| Specific format IDs | 137+251 |
| Single audio track (by ID) | bv*[height<=?1080]+251/bv*[height<=?1080]+ba/b |
| Multiple audio tracks (by ID) | bv*+251+140/bv*+mergeall[vcodec=none]/bv*+ba/b |
| All audio tracks mode | bv*+mergeall[vcodec=none]/b |
The mergeall[vcodec=none] fallback ensures multi-audio downloads never silently fail if a format ID becomes stale between analysis and download (common with YouTube).
yt-dlp returns all audio-only formats in the format list. The app groups them by language (or track name/format ID as fallback), keeping the best bitrate per unique track.
Audio tracks are keyed by their format ID (e.g. 251, 140, 233-dub-en). Format IDs are used directly in the yt-dlp format string, which is more reliable than language attribute filters that often return nothing.
--audio-multistreams (added automatically)player_client=all is used (always enabled in this app)The app uses yt-dlp's embed flow: --write-auto-subs --embed-subs --sub-langs LANGS.
Intermediate subtitle files are not intentionally retained.
yt-dlp's FFmpegEmbedSubtitlePP post-processor:
.vtt/.srt files| Mode | yt-dlp flags |
|---|---|
| None | (omitted) |
| All Available | --write-auto-subs --embed-subs --sub-langs all |
| Custom (e.g. en + fr) | --write-auto-subs --embed-subs --sub-langs en,fr |
Note: MKV is recommended for subtitle embedding (auto-switched when subtitles are enabled).
Runs FFmpeg locally on existing media files — no internet required.
| Profile | Equivalent FFmpeg command |
|---|---|
| Lossless Remux | ffmpeg -i input -c copy output.mkv |
| H.264 + AAC → MP4 | ffmpeg -i input -c:v libx264 -c:a aac output.mp4 |
| H.265 + AAC → MKV | ffmpeg -i input -c:v libx265 -c:a aac output.mkv |
| Extract MP3 | ffmpeg -i input -c:a libmp3lame -q:a 2 output.mp3 |
| Extract FLAC | ffmpeg -i input -c:a flac output.flac |
DownloadManager (src/services/download_manager.rs)yt-dlp as a child process[download] lines are parsed for %, speed, ETAhistory.json alongside settings.json| Setting | Default | Description |
|---|---|---|
| Download Directory | ~/Downloads/VideoDownloader |
Where files are saved |
| Max Concurrent Downloads | 8 |
Parallel downloads (1-16) |
| Concurrent Fragments | 1 |
Fragment threads per download |
| Proxy | (empty) | http://host:port or socks5://host:port |
| Cookies from Browser | (empty) | chrome, firefox, edge, brave, etc. |
| Rate Limit (KB/s) | 0 |
0 = unlimited |
| Write Subtitles | false |
Default subtitle download on/off |
| Multi Audio | false |
Default multi-audio track mode |
| Embed Thumbnail | true |
Embed cover art into output file |
| Embed Metadata | true |
Embed title, tags, description |
| Custom HTTP Headers | (empty) | One Header-Name: value per line; invalid lines are ignored |
| Theme | System |
System / Dark / Light preference |
Settings are persisted as JSON in the OS application config directory:
| Platform | Path |
|---|---|
| Windows | %LOCALAPPDATA%\VideoDownloader\settings.json |
| macOS | ~/Library/Application Support/VideoDownloader/settings.json |
| Linux | ~/.config/VideoDownloader/settings.json |
History is stored alongside as history.json.
Example settings.json:
{
"download_dir": "C:\\Users\\User\\Downloads\\VideoDownloader",
"max_concurrent": 8,
"concurrent_fragments": 4,
"proxy": "",
"custom_headers": "",
"cookies_browser": "",
"rate_limit_kbps": 0,
"write_subtitles": false,
"subtitle_langs": [],
"multi_audio": false,
"embed_thumbnail": true,
"embed_metadata": true,
"prefer_vp9_video": true,
"theme_mode": "system"
}
src/
├── main.rs # App bootstrap, window options
├── config/
│ └── mod.rs # AppSettings - load/save JSON
├── core/
│ ├── app_state.rs # Shared AppState entity (media, options, services)
│ ├── errors.rs # AppError types
│ ├── events.rs # Cross-component events
│ └── navigation.rs # AppRoute enum (Dashboard, Configure, ...)
├── models/
│ ├── media.rs # MediaInfo, FormatInfo, AudioTrack, SubtitleTrack,
│ │ # DownloadOptions, DownloadMode, SubtitleMode,
│ │ # AudioTracksMode, ResolutionPreset, Container
│ ├── download.rs # DownloadTask, DownloadStatus
│ ├── conversion.rs # ConversionTask, ConversionProfile
│ └── history.rs # HistoryEntry
├── services/
│ ├── ytdlp.rs # CLI spawn -> JSON parse -> MediaInfo / PlaylistInfo
│ ├── format_builder.rs # DownloadOptions -> yt-dlp format string + extra args
│ ├── download_manager.rs # Thread pool, process spawn, progress parse, cancel
│ ├── conversion.rs # FFmpeg conversion queue
│ ├── ffmpeg.rs # FFmpeg helpers (remux, multi-audio merge)
│ ├── history.rs # Persistent history read/write
│ └── network_options.rs # Proxy, cookies, rate limit, fragments -> CLI args
└── ui/
├── app_shell.rs # Root layout: sidebar + routed view
├── theme.rs # Theme tokens (dark/light/system)
├── prelude.rs # Common re-exports for views
├── components/
│ ├── media_card.rs # Single / playlist media info card
│ ├── empty_state.rs # Empty placeholder component
│ └── status_pill.rs # Download status badge
└── views/
├── dashboard.rs # URL input, analyze, media result cards
├── config_view.rs # Mode / format / audio / subtitle config + download button
├── downloads.rs # Live download queue list
├── converter.rs # Local file converter UI
├── history.rs # Download history list
├── settings.rs # App settings form
└── about.rs # Dependency info + auto runtime check
Entity<AppState> owned by AppShell, passed by reference to every view.FormatBuilder::compile is a pure function (no I/O, no side effects). Fully unit-testable./fallback chain so downloads never silently fail.cargo run # debug build with live logging
cargo test # run all unit tests
cargo clippy --all-targets # lint
cargo fmt --all # format
| Crate | Purpose |
|---|---|
gpui-kit 0.7 |
Native UI framework (GPUI + component library) |
serde + serde_json |
Settings and history serialization |
dirs 6 |
OS-specific config/data directories |
thiserror 2 |
Ergonomic error types |
uuid 1 |
Unique download task IDs |
chrono 0.4 |
Timestamps for history entries |
log + env_logger |
Structured logging |
| Workflow | Trigger | Steps |
|---|---|---|
ci.yml |
Push / PR | fmt check -> clippy -> test on Linux, Windows, macOS |
build.yml |
Published release | Release binaries for all three platforms as artifacts |
git tag v0.2.0
git push origin v0.2.0
# Publish a GitHub Release from the tag
# CI builds and attaches platform binaries automatically
Release profile (Cargo.toml):
[profile.release]
lto = true # Link-time optimisation
codegen-units = 1 # Best optimisation
strip = true # Strip debug symbols
MIT — see LICENSE.
Crafted as a Rust + GPUI-kit port of the original Video Downloader by Fazi Gondal.