diff --git a/crates/media/src/layered.rs b/crates/media/src/layered.rs index 45ef5291..0fb40d81 100644 --- a/crates/media/src/layered.rs +++ b/crates/media/src/layered.rs @@ -87,7 +87,7 @@ pub(crate) fn decode(path: &str, bytes: &[u8], footage: &Footage, op: AlphaOp) - let inner = effectcraft_psd::Psd::parse(data.to_vec()).map_err(|e| MediaError::Decode(format!("{path} (smart object): {e}")))?; inner.composite().map_err(|e| MediaError::Decode(format!("{path} (smart object): {e}")))? } else { - let img = crate::probe::load_oriented(data).map_err(|e| MediaError::Decode(format!("{path} (smart object): {e}")))?; + let img = crate::probe::load_oriented(data, None).map_err(|e| MediaError::Decode(format!("{path} (smart object): {e}")))?; let rgba = img.to_rgba32f(); effectcraft_psd::Pixels { width: rgba.width(), height: rgba.height(), data: rgba.pixels().map(|p| p.0).collect() } }; diff --git a/crates/media/src/pool.rs b/crates/media/src/pool.rs index 76c4e898..b58c9f07 100644 --- a/crates/media/src/pool.rs +++ b/crates/media/src/pool.rs @@ -770,10 +770,7 @@ impl Inner { { return Err(MediaError::Decode(format!("{path}: {why}"))); } - let loaded = match crate::still_format(path, &bytes) { - Some(fmt) => image::load_from_memory_with_format(&bytes, fmt), - None => crate::probe::load_oriented(&bytes), - }; + let loaded = crate::probe::load_oriented(&bytes, crate::still_format(path, &bytes)); let img = match loaded { Ok(img) => img, // A multi-layer OpenEXR file without an unnamed RGB layer: its colour layer. diff --git a/crates/media/src/probe.rs b/crates/media/src/probe.rs index e6be31c2..bae714c8 100644 --- a/crates/media/src/probe.rs +++ b/crates/media/src/probe.rs @@ -221,10 +221,15 @@ pub(crate) fn swaps_sides(o: Orientation) -> bool { /// Decode a still the way it is meant to be seen: its Exif orientation (a camera held sideways, a /// mirrored scan) is applied to the pixels, as image viewers do. A file without -/// the tag, or with a broken one, decodes as stored. -pub(crate) fn load_oriented(bytes: &[u8]) -> image::ImageResult { +/// the tag, or with a broken one, decodes as stored. `fmt`: the format when the file's extension +/// decides it (Targa has no signature to guess from), else guessed from the bytes. +pub(crate) fn load_oriented(bytes: &[u8], fmt: Option) -> image::ImageResult { use image::ImageDecoder; - let mut dec = image::ImageReader::new(std::io::Cursor::new(bytes)).with_guessed_format()?.into_decoder()?; + let reader = match fmt { + Some(fmt) => image::ImageReader::with_format(std::io::Cursor::new(bytes), fmt), + None => image::ImageReader::new(std::io::Cursor::new(bytes)).with_guessed_format()?, + }; + let mut dec = reader.into_decoder()?; let orientation = dec.orientation().unwrap_or(Orientation::NoTransforms); let mut img = image::DynamicImage::from_decoder(dec)?; img.apply_orientation(orientation); diff --git a/crates/media/tests/footage.rs b/crates/media/tests/footage.rs index 4734eb35..cc202503 100644 --- a/crates/media/tests/footage.rs +++ b/crates/media/tests/footage.rs @@ -943,7 +943,9 @@ fn dpx_stills_match_ffmpeg() { assert!(effectcraft_media::is_importable(&p)); let f = probe(&p).expect(pf); assert_eq!(f.kind, FootageKind::Still); - assert_eq!((f.width, f.height, f.codec.as_str()), (250, 100, codec), "{pf}"); + assert_eq!((f.width, f.height), (250, 100), "{pf}"); + // Some ffmpeg builds state the transfer (", linear"), which the codec name then carries. + assert!(f.codec == codec || f.codec.starts_with(&format!("{codec}, ")), "{pf}: {}", f.codec); assert_eq!(f.alpha, if alpha { AlphaMode::Straight } else { AlphaMode::Ignore }, "{pf}"); let img = MediaPool::new().frame_at(&f, Tick::ZERO).expect(pf); let want = ffmpeg_rgba(&p, !codec.contains("8-bit"));