Add automatic FFmpeg download for standalone Windows experience
FFmpeg is now automatically downloaded on first run if not found, eliminating the need for vcpkg or manual FFmpeg installation on Windows. Changes: - Add FFmpeg availability check and auto-download on startup - Use ffprobe_path() for auto-downloaded binaries in video decoder - Track FFmpeg availability in app state with graceful degradation - Show warning banner when video conversion is unavailable - Skip video files when adding to queue if FFmpeg unavailable - Simplify build instructions in README (remove vcpkg recommendation)
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -536,7 +536,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "avif-maker"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"directories",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "avif-maker"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
description = "Convert images and videos to AVIF format with a native GUI"
|
||||
license = "MIT"
|
||||
@@ -19,6 +19,7 @@ image = "0.25"
|
||||
gif = "0.14"
|
||||
|
||||
# Video decoding (runs ffmpeg as subprocess - no FFI issues)
|
||||
# Default features include "download_ffmpeg" for auto-downloading FFmpeg on first run
|
||||
ffmpeg-sidecar = "2"
|
||||
|
||||
# Async & utilities
|
||||
|
||||
30
README.md
30
README.md
@@ -20,45 +20,51 @@ A native GUI application to convert images and videos to AVIF format.
|
||||
| PNG/APNG | `image` crate | Full | Static images |
|
||||
| JPEG | `image` crate | N/A | No alpha |
|
||||
| WebP | `image` crate | Full | Static only |
|
||||
| MP4/MOV/WebM | `ffmpeg-sidecar` | Codec-dependent | Requires ffmpeg in PATH |
|
||||
| MP4/MOV/WebM | `ffmpeg-sidecar` | Codec-dependent | FFmpeg auto-downloaded on first run |
|
||||
|
||||
## Build Requirements
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
brew install libavif ffmpeg
|
||||
# Install build dependencies (libavif is built from source)
|
||||
brew install cmake nasm
|
||||
|
||||
# Build and run
|
||||
cargo build --release
|
||||
./target/release/avif-maker
|
||||
```
|
||||
|
||||
FFmpeg is automatically downloaded on first run if not found in PATH.
|
||||
|
||||
### Linux (Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
sudo apt install libavif-dev ffmpeg
|
||||
# Install build dependencies (libavif is built from source)
|
||||
sudo apt install build-essential cmake nasm
|
||||
|
||||
# Build and run
|
||||
cargo build --release
|
||||
./target/release/avif-maker
|
||||
```
|
||||
|
||||
FFmpeg is automatically downloaded on first run if not found in PATH.
|
||||
|
||||
### Windows
|
||||
|
||||
Use vcpkg or pre-built binaries for libavif and FFmpeg.
|
||||
The application automatically downloads FFmpeg on first run if not found. No manual setup required for end users.
|
||||
|
||||
To build from source, install:
|
||||
- [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) (with C++ workload)
|
||||
- [CMake](https://cmake.org/download/)
|
||||
- [NASM](https://www.nasm.us/) (add to PATH)
|
||||
|
||||
```powershell
|
||||
# With vcpkg
|
||||
vcpkg install libavif ffmpeg
|
||||
|
||||
# Set environment variables
|
||||
$env:VCPKG_ROOT = "C:\path\to\vcpkg"
|
||||
|
||||
# Build
|
||||
cargo build --release
|
||||
|
||||
# Run
|
||||
.\target\release\avif-maker.exe
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
48
src/app.rs
48
src/app.rs
@@ -118,10 +118,12 @@ pub struct AvifMakerApp {
|
||||
pub show_console: bool,
|
||||
/// Start time for relative timestamps
|
||||
start_time: std::time::Instant,
|
||||
/// Whether FFmpeg is available for video conversion
|
||||
ffmpeg_available: bool,
|
||||
}
|
||||
|
||||
impl AvifMakerApp {
|
||||
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
pub fn new(cc: &eframe::CreationContext<'_>, ffmpeg_available: bool) -> Self {
|
||||
// Configure larger fonts for accessibility
|
||||
let mut style = (*cc.egui_ctx.style()).clone();
|
||||
|
||||
@@ -154,9 +156,13 @@ impl AvifMakerApp {
|
||||
console_log: Vec::new(),
|
||||
show_console: false,
|
||||
start_time: std::time::Instant::now(),
|
||||
ffmpeg_available,
|
||||
};
|
||||
|
||||
app.log("AVIF Maker started");
|
||||
if !ffmpeg_available {
|
||||
app.log("WARNING: FFmpeg not available - video conversion disabled");
|
||||
}
|
||||
app
|
||||
}
|
||||
|
||||
@@ -205,8 +211,16 @@ impl AvifMakerApp {
|
||||
}
|
||||
|
||||
pub fn add_files(&mut self, paths: Vec<PathBuf>) {
|
||||
let mut skipped_videos = 0;
|
||||
|
||||
for path in paths {
|
||||
if Self::is_supported_format(&path) {
|
||||
// Skip video files if FFmpeg is not available
|
||||
if Self::is_video_format(&path) && !self.ffmpeg_available {
|
||||
skipped_videos += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let job = ConversionJob {
|
||||
id: self.next_job_id,
|
||||
input_path: path,
|
||||
@@ -220,6 +234,25 @@ impl AvifMakerApp {
|
||||
self.next_job_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if skipped_videos > 0 {
|
||||
self.log(format!(
|
||||
"Skipped {} video file(s) - FFmpeg not available. Restart the app with internet to download FFmpeg.",
|
||||
skipped_videos
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_video_format(path: &PathBuf) -> bool {
|
||||
let ext = path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| e.to_lowercase());
|
||||
|
||||
matches!(
|
||||
ext.as_deref(),
|
||||
Some("mp4") | Some("mov") | Some("webm") | Some("mkv") | Some("avi") | Some("m4v")
|
||||
)
|
||||
}
|
||||
|
||||
fn is_supported_format(path: &PathBuf) -> bool {
|
||||
@@ -564,6 +597,19 @@ impl eframe::App for AvifMakerApp {
|
||||
ui.label(format!("{} files in queue", self.jobs.len()));
|
||||
});
|
||||
});
|
||||
|
||||
// Show warning if FFmpeg is not available
|
||||
if !self.ffmpeg_available {
|
||||
ui.add_space(4.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
egui::RichText::new("Video conversion unavailable - FFmpeg could not be downloaded. Restart with internet connection.")
|
||||
.color(egui::Color32::from_rgb(255, 180, 100))
|
||||
.small()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
ui.add_space(4.0);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::{DecodeError, DecodedFrames, DecoderTrait, Frame, ProgressCallback};
|
||||
use ffmpeg_sidecar::command::FfmpegCommand;
|
||||
use ffmpeg_sidecar::event::{FfmpegEvent, OutputVideoFrame};
|
||||
use ffmpeg_sidecar::ffprobe::ffprobe_path;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct VideoDecoder {
|
||||
@@ -23,7 +24,8 @@ impl VideoDecoder {
|
||||
fn get_video_info(path: &Path) -> Result<(f64, Option<f64>, Option<usize>), DecodeError> {
|
||||
use std::process::Command;
|
||||
|
||||
let output = Command::new("ffprobe")
|
||||
// Use ffprobe_path() to find the binary (works with auto-downloaded FFmpeg)
|
||||
let output = Command::new(ffprobe_path())
|
||||
.args([
|
||||
"-v", "quiet",
|
||||
"-select_streams", "v:0",
|
||||
|
||||
35
src/main.rs
35
src/main.rs
@@ -7,11 +7,44 @@ mod ui;
|
||||
|
||||
use app::AvifMakerApp;
|
||||
use eframe::egui;
|
||||
use ffmpeg_sidecar::command::ffmpeg_is_installed;
|
||||
use ffmpeg_sidecar::download::auto_download;
|
||||
|
||||
/// Ensures FFmpeg is available, downloading it if necessary.
|
||||
/// Returns true if FFmpeg is available, false if download failed.
|
||||
fn ensure_ffmpeg() -> bool {
|
||||
if ffmpeg_is_installed() {
|
||||
tracing::info!("FFmpeg found in PATH or alongside executable");
|
||||
return true;
|
||||
}
|
||||
|
||||
tracing::info!("FFmpeg not found, attempting to download...");
|
||||
|
||||
// On Windows without a console, we can't show progress easily before the GUI starts.
|
||||
// The download is ~100MB and happens once on first run.
|
||||
match auto_download() {
|
||||
Ok(()) => {
|
||||
tracing::info!("FFmpeg downloaded successfully");
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to download FFmpeg: {}. Video features will be unavailable.", e);
|
||||
// Don't fail completely - the app can still work with images
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
// Initialize logging
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Ensure FFmpeg is available (downloads automatically on first run if needed)
|
||||
let ffmpeg_available = ensure_ffmpeg();
|
||||
if !ffmpeg_available {
|
||||
tracing::warn!("Starting without FFmpeg - video conversion will not be available");
|
||||
}
|
||||
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([1000.0, 900.0])
|
||||
@@ -23,6 +56,6 @@ fn main() -> eframe::Result<()> {
|
||||
eframe::run_native(
|
||||
"AVIF Maker",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(AvifMakerApp::new(cc)))),
|
||||
Box::new(move |cc| Ok(Box::new(AvifMakerApp::new(cc, ffmpeg_available)))),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user