videre-core 0.49.1

Shared SQLite, caching, and search helpers for the videre media library CLI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
//! Identify a file's real type from its leading bytes.
//!
//! `file_hashes.ext` is the filename extension and nothing more, yet it drives
//! which decoder each file reaches. A misnamed file is routed by its name: a
//! real library contains a JPEG called `.png` that fails every `videre embed`
//! run with "Invalid PNG signature".
//!
//! Detection reads no I/O of its own. `hasher::hash_file_inner` already fills
//! a 64KB buffer to compute BLAKE3, and the signature lives in the first 12
//! bytes of it.

/// Top-level boxes that identify a classic QuickTime file, which predates
/// ISO-BMFF's `ftyp` and may begin with any of these.
const QUICKTIME_BOXES: [&[u8; 4]; 7] = [
    b"ftyp", b"wide", b"mdat", b"moov", b"free", b"skip", b"pnot",
];

/// The IANA type identified by `head`'s leading bytes, or None if
/// unrecognised. Needs at least 12 bytes; shorter input is always None.
///
/// Returns `&'static str` rather than an enum so the value stored in the
/// database and the value matched against are the same thing, with no mapping
/// table to drift.
pub fn sniff(head: &[u8]) -> Option<&'static str> {
    if head.len() < 12 {
        return None;
    }
    if head.starts_with(b"\xff\xd8\xff") {
        return Some("image/jpeg");
    }
    if head.starts_with(b"\x89PNG\r\n\x1a\n") {
        return Some("image/png");
    }
    if head.starts_with(b"GIF87a") || head.starts_with(b"GIF89a") {
        return Some("image/gif");
    }
    if head.starts_with(b"BM") {
        return Some("image/bmp");
    }
    if head.starts_with(b"II*\x00") || head.starts_with(b"MM\x00*") {
        return Some("image/tiff");
    }
    // RIFF alone is also WAV and AVI, so the WEBP tag is required.
    if head.starts_with(b"RIFF") && &head[8..12] == b"WEBP" {
        return Some("image/webp");
    }
    let box_type = &head[4..8];
    if box_type == b"ftyp" {
        let brand = &head[8..12];
        // HEIC and MP4 share the ftyp box; only the brand separates them.
        if matches!(
            brand,
            b"heic" | b"heix" | b"hevc" | b"hevx" | b"mif1" | b"msf1"
        ) {
            return Some("image/heic");
        }
        if &brand[..2] == b"qt" {
            return Some("video/quicktime");
        }
        return Some("video/mp4");
    }
    if QUICKTIME_BOXES.iter().any(|b| box_type == *b) {
        return Some("video/quicktime");
    }
    None
}

/// Types the embedding pipeline can decode.
pub const EMBEDDABLE_MIMES: &[&str] = &[
    "image/jpeg",
    "image/png",
    "image/gif",
    "image/webp",
    "image/bmp",
    "image/tiff",
    "image/heic",
    "video/quicktime",
    "video/mp4",
];

/// Types carrying EXIF metadata worth extracting.
pub const EXIF_MIMES: &[&str] = &["image/jpeg", "image/tiff", "image/heic"];

/// Types counted as photos by `library_stats`.
pub const PHOTO_MIMES: &[&str] = &[
    "image/jpeg",
    "image/png",
    "image/gif",
    "image/webp",
    "image/bmp",
    "image/tiff",
    "image/heic",
];

/// Types counted as videos.
pub const VIDEO_MIMES: &[&str] = &["video/quicktime", "video/mp4"];

/// Stored when a file was read successfully but its bytes match no known
/// signature.
///
/// Distinguishes "checked, unidentifiable" from NULL, which after this exists
/// means only "never scanned". That distinction is what makes an incremental
/// `videre scan` terminate instead of re-reading the same unidentifiable files
/// on every run, the same reason `faces_scanned` records
/// images where zero faces were found.
///
/// The IANA type for unclassified binary data, so a database inspected by hand
/// reads sensibly rather than carrying a private magic value.
pub const UNKNOWN_MIME: &str = "application/octet-stream";

