All tests run on an 8-year-old MacBook Air. All results from shipping 7 Mac apps as a solo developer. No sponsored opinion.
iPhones shoot HEIC. Most apps want JPEG. HiyokoAutoSync converts automatically during sync. The solution is already on every Mac: sips.
What sips is
sips (Scriptable Image Processing System) is a macOS built-in command-line tool for image conversion. It ships with every Mac, handles HEIC natively, and requires zero bundling.
sips -s format jpeg photo.heic --out photo.jpg
One command. No dependencies. Works on Intel and Apple Silicon.
Calling sips from Rust
use std::process::Command;
pub async fn heic_to_jpeg(
input: &Path,
output: &Path,
) -> Result<(), AppError> {
let status = Command::new("sips")
.args([
"-s", "format", "jpeg",
"-s", "formatOptions", "85", // quality 0-100
input.to_str().unwrap(),
"--out",
output.to_str().unwrap(),
])
.status()?;
if !status.success() {
return Err(AppError::Conversion(
format!("sips failed for {:?}", input)
));
}
Ok(())
}
formatOptions sets JPEG quality. 85 is a good default — smaller than 100, visually indistinguishable for most photos.
Async conversion with Tauri
Command::new is blocking. Wrap in spawn_blocking for use in async Tauri commands:
let input = input_path.clone();
let output = output_path.clone();
tokio::task::spawn_blocking(move || {
heic_to_jpeg_sync(&input, &output)
}).await??;
Batch conversion
For parallel HEIC conversion during sync, the same semaphore pattern applies:
let semaphore = Arc::new(Semaphore::new(4)); // 4 concurrent conversions
for heic_file in heic_files {
let sem = Arc::clone(&semaphore);
tokio::spawn(async move {
let _permit = sem.acquire().await.unwrap();
heic_to_jpeg(&heic_file, &jpeg_output(&heic_file)).await
});
}
4 concurrent sips processes is comfortable on most Macs. More than 6 and you'll see diminishing returns.
The alternative: pure Rust
There are Rust crates for HEIC decoding. They require bundling native libraries, add significant binary size, and don't handle all HEIC variants as well as Apple's own implementation.
For a macOS-only app, sips is the correct choice. Use the platform.
TL;DR: For HEIC→JPEG on macOS, skip the Rust crates and use the built-in sips command. Call it via Command::new, wrap in spawn_blocking for async Tauri commands, and use a Semaphore with 4 concurrent processes for batch conversion. Zero dependencies, Apple-quality output.
If this was useful, a ❤️ helps more than you'd think — thanks!
HiyokoAutoSync | X → @hiyoyok
Top comments (0)