mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2025-12-11 20:15:30 +01:00
[ENG-603] Support HEIC images (#834)
* working HEIC thumbnails * better error handling * better error handling and some cleanup * fix type of maximum size, and clippy * better extension support, WIP quick preview, better image resolution * remove unnecessary x86_64 macos rustflags * add correct rustflags to setup script * add fedora libheif deps * debian libheif deps * arch libheif too * add sd-heif as a dep and feature gate it * enable aforementioned feature in tauri only * add URI support for heif/heic (quick preview still won't work) * correct feature gating on everything * dedicated sd-heif crate --------- Co-authored-by: brxken128 <77554505+brxken128@users.noreply.github.com>
This commit is contained in:
parent
a42bc63f5d
commit
df70781af3
@ -1,9 +1,3 @@
|
||||
[alias]
|
||||
prisma = "run -p prisma-cli --bin prisma --"
|
||||
prisma-sync = "run -p prisma-cli --bin sync --"
|
||||
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-undefined",
|
||||
"-C", "link-arg=dynamic_lookup",
|
||||
]
|
||||
|
||||
19
.github/scripts/setup-system.sh
vendored
19
.github/scripts/setup-system.sh
vendored
@ -146,6 +146,8 @@ if [ "$SYSNAME" = "Linux" ]; then
|
||||
# FFmpeg dependencies
|
||||
DEBIAN_FFMPEG_DEPS="libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev ffmpeg"
|
||||
|
||||
DEBIAN_LIBHEIF_DEPS="libheif1 libheif-dev"
|
||||
|
||||
# Webkit2gtk requires gstreamer plugins for video playback to work
|
||||
DEBIAN_VIDEO_DEPS="gstreamer1.0-libav gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly"
|
||||
|
||||
@ -156,7 +158,7 @@ if [ "$SYSNAME" = "Linux" ]; then
|
||||
DEBIAN_LIBP2P_DEPS="protobuf-compiler"
|
||||
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install ${SPACEDRIVE_CUSTOM_APT_FLAGS:-} $DEBIAN_TAURI_DEPS $DEBIAN_FFMPEG_DEPS $DEBIAN_BINDGEN_DEPS $DEBIAN_LIBP2P_DEPS $DEBIAN_VIDEO_DEPS
|
||||
sudo apt-get -y install ${SPACEDRIVE_CUSTOM_APT_FLAGS:-} $DEBIAN_TAURI_DEPS $DEBIAN_FFMPEG_DEPS $DEBIAN_LIBHEIF_DEPS $DEBIAN_BINDGEN_DEPS $DEBIAN_LIBP2P_DEPS $DEBIAN_VIDEO_DEPS
|
||||
elif has pacman; then
|
||||
echo "Detected pacman!"
|
||||
echo "Installing dependencies with pacman..."
|
||||
@ -170,13 +172,15 @@ if [ "$SYSNAME" = "Linux" ]; then
|
||||
# FFmpeg dependencies
|
||||
ARCH_FFMPEG_DEPS="ffmpeg"
|
||||
|
||||
ARCH_LIBHEIF_DEPS="libheif"
|
||||
|
||||
# Bindgen dependencies - it's used by a dependency of Spacedrive
|
||||
ARCH_BINDGEN_DEPS="clang"
|
||||
|
||||
# Protobuf compiler - https://github.com/archlinux/svntogit-packages/blob/packages/protobuf/trunk/PKGBUILD provides `libprotoc`
|
||||
ARCH_LIBP2P_DEPS="protobuf"
|
||||
|
||||
sudo pacman -Sy --needed $ARCH_TAURI_DEPS $ARCH_FFMPEG_DEPS $ARCH_BINDGEN_DEPS $ARCH_LIBP2P_DEPS $ARCH_VIDEO_DEPS
|
||||
sudo pacman -Sy --needed $ARCH_TAURI_DEPS $ARCH_FFMPEG_DEPS $ARCH_LIBHEIF_DEPS $ARCH_BINDGEN_DEPS $ARCH_LIBP2P_DEPS $ARCH_VIDEO_DEPS
|
||||
elif has dnf; then
|
||||
echo "Detected dnf!"
|
||||
echo "Installing dependencies with dnf..."
|
||||
@ -198,6 +202,9 @@ if [ "$SYSNAME" = "Linux" ]; then
|
||||
# FFmpeg dependencies
|
||||
FEDORA_FFMPEG_DEPS="ffmpeg ffmpeg-devel"
|
||||
|
||||
# libheif dependencies
|
||||
FEDORA_LIBHEIF_DEPS="libheif libheif-devel"
|
||||
|
||||
# Webkit2gtk requires gstreamer plugins for video playback to work
|
||||
FEDORA_VIDEO_DEPS="gstreamer1-plugin-libav gstreamer1-plugins-base gstreamer1-plugins-good gstreamer1-plugins-good-extras gstreamer1-plugins-bad-free gstreamer1-plugins-bad-free-extras gstreamer1-plugins-ugly-free"
|
||||
|
||||
@ -219,7 +226,7 @@ if [ "$SYSNAME" = "Linux" ]; then
|
||||
'https://docs.fedoraproject.org/en-US/quick-docs/setup_rpmfusion'
|
||||
fi
|
||||
|
||||
sudo dnf install $FEDORA_TAURI_DEPS $FEDORA_BINDGEN_DEPS $FEDORA_LIBP2P_DEPS $FEDORA_VIDEO_DEPS
|
||||
sudo dnf install $FEDORA_TAURI_DEPS $FEDORA_BINDGEN_DEPS $FEDORA_LIBP2P_DEPS $FEDORA_VIDEO_DEPS $FEDORA_LIBHEIF_DEPS
|
||||
sudo dnf group install "C Development Tools and Libraries"
|
||||
else
|
||||
err "Your Linux distro '$(lsb_release -s -d)' is not supported by this script." \
|
||||
@ -386,6 +393,12 @@ elif [ "$SYSNAME" = "Darwin" ]; then
|
||||
PROTOC = "${_frameworks_dir}/bin/protoc"
|
||||
FFMPEG_DIR = "${_frameworks_dir}"
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags = ["-L ${_frameworks_dir}/lib"]
|
||||
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = ["-L ${_frameworks_dir}/lib"]
|
||||
|
||||
$(cat "${_cargo_config}/config.toml")
|
||||
EOF
|
||||
else
|
||||
|
||||
114
Cargo.lock
generated
114
Cargo.lock
generated
@ -2128,6 +2128,17 @@ dependencies = [
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumn"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48016319042fb7c87b78d2993084a831793a897a5cd1a2a67cab9d1eeb4b7d76"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
@ -2200,6 +2211,15 @@ dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.12.1"
|
||||
@ -2317,6 +2337,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "four-cc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d73a076bdabd78c2f9045dba1b90664a655fa8372581c238596e1eb3a5e1b7"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
@ -2625,9 +2651,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.11.4"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06"
|
||||
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
@ -3232,9 +3258,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.4"
|
||||
version = "0.24.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd8e4fb07cf672b1642304e731ef8a6a4c7891d67bb4fd4f5ce58cd6ed86803c"
|
||||
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
@ -3245,7 +3271,7 @@ dependencies = [
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
"scoped_threadpool",
|
||||
"qoi",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
@ -3487,9 +3513,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.2.6"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
|
||||
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
@ -3597,6 +3623,27 @@ version = "0.2.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||
|
||||
[[package]]
|
||||
name = "libheif-rs"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "749fcebc2069f334599304546cfa891c30be08cdf4f358ed984a2c71c5e0031f"
|
||||
dependencies = [
|
||||
"enumn",
|
||||
"four-cc",
|
||||
"libc",
|
||||
"libheif-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libheif-sys"
|
||||
version = "1.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af8b7a4151ae10f6d2e8684f7172c43f09c0258c84190fd9704422588ceec63"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.3"
|
||||
@ -4287,6 +4334,16 @@ dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.4"
|
||||
@ -5454,14 +5511,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.6"
|
||||
version = "0.17.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c"
|
||||
checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.5.4",
|
||||
"miniz_oxide 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5861,6 +5919,15 @@ dependencies = [
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quaint"
|
||||
version = "0.2.0-alpha.13"
|
||||
@ -6682,12 +6749,6 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
|
||||
[[package]]
|
||||
name = "scoped_threadpool"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@ -6751,6 +6812,7 @@ dependencies = [
|
||||
"sd-crypto",
|
||||
"sd-ffmpeg",
|
||||
"sd-file-ext",
|
||||
"sd-heif",
|
||||
"sd-p2p",
|
||||
"sd-sync",
|
||||
"serde",
|
||||
@ -6832,6 +6894,16 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sd-heif"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"libheif-rs",
|
||||
"png",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sd-macos"
|
||||
version = "0.1.0"
|
||||
@ -7328,6 +7400,12 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
@ -8245,9 +8323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.7.3"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65"
|
||||
checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"jpeg-decoder",
|
||||
|
||||
@ -9,13 +9,21 @@ repository.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1.3.0", features = ["api-all", "linux-protocol-headers", "macos-private-api"] }
|
||||
tauri = { version = "1.3.0", features = [
|
||||
"api-all",
|
||||
"linux-protocol-headers",
|
||||
"macos-private-api",
|
||||
] }
|
||||
rspc = { workspace = true, features = ["tauri"] }
|
||||
httpz = { workspace = true, features = [
|
||||
"axum",
|
||||
"tauri",
|
||||
] } # TODO: The `axum` feature should be only enabled on Linux but this currently can't be done: https://github.com/rust-lang/cargo/issues/1197
|
||||
sd-core = { path = "../../../core", features = ["ffmpeg", "location-watcher"] }
|
||||
sd-core = { path = "../../../core", features = [
|
||||
"ffmpeg",
|
||||
"location-watcher",
|
||||
"heif",
|
||||
] }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
window-shadows = "0.2.0"
|
||||
tracing = "0.1.36"
|
||||
|
||||
@ -17,6 +17,7 @@ ffmpeg = [
|
||||
] # This feature controls whether the Spacedrive Core contains functionality which requires FFmpeg.
|
||||
location-watcher = ["dep:notify"]
|
||||
sync-messages = []
|
||||
heif = ["dep:sd-heif"]
|
||||
|
||||
[dependencies]
|
||||
sd-ffmpeg = { path = "../crates/ffmpeg", optional = true }
|
||||
@ -26,6 +27,7 @@ sd-crypto = { path = "../crates/crypto", features = [
|
||||
"serde",
|
||||
"keymanager",
|
||||
] }
|
||||
sd-heif = { path = "../crates/heif", optional = true }
|
||||
sd-file-ext = { path = "../crates/file-ext" }
|
||||
sd-sync = { path = "../crates/sync" }
|
||||
sd-p2p = { path = "../crates/p2p", features = ["specta", "serde"] }
|
||||
@ -62,7 +64,7 @@ sysinfo = "0.28.3"
|
||||
thiserror = "1.0.37"
|
||||
include_dir = { version = "0.7.2", features = ["glob"] }
|
||||
async-trait = "^0.1.57"
|
||||
image = "0.24.4"
|
||||
image = "0.24.6"
|
||||
webp = "0.2.2"
|
||||
tracing = "0.1.36"
|
||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
||||
@ -86,6 +88,7 @@ normpath = { version = "1.1.1", features = ["localization"] }
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
strum_macros = "0.24"
|
||||
|
||||
|
||||
[target.'cfg(windows)'.dependencies.winapi-util]
|
||||
version = "0.1.5"
|
||||
|
||||
|
||||
@ -244,10 +244,8 @@ async fn handle_file(
|
||||
"3gp" => "video/3gpp",
|
||||
// 3GPP2 audio/video container (TODO: audio/3gpp2 if it doesn't contain video)
|
||||
"3g2" => "video/3gpp2",
|
||||
// Quicktime movies
|
||||
// Quicktime movies
|
||||
"mov" => "video/quicktime",
|
||||
// AVIF image
|
||||
"avif" => "image/avif",
|
||||
// Windows OS/2 Bitmap Graphics
|
||||
"bmp" => "image/bmp",
|
||||
// Graphics Interchange Format (GIF)
|
||||
@ -266,6 +264,12 @@ async fn handle_file(
|
||||
"webp" => "image/webp",
|
||||
// PDF document
|
||||
"pdf" => "application/pdf",
|
||||
|
||||
// HEIF/HEIC images
|
||||
"heif" | "heifs" => "image/heif,image/heif-sequence",
|
||||
"heic" | "heics" => "image/heic,image/heic-sequence",
|
||||
// AVIF images
|
||||
"avif" | "avci" | "avcs" => "image/avif",
|
||||
_ => {
|
||||
return Err(HandleCustomUriError::BadRequest(
|
||||
"TODO: This filetype is not supported because of the missing mime type!",
|
||||
|
||||
@ -27,7 +27,11 @@ use image::{self, imageops, DynamicImage, GenericImageView};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::{fs, io, task::block_in_place};
|
||||
use tokio::{
|
||||
fs::{self},
|
||||
io::{self},
|
||||
task::block_in_place,
|
||||
};
|
||||
use tracing::{error, info, trace, warn};
|
||||
use webp::Encoder;
|
||||
|
||||
@ -108,14 +112,32 @@ pub struct ThumbnailerJobStep {
|
||||
kind: ThumbnailerJobStepKind,
|
||||
}
|
||||
|
||||
// TOOD(brxken128): validate avci and avcs
|
||||
#[cfg(all(feature = "heif", any(target_os = "macos", target_os = "linux")))]
|
||||
const HEIF_EXTENSIONS: [&str; 7] = ["heif", "heifs", "heic", "heics", "avif", "avci", "avcs"];
|
||||
|
||||
pub async fn generate_image_thumbnail<P: AsRef<Path>>(
|
||||
file_path: P,
|
||||
output_path: P,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// Webp creation has blocking code
|
||||
let webp = block_in_place(|| -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
// Using `image` crate, open the included .jpg file
|
||||
#[cfg(all(feature = "heif", any(target_os = "macos", target_os = "linux")))]
|
||||
let img = {
|
||||
let ext = file_path.as_ref().extension().unwrap().to_ascii_lowercase();
|
||||
if HEIF_EXTENSIONS
|
||||
.iter()
|
||||
.any(|e| ext == std::ffi::OsStr::new(e))
|
||||
{
|
||||
sd_heif::heif_to_dynamic_image(file_path.as_ref())?
|
||||
} else {
|
||||
image::open(file_path)?
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(all(feature = "heif", any(target_os = "macos", target_os = "linux"))))]
|
||||
let img = image::open(file_path)?;
|
||||
|
||||
let (w, h) = img.dimensions();
|
||||
// Optionally, resize the existing photo and convert back into DynamicImage
|
||||
let img = DynamicImage::ImageRgba8(imageops::resize(
|
||||
@ -160,7 +182,17 @@ pub const fn can_generate_thumbnail_for_video(video_extension: &VideoExtension)
|
||||
|
||||
pub const fn can_generate_thumbnail_for_image(image_extension: &ImageExtension) -> bool {
|
||||
use ImageExtension::*;
|
||||
matches!(image_extension, Jpg | Jpeg | Png | Webp | Gif)
|
||||
|
||||
#[cfg(all(feature = "heif", any(target_os = "macos", target_os = "linux")))]
|
||||
let res = matches!(
|
||||
image_extension,
|
||||
Jpg | Jpeg | Png | Webp | Gif | Heic | Heics | Heif | Heifs | Avif
|
||||
);
|
||||
|
||||
#[cfg(not(all(feature = "heif", any(target_os = "macos", target_os = "linux"))))]
|
||||
let res = matches!(image_extension, Jpg | Jpeg | Png | Webp | Gif);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn finalize_thumbnailer(data: &ThumbnailerJobState, ctx: WorkerContext) -> JobResult {
|
||||
|
||||
@ -76,6 +76,12 @@ extension_category_enum! {
|
||||
Svg = [0x3C, 0x73, 0x76, 0x67],
|
||||
Ico = [0x00, 0x00, 0x01, 0x00],
|
||||
Heic = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63],
|
||||
Heics = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63],
|
||||
Heif = [],
|
||||
Heifs = [],
|
||||
Avif = [],
|
||||
Avci = [],
|
||||
Avcs = [],
|
||||
Raw = [],
|
||||
Akw = [0x41, 0x4B, 0x57, 0x42],
|
||||
Dng = [0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x4E, 0x47, 0x00],
|
||||
|
||||
13
crates/heif/Cargo.toml
Normal file
13
crates/heif/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "sd-heif"
|
||||
version = "0.1.0"
|
||||
authors = ["Jake Robinson <jake@spacedrive.com>"]
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libheif-rs = "0.19.2"
|
||||
png = "0.17.8"
|
||||
thiserror = "1.0.40"
|
||||
image = "0.24.6"
|
||||
89
crates/heif/src/lib.rs
Normal file
89
crates/heif/src/lib.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::{Cursor, Read, Seek, SeekFrom},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use image::DynamicImage;
|
||||
use libheif_rs::{ColorSpace, HeifContext, LibHeif, RgbChroma};
|
||||
use png::{BitDepth, ColorType};
|
||||
use thiserror::Error;
|
||||
|
||||
type HeifResult<T> = Result<T, HeifError>;
|
||||
|
||||
/// The maximum file size that an image can be in order to have a thumbnail generated.
|
||||
///
|
||||
/// This value is in MiB.
|
||||
const HEIF_MAXIMUM_FILE_SIZE: u64 = 1048576 * 20;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum HeifError {
|
||||
#[error("error with libheif: {0}")]
|
||||
LibHeif(#[from] libheif_rs::HeifError),
|
||||
#[error("error while encoding to png: {0}")]
|
||||
PngEncode(#[from] png::EncodingError),
|
||||
#[error("error while loading the image (via the `image` crate): {0}")]
|
||||
Image(#[from] image::ImageError),
|
||||
#[error("io error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("the image provided is unsupported")]
|
||||
Unsupported,
|
||||
#[error("the image provided is too large (over 20MiB)")]
|
||||
TooLarge,
|
||||
#[error("the provided bit depth is invalid")]
|
||||
InvalidBitDepth,
|
||||
#[error("invalid path provided (non UTF-8)")]
|
||||
InvalidPath,
|
||||
}
|
||||
|
||||
pub fn heif_to_dynamic_image(path: &Path) -> HeifResult<DynamicImage> {
|
||||
if fs::metadata(path)?.len() > HEIF_MAXIMUM_FILE_SIZE {
|
||||
return Err(HeifError::TooLarge);
|
||||
}
|
||||
|
||||
let img = {
|
||||
// do this in a separate block so we drop the raw (potentially huge) image handle
|
||||
let ctx = HeifContext::read_from_file(path.to_str().ok_or(HeifError::InvalidPath)?)?;
|
||||
let heif = LibHeif::new();
|
||||
let handle = ctx.primary_image_handle()?;
|
||||
|
||||
heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?
|
||||
};
|
||||
|
||||
// TODO(brxken128): add support for images with individual r/g/b channels
|
||||
// i'm unable to find a sample to test with, but it should follow the same principles as this one
|
||||
if let Some(i) = img.planes().interleaved {
|
||||
let data = i.data.to_vec();
|
||||
let mut reader = Cursor::new(data);
|
||||
|
||||
let mut sequence = vec![];
|
||||
let mut buffer = [0u8; 3]; // [r, g, b]
|
||||
|
||||
// this is the interpolation stuff, it essentially just makes the image correct
|
||||
// in regards to stretching/resolution, etc
|
||||
for y in 0..img.height() {
|
||||
reader.seek(SeekFrom::Start((i.stride * y as usize) as u64))?;
|
||||
|
||||
for _ in 0..img.width() {
|
||||
reader.read_exact(&mut buffer)?;
|
||||
sequence.extend_from_slice(&buffer);
|
||||
}
|
||||
}
|
||||
|
||||
let mut writer = Cursor::new(vec![]);
|
||||
|
||||
let mut png_encoder = png::Encoder::new(&mut writer, i.width, i.height);
|
||||
png_encoder.set_color(ColorType::Rgb);
|
||||
png_encoder
|
||||
.set_depth(BitDepth::from_u8(i.bits_per_pixel).ok_or(HeifError::InvalidBitDepth)?);
|
||||
|
||||
let mut png_writer = png_encoder.write_header()?;
|
||||
png_writer.write_image_data(&sequence)?;
|
||||
png_writer.finish()?;
|
||||
|
||||
image::load_from_memory_with_format(&writer.into_inner(), image::ImageFormat::Png)
|
||||
.map_err(HeifError::Image)
|
||||
} else {
|
||||
Err(HeifError::Unsupported)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user