/// The type a filename extension implies, used only when `mime` is NULL.
fn mime_for_ext(ext: &str) -> Option<&'static str> {
    Some(match ext.to_lowercase().as_str() {
        "jpg" | "jpeg" => "image/jpeg",
        "png" => "image/png",
        "gif" => "image/gif",
        "webp" => "image/webp",
        "bmp" => "image/bmp",
        // DNG really is TIFF; `is_embeddable` vetoes it separately.
        "tiff" | "tif" | "dng" => "image/tiff",
        "heic" => "image/heic",
        "mov" => "video/quicktime",
        "mp4" | "m4v" => "video/mp4",
        _ => return None,
    })
}

/// Whether the stored filename extension conflicts with a known MIME found
/// during the last scan. Missing or unrecognised scan evidence proves nothing.
pub fn is_content_mismatch(ext: &str, detected_mime: Option<&str>) -> bool {
    let Some(mime) = detected_mime else {
        return false;
    };
    if !PHOTO_MIMES.contains(&mime) && !VIDEO_MIMES.contains(&mime) {
        return false;
    }
    if matches!(ext.to_ascii_lowercase().as_str(), "mov" | "mp4" | "m4v") {
        return !VIDEO_MIMES.contains(&mime);
    }
    mime_for_ext(ext).is_some_and(|expected| expected != mime)
}

/// The type to route on: the detected mime when known, else derived from the
/// extension.
///
/// The fallback is not compatibility support for older versions; it is how a
/// nullable column behaves before a library has been re-scanned.
pub fn effective_mime(mime: Option<&str>, ext: &str) -> Option<&'static str> {
    // The sentinel is bookkeeping, not a type: fall through to the extension
    // exactly as a NULL would. Routing on it would silently stop videre
    // processing files whose bytes it merely failed to identify.
    let mime = mime.filter(|m| *m != UNKNOWN_MIME);
    if let Some(m) = mime {
        // Normalise to a &'static str from the tables so callers compare
        // against the same values the constants hold.
        if let Some(known) = EMBEDDABLE_MIMES
            .iter()
            .chain(PHOTO_MIMES)
            .chain(VIDEO_MIMES)
            .find(|k| **k == m)
        {
            return Some(known);
        }
    }
    mime_for_ext(ext)
}

/// Whether `videre embed` should attempt this file.
///
/// `ext == "dng"` vetoes regardless of mime. DNG is a TIFF variant, so its
/// magic bytes are TIFF and TIFF is embeddable, but the `image` crate has no
/// DNG decoder: without this veto every DNG is queried as pending and fails
/// to decode on every run, forever. That exact bug was fixed 2026-08-01 by
/// excluding `dng` from the extension list, and routing on mime would revive
/// it.
pub fn is_embeddable(mime: Option<&str>, ext: &str) -> bool {
    if ext.eq_ignore_ascii_case("dng") {
        return false;
    }
    effective_mime(mime, ext).is_some_and(|m| EMBEDDABLE_MIMES.contains(&m))
}

/// Whether this is a video, for the single-frame QuickLook path.
pub fn is_video_mime(mime: &str) -> bool {
    VIDEO_MIMES.contains(&mime)
}

#[cfg(test)]
mod tests {
    use super::*;

    /// An ISO-BMFF header: 4 size bytes, then the box type, then a brand.
    fn iso(box_type: &[u8; 4], brand: &[u8; 4]) -> Vec<u8> {
        let mut v = vec![0, 0, 0, 0x20];
        v.extend_from_slice(box_type);
        v.extend_from_slice(brand);
        v
    }

