feat: apply EXIF orientation and colorspace correction to ffmpeg thumbnails
HEIC/HEIF sources use Display P3 color primaries. Without colorspace=bt709 the mjpeg encoder treated P3 values as sRGB, producing warm/oversaturated output. Also bake EXIF Orientation tag into pixels so saved JPEGs are canonically oriented. - Extract orientation from exif-reader and pass through all ffmpeg thumbnail paths (small, large, xlarge previews) - Add shared build_image_thumb_filter() for the 200px path - Add rotation + colorspace=bt709 to large/xlarge ffmpeg paths
This commit is contained in:
+34
-5
@@ -90,10 +90,39 @@ pub fn generate_video_thumbnail(path: &Path, destination: &Path) -> std::io::Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Use ffmpeg to extract a 200px-wide thumbnail from formats the `image` crate
|
||||
/// can't decode (RAW: NEF/ARW, HEIC/HEIF). Writes JPEG bytes to `destination`
|
||||
/// regardless of its extension.
|
||||
pub fn generate_image_thumbnail_ffmpeg(path: &Path, destination: &Path) -> std::io::Result<()> {
|
||||
/// Build the ffmpeg filter chain for image thumbnails: rotation (from EXIF
|
||||
/// orientation) + scale + color-space conversion. HEIC sources use Display P3
|
||||
/// primaries; without `colorspace=bt709` the mjpeg encoder treats P3 values
|
||||
/// as sRGB, producing warm/oversaturated output.
|
||||
fn build_image_thumb_filter(orientation: i32, scale_w: u32) -> String {
|
||||
let rotation = match orientation {
|
||||
2 => "hflip",
|
||||
3 => "transpose=2",
|
||||
4 => "vflip",
|
||||
5 => "transpose=0,hflip",
|
||||
6 => "transpose=0",
|
||||
7 => "transpose=1,hflip",
|
||||
8 => "transpose=1",
|
||||
_ => "", // orientation 1 or unknown — no rotation needed
|
||||
};
|
||||
if rotation.is_empty() {
|
||||
format!("scale={}:{{-1}},colorspace=bt709", scale_w)
|
||||
} else {
|
||||
format!("{},scale={}:{{-1}},colorspace=bt709", rotation, scale_w)
|
||||
}
|
||||
}
|
||||
|
||||
/// Use ffmpeg to extract a thumbnail from formats the `image` crate can't
|
||||
/// decode (HEIC/HEIF, RAW: NEF/ARW). `orientation` is the EXIF Orientation
|
||||
/// tag value (1..=8) — baked into the pixels so the saved JPEG is
|
||||
/// canonically oriented. Writes JPEG bytes to `destination` regardless of
|
||||
/// its extension.
|
||||
pub fn generate_image_thumbnail_ffmpeg(
|
||||
path: &Path,
|
||||
destination: &Path,
|
||||
orientation: i32,
|
||||
) -> std::io::Result<()> {
|
||||
let vf = build_image_thumb_filter(orientation, 200);
|
||||
let output = Command::new("ffmpeg")
|
||||
.arg("-y")
|
||||
.arg("-i")
|
||||
@@ -101,7 +130,7 @@ pub fn generate_image_thumbnail_ffmpeg(path: &Path, destination: &Path) -> std::
|
||||
.arg("-vframes")
|
||||
.arg("1")
|
||||
.arg("-vf")
|
||||
.arg("scale=200:-1")
|
||||
.arg(&vf)
|
||||
.arg("-f")
|
||||
.arg("image2")
|
||||
.arg("-c:v")
|
||||
|
||||
Reference in New Issue
Block a user