1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[allow(non_camel_case_types)]
9pub enum FileType {
10 Jpeg,
12 Tiff,
13 Png,
14 Gif,
15 Bmp,
16 WebP,
17 Heif,
18 Avif,
19 Psd,
20 Jp2,
21 J2c,
22 Jxl,
23 Jxr,
24 Flif,
25 Bpg,
26 Exr,
27 Ico,
28 DjVu,
30 Xcf,
31 Pcx,
32 Pict,
33 Psp,
34 Hdr,
35 Rwz,
36 Btf,
37 Mng,
38 Cr2,
40 Cr3,
41 Crw,
42 Nef,
43 Arw,
44 Sr2,
45 Srf,
46 Orf,
47 Rw2,
48 Dng,
49 Raf,
50 Pef,
51 Dcr,
52 Mrw,
53 Erf,
54 Fff,
55 Iiq,
56 Rwl,
57 Mef,
58 Srw,
59 X3f,
60 Gpr,
61 Arq,
62 ThreeFR,
63 Crm,
64 Mp4,
66 QuickTime,
67 Avi,
68 Mkv,
69 WebM,
70 Wmv,
71 Asf,
72 Flv,
73 Mxf,
74 M2ts,
75 Mpeg,
76 ThreeGP,
77 RealMedia,
78 R3d,
79 Dvb,
80 Lrv,
81 Mqv,
82 F4v,
83 Wtv,
84 DvrMs,
85 Mp3,
87 Flac,
88 Ogg,
89 Wav,
90 Aiff,
91 Aac,
92 Opus,
93 Mpc,
94 Ape,
95 WavPack,
96 Ofr,
97 Dsf,
98 Audible,
99 RealAudio,
100 Wma,
101 M4a,
102 Pdf,
104 PostScript,
105 Doc,
106 Docx,
107 Xls,
108 Xlsx,
109 Ppt,
110 Pptx,
111 InDesign,
112 Rtf,
113 Zip,
115 Rar,
116 SevenZ,
117 Gzip,
118 Xmp,
120 Mie,
121 Exv,
122 Vrd,
123 Icc,
124 Html,
125 Exe,
126 Font,
127 Swf,
128 Dicom,
129 Fits,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum Support {
135 Read,
136 ReadWrite,
137 ReadWriteCreate,
138}
139
140impl FileType {
141 pub fn description(self) -> &'static str {
143 match self {
144 FileType::Jpeg => "JPEG image",
146 FileType::Tiff => "TIFF image",
147 FileType::Png => "PNG image",
148 FileType::Gif => "GIF image",
149 FileType::Bmp => "BMP image",
150 FileType::WebP => "WebP image",
151 FileType::Heif => "HEIF/HEIC image",
152 FileType::Avif => "AVIF image",
153 FileType::Psd => "Adobe Photoshop Document",
154 FileType::Jp2 => "JPEG 2000 image",
155 FileType::J2c => "JPEG 2000 Codestream",
156 FileType::Jxl => "JPEG XL image",
157 FileType::Jxr => "JPEG XR / HD Photo",
158 FileType::Flif => "Free Lossless Image Format",
159 FileType::Bpg => "Better Portable Graphics",
160 FileType::Exr => "OpenEXR image",
161 FileType::Ico => "Windows Icon",
162 FileType::DjVu => "DjVu document",
164 FileType::Xcf => "GIMP image",
165 FileType::Pcx => "PCX image",
166 FileType::Pict => "Apple PICT",
167 FileType::Psp => "Paint Shop Pro image",
168 FileType::Hdr => "Radiance HDR",
169 FileType::Rwz => "Rawzor compressed image",
170 FileType::Btf => "BigTIFF image",
171 FileType::Mng => "MNG animation",
172 FileType::Cr2 => "Canon CR2 RAW",
174 FileType::Cr3 => "Canon CR3 RAW",
175 FileType::Crw => "Canon CRW RAW",
176 FileType::Nef => "Nikon NEF RAW",
177 FileType::Arw => "Sony ARW RAW",
178 FileType::Sr2 => "Sony SR2 RAW",
179 FileType::Srf => "Sony SRF RAW",
180 FileType::Orf => "Olympus ORF RAW",
181 FileType::Rw2 => "Panasonic RW2 RAW",
182 FileType::Dng => "Adobe Digital Negative",
183 FileType::Raf => "Fujifilm RAF RAW",
184 FileType::Pef => "Pentax PEF RAW",
185 FileType::Dcr => "Kodak DCR RAW",
186 FileType::Mrw => "Minolta MRW RAW",
187 FileType::Erf => "Epson ERF RAW",
188 FileType::Fff => "Hasselblad FFF RAW",
189 FileType::Iiq => "Phase One IIQ RAW",
190 FileType::Rwl => "Leica RWL RAW",
191 FileType::Mef => "Mamiya MEF RAW",
192 FileType::Srw => "Samsung SRW RAW",
193 FileType::X3f => "Sigma X3F RAW",
194 FileType::Gpr => "GoPro GPR RAW",
195 FileType::Arq => "Sony ARQ RAW",
196 FileType::ThreeFR => "Hasselblad 3FR RAW",
197 FileType::Crm => "Canon Cinema RAW",
198 FileType::Mp4 => "MP4 video",
200 FileType::QuickTime => "QuickTime video",
201 FileType::Avi => "AVI video",
202 FileType::Mkv => "Matroska video",
203 FileType::WebM => "WebM video",
204 FileType::Wmv => "Windows Media Video",
205 FileType::Asf => "Advanced Systems Format",
206 FileType::Flv => "Flash Video",
207 FileType::Mxf => "Material Exchange Format",
208 FileType::M2ts => "MPEG-2 Transport Stream",
209 FileType::Mpeg => "MPEG video",
210 FileType::ThreeGP => "3GPP multimedia",
211 FileType::RealMedia => "RealMedia",
212 FileType::R3d => "Redcode RAW video",
213 FileType::Dvb => "Digital Video Broadcasting",
214 FileType::Lrv => "GoPro Low-Res Video",
215 FileType::Mqv => "Sony Movie",
216 FileType::F4v => "Adobe Flash Video",
217 FileType::Wtv => "Windows Recorded TV",
218 FileType::DvrMs => "Microsoft DVR",
219 FileType::Mp3 => "MP3 audio",
221 FileType::Flac => "FLAC audio",
222 FileType::Ogg => "Ogg Vorbis audio",
223 FileType::Wav => "WAV audio",
224 FileType::Aiff => "AIFF audio",
225 FileType::Aac => "AAC audio",
226 FileType::Opus => "Opus audio",
227 FileType::Mpc => "Musepack audio",
228 FileType::Ape => "Monkey's Audio",
229 FileType::WavPack => "WavPack audio",
230 FileType::Ofr => "OptimFROG audio",
231 FileType::Dsf => "DSD Stream File",
232 FileType::Audible => "Audible audiobook",
233 FileType::RealAudio => "RealAudio",
234 FileType::Wma => "Windows Media Audio",
235 FileType::M4a => "MPEG-4 Audio",
236 FileType::Pdf => "PDF document",
238 FileType::PostScript => "PostScript",
239 FileType::Doc => "Microsoft Word (legacy)",
240 FileType::Docx => "Microsoft Word",
241 FileType::Xls => "Microsoft Excel (legacy)",
242 FileType::Xlsx => "Microsoft Excel",
243 FileType::Ppt => "Microsoft PowerPoint (legacy)",
244 FileType::Pptx => "Microsoft PowerPoint",
245 FileType::InDesign => "Adobe InDesign",
246 FileType::Rtf => "Rich Text Format",
247 FileType::Zip => "ZIP archive",
249 FileType::Rar => "RAR archive",
250 FileType::SevenZ => "7-Zip archive",
251 FileType::Gzip => "GZIP archive",
252 FileType::Xmp => "XMP sidecar",
254 FileType::Mie => "MIE metadata",
255 FileType::Exv => "Exiv2 metadata",
256 FileType::Vrd => "Canon VRD recipe",
257 FileType::Icc => "ICC color profile",
258 FileType::Html => "HTML document",
259 FileType::Exe => "Windows executable",
260 FileType::Font => "Font file",
261 FileType::Swf => "Shockwave Flash",
262 FileType::Dicom => "DICOM medical image",
263 FileType::Fits => "FITS astronomical image",
264 }
265 }
266
267 pub fn mime_type(self) -> &'static str {
269 match self {
270 FileType::Jpeg => "image/jpeg",
271 FileType::Tiff | FileType::Btf => "image/tiff",
272 FileType::Png => "image/png",
273 FileType::Gif => "image/gif",
274 FileType::Bmp => "image/bmp",
275 FileType::WebP => "image/webp",
276 FileType::Heif => "image/heif",
277 FileType::Avif => "image/avif",
278 FileType::Psd => "image/vnd.adobe.photoshop",
279 FileType::Jp2 => "image/jp2",
280 FileType::J2c => "image/x-j2c",
281 FileType::Jxl => "image/jxl",
282 FileType::Jxr => "image/jxr",
283 FileType::Flif => "image/flif",
284 FileType::Bpg => "image/bpg",
285 FileType::Exr => "image/x-exr",
286 FileType::Ico => "image/x-icon",
287 FileType::DjVu => "image/vnd.djvu",
288 FileType::Xcf => "image/x-xcf",
289 FileType::Pcx => "image/x-pcx",
290 FileType::Pict => "image/x-pict",
291 FileType::Psp => "image/x-psp",
292 FileType::Hdr => "image/vnd.radiance",
293 FileType::Rwz => "image/x-rawzor",
294 FileType::Mng => "video/x-mng",
295 FileType::Cr2 => "image/x-canon-cr2",
297 FileType::Cr3 | FileType::Crm => "image/x-canon-cr3",
298 FileType::Crw => "image/x-canon-crw",
299 FileType::Nef => "image/x-nikon-nef",
300 FileType::Arw | FileType::Arq => "image/x-sony-arw",
301 FileType::Sr2 => "image/x-sony-sr2",
302 FileType::Srf => "image/x-sony-srf",
303 FileType::Orf => "image/x-olympus-orf",
304 FileType::Rw2 => "image/x-panasonic-rw2",
305 FileType::Dng | FileType::Gpr => "image/x-adobe-dng",
306 FileType::Raf => "image/x-fuji-raf",
307 FileType::Pef => "image/x-pentax-pef",
308 FileType::Dcr => "image/x-kodak-dcr",
309 FileType::Mrw => "image/x-minolta-mrw",
310 FileType::Erf => "image/x-epson-erf",
311 FileType::Fff | FileType::ThreeFR => "image/x-hasselblad-fff",
312 FileType::Iiq => "image/x-phaseone-iiq",
313 FileType::Rwl => "image/x-leica-rwl",
314 FileType::Mef => "image/x-mamiya-mef",
315 FileType::Srw => "image/x-samsung-srw",
316 FileType::X3f => "image/x-sigma-x3f",
317 FileType::Mp4 | FileType::F4v => "video/mp4",
319 FileType::QuickTime | FileType::Mqv => "video/quicktime",
320 FileType::Avi => "video/x-msvideo",
321 FileType::Mkv => "video/x-matroska",
322 FileType::WebM => "video/webm",
323 FileType::Wmv => "video/x-ms-wmv",
324 FileType::Asf => "video/x-ms-asf",
325 FileType::Flv => "video/x-flv",
326 FileType::Mxf => "application/mxf",
327 FileType::M2ts => "video/mp2t",
328 FileType::Mpeg => "video/mpeg",
329 FileType::ThreeGP => "video/3gpp",
330 FileType::RealMedia => "application/vnd.rn-realmedia",
331 FileType::R3d => "video/x-red-r3d",
332 FileType::Dvb => "video/dvb",
333 FileType::Lrv => "video/mp4",
334 FileType::Wtv => "video/x-ms-wtv",
335 FileType::DvrMs => "video/x-ms-dvr",
336 FileType::Mp3 => "audio/mpeg",
338 FileType::Flac => "audio/flac",
339 FileType::Ogg | FileType::Opus => "audio/ogg",
340 FileType::Wav => "audio/wav",
341 FileType::Aiff => "audio/aiff",
342 FileType::Aac => "audio/aac",
343 FileType::Mpc => "audio/x-musepack",
344 FileType::Ape => "audio/x-ape",
345 FileType::WavPack => "audio/x-wavpack",
346 FileType::Ofr => "audio/x-ofr",
347 FileType::Dsf => "audio/dsf",
348 FileType::Audible => "audio/x-pn-audibleaudio",
349 FileType::RealAudio => "audio/x-pn-realaudio",
350 FileType::Wma => "audio/x-ms-wma",
351 FileType::M4a => "audio/mp4",
352 FileType::Pdf => "application/pdf",
354 FileType::PostScript => "application/postscript",
355 FileType::Doc => "application/msword",
356 FileType::Docx => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
357 FileType::Xls => "application/vnd.ms-excel",
358 FileType::Xlsx => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
359 FileType::Ppt => "application/vnd.ms-powerpoint",
360 FileType::Pptx => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
361 FileType::InDesign => "application/x-indesign",
362 FileType::Rtf => "application/rtf",
363 FileType::Zip => "application/zip",
365 FileType::Rar => "application/x-rar-compressed",
366 FileType::SevenZ => "application/x-7z-compressed",
367 FileType::Gzip => "application/gzip",
368 FileType::Xmp => "application/rdf+xml",
370 FileType::Mie => "application/x-mie",
371 FileType::Exv => "application/x-exv",
372 FileType::Vrd => "application/x-canon-vrd",
373 FileType::Icc => "application/vnd.icc.profile",
374 FileType::Html => "text/html",
375 FileType::Exe => "application/x-dosexec",
376 FileType::Font => "font/sfnt",
377 FileType::Swf => "application/x-shockwave-flash",
378 FileType::Dicom => "application/dicom",
379 FileType::Fits => "application/fits",
380 }
381 }
382
383 pub fn extensions(self) -> &'static [&'static str] {
385 match self {
386 FileType::Jpeg => &["jpg", "jpeg", "jpe", "jif", "jfif"],
387 FileType::Tiff => &["tif", "tiff"],
388 FileType::Png => &["png"],
389 FileType::Gif => &["gif"],
390 FileType::Bmp => &["bmp", "dib"],
391 FileType::WebP => &["webp"],
392 FileType::Heif => &["heif", "heic", "hif"],
393 FileType::Avif => &["avif"],
394 FileType::Psd => &["psd", "psb", "psdt"],
395 FileType::Jp2 => &["jp2", "jpf", "jpm", "jpx", "jph"],
396 FileType::J2c => &["j2c", "j2k", "jpc"],
397 FileType::Jxl => &["jxl"],
398 FileType::Jxr => &["jxr", "hdp", "wdp"],
399 FileType::Flif => &["flif"],
400 FileType::Bpg => &["bpg"],
401 FileType::Exr => &["exr"],
402 FileType::Ico => &["ico", "cur"],
403 FileType::DjVu => &["djvu", "djv"],
404 FileType::Xcf => &["xcf"],
405 FileType::Pcx => &["pcx"],
406 FileType::Pict => &["pict", "pct"],
407 FileType::Psp => &["psp", "pspimage"],
408 FileType::Hdr => &["hdr"],
409 FileType::Rwz => &["rwz"],
410 FileType::Btf => &["btf"],
411 FileType::Mng => &["mng", "jng"],
412 FileType::Cr2 => &["cr2"],
414 FileType::Cr3 => &["cr3"],
415 FileType::Crw => &["crw", "ciff"],
416 FileType::Nef => &["nef", "nrw"],
417 FileType::Arw => &["arw"],
418 FileType::Sr2 => &["sr2"],
419 FileType::Srf => &["srf"],
420 FileType::Orf => &["orf", "ori"],
421 FileType::Rw2 => &["rw2"],
422 FileType::Dng => &["dng"],
423 FileType::Raf => &["raf"],
424 FileType::Pef => &["pef"],
425 FileType::Dcr => &["dcr"],
426 FileType::Mrw => &["mrw"],
427 FileType::Erf => &["erf"],
428 FileType::Fff => &["fff"],
429 FileType::Iiq => &["iiq"],
430 FileType::Rwl => &["rwl"],
431 FileType::Mef => &["mef"],
432 FileType::Srw => &["srw"],
433 FileType::X3f => &["x3f"],
434 FileType::Gpr => &["gpr"],
435 FileType::Arq => &["arq"],
436 FileType::ThreeFR => &["3fr"],
437 FileType::Crm => &["crm"],
438 FileType::Mp4 => &["mp4", "m4v"],
440 FileType::QuickTime => &["mov", "qt"],
441 FileType::Avi => &["avi"],
442 FileType::Mkv => &["mkv", "mks"],
443 FileType::WebM => &["webm"],
444 FileType::Wmv => &["wmv"],
445 FileType::Asf => &["asf"],
446 FileType::Flv => &["flv"],
447 FileType::Mxf => &["mxf"],
448 FileType::M2ts => &["m2ts", "mts", "m2t", "ts"],
449 FileType::Mpeg => &["mpg", "mpeg", "m2v", "mpv"],
450 FileType::ThreeGP => &["3gp", "3gpp", "3g2", "3gp2"],
451 FileType::RealMedia => &["rm", "rv", "rmvb"],
452 FileType::R3d => &["r3d"],
453 FileType::Dvb => &["dvb"],
454 FileType::Lrv => &["lrv", "lrf"],
455 FileType::Mqv => &["mqv"],
456 FileType::F4v => &["f4v", "f4a", "f4b", "f4p"],
457 FileType::Wtv => &["wtv"],
458 FileType::DvrMs => &["dvr-ms"],
459 FileType::Mp3 => &["mp3"],
461 FileType::Flac => &["flac"],
462 FileType::Ogg => &["ogg", "oga", "ogv"],
463 FileType::Wav => &["wav"],
464 FileType::Aiff => &["aiff", "aif", "aifc"],
465 FileType::Aac => &["aac"],
466 FileType::Opus => &["opus"],
467 FileType::Mpc => &["mpc"],
468 FileType::Ape => &["ape"],
469 FileType::WavPack => &["wv", "wvp"],
470 FileType::Ofr => &["ofr"],
471 FileType::Dsf => &["dsf"],
472 FileType::Audible => &["aa", "aax"],
473 FileType::RealAudio => &["ra"],
474 FileType::Wma => &["wma"],
475 FileType::M4a => &["m4a", "m4b", "m4p"],
476 FileType::Pdf => &["pdf"],
478 FileType::PostScript => &["ps", "eps", "epsf"],
479 FileType::Doc => &["doc", "dot"],
480 FileType::Docx => &["docx", "docm"],
481 FileType::Xls => &["xls", "xlt"],
482 FileType::Xlsx => &["xlsx", "xlsm", "xlsb"],
483 FileType::Ppt => &["ppt", "pps", "pot"],
484 FileType::Pptx => &["pptx", "pptm"],
485 FileType::InDesign => &["ind", "indd", "indt"],
486 FileType::Rtf => &["rtf"],
487 FileType::Zip => &["zip"],
489 FileType::Rar => &["rar"],
490 FileType::SevenZ => &["7z"],
491 FileType::Gzip => &["gz", "gzip"],
492 FileType::Xmp => &["xmp", "inx", "xml"],
494 FileType::Mie => &["mie"],
495 FileType::Exv => &["exv"],
496 FileType::Vrd => &["vrd", "dr4"],
497 FileType::Icc => &["icc", "icm"],
498 FileType::Html => &["html", "htm", "xhtml", "svg"],
499 FileType::Exe => &["exe", "dll", "elf", "so", "dylib", "a", "macho", "o"],
500 FileType::Font => &["ttf", "otf", "woff", "woff2", "ttc", "dfont", "afm", "pfa", "pfb", "pfm"],
501 FileType::Swf => &["swf"],
502 FileType::Dicom => &["dcm"],
503 FileType::Fits => &["fits", "fit", "fts"],
504 }
505 }
506
507 pub fn support(self) -> Support {
509 match self {
510 FileType::Xmp | FileType::Mie | FileType::Exv => Support::ReadWriteCreate,
512 FileType::Jpeg
514 | FileType::Tiff
515 | FileType::Png
516 | FileType::Gif
517 | FileType::WebP
518 | FileType::Heif
519 | FileType::Avif
520 | FileType::Psd
521 | FileType::Jp2
522 | FileType::Jxl
523 | FileType::Jxr
524 | FileType::Flif
525 | FileType::Cr2
526 | FileType::Cr3
527 | FileType::Crw
528 | FileType::Nef
529 | FileType::Arw
530 | FileType::Arq
531 | FileType::Sr2
532 | FileType::Orf
533 | FileType::Rw2
534 | FileType::Dng
535 | FileType::Raf
536 | FileType::Pef
537 | FileType::Erf
538 | FileType::Fff
539 | FileType::Iiq
540 | FileType::Rwl
541 | FileType::Mef
542 | FileType::Srw
543 | FileType::X3f
544 | FileType::Gpr
545 | FileType::Crm
546 | FileType::Mp4
547 | FileType::QuickTime
548 | FileType::ThreeGP
549 | FileType::Dvb
550 | FileType::Lrv
551 | FileType::Mqv
552 | FileType::F4v
553 | FileType::Pdf
554 | FileType::PostScript
555 | FileType::InDesign
556 | FileType::Vrd
557 | FileType::Audible => Support::ReadWrite,
558 _ => Support::Read,
560 }
561 }
562
563 pub fn all() -> &'static [FileType] {
565 ALL_FILE_TYPES
566 }
567}
568
569static ALL_FILE_TYPES: &[FileType] = &[
570 FileType::Jpeg, FileType::Tiff, FileType::Png, FileType::Gif, FileType::Bmp,
572 FileType::WebP, FileType::Heif, FileType::Avif, FileType::Psd, FileType::Jp2,
573 FileType::J2c, FileType::Jxl, FileType::Jxr, FileType::Flif, FileType::Bpg,
574 FileType::Exr, FileType::Ico,
575 FileType::DjVu, FileType::Xcf, FileType::Pcx, FileType::Pict, FileType::Psp,
577 FileType::Hdr, FileType::Rwz, FileType::Btf, FileType::Mng,
578 FileType::Cr2, FileType::Cr3, FileType::Crw, FileType::Nef, FileType::Arw,
580 FileType::Sr2, FileType::Srf, FileType::Orf, FileType::Rw2, FileType::Dng,
581 FileType::Raf, FileType::Pef, FileType::Dcr, FileType::Mrw, FileType::Erf,
582 FileType::Fff, FileType::Iiq, FileType::Rwl, FileType::Mef, FileType::Srw,
583 FileType::X3f, FileType::Gpr, FileType::Arq, FileType::ThreeFR, FileType::Crm,
584 FileType::Mp4, FileType::QuickTime, FileType::Avi, FileType::Mkv, FileType::WebM,
586 FileType::Wmv, FileType::Asf, FileType::Flv, FileType::Mxf, FileType::M2ts,
587 FileType::Mpeg, FileType::ThreeGP, FileType::RealMedia, FileType::R3d,
588 FileType::Dvb, FileType::Lrv, FileType::Mqv, FileType::F4v, FileType::Wtv,
589 FileType::DvrMs,
590 FileType::Mp3, FileType::Flac, FileType::Ogg, FileType::Wav, FileType::Aiff,
592 FileType::Aac, FileType::Opus, FileType::Mpc, FileType::Ape, FileType::WavPack,
593 FileType::Ofr, FileType::Dsf, FileType::Audible, FileType::RealAudio,
594 FileType::Wma, FileType::M4a,
595 FileType::Pdf, FileType::PostScript, FileType::Doc, FileType::Docx,
597 FileType::Xls, FileType::Xlsx, FileType::Ppt, FileType::Pptx,
598 FileType::InDesign, FileType::Rtf,
599 FileType::Zip, FileType::Rar, FileType::SevenZ, FileType::Gzip,
601 FileType::Xmp, FileType::Mie, FileType::Exv, FileType::Vrd, FileType::Icc,
603 FileType::Html, FileType::Exe, FileType::Font, FileType::Swf,
604 FileType::Dicom, FileType::Fits,
605];
606
607pub fn detect_from_magic(header: &[u8]) -> Option<FileType> {
609 if header.len() < 4 {
610 return None;
611 }
612
613 if header.starts_with(&[0xFF, 0xD8, 0xFF]) {
617 return Some(FileType::Jpeg);
618 }
619
620 if header.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
622 return Some(FileType::Png);
623 }
624
625 if header.starts_with(b"GIF8") && header.len() >= 6 && (header[4] == b'7' || header[4] == b'9') {
627 return Some(FileType::Gif);
628 }
629
630 if header.len() >= 4 {
632 let is_le = header[0] == b'I' && header[1] == b'I' && header[2] == 0x2A && header[3] == 0x00;
633 let is_be = header[0] == b'M' && header[1] == b'M' && header[2] == 0x00 && header[3] == 0x2A;
634 if is_le || is_be {
635 if header.len() >= 10 && is_le && header[8] == b'C' && header[9] == b'R' {
637 return Some(FileType::Cr2);
638 }
639 if header.len() >= 4 && is_le && header[0] == b'I' && header[1] == b'I' {
641 if header.len() >= 8 {
642 }
645 }
646 return Some(FileType::Tiff);
649 }
650 let is_btf_le = header[0] == b'I' && header[1] == b'I' && header[2] == 0x2B && header[3] == 0x00;
652 let is_btf_be = header[0] == b'M' && header[1] == b'M' && header[2] == 0x00 && header[3] == 0x2B;
653 if is_btf_le || is_btf_be {
654 return Some(FileType::Btf);
655 }
656 }
657
658 if header.starts_with(b"BM") && header.len() >= 6 {
660 return Some(FileType::Bmp);
661 }
662
663 if header.len() >= 12 && header.starts_with(b"RIFF") {
665 match &header[8..12] {
666 b"WEBP" => return Some(FileType::WebP),
667 b"AVI " => return Some(FileType::Avi),
668 b"WAVE" => return Some(FileType::Wav),
669 _ => {}
670 }
671 }
672
673 if header.starts_with(b"8BPS") {
675 return Some(FileType::Psd);
676 }
677
678 if header.len() >= 12 && header.starts_with(&[0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20]) {
680 return Some(FileType::Jp2);
681 }
682
683 if header.starts_with(&[0xFF, 0x4F, 0xFF, 0x51]) {
685 return Some(FileType::J2c);
686 }
687
688 if header.len() >= 2 && header[0] == 0xFF && header[1] == 0x0A {
690 return Some(FileType::Jxl);
691 }
692 if header.len() >= 12 && header.starts_with(&[0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20]) {
693 return Some(FileType::Jxl);
694 }
695
696 if header.starts_with(b"FLIF") {
698 return Some(FileType::Flif);
699 }
700
701 if header.starts_with(&[0x42, 0x50, 0x47, 0xFB]) {
703 return Some(FileType::Bpg);
704 }
705
706 if header.starts_with(&[0x76, 0x2F, 0x31, 0x01]) {
708 return Some(FileType::Exr);
709 }
710
711 if header.len() >= 4 && header[0] == 0 && header[1] == 0 && (header[2] == 1 || header[2] == 2) && header[3] == 0 {
713 return Some(FileType::Ico);
714 }
715
716 if header.len() >= 8 && header.starts_with(b"AT&TFORM") {
718 return Some(FileType::DjVu);
719 }
720
721 if header.starts_with(b"gimp xcf") {
723 return Some(FileType::Xcf);
724 }
725
726 if header.starts_with(&[0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
728 return Some(FileType::Mng);
729 }
730
731 if header.starts_with(&[0x8B, 0x4A, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
733 return Some(FileType::Mng);
734 }
735
736 if header.len() >= 10 && header.starts_with(b"#?RADIANCE") {
738 return Some(FileType::Hdr);
739 }
740
741 if header.len() >= 15 && header.starts_with(b"FUJIFILMCCD-RAW") {
745 return Some(FileType::Raf);
746 }
747
748 if header.len() >= 14 && header[0] == b'I' && header[1] == b'I'
750 && header[2] == 0x1A && header[3] == 0x00
751 && &header[6..14] == b"HEAPCCDR"
752 {
753 return Some(FileType::Crw);
754 }
755
756 if header.starts_with(&[0x00, 0x4D, 0x52, 0x4D]) {
758 return Some(FileType::Mrw);
759 }
760
761 if header.starts_with(b"FOVb") {
763 return Some(FileType::X3f);
764 }
765
766 if header.len() >= 4 && header[0] == b'I' && header[1] == b'I' && header[2] == 0x55 && header[3] == 0x00 {
768 return Some(FileType::Rw2);
769 }
770
771 if header.len() >= 12 && &header[4..8] == b"ftyp" {
775 let brand = &header[8..12];
776 if brand == b"heic" || brand == b"mif1" || brand == b"heim" || brand == b"heis"
778 || brand == b"msf1"
779 {
780 return Some(FileType::Heif);
781 }
782 if brand == b"avif" || brand == b"avis" {
784 return Some(FileType::Avif);
785 }
786 if brand == b"crx " {
788 return Some(FileType::Cr3);
789 }
790 if brand == b"qt " {
792 return Some(FileType::QuickTime);
793 }
794 if brand == b"3gp4" || brand == b"3gp5" || brand == b"3gp6" || brand == b"3g2a" {
796 return Some(FileType::ThreeGP);
797 }
798 if brand == b"M4A " || brand == b"M4B " || brand == b"M4P " {
800 return Some(FileType::M4a);
801 }
802 if brand == b"M4V " || brand == b"M4VH" || brand == b"M4VP" {
803 return Some(FileType::Mp4);
804 }
805 if brand == b"F4V " || brand == b"F4P " {
807 return Some(FileType::F4v);
808 }
809 return Some(FileType::Mp4);
811 }
812
813 if header.len() >= 8 {
815 let atom_type = &header[4..8];
816 if atom_type == b"moov" || atom_type == b"mdat" || atom_type == b"wide"
817 || atom_type == b"free" || atom_type == b"pnot" || atom_type == b"skip"
818 {
819 return Some(FileType::QuickTime);
820 }
821 }
822
823 if header.starts_with(&[0x1A, 0x45, 0xDF, 0xA3]) {
825 return Some(FileType::Mkv);
826 }
828
829 if header.starts_with(b"FLV\x01") {
831 return Some(FileType::Flv);
832 }
833
834 if header.len() >= 16 && header.starts_with(&[0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11]) {
836 return Some(FileType::Asf);
837 }
839
840 if header.len() >= 8 && header.starts_with(&[0x06, 0x0E, 0x2B, 0x34]) {
842 return Some(FileType::Mxf);
843 }
844
845 if header.len() >= 40 && &header[36..40] == b"acsp" {
847 return Some(FileType::Icc);
848 }
849
850 if header.len() >= 4 && header[0] == 0 && header[1] == 0 && header[2] == 1
852 && (header[3] == 0xBA || header[3] == 0xBB || (header[3] & 0xF0) == 0xE0)
853 {
854 return Some(FileType::Mpeg);
855 }
856
857 if header.len() >= 1 && header[0] == 0x47 {
859 if header.len() >= 376 && header[188] == 0x47 {
860 return Some(FileType::M2ts);
861 }
862 if header.len() >= 384 && header[192] == 0x47 {
863 return Some(FileType::M2ts);
864 }
865 }
866
867 if header.starts_with(b".RMF") {
869 return Some(FileType::RealMedia);
870 }
871
872 if header.starts_with(b"RED1") || header.starts_with(b"RED2") {
874 return Some(FileType::R3d);
875 }
876
877 if header.starts_with(b"ID3") {
881 return Some(FileType::Mp3);
882 }
883 if header.len() >= 2 && header[0] == 0xFF && (header[1] & 0xE0) == 0xE0 {
885 return Some(FileType::Mp3);
886 }
887
888 if header.starts_with(b"fLaC") {
890 return Some(FileType::Flac);
891 }
892
893 if header.starts_with(b"OggS") {
895 return Some(FileType::Ogg);
896 }
897
898 if header.len() >= 12 && header.starts_with(b"FORM") {
900 if &header[8..12] == b"AIFF" || &header[8..12] == b"AIFC" {
901 return Some(FileType::Aiff);
902 }
903 }
904
905 if header.starts_with(b"MAC ") {
907 return Some(FileType::Ape);
908 }
909
910 if header.starts_with(b"MP+") || header.starts_with(b"MPCK") {
912 return Some(FileType::Mpc);
913 }
914
915 if header.starts_with(b"wvpk") {
917 return Some(FileType::WavPack);
918 }
919
920 if header.starts_with(b"DSD ") {
922 return Some(FileType::Dsf);
923 }
924
925 if header.starts_with(b"OFR ") {
927 return Some(FileType::Ofr);
928 }
929
930 if header.len() >= 4 && header[0] == b'.' && header[1] == b'r' && header[2] == b'a' && header[3] == 0xFD {
932 return Some(FileType::RealAudio);
933 }
934
935 if header.starts_with(b"%PDF-") {
939 return Some(FileType::Pdf);
940 }
941
942 if header.starts_with(b"%!PS") || header.starts_with(b"%!Adobe") {
944 return Some(FileType::PostScript);
945 }
946
947 if header.starts_with(&[0xD0, 0xCF, 0x11, 0xE0]) {
949 return Some(FileType::Doc); }
951
952 if header.starts_with(b"{\\rtf") {
954 return Some(FileType::Rtf);
955 }
956
957 if header.len() >= 8 && header.starts_with(&[0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5]) {
959 return Some(FileType::InDesign);
960 }
961
962 if header.starts_with(&[0x50, 0x4B, 0x03, 0x04]) {
966 return Some(FileType::Zip);
967 }
969
970 if header.len() >= 6 && header.starts_with(b"Rar!\x1A\x07") {
972 return Some(FileType::Rar);
973 }
974
975 if header.len() >= 6 && header.starts_with(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]) {
977 return Some(FileType::SevenZ);
978 }
979
980 if header.len() >= 2 && header[0] == 0x1F && header[1] == 0x8B {
982 return Some(FileType::Gzip);
983 }
984
985 if header.len() >= 3
989 && ((header[0] == b'F' || header[0] == b'C' || header[0] == b'Z')
990 && header[1] == b'W'
991 && header[2] == b'S')
992 {
993 return Some(FileType::Swf);
994 }
995
996 if header.len() >= 132 && &header[128..132] == b"DICM" {
998 return Some(FileType::Dicom);
999 }
1000
1001 if header.len() >= 9 && header.starts_with(b"SIMPLE =") {
1003 return Some(FileType::Fits);
1004 }
1005
1006 if header.len() >= 4 && header[0] == 0x7E && header[1] == 0x10 && header[2] == 0x04 {
1008 return Some(FileType::Mie);
1009 }
1010
1011 if header.starts_with(b"<?xpacket") || header.starts_with(b"<x:xmpmeta") {
1013 return Some(FileType::Xmp);
1014 }
1015
1016 if header.starts_with(b"<?xml") || header.starts_with(b"<svg") {
1018 let preview = &header[..header.len().min(512)];
1019 if preview.windows(4).any(|w| w == b"<svg") {
1020 return Some(FileType::Html); }
1022 if preview.windows(5).any(|w| w == b"<html" || w == b"<HTML") {
1023 return Some(FileType::Html); }
1025 if preview.windows(10).any(|w| w == b"<x:xmpmeta") || preview.windows(9).any(|w| w == b"<?xpacket") {
1026 return Some(FileType::Xmp);
1027 }
1028 if preview.windows(4).any(|w| w == b"<rdf" || w == b"<RDF") {
1029 return Some(FileType::Xmp);
1030 }
1031 return Some(FileType::Xmp);
1033 }
1034
1035 if header.starts_with(b"<!DOCTYPE html") || header.starts_with(b"<!doctype html")
1037 || header.starts_with(b"<!DOCTYPE HTML") || header.starts_with(b"<html") || header.starts_with(b"<HTML")
1038 {
1039 return Some(FileType::Html);
1040 }
1041
1042 if header.starts_with(b"MZ") {
1044 return Some(FileType::Exe);
1045 }
1046
1047 if (header.starts_with(&[0x00, 0x01, 0x00, 0x00]) || header.starts_with(b"true") || header.starts_with(b"typ1"))
1049 && header.len() >= 12
1050 {
1051 return Some(FileType::Font);
1052 }
1053
1054 if header.starts_with(b"ttcf") {
1056 return Some(FileType::Font);
1057 }
1058
1059 if header.starts_with(b"OTTO") {
1061 return Some(FileType::Font);
1062 }
1063
1064 if header.starts_with(b"wOFF") {
1066 return Some(FileType::Font);
1067 }
1068
1069 if header.starts_with(b"wOF2") {
1071 return Some(FileType::Font);
1072 }
1073
1074 None
1075}
1076
1077pub fn detect_from_extension(ext: &str) -> Option<FileType> {
1079 let ext_lower = ext.to_ascii_lowercase();
1080 let ext_lower = ext_lower.trim_start_matches('.');
1081
1082 for &ft in ALL_FILE_TYPES {
1083 for known_ext in ft.extensions() {
1084 if ext_lower == *known_ext {
1085 return Some(ft);
1086 }
1087 }
1088 }
1089
1090 None
1091}
1092
1093impl std::fmt::Display for FileType {
1094 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1095 write!(f, "{}", self.description())
1096 }
1097}
1098
1099impl std::fmt::Display for Support {
1100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1101 match self {
1102 Support::Read => write!(f, "R"),
1103 Support::ReadWrite => write!(f, "R/W"),
1104 Support::ReadWriteCreate => write!(f, "R/W/C"),
1105 }
1106 }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111 use super::*;
1112
1113 #[test]
1114 fn test_detect_jpeg() {
1115 let header = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, b'J', b'F', b'I', b'F'];
1116 assert_eq!(detect_from_magic(&header), Some(FileType::Jpeg));
1117 }
1118
1119 #[test]
1120 fn test_detect_png() {
1121 let header = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
1122 assert_eq!(detect_from_magic(&header), Some(FileType::Png));
1123 }
1124
1125 #[test]
1126 fn test_detect_tiff_le() {
1127 let header = [b'I', b'I', 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00];
1128 assert_eq!(detect_from_magic(&header), Some(FileType::Tiff));
1129 }
1130
1131 #[test]
1132 fn test_detect_tiff_be() {
1133 let header = [b'M', b'M', 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08];
1134 assert_eq!(detect_from_magic(&header), Some(FileType::Tiff));
1135 }
1136
1137 #[test]
1138 fn test_detect_cr2() {
1139 let header = [b'I', b'I', 0x2A, 0x00, 0x10, 0x00, 0x00, 0x00, b'C', b'R'];
1140 assert_eq!(detect_from_magic(&header), Some(FileType::Cr2));
1141 }
1142
1143 #[test]
1144 fn test_detect_pdf() {
1145 let header = b"%PDF-1.7 some more data here";
1146 assert_eq!(detect_from_magic(header), Some(FileType::Pdf));
1147 }
1148
1149 #[test]
1150 fn test_detect_webp() {
1151 let header = b"RIFF\x00\x00\x00\x00WEBP";
1152 assert_eq!(detect_from_magic(header), Some(FileType::WebP));
1153 }
1154
1155 #[test]
1156 fn test_detect_heif() {
1157 let mut header = [0u8; 16];
1158 header[4..8].copy_from_slice(b"ftyp");
1159 header[8..12].copy_from_slice(b"heic");
1160 assert_eq!(detect_from_magic(&header), Some(FileType::Heif));
1161 }
1162
1163 #[test]
1164 fn test_detect_avif() {
1165 let mut header = [0u8; 16];
1166 header[4..8].copy_from_slice(b"ftyp");
1167 header[8..12].copy_from_slice(b"avif");
1168 assert_eq!(detect_from_magic(&header), Some(FileType::Avif));
1169 }
1170
1171 #[test]
1172 fn test_detect_cr3() {
1173 let mut header = [0u8; 16];
1174 header[4..8].copy_from_slice(b"ftyp");
1175 header[8..12].copy_from_slice(b"crx ");
1176 assert_eq!(detect_from_magic(&header), Some(FileType::Cr3));
1177 }
1178
1179 #[test]
1180 fn test_detect_flac() {
1181 assert_eq!(detect_from_magic(b"fLaC\x00\x00"), Some(FileType::Flac));
1182 }
1183
1184 #[test]
1185 fn test_detect_ogg() {
1186 assert_eq!(detect_from_magic(b"OggS\x00\x02"), Some(FileType::Ogg));
1187 }
1188
1189 #[test]
1190 fn test_detect_mp3_id3() {
1191 assert_eq!(detect_from_magic(b"ID3\x04\x00"), Some(FileType::Mp3));
1192 }
1193
1194 #[test]
1195 fn test_detect_rar() {
1196 assert_eq!(detect_from_magic(b"Rar!\x1A\x07\x01\x00"), Some(FileType::Rar));
1197 }
1198
1199 #[test]
1200 fn test_detect_7z() {
1201 assert_eq!(detect_from_magic(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]), Some(FileType::SevenZ));
1202 }
1203
1204 #[test]
1205 fn test_detect_gzip() {
1206 assert_eq!(detect_from_magic(&[0x1F, 0x8B, 0x08, 0x00]), Some(FileType::Gzip));
1207 }
1208
1209 #[test]
1210 fn test_detect_raf() {
1211 assert_eq!(detect_from_magic(b"FUJIFILMCCD-RAW 0201"), Some(FileType::Raf));
1212 }
1213
1214 #[test]
1215 fn test_detect_psd() {
1216 assert_eq!(detect_from_magic(b"8BPS\x00\x01"), Some(FileType::Psd));
1217 }
1218
1219 #[test]
1220 fn test_detect_from_extension() {
1221 assert_eq!(detect_from_extension("jpg"), Some(FileType::Jpeg));
1222 assert_eq!(detect_from_extension(".JPEG"), Some(FileType::Jpeg));
1223 assert_eq!(detect_from_extension("cr2"), Some(FileType::Cr2));
1224 assert_eq!(detect_from_extension("cr3"), Some(FileType::Cr3));
1225 assert_eq!(detect_from_extension("nef"), Some(FileType::Nef));
1226 assert_eq!(detect_from_extension("arw"), Some(FileType::Arw));
1227 assert_eq!(detect_from_extension("dng"), Some(FileType::Dng));
1228 assert_eq!(detect_from_extension("raf"), Some(FileType::Raf));
1229 assert_eq!(detect_from_extension("mp4"), Some(FileType::Mp4));
1230 assert_eq!(detect_from_extension("mov"), Some(FileType::QuickTime));
1231 assert_eq!(detect_from_extension("flac"), Some(FileType::Flac));
1232 assert_eq!(detect_from_extension("docx"), Some(FileType::Docx));
1233 assert_eq!(detect_from_extension("xlsx"), Some(FileType::Xlsx));
1234 assert_eq!(detect_from_extension("3fr"), Some(FileType::ThreeFR));
1235 assert_eq!(detect_from_extension("xyz"), None);
1236 }
1237
1238 #[test]
1239 fn test_all_types_have_extensions() {
1240 for &ft in FileType::all() {
1241 assert!(!ft.extensions().is_empty(), "{:?} has no extensions", ft);
1242 }
1243 }
1244
1245 #[test]
1246 fn test_all_types_have_mime() {
1247 for &ft in FileType::all() {
1248 assert!(!ft.mime_type().is_empty(), "{:?} has no MIME type", ft);
1249 }
1250 }
1251
1252 #[test]
1253 fn test_total_format_count() {
1254 assert!(FileType::all().len() >= 100, "Expected 100+ formats, got {}", FileType::all().len());
1255 }
1256}