    #[test]
    fn jpeg_is_detected() {
        assert_eq!(
            sniff(b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01"),
            Some("image/jpeg")
        );
    }

    #[test]
    fn png_is_detected() {
        assert_eq!(
            sniff(b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0d"),
            Some("image/png")
        );
    }

    #[test]
    fn gif_both_versions_are_detected() {
        assert_eq!(sniff(b"GIF87a\x00\x00\x00\x00\x00\x00"), Some("image/gif"));
        assert_eq!(sniff(b"GIF89a\x00\x00\x00\x00\x00\x00"), Some("image/gif"));
    }

    #[test]
    fn bmp_is_detected() {
        assert_eq!(
            sniff(b"BM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
            Some("image/bmp")
        );
    }

    #[test]
    fn tiff_both_endiannesses_are_detected() {
        assert_eq!(
            sniff(b"II*\x00\x08\x00\x00\x00\x00\x00\x00\x00"),
            Some("image/tiff")
        );
        assert_eq!(
            sniff(b"MM\x00*\x00\x00\x00\x08\x00\x00\x00\x00"),
            Some("image/tiff")
        );
    }

    #[test]
    fn webp_needs_both_riff_and_webp() {
        assert_eq!(sniff(b"RIFF\x00\x00\x00\x00WEBP"), Some("image/webp"));
        // RIFF alone is also WAV and AVI; it must not claim those.
        assert_eq!(sniff(b"RIFF\x00\x00\x00\x00WAVE"), None);
    }

    #[test]
    fn heic_brands_are_images_not_video() {
        // HEIC and MP4 both carry `ftyp`; only the brand separates them.
        for brand in [b"heic", b"heix", b"hevc", b"hevx", b"mif1", b"msf1"] {
            assert_eq!(sniff(&iso(b"ftyp", brand)), Some("image/heic"), "{brand:?}");
        }
    }

    #[test]
    fn mp4_and_quicktime_brands_are_distinguished() {
        assert_eq!(sniff(&iso(b"ftyp", b"isom")), Some("video/mp4"));
        assert_eq!(sniff(&iso(b"ftyp", b"mp42")), Some("video/mp4"));
        assert_eq!(sniff(&iso(b"ftyp", b"qt  ")), Some("video/quicktime"));
    }

    #[test]
    fn classic_quicktime_without_ftyp_is_detected() {
        // 2.5% of a real library: 75 of 3,000 sampled .mov files begin with a
        // `wide` box then `mdat`, with no ftyp at all. `file(1)` calls them
        // plain data. A naive ftyp-only check leaves them NULL.
        for b in [b"wide", b"mdat", b"moov", b"free", b"skip", b"pnot"] {
            let mut v = vec![0, 0, 0, 8];
            v.extend_from_slice(b);
            v.extend_from_slice(&[0u8; 8]);
            assert_eq!(sniff(&v), Some("video/quicktime"), "{b:?}");
        }
    }

    #[test]
    fn short_input_is_none() {
        assert_eq!(sniff(b""), None);
        assert_eq!(
            sniff(b"\xff\xd8\xff"),
            None,
            "under 12 bytes is never enough"
        );
    }

    #[test]
    fn unrecognised_bytes_are_none() {
        assert_eq!(sniff(b"not a real file header at all"), None);
    }

    #[test]
    fn effective_mime_prefers_the_detected_value() {
        assert_eq!(
            effective_mime(Some("image/jpeg"), "png"),
            Some("image/jpeg")
        );
    }

    #[test]
    fn effective_mime_falls_back_to_the_extension_when_null() {
        // mime is NULL until a library is re-scanned; without this fallback an
        // existing library reports zero photos and embeds nothing.
        assert_eq!(effective_mime(None, "jpg"), Some("image/jpeg"));
        assert_eq!(effective_mime(None, "MOV"), Some("video/quicktime"));
        assert_eq!(effective_mime(None, "xyz"), None);
    }

    #[test]
    fn a_misnamed_jpeg_is_embeddable() {
        // The real file this exists for: a JPEG named .png that fails every
        // embed run with "Invalid PNG signature".
        assert!(is_embeddable(Some("image/jpeg"), "png"));
    }

    #[test]
    fn dng_is_never_embeddable_even_though_it_reports_tiff() {
        // Regression guard for the 2026-08-01 fix. DNG is a TIFF variant, so
        // its magic bytes genuinely are TIFF, and tiff IS embeddable. Without
        // this veto every .dng is queried as pending and fails to decode, on
        // every run, forever: the `image` crate has no DNG decoder.
        assert!(!is_embeddable(Some("image/tiff"), "dng"));
        assert!(!is_embeddable(None, "dng"));
    }

    #[test]
    fn a_real_tiff_is_still_embeddable_so_the_veto_stays_narrow() {
        assert!(is_embeddable(Some("image/tiff"), "tiff"));
    }

    #[test]
    fn videos_are_embeddable_and_identified_as_video() {
        assert!(is_embeddable(Some("video/quicktime"), "mov"));
        assert!(is_video_mime("video/quicktime"));
        assert!(is_video_mime("video/mp4"));
        assert!(!is_video_mime("image/jpeg"));
    }

    #[test]
    fn an_unknown_type_is_not_embeddable() {
        assert!(!is_embeddable(None, "xyz"));
    }

    #[test]
    fn the_sentinel_is_treated_as_unknown_not_as_a_type() {
        // Bookkeeping only. If the sentinel reached routing, a malformed JPEG
        // named .jpg would stop being embeddable even though the image crate
        // decodes it fine: a regression caused purely by better bookkeeping.
        assert_eq!(
            effective_mime(Some(UNKNOWN_MIME), "jpg"),
            Some("image/jpeg")
        );
        assert_eq!(
            effective_mime(Some(UNKNOWN_MIME), "mov"),
            Some("video/quicktime")
        );
    }

    #[test]
    fn a_file_with_the_sentinel_is_still_embeddable() {
        assert!(is_embeddable(Some(UNKNOWN_MIME), "jpg"));
    }

    #[test]
    fn the_sentinel_with_an_unknown_extension_stays_unknown() {
        assert_eq!(effective_mime(Some(UNKNOWN_MIME), "xyz"), None);
        assert!(!is_embeddable(Some(UNKNOWN_MIME), "xyz"));
    }

    #[test]
    fn content_mismatch_requires_known_conflicting_evidence() {
        for (ext, mime) in [
            ("jpg", "image/jpeg"),
            ("jpeg", "image/jpeg"),
            ("JPG", "image/jpeg"),
            ("JPEG", "image/jpeg"),
            ("png", "image/png"),
            ("gif", "image/gif"),
            ("webp", "image/webp"),
            ("bmp", "image/bmp"),
            ("tif", "image/tiff"),
            ("tiff", "image/tiff"),
            ("dng", "image/tiff"),
            ("heic", "image/heic"),
        ] {
            assert!(!is_content_mismatch(ext, Some(mime)), "{ext} {mime}");
        }
        for ext in ["mov", "mp4", "m4v"] {
            for mime in ["video/quicktime", "video/mp4"] {
                assert!(!is_content_mismatch(ext, Some(mime)), "{ext} {mime}");
            }
        }
        for (ext, mime) in [
            ("png", "image/jpeg"),
            ("jpg", "image/png"),
            ("mp4", "image/jpeg"),
        ] {
            assert!(is_content_mismatch(ext, Some(mime)), "{ext} {mime}");
        }
        for mime in [
            None,
            Some(""),
            Some(UNKNOWN_MIME),
            Some("application/x-unknown"),
        ] {
            assert!(!is_content_mismatch("jpg", mime), "{mime:?}");
        }
        assert!(!is_content_mismatch("xyz", Some("image/jpeg")));
        assert!(!is_content_mismatch("", Some("image/jpeg")));
    }
}