1use anyhow::{Context, Result, anyhow, bail};
16use std::path::Path;
17use std::sync::Mutex;
18
19pub mod exif;
20pub mod jpeg;
21
22pub const MIN_PREVIEW_LONG_EDGE: u16 = 1600;
25
26#[derive(Debug)]
28pub struct RenderedJpeg {
29 pub data: Vec<u8>,
30}
31
32pub fn rendered_file_name(path: &Path) -> String {
35 let stem = path
36 .file_stem()
37 .map(|s| s.to_string_lossy().into_owned())
38 .unwrap_or_else(|| "image".to_string());
39 format!("{}.jpg", stem)
40}
41
42pub fn render_jpeg(path: &Path) -> Result<RenderedJpeg> {
44 let data = std::fs::read(path)?;
45 render_jpeg_bytes(&data)
46}
47
48const CONVERTED_JPEG_QUALITY: u8 = 92;
50
51pub fn render_jpeg_bytes(data: &[u8]) -> Result<RenderedJpeg> {
52 let mut out = match embedded_preview(data) {
53 Ok(preview) if preview.has_exif => {
54 return Ok(RenderedJpeg {
55 data: data[preview.start..preview.end].to_vec(),
56 });
57 }
58 Ok(preview) => data[preview.start..preview.end].to_vec(),
59 Err(no_preview) => convert_raw(data)
60 .map_err(|e| anyhow!("{no_preview}, and converting the RAW data failed: {e:#}"))?,
61 };
62
63 if let Some(exif) = exif::read_raw(data).filter(|e| !e.is_empty()) {
66 let mut with_exif = out.clone();
67 if exif.write_into_jpeg(&mut with_exif).is_ok() {
68 out = with_exif;
69 }
70 }
71
72 Ok(RenderedJpeg { data: out })
73}
74
75fn embedded_preview(data: &[u8]) -> Result<jpeg::EmbeddedJpeg> {
78 let Some(best) = jpeg::find_jpegs(data)
79 .into_iter()
80 .reduce(|best, p| if p.pixels() > best.pixels() { p } else { best })
81 else {
82 bail!("no embedded JPEG preview found");
83 };
84 if best.long_edge() < MIN_PREVIEW_LONG_EDGE {
85 bail!(
86 "embedded preview is only {}x{} (need at least {} px on the long edge)",
87 best.width,
88 best.height,
89 MIN_PREVIEW_LONG_EDGE
90 );
91 }
92 Ok(best)
93}
94
95fn convert_raw(data: &[u8]) -> Result<Vec<u8>> {
97 use image::codecs::jpeg::JpegEncoder;
98 use rawler::decoders::RawDecodeParams;
99 use rawler::imgop::develop::RawDevelop;
100 use rawler::rawsource::RawSource;
101
102 static CONVERTING: Mutex<()> = Mutex::new(());
105 let _one_at_a_time = CONVERTING.lock().unwrap_or_else(|e| e.into_inner());
106
107 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
110 let source = RawSource::new_from_slice(data);
111 let raw = rawler::decode(&source, &RawDecodeParams::default())?;
112 let developed = RawDevelop::default()
113 .develop_intermediate(&raw)?
114 .to_dynamic_image()
115 .context("developed image has an unexpected size")?;
116
117 let mut out = Vec::new();
118 JpegEncoder::new_with_quality(&mut out, CONVERTED_JPEG_QUALITY)
119 .encode_image(&developed.into_rgb8())?;
120 Ok(out)
121 }))
122 .map_err(|_| anyhow!("the RAW decoder crashed on this file"))?
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::raw::exif::tests::fake_tiff_raw_be;
129 use crate::raw::jpeg::tests::fake_jpeg;
130
131 #[test]
132 fn test_rendered_file_name() {
133 assert_eq!(
134 rendered_file_name(Path::new("/a/IMG_1234.CR2")),
135 "IMG_1234.jpg"
136 );
137 assert_eq!(rendered_file_name(Path::new("x.y.nef")), "x.y.jpg");
138 }
139
140 #[test]
141 fn picks_largest_preview_and_adds_exif() {
142 let mut raw = fake_tiff_raw_be();
143 let thumb = fake_jpeg(0xC0, 160, 120, None);
144 let full = fake_jpeg(0xC0, 6000, 4000, None);
145 let sensor = fake_jpeg(0xC3, 6100, 4100, None);
146 raw.extend(&thumb);
147 raw.extend(&full);
148 raw.extend(&sensor);
149
150 let rendered = render_jpeg_bytes(&raw).unwrap();
151
152 assert!(rendered.data.ends_with(&full[2..]));
155 let found = jpeg::find_jpegs(&rendered.data);
156 assert_eq!(found.len(), 1);
157 assert!(found[0].has_exif);
158 assert_eq!((found[0].width, found[0].height), (6000, 4000));
159 assert_eq!(found[0].end, rendered.data.len());
160
161 let exif = exif::read_tiff_raw(exif::tests::exif_tiff(&rendered.data).unwrap()).unwrap();
162 assert_eq!(exif.orientation(), Some(6));
163
164 assert_eq!(render_jpeg_bytes(&raw).unwrap().data, rendered.data);
166 }
167
168 #[test]
169 fn keeps_preview_exif_when_present() {
170 let mut raw = fake_tiff_raw_be();
171 let full = fake_jpeg(0xC0, 6000, 4000, Some(b"Exif\0\0own"));
172 raw.extend(&full);
173
174 let rendered = render_jpeg_bytes(&raw).unwrap();
175 assert_eq!(rendered.data, full);
176 }
177
178 #[test]
179 fn falls_back_to_converting_without_a_usable_preview() {
180 let mut raw = fake_tiff_raw_be();
183 let err = render_jpeg_bytes(&raw).unwrap_err().to_string();
184 assert!(err.contains("no embedded JPEG preview"), "{err}");
185 assert!(err.contains("converting the RAW data failed"), "{err}");
186
187 raw.extend(fake_jpeg(0xC0, 1024, 683, None));
188 let err = render_jpeg_bytes(&raw).unwrap_err().to_string();
189 assert!(err.contains("1024x683"), "{err}");
190 assert!(err.contains("converting the RAW data failed"), "{err}");
191 }
192}