惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏
腾讯CDC
J
Java Code Geeks
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
美团技术团队
云风的 BLOG
云风的 BLOG
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
雷峰网
雷峰网
B
Blog RSS Feed
博客园_首页
量子位
F
Fortinet All Blogs
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
HEIC to JPEG on macOS in Rust — Using sips Without Reinve...
hiyoyo · 2026-06-15 · via DEV Community
Cover image for HEIC to JPEG on macOS in Rust — Using sips Without Reinventing the Wheel

hiyoyo

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