Skip to main content

exiftool_rs/
file_type.rs

1/// Supported file types and their detection logic.
2///
3/// Mirrors ExifTool's %fileTypeLookup, %magicNumber, and %mimeType.
4/// Covers all 150+ formats supported by ExifTool.
5///
6/// Known file types that exiftool can process.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[allow(non_camel_case_types)]
9pub enum FileType {
10    // ===== Images - Standard =====
11    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    Jps,
29    // ===== Images - Specialized =====
30    DjVu,
31    Xcf,
32    Pcx,
33    Pict,
34    Psp,
35    Hdr,
36    Rwz,
37    Btf,
38    Mng,
39    PhotoCd,
40    // ===== Images - RAW =====
41    Cr2,
42    Cr3,
43    Crw,
44    Nef,
45    Arw,
46    Sr2,
47    Srf,
48    Orf,
49    Rw2,
50    Dng,
51    Raf,
52    Pef,
53    Dcr,
54    Mrw,
55    Erf,
56    Fff,
57    Iiq,
58    Rwl,
59    Mef,
60    Srw,
61    X3f,
62    Gpr,
63    Arq,
64    ThreeFR,
65    Crm,
66    // ===== Video =====
67    Mp4,
68    QuickTime,
69    Avi,
70    Mkv,
71    WebM,
72    Wmv,
73    Asf,
74    Flv,
75    Mxf,
76    Czi,
77    M2ts,
78    Mpeg,
79    ThreeGP,
80    RealMedia,
81    R3d,
82    Dvb,
83    Lrv,
84    Mqv,
85    F4v,
86    Wtv,
87    DvrMs,
88    // ===== Audio =====
89    Mp3,
90    Flac,
91    Ogg,
92    Wav,
93    Aiff,
94    Aac,
95    Opus,
96    Mpc,
97    Ape,
98    WavPack,
99    Ofr,
100    Dsf,
101    Audible,
102    RealAudio,
103    Wma,
104    M4a,
105    Dss,
106    // ===== Documents =====
107    Pdf,
108    PostScript,
109    Doc,
110    Docx,
111    Xls,
112    Xlsx,
113    Ppt,
114    Pptx,
115    Numbers,
116    Pages,
117    Key,
118    InDesign,
119    Rtf,
120    // ===== Archives =====
121    Zip,
122    Rar,
123    SevenZ,
124    Gzip,
125    // ===== Metadata / Other =====
126    Xmp,
127    Mie,
128    Exv,
129    Vrd,
130    Dr4,
131    Icc,
132    Html,
133    Exe,
134    Font,
135    Swf,
136    Dicom,
137    Fits,
138    // ===== Newly added =====
139    Mrc,
140    Moi,
141    MacOs,
142    Json,
143    Pcap,
144    Pcapng,
145    Svg,
146    // ===== New formats =====
147    Pgf,
148    Xisf,
149    Torrent,
150    Mobi,
151    SonyPmp,
152    // ===== Additional formats =====
153    Plist,
154    Aae,
155    KyoceraRaw,
156    // ===== Lytro Light Field Picture =====
157    Lfp,
158    // ===== Portable Float Map =====
159    PortableFloatMap,
160    // ===== FLIR thermal =====
161    Fpf,
162    // ===== OpenDocument =====
163    Ods,
164    Odt,
165    Odp,
166    Odg,
167    Odf,
168    Odb,
169    Odi,
170    Odc,
171    // ===== CaptureOne Enhanced Image Package =====
172    Eip,
173    // ===== Leica Image Format =====
174    Lif,
175    // ===== Plain text formats =====
176    Txt,
177    Csv,
178    Vcard,
179    Ics,
180    Url,
181    Lnk,
182    Ppm,
183    Dpx,
184    Ram,
185    // ===== Formats with readers but previously lacking a FileType identity =====
186    Miff,
187    Tnef,
188    Wpg,
189    Dv,
190    Itc,
191    Iso,
192    Afm,
193    Pfa,
194    Pfb,
195    Dfont,
196    Xml,
197    Inx,
198    Eps,
199}
200
201/// Indicates the read/write capability for a file type.
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum Support {
204    Read,
205    ReadWrite,
206    ReadWriteCreate,
207}
208
209impl FileType {
210    /// Human-readable file type description.
211    pub fn description(self) -> &'static str {
212        match self {
213            // Standard images
214            FileType::Jpeg => "JPEG image",
215            FileType::Tiff => "TIFF image",
216            FileType::Png => "PNG image",
217            FileType::Gif => "GIF image",
218            FileType::Bmp => "BMP image",
219            FileType::WebP => "WebP image",
220            FileType::Heif => "HEIF/HEIC image",
221            FileType::Avif => "AVIF image",
222            FileType::Psd => "Adobe Photoshop Document",
223            FileType::Jp2 => "JPEG 2000 image",
224            FileType::J2c => "JPEG 2000 Codestream",
225            FileType::Jxl => "JPEG XL image",
226            FileType::Jxr => "JPEG XR / HD Photo",
227            FileType::Flif => "Free Lossless Image Format",
228            FileType::Bpg => "Better Portable Graphics",
229            FileType::Exr => "OpenEXR image",
230            FileType::Ico => "Windows Icon",
231            FileType::Jps => "JPEG Stereo image",
232            // Specialized images
233            FileType::DjVu => "DjVu document",
234            FileType::Xcf => "GIMP image",
235            FileType::Pcx => "PCX image",
236            FileType::Pict => "Apple PICT",
237            FileType::Psp => "Paint Shop Pro image",
238            FileType::Pgf => "Progressive Graphics File",
239            FileType::Xisf => "PixInsight XISF image",
240            FileType::Torrent => "BitTorrent descriptor",
241            FileType::Mobi => "Mobipocket Book",
242            FileType::SonyPmp => "Sony PMP video",
243            FileType::Plist => "PLIST",
244            FileType::Aae => "AAE",
245            FileType::KyoceraRaw => "Kyocera Contax N RAW",
246            FileType::Lfp => "LFP",
247            FileType::PortableFloatMap => "Portable Float Map",
248            FileType::Hdr => "Radiance HDR",
249            FileType::Rwz => "Rawzor compressed image",
250            FileType::Btf => "BigTIFF image",
251            FileType::Mng => "MNG animation",
252            FileType::PhotoCd => "Kodak Photo CD",
253            // RAW
254            FileType::Cr2 => "Canon CR2 RAW",
255            FileType::Cr3 => "Canon CR3 RAW",
256            FileType::Crw => "Canon CRW RAW",
257            FileType::Nef => "Nikon NEF RAW",
258            FileType::Arw => "Sony ARW RAW",
259            FileType::Sr2 => "Sony SR2 RAW",
260            FileType::Srf => "Sony SRF RAW",
261            FileType::Orf => "Olympus ORF RAW",
262            FileType::Rw2 => "Panasonic RW2 RAW",
263            FileType::Dng => "Adobe Digital Negative",
264            FileType::Raf => "Fujifilm RAF RAW",
265            FileType::Pef => "Pentax PEF RAW",
266            FileType::Dcr => "Kodak DCR RAW",
267            FileType::Mrw => "Minolta MRW RAW",
268            FileType::Erf => "Epson ERF RAW",
269            FileType::Fff => "Hasselblad FFF RAW",
270            FileType::Iiq => "Phase One IIQ RAW",
271            FileType::Rwl => "Leica RWL RAW",
272            FileType::Mef => "Mamiya MEF RAW",
273            FileType::Srw => "Samsung SRW RAW",
274            FileType::X3f => "Sigma X3F RAW",
275            FileType::Gpr => "GoPro GPR RAW",
276            FileType::Arq => "Sony ARQ RAW",
277            FileType::ThreeFR => "Hasselblad 3FR RAW",
278            FileType::Crm => "Canon Cinema RAW",
279            // Video
280            FileType::Mp4 => "MP4 video",
281            FileType::QuickTime => "QuickTime video",
282            FileType::Avi => "AVI",
283            FileType::Mkv => "Matroska video",
284            FileType::WebM => "WebM video",
285            FileType::Wmv => "Windows Media Video",
286            FileType::Asf => "Advanced Systems Format",
287            FileType::Flv => "Flash Video",
288            FileType::Mxf => "Material Exchange Format",
289            FileType::Czi => "CZI",
290            FileType::M2ts => "MPEG-2 Transport Stream",
291            FileType::Mpeg => "MPEG video",
292            FileType::ThreeGP => "3GPP multimedia",
293            FileType::RealMedia => "RealMedia",
294            FileType::R3d => "Redcode RAW video",
295            FileType::Dvb => "Digital Video Broadcasting",
296            FileType::Lrv => "GoPro Low-Res Video",
297            FileType::Mqv => "Sony Movie",
298            FileType::F4v => "Adobe Flash Video",
299            FileType::Wtv => "Windows Recorded TV",
300            FileType::DvrMs => "Microsoft DVR",
301            // Audio
302            FileType::Mp3 => "MP3 audio",
303            FileType::Flac => "FLAC audio",
304            FileType::Ogg => "Ogg Vorbis audio",
305            FileType::Wav => "WAV audio",
306            FileType::Aiff => "AIFF",
307            FileType::Aac => "AAC audio",
308            FileType::Opus => "Opus audio",
309            FileType::Mpc => "Musepack audio",
310            FileType::Ape => "Monkey's Audio",
311            FileType::WavPack => "WavPack audio",
312            FileType::Ofr => "OptimFROG audio",
313            FileType::Dsf => "DSD Stream File",
314            FileType::Audible => "Audible audiobook",
315            FileType::RealAudio => "RealAudio",
316            FileType::Wma => "Windows Media Audio",
317            FileType::M4a => "MPEG-4 Audio",
318            FileType::Dss => "DSS",
319            // Documents
320            FileType::Pdf => "PDF document",
321            FileType::PostScript => "PostScript",
322            FileType::Doc => "Microsoft Word (legacy)",
323            FileType::Docx => "Microsoft Word",
324            FileType::Xls => "Microsoft Excel (legacy)",
325            FileType::Xlsx => "Microsoft Excel",
326            FileType::Ppt => "Microsoft PowerPoint (legacy)",
327            FileType::Pptx => "Microsoft PowerPoint",
328            FileType::Numbers => "Apple Numbers",
329            FileType::Pages => "Apple Pages",
330            FileType::Key => "Apple Keynote",
331            FileType::InDesign => "Adobe InDesign",
332            FileType::Rtf => "Rich Text Format",
333            // Archives
334            FileType::Zip => "ZIP archive",
335            FileType::Rar => "RAR archive",
336            FileType::SevenZ => "7-Zip archive",
337            FileType::Gzip => "GZIP",
338            // Metadata / Other
339            FileType::Xmp => "XMP sidecar",
340            FileType::Mie => "MIE metadata",
341            FileType::Exv => "Exiv2 metadata",
342            FileType::Vrd => "VRD",
343            FileType::Dr4 => "DR4",
344            FileType::Icc => "ICC color profile",
345            FileType::Html => "HTML document",
346            FileType::Exe => "Windows executable",
347            FileType::Font => "Font file",
348            FileType::Swf => "Shockwave Flash",
349            FileType::Dicom => "DICOM medical image",
350            FileType::Fits => "FITS astronomical image",
351            FileType::Mrc => "MRC image",
352            FileType::Moi => "MOI",
353            FileType::MacOs => "MacOS",
354            FileType::Json => "JSON",
355            FileType::Pcap => "PCAP",
356            FileType::Pcapng => "PCAPNG",
357            FileType::Svg => "SVG",
358            FileType::Fpf => "FPF",
359            FileType::Ods => "ODS",
360            FileType::Odt => "ODT",
361            FileType::Odp => "ODP",
362            FileType::Odg => "ODG",
363            FileType::Odf => "ODF",
364            FileType::Odb => "ODB",
365            FileType::Odi => "ODI",
366            FileType::Odc => "ODC",
367            FileType::Eip => "EIP",
368            FileType::Lif => "Leica Image Format",
369            FileType::Txt => "Text",
370            FileType::Csv => "Comma-Separated Values",
371            FileType::Vcard => "vCard",
372            FileType::Ics => "iCalendar",
373            FileType::Url => "Windows URL Shortcut",
374            FileType::Lnk => "Windows Shortcut",
375            FileType::Ppm => "Portable Pixel Map",
376            FileType::Dpx => "Digital Picture Exchange",
377            FileType::Ram => "Real Audio Metadata",
378            FileType::Miff => "Magick Image File Format",
379            FileType::Tnef => "Transport Neutral Encapsulation Format",
380            FileType::Wpg => "WordPerfect Graphics",
381            FileType::Dv => "Digital Video",
382            FileType::Itc => "iTunes Cover Flow",
383            FileType::Iso => "ISO 9660 disk image",
384            FileType::Afm => "Adobe Font Metrics",
385            FileType::Pfa => "PostScript Font ASCII",
386            FileType::Pfb => "PostScript Font Binary",
387            FileType::Dfont => "Macintosh Font",
388            FileType::Xml => "Extensible Markup Language",
389            FileType::Inx => "Adobe InDesign Interchange",
390            FileType::Eps => "Encapsulated PostScript",
391        }
392    }
393
394    /// ExifTool's short `FileType` value (the `%fileTypeLookup` key), e.g. "JPEG",
395    /// "NEF", "MOV" — distinct from the human-readable [`Self::description`].
396    pub fn code(self) -> &'static str {
397        match self {
398            FileType::Jpeg => "JPEG",
399            FileType::Tiff => "TIFF",
400            FileType::Png => "PNG",
401            FileType::Gif => "GIF",
402            FileType::Bmp => "BMP",
403            FileType::WebP => "WEBP",
404            FileType::Heif => "HEIF",
405            FileType::Avif => "AVIF",
406            FileType::Psd => "PSD",
407            FileType::Jp2 => "JP2",
408            FileType::J2c => "J2C",
409            FileType::Jxl => "JXL",
410            FileType::Jxr => "JXR",
411            FileType::Flif => "FLIF",
412            FileType::Bpg => "BPG",
413            FileType::Exr => "EXR",
414            FileType::Ico => "ICO",
415            FileType::Jps => "JPS",
416            FileType::DjVu => "DJVU",
417            FileType::Xcf => "XCF",
418            FileType::Pcx => "PCX",
419            FileType::Pict => "PICT",
420            FileType::Psp => "PSP",
421            FileType::Pgf => "PGF",
422            FileType::Xisf => "XISF",
423            FileType::Torrent => "Torrent",
424            FileType::Mobi => "MOBI",
425            FileType::SonyPmp => "PMP",
426            FileType::Plist => "PLIST",
427            FileType::Aae => "AAE",
428            FileType::KyoceraRaw => "RAW",
429            FileType::Lfp => "LFP",
430            FileType::PortableFloatMap => "PFM",
431            FileType::Hdr => "HDR",
432            FileType::Rwz => "RWZ",
433            FileType::Btf => "BTF",
434            FileType::Mng => "MNG",
435            FileType::PhotoCd => "PCD",
436            FileType::Cr2 => "CR2",
437            FileType::Cr3 => "CR3",
438            FileType::Crw => "CRW",
439            FileType::Nef => "NEF",
440            FileType::Arw => "ARW",
441            FileType::Sr2 => "SR2",
442            FileType::Srf => "SRF",
443            FileType::Orf => "ORF",
444            FileType::Rw2 => "RW2",
445            FileType::Dng => "DNG",
446            FileType::Raf => "RAF",
447            FileType::Pef => "PEF",
448            FileType::Dcr => "DCR",
449            FileType::Mrw => "MRW",
450            FileType::Erf => "ERF",
451            FileType::Fff => "FFF",
452            FileType::Iiq => "IIQ",
453            FileType::Rwl => "RWL",
454            FileType::Mef => "MEF",
455            FileType::Srw => "SRW",
456            FileType::X3f => "X3F",
457            FileType::Gpr => "GPR",
458            FileType::Arq => "ARQ",
459            FileType::ThreeFR => "3FR",
460            FileType::Crm => "CRM",
461            FileType::Mp4 => "MP4",
462            FileType::QuickTime => "MOV",
463            FileType::Avi => "AVI",
464            FileType::Mkv => "MKV",
465            FileType::WebM => "WEBM",
466            FileType::Wmv => "WMV",
467            FileType::Asf => "ASF",
468            FileType::Flv => "FLV",
469            FileType::Mxf => "MXF",
470            FileType::Czi => "CZI",
471            FileType::M2ts => "M2TS",
472            FileType::Mpeg => "MPEG",
473            FileType::ThreeGP => "3GP",
474            FileType::RealMedia => "RM",
475            FileType::R3d => "R3D",
476            FileType::Dvb => "DVB",
477            FileType::Lrv => "LRV",
478            FileType::Mqv => "MQV",
479            FileType::F4v => "F4V",
480            FileType::Wtv => "WTV",
481            FileType::DvrMs => "DVR-MS",
482            FileType::Mp3 => "MP3",
483            FileType::Flac => "FLAC",
484            FileType::Ogg => "OGG",
485            FileType::Wav => "WAV",
486            FileType::Aiff => "AIFF",
487            FileType::Aac => "AAC",
488            FileType::Opus => "OPUS",
489            FileType::Mpc => "MPC",
490            FileType::Ape => "APE",
491            FileType::WavPack => "WV",
492            FileType::Ofr => "OFR",
493            FileType::Dsf => "DSF",
494            FileType::Audible => "AA",
495            FileType::RealAudio => "RA",
496            FileType::Wma => "WMA",
497            FileType::M4a => "M4A",
498            FileType::Dss => "DSS",
499            FileType::Pdf => "PDF",
500            FileType::PostScript => "PS",
501            FileType::Doc => "DOC",
502            FileType::Docx => "DOCX",
503            FileType::Xls => "XLS",
504            FileType::Xlsx => "XLSX",
505            FileType::Ppt => "PPT",
506            FileType::Pptx => "PPTX",
507            FileType::Numbers => "NUMBERS",
508            FileType::Pages => "PAGES",
509            FileType::Key => "KEY",
510            FileType::InDesign => "INDD",
511            FileType::Rtf => "RTF",
512            FileType::Zip => "ZIP",
513            FileType::Rar => "RAR",
514            FileType::SevenZ => "7Z",
515            FileType::Gzip => "GZIP",
516            FileType::Xmp => "XMP",
517            FileType::Mie => "MIE",
518            FileType::Exv => "EXV",
519            FileType::Vrd => "VRD",
520            FileType::Dr4 => "DR4",
521            FileType::Icc => "ICC",
522            FileType::Html => "HTML",
523            FileType::Exe => "EXE",
524            FileType::Font => "TTF",
525            FileType::Swf => "SWF",
526            FileType::Dicom => "DICOM",
527            FileType::Fits => "FITS",
528            FileType::Mrc => "MRC",
529            FileType::Moi => "MOI",
530            FileType::MacOs => "MacOS",
531            FileType::Json => "JSON",
532            FileType::Pcap => "PCAP",
533            FileType::Pcapng => "PCAPNG",
534            FileType::Svg => "SVG",
535            FileType::Fpf => "FPF",
536            FileType::Ods => "ODS",
537            FileType::Odt => "ODT",
538            FileType::Odp => "ODP",
539            FileType::Odg => "ODG",
540            FileType::Odf => "ODF",
541            FileType::Odb => "ODB",
542            FileType::Odi => "ODI",
543            FileType::Odc => "ODC",
544            FileType::Eip => "EIP",
545            FileType::Lif => "LIF",
546            FileType::Txt => "TXT",
547            FileType::Csv => "CSV",
548            FileType::Vcard => "VCard",
549            FileType::Ics => "ICS",
550            FileType::Url => "URL",
551            FileType::Lnk => "LNK",
552            FileType::Ppm => "PPM",
553            FileType::Dpx => "DPX",
554            FileType::Ram => "RAM",
555            FileType::Miff => "MIFF",
556            FileType::Tnef => "TNEF",
557            FileType::Wpg => "WPG",
558            FileType::Dv => "DV",
559            FileType::Itc => "ITC",
560            FileType::Iso => "ISO",
561            FileType::Afm => "AFM",
562            FileType::Pfa => "PFA",
563            FileType::Pfb => "PFB",
564            FileType::Dfont => "DFONT",
565            FileType::Xml => "XML",
566            FileType::Inx => "INX",
567            FileType::Eps => "EPS",
568        }
569    }
570
571    /// MIME type for this file type.
572    pub fn mime_type(self) -> &'static str {
573        match self {
574            FileType::Jpeg => "image/jpeg",
575            FileType::Tiff => "image/tiff",
576            FileType::Btf => "image/x-tiff-big",
577            FileType::Png => "image/png",
578            FileType::Gif => "image/gif",
579            FileType::Bmp => "image/bmp",
580            FileType::WebP => "image/webp",
581            FileType::Heif => "image/heif",
582            FileType::Avif => "image/avif",
583            FileType::Psd => "application/vnd.adobe.photoshop",
584            FileType::Jp2 => "image/jp2",
585            FileType::J2c => "image/x-j2c",
586            FileType::Jxl => "image/jxl",
587            FileType::Jxr => "image/jxr",
588            FileType::Flif => "image/flif",
589            FileType::Bpg => "image/bpg",
590            FileType::Exr => "image/x-exr",
591            FileType::Ico => "image/x-icon",
592            FileType::Jps => "image/x-jps",
593            FileType::DjVu => "image/vnd.djvu",
594            FileType::Xcf => "image/x-xcf",
595            FileType::Pcx => "image/pcx",
596            FileType::Pict => "image/pict",
597            FileType::Psp => "image/x-paintshoppro",
598            FileType::Hdr => "image/vnd.radiance",
599            FileType::Rwz => "image/x-rawzor",
600            FileType::Mng => "video/x-mng",
601            FileType::PhotoCd => "image/x-photo-cd",
602            // RAW → use specific MIME where available
603            FileType::Cr2 => "image/x-canon-cr2",
604            FileType::Cr3 | FileType::Crm => "image/x-canon-cr3",
605            FileType::Crw => "image/x-canon-crw",
606            FileType::Nef => "image/x-nikon-nef",
607            FileType::Arw | FileType::Arq => "image/x-sony-arw",
608            FileType::Sr2 => "image/x-sony-sr2",
609            FileType::Srf => "image/x-sony-srf",
610            FileType::Orf => "image/x-olympus-orf",
611            FileType::Rw2 => "image/x-panasonic-rw2",
612            FileType::Dng | FileType::Gpr => "image/x-adobe-dng",
613            FileType::Raf => "image/x-fujifilm-raf",
614            FileType::Pef => "image/x-pentax-pef",
615            FileType::Dcr => "image/x-kodak-dcr",
616            FileType::Mrw => "image/x-minolta-mrw",
617            FileType::Erf => "image/x-epson-erf",
618            FileType::Fff | FileType::ThreeFR => "image/x-hasselblad-fff",
619            FileType::Iiq => "image/x-raw",
620            FileType::Rwl => "image/x-leica-rwl",
621            FileType::Mef => "image/x-mamiya-mef",
622            FileType::Srw => "image/x-samsung-srw",
623            FileType::X3f => "image/x-sigma-x3f",
624            // Video
625            FileType::Mp4 | FileType::F4v => "video/mp4",
626            FileType::QuickTime | FileType::Mqv => "video/quicktime",
627            FileType::Avi => "video/x-msvideo",
628            FileType::Mkv => "video/x-matroska",
629            FileType::WebM => "video/webm",
630            FileType::Wmv => "video/x-ms-wmv",
631            FileType::Asf => "video/x-ms-asf",
632            FileType::Flv => "video/x-flv",
633            FileType::Mxf => "application/mxf",
634            FileType::Czi => "image/x-zeiss-czi",
635            FileType::M2ts => "video/m2ts",
636            FileType::Mpeg => "video/mpeg",
637            FileType::ThreeGP => "video/3gpp",
638            FileType::RealMedia => "application/vnd.rn-realmedia",
639            FileType::R3d => "video/x-red-r3d",
640            FileType::Dvb => "video/dvb",
641            FileType::Lrv => "video/mp4",
642            FileType::Wtv => "video/x-ms-wtv",
643            FileType::DvrMs => "video/x-ms-dvr",
644            // Audio
645            FileType::Mp3 => "audio/mpeg",
646            FileType::Flac => "audio/flac",
647            FileType::Ogg | FileType::Opus => "audio/ogg",
648            FileType::Wav => "audio/x-wav",
649            FileType::Aiff => "audio/x-aiff",
650            FileType::Aac => "audio/aac",
651            FileType::Mpc => "audio/x-musepack",
652            FileType::Ape => "audio/x-monkeys-audio",
653            FileType::WavPack => "audio/x-wavpack",
654            FileType::Ofr => "audio/x-ofr",
655            FileType::Dsf => "audio/dsf",
656            FileType::Audible => "audio/audible",
657            FileType::RealAudio => "audio/x-pn-realaudio",
658            FileType::Wma => "audio/x-ms-wma",
659            FileType::M4a => "audio/mp4",
660            FileType::Dss => "audio/x-dss",
661            // Documents
662            FileType::Pdf => "application/pdf",
663            FileType::PostScript => "application/postscript",
664            FileType::Doc => "application/msword",
665            FileType::Docx => {
666                "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
667            }
668            FileType::Xls => "application/vnd.ms-excel",
669            FileType::Xlsx => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
670            FileType::Ppt => "application/vnd.ms-powerpoint",
671            FileType::Pptx => {
672                "application/vnd.openxmlformats-officedocument.presentationml.presentation"
673            }
674            FileType::Numbers => "application/x-iwork-numbers-sffnumbers",
675            FileType::Pages => "application/x-iwork-pages-sffpages",
676            FileType::Key => "application/x-iwork-keynote-sffkey",
677            FileType::InDesign => "application/x-indesign",
678            FileType::Rtf => "text/rtf",
679            // Archives
680            FileType::Zip => "application/zip",
681            FileType::Rar => "application/x-rar-compressed",
682            FileType::SevenZ => "application/x-7z-compressed",
683            FileType::Gzip => "application/x-gzip",
684            // Metadata / Other
685            FileType::Xmp => "application/rdf+xml",
686            FileType::Mie => "application/x-mie",
687            FileType::Exv => "application/x-exv",
688            FileType::Vrd => "application/octet-stream",
689            FileType::Dr4 => "application/octet-stream",
690            FileType::Icc => "application/vnd.iccprofile",
691            FileType::Html => "text/html",
692            FileType::Exe => "application/x-dosexec",
693            FileType::Font => "application/font-ttf",
694            FileType::Swf => "application/x-shockwave-flash",
695            FileType::Dicom => "application/dicom",
696            FileType::Fits => "image/fits",
697            FileType::Mrc => "image/x-mrc",
698            FileType::Moi => "application/octet-stream",
699            FileType::MacOs => "application/unknown",
700            FileType::Json => "application/json",
701            FileType::Pcap => "application/vnd.tcpdump.pcap",
702            FileType::Pcapng => "application/vnd.tcpdump.pcap",
703            FileType::Svg => "image/svg+xml",
704            FileType::Pgf => "image/pgf",
705            FileType::Xisf => "image/x-xisf",
706            FileType::Torrent => "application/x-bittorrent",
707            FileType::Mobi => "application/x-mobipocket-ebook",
708            FileType::SonyPmp => "image/x-sony-pmp",
709            FileType::Plist => "application/x-plist",
710            FileType::Aae => "application/vnd.apple.photos",
711            FileType::KyoceraRaw => "image/x-raw",
712            FileType::Lfp => "image/x-lytro-lfp",
713            FileType::PortableFloatMap => "image/x-pfm",
714            FileType::Fpf => "image/x-flir-fpf",
715            FileType::Ods => "application/vnd.oasis.opendocument.spreadsheet",
716            FileType::Odt => "application/vnd.oasis.opendocument.text",
717            FileType::Odp => "application/vnd.oasis.opendocument.presentation",
718            FileType::Odg => "application/vnd.oasis.opendocument.graphics",
719            FileType::Odf => "application/vnd.oasis.opendocument.formula",
720            FileType::Odb => "application/vnd.oasis.opendocument.database",
721            FileType::Odi => "application/vnd.oasis.opendocument.image",
722            FileType::Odc => "application/vnd.oasis.opendocument.chart",
723            FileType::Eip => "application/x-captureone",
724            FileType::Lif => "image/x-leica-lif",
725            FileType::Txt => "text/plain",
726            FileType::Csv => "text/csv",
727            FileType::Vcard => "text/vcard",
728            FileType::Ics => "text/calendar",
729            FileType::Url => "application/x-mswinurl",
730            FileType::Lnk => "application/octet-stream",
731            FileType::Ppm => "image/x-portable-pixmap",
732            FileType::Dpx => "image/x-dpx",
733            FileType::Ram => "audio/x-pn-realaudio",
734            FileType::Miff => "application/x-magick-image",
735            FileType::Tnef => "application/vnd.ms-tnef",
736            FileType::Wpg => "image/x-wpg",
737            FileType::Dv => "video/x-dv",
738            FileType::Itc => "application/itunes",
739            FileType::Iso => "application/x-iso9660-image",
740            FileType::Afm => "application/x-font-afm",
741            FileType::Pfa => "application/x-font-type1",
742            FileType::Pfb => "application/x-font-type1",
743            FileType::Dfont => "application/x-dfont",
744            FileType::Xml => "application/xml",
745            FileType::Inx => "application/x-indesign-interchange",
746            FileType::Eps => "application/postscript",
747        }
748    }
749
750    /// Common file extensions for this type.
751    pub fn extensions(self) -> &'static [&'static str] {
752        match self {
753            FileType::Jpeg => &["jpg", "jpeg", "jpe", "jif", "jfif"],
754            FileType::Tiff => &["tif", "tiff"],
755            FileType::Png => &["png"],
756            FileType::Gif => &["gif"],
757            FileType::Bmp => &["bmp", "dib"],
758            FileType::WebP => &["webp"],
759            FileType::Heif => &["heif", "heic", "hif"],
760            FileType::Avif => &["avif"],
761            FileType::Psd => &["psd", "psb", "psdt"],
762            FileType::Jp2 => &["jp2", "jpf", "jpm", "jpx", "jph"],
763            FileType::J2c => &["j2c", "j2k", "jpc"],
764            FileType::Jxl => &["jxl"],
765            FileType::Jxr => &["jxr", "hdp", "wdp"],
766            FileType::Flif => &["flif"],
767            FileType::Bpg => &["bpg"],
768            FileType::Exr => &["exr"],
769            FileType::Ico => &["ico", "cur"],
770            FileType::Jps => &["jps"],
771            FileType::DjVu => &["djvu", "djv"],
772            FileType::Xcf => &["xcf"],
773            FileType::Pcx => &["pcx"],
774            FileType::Pict => &["pict", "pct"],
775            FileType::Psp => &["psp", "pspimage"],
776            FileType::Hdr => &["hdr"],
777            FileType::Rwz => &["rwz"],
778            FileType::Btf => &["btf"],
779            FileType::Mng => &["mng", "jng"],
780            FileType::PhotoCd => &["pcd"],
781            // RAW
782            FileType::Cr2 => &["cr2"],
783            FileType::Cr3 => &["cr3"],
784            FileType::Crw => &["crw", "ciff"],
785            FileType::Nef => &["nef", "nrw"],
786            FileType::Arw => &["arw"],
787            FileType::Sr2 => &["sr2"],
788            FileType::Srf => &["srf"],
789            FileType::Orf => &["orf", "ori"],
790            FileType::Rw2 => &["rw2"],
791            FileType::Dng => &["dng"],
792            FileType::Raf => &["raf"],
793            FileType::Pef => &["pef"],
794            FileType::Dcr => &["dcr"],
795            FileType::Mrw => &["mrw"],
796            FileType::Erf => &["erf"],
797            FileType::Fff => &["fff"],
798            FileType::Iiq => &["iiq"],
799            FileType::Rwl => &["rwl"],
800            FileType::Mef => &["mef"],
801            FileType::Srw => &["srw"],
802            FileType::X3f => &["x3f"],
803            FileType::Gpr => &["gpr"],
804            FileType::Arq => &["arq"],
805            FileType::ThreeFR => &["3fr"],
806            FileType::Crm => &["crm"],
807            // Video
808            FileType::Mp4 => &["mp4", "m4v"],
809            FileType::QuickTime => &["mov", "qt"],
810            FileType::Avi => &["avi"],
811            FileType::Mkv => &["mkv", "mks"],
812            FileType::WebM => &["webm"],
813            FileType::Wmv => &["wmv"],
814            FileType::Asf => &["asf"],
815            FileType::Flv => &["flv"],
816            FileType::Mxf => &["mxf"],
817            FileType::Czi => &["czi"],
818            FileType::M2ts => &["mts", "m2ts", "m2t", "ts"],
819            FileType::Mpeg => &["mpg", "mpeg", "m2v", "mpv"],
820            FileType::ThreeGP => &["3gp", "3gpp", "3g2", "3gp2"],
821            FileType::RealMedia => &["rm", "rv", "rmvb"],
822            FileType::R3d => &["r3d"],
823            FileType::Dvb => &["dvb"],
824            FileType::Lrv => &["lrv", "lrf"],
825            FileType::Mqv => &["mqv"],
826            FileType::F4v => &["f4v", "f4a", "f4b", "f4p"],
827            FileType::Wtv => &["wtv"],
828            FileType::DvrMs => &["dvr-ms"],
829            // Audio
830            FileType::Mp3 => &["mp3"],
831            FileType::Flac => &["flac"],
832            FileType::Ogg => &["ogg", "oga", "ogv"],
833            FileType::Wav => &["wav"],
834            FileType::Aiff => &["aiff", "aif", "aifc"],
835            FileType::Aac => &["aac"],
836            FileType::Opus => &["opus"],
837            FileType::Mpc => &["mpc"],
838            FileType::Ape => &["ape"],
839            FileType::WavPack => &["wv", "wvp"],
840            FileType::Ofr => &["ofr"],
841            FileType::Dsf => &["dsf"],
842            FileType::Audible => &["aa", "aax"],
843            FileType::RealAudio => &["ra"],
844            FileType::Wma => &["wma"],
845            FileType::M4a => &["m4a", "m4b", "m4p"],
846            FileType::Dss => &["dss"],
847            // Documents
848            FileType::Pdf => &["pdf"],
849            FileType::PostScript => &["ps", "eps", "epsf"],
850            FileType::Doc => &["doc", "dot"],
851            FileType::Docx => &["docx", "docm"],
852            FileType::Xls => &["xls", "xlt"],
853            FileType::Xlsx => &["xlsx", "xlsm", "xlsb"],
854            FileType::Ppt => &["ppt", "pps", "pot"],
855            FileType::Pptx => &["pptx", "pptm"],
856            FileType::Numbers => &["numbers", "nmbtemplate"],
857            FileType::Pages => &["pages"],
858            FileType::Key => &["key", "kth"],
859            FileType::InDesign => &["indd", "ind", "indt"],
860            FileType::Rtf => &["rtf"],
861            // Archives
862            FileType::Zip => &["zip"],
863            FileType::Rar => &["rar"],
864            FileType::SevenZ => &["7z"],
865            FileType::Gzip => &["gz", "gzip"],
866            // Metadata / Other
867            FileType::Xmp => &["xmp"],
868            FileType::Mie => &["mie"],
869            FileType::Exv => &["exv"],
870            FileType::Vrd => &["vrd"],
871            FileType::Dr4 => &["dr4"],
872            FileType::Icc => &["icc", "icm"],
873            FileType::Html => &["html", "htm", "xhtml", "svg"],
874            FileType::Exe => &["exe", "dll", "elf", "so", "dylib", "a", "macho", "o"],
875            FileType::Font => &["ttf", "otf", "woff", "woff2", "ttc"],
876            FileType::Swf => &["swf"],
877            FileType::Dicom => &["dcm"],
878            FileType::Fits => &["fits", "fit", "fts"],
879            FileType::Mrc => &["mrc"],
880            FileType::Moi => &["moi"],
881            FileType::MacOs => &["macos"],
882            FileType::Json => &["json"],
883            FileType::Pcap => &["pcap", "cap"],
884            FileType::Pcapng => &["pcapng", "ntar"],
885            FileType::Svg => &["svg"],
886            FileType::Pgf => &["pgf"],
887            FileType::Xisf => &["xisf"],
888            FileType::Torrent => &["torrent"],
889            FileType::Mobi => &["mobi", "azw", "azw3"],
890            FileType::SonyPmp => &["pmp"],
891            FileType::Plist => &["plist"],
892            FileType::Aae => &["aae"],
893            FileType::KyoceraRaw => &["raw"],
894            FileType::PortableFloatMap => &["pfm"],
895            FileType::Fpf => &["fpf"],
896            FileType::Ods => &["ods"],
897            FileType::Odt => &["odt"],
898            FileType::Odp => &["odp"],
899            FileType::Odg => &["odg"],
900            FileType::Odf => &["odf"],
901            FileType::Odb => &["odb"],
902            FileType::Odi => &["odi"],
903            FileType::Odc => &["odc"],
904            FileType::Lfp => &["lfp", "lfr"],
905            FileType::Eip => &["eip"],
906            FileType::Lif => &["lif"],
907            FileType::Txt => &["txt", "log", "igc"],
908            FileType::Csv => &["csv"],
909            FileType::Vcard => &["vcf", "vcard"],
910            FileType::Ics => &["ics"],
911            FileType::Url => &["url"],
912            FileType::Lnk => &["lnk"],
913            FileType::Ppm => &["ppm"],
914            FileType::Dpx => &["dpx"],
915            FileType::Ram => &["ram"],
916            FileType::Miff => &["miff", "mif"],
917            FileType::Tnef => &["tnef"],
918            FileType::Wpg => &["wpg"],
919            FileType::Dv => &["dv"],
920            FileType::Itc => &["itc"],
921            FileType::Iso => &["iso"],
922            FileType::Afm => &["afm", "acfm", "amfm"],
923            FileType::Pfa => &["pfa"],
924            FileType::Pfb => &["pfb"],
925            FileType::Dfont => &["dfont"],
926            FileType::Xml => &["xml"],
927            FileType::Inx => &["inx"],
928            FileType::Eps => &["eps", "epsf", "eps3", "epsi", "ept"],
929        }
930    }
931
932    /// Read/Write/Create support level.
933    pub fn support(self) -> Support {
934        match self {
935            // R/W/C
936            FileType::Xmp | FileType::Mie | FileType::Exv => Support::ReadWriteCreate,
937            // R/W
938            FileType::Jpeg
939            | FileType::Tiff
940            | FileType::Png
941            | FileType::Gif
942            | FileType::WebP
943            | FileType::Heif
944            | FileType::Avif
945            | FileType::Psd
946            | FileType::Jp2
947            | FileType::Jxl
948            | FileType::Jxr
949            | FileType::Flif
950            | FileType::Cr2
951            | FileType::Cr3
952            | FileType::Crw
953            | FileType::Nef
954            | FileType::Arw
955            | FileType::Arq
956            | FileType::Sr2
957            | FileType::Orf
958            | FileType::Rw2
959            | FileType::Dng
960            | FileType::Raf
961            | FileType::Pef
962            | FileType::Erf
963            | FileType::Fff
964            | FileType::Iiq
965            | FileType::Rwl
966            | FileType::Mef
967            | FileType::Srw
968            | FileType::X3f
969            | FileType::Gpr
970            | FileType::Crm
971            | FileType::Mp4
972            | FileType::QuickTime
973            | FileType::ThreeGP
974            | FileType::Dvb
975            | FileType::Lrv
976            | FileType::Mqv
977            | FileType::F4v
978            | FileType::Pdf
979            | FileType::PostScript
980            | FileType::InDesign
981            | FileType::Vrd
982            | FileType::Dr4
983            | FileType::Audible => Support::ReadWrite,
984            // R only
985            _ => Support::Read,
986        }
987    }
988
989    /// Returns an iterator over all file types.
990    pub fn all() -> &'static [FileType] {
991        ALL_FILE_TYPES
992    }
993}
994
995static ALL_FILE_TYPES: &[FileType] = &[
996    // Images - Standard
997    FileType::Jpeg,
998    FileType::Tiff,
999    FileType::Png,
1000    FileType::Gif,
1001    FileType::Bmp,
1002    FileType::WebP,
1003    FileType::Heif,
1004    FileType::Avif,
1005    FileType::Psd,
1006    FileType::Jp2,
1007    FileType::J2c,
1008    FileType::Jxl,
1009    FileType::Jxr,
1010    FileType::Flif,
1011    FileType::Bpg,
1012    FileType::Exr,
1013    FileType::Ico,
1014    FileType::Jps,
1015    // Images - Specialized
1016    FileType::DjVu,
1017    FileType::Xcf,
1018    FileType::Pcx,
1019    FileType::Pict,
1020    FileType::Psp,
1021    FileType::Hdr,
1022    FileType::Rwz,
1023    FileType::Btf,
1024    FileType::Mng,
1025    FileType::PhotoCd,
1026    FileType::Lif,
1027    FileType::Txt,
1028    FileType::Csv,
1029    FileType::Vcard,
1030    FileType::Ics,
1031    FileType::Url,
1032    FileType::Lnk,
1033    FileType::Ppm,
1034    FileType::Dpx,
1035    FileType::Ram,
1036    FileType::Miff,
1037    FileType::Tnef,
1038    FileType::Wpg,
1039    FileType::Dv,
1040    FileType::Itc,
1041    FileType::Iso,
1042    FileType::Afm,
1043    FileType::Pfa,
1044    FileType::Pfb,
1045    FileType::Dfont,
1046    FileType::Xml,
1047    FileType::Inx,
1048    FileType::Eps,
1049    // RAW
1050    FileType::Cr2,
1051    FileType::Cr3,
1052    FileType::Crw,
1053    FileType::Nef,
1054    FileType::Arw,
1055    FileType::Sr2,
1056    FileType::Srf,
1057    FileType::Orf,
1058    FileType::Rw2,
1059    FileType::Dng,
1060    FileType::Raf,
1061    FileType::Pef,
1062    FileType::Dcr,
1063    FileType::Mrw,
1064    FileType::Erf,
1065    FileType::Fff,
1066    FileType::Iiq,
1067    FileType::Rwl,
1068    FileType::Mef,
1069    FileType::Srw,
1070    FileType::X3f,
1071    FileType::Gpr,
1072    FileType::Arq,
1073    FileType::ThreeFR,
1074    FileType::Crm,
1075    // Video
1076    FileType::Mp4,
1077    FileType::QuickTime,
1078    FileType::Avi,
1079    FileType::Mkv,
1080    FileType::WebM,
1081    FileType::Wmv,
1082    FileType::Asf,
1083    FileType::Flv,
1084    FileType::Mxf,
1085    FileType::M2ts,
1086    FileType::Mpeg,
1087    FileType::ThreeGP,
1088    FileType::RealMedia,
1089    FileType::R3d,
1090    FileType::Dvb,
1091    FileType::Lrv,
1092    FileType::Mqv,
1093    FileType::F4v,
1094    FileType::Wtv,
1095    FileType::DvrMs,
1096    // Audio
1097    FileType::Mp3,
1098    FileType::Flac,
1099    FileType::Ogg,
1100    FileType::Wav,
1101    FileType::Aiff,
1102    FileType::Aac,
1103    FileType::Opus,
1104    FileType::Mpc,
1105    FileType::Ape,
1106    FileType::WavPack,
1107    FileType::Ofr,
1108    FileType::Dsf,
1109    FileType::Audible,
1110    FileType::RealAudio,
1111    FileType::Wma,
1112    FileType::M4a,
1113    FileType::Dss,
1114    // Documents
1115    FileType::Pdf,
1116    FileType::PostScript,
1117    FileType::Doc,
1118    FileType::Docx,
1119    FileType::Xls,
1120    FileType::Xlsx,
1121    FileType::Ppt,
1122    FileType::Pptx,
1123    FileType::Numbers,
1124    FileType::Pages,
1125    FileType::Key,
1126    FileType::InDesign,
1127    FileType::Rtf,
1128    // Archives
1129    FileType::Zip,
1130    FileType::Rar,
1131    FileType::SevenZ,
1132    FileType::Gzip,
1133    // Metadata / Other
1134    FileType::Xmp,
1135    FileType::Mie,
1136    FileType::Exv,
1137    FileType::Vrd,
1138    FileType::Dr4,
1139    FileType::Icc,
1140    FileType::Html,
1141    FileType::Exe,
1142    FileType::Font,
1143    FileType::Swf,
1144    FileType::Dicom,
1145    FileType::Fits,
1146    FileType::Mrc,
1147    FileType::Moi,
1148    FileType::MacOs,
1149    FileType::Json,
1150    FileType::Pcap,
1151    FileType::Pcapng,
1152    FileType::Svg,
1153    FileType::Pgf,
1154    FileType::Xisf,
1155    FileType::Torrent,
1156    FileType::Mobi,
1157    FileType::SonyPmp,
1158    FileType::Plist,
1159    FileType::Aae,
1160    FileType::KyoceraRaw,
1161    FileType::PortableFloatMap,
1162    FileType::Fpf,
1163    FileType::Lfp,
1164    // OpenDocument
1165    FileType::Ods,
1166    FileType::Odt,
1167    FileType::Odp,
1168    FileType::Odg,
1169    FileType::Odf,
1170    FileType::Odb,
1171    FileType::Odi,
1172    FileType::Odc,
1173    // CaptureOne
1174    FileType::Eip,
1175];
1176
1177/// Detect file type from magic bytes (first 64+ bytes of a file).
1178pub fn detect_from_magic(header: &[u8]) -> Option<FileType> {
1179    if header.len() < 4 {
1180        return None;
1181    }
1182
1183    // ===== Images =====
1184
1185    // JPEG: FF D8 FF
1186    if header.starts_with(&[0xFF, 0xD8, 0xFF]) {
1187        return Some(FileType::Jpeg);
1188    }
1189
1190    // PNG: 89 50 4E 47 0D 0A 1A 0A
1191    if header.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
1192        return Some(FileType::Png);
1193    }
1194
1195    // Lytro LFP: 89 4C 46 50 0D 0A 1A 0A  ("\x89LFP\r\n\x1a\n")
1196    if header.starts_with(&[0x89, 0x4C, 0x46, 0x50, 0x0D, 0x0A, 0x1A, 0x0A]) {
1197        return Some(FileType::Lfp);
1198    }
1199
1200    // GIF: "GIF87a" or "GIF89a"
1201    if header.starts_with(b"GIF8") && header.len() >= 6 && (header[4] == b'7' || header[4] == b'9')
1202    {
1203        return Some(FileType::Gif);
1204    }
1205
1206    // Canon DR4: "IIII" + 04 00 04 00 (or 05 00 04 00)
1207    if header.len() >= 8
1208        && header.starts_with(b"IIII")
1209        && (header[4] == 0x04 || header[4] == 0x05)
1210        && header[5] == 0x00
1211        && header[6] == 0x04
1212        && header[7] == 0x00
1213    {
1214        return Some(FileType::Dr4);
1215    }
1216
1217    // Canon VRD: "CANON OPTIONAL DATA\0"
1218    if header.starts_with(b"CANON OPTIONAL DATA\0") {
1219        return Some(FileType::Vrd);
1220    }
1221
1222    // TIFF / TIFF-based RAW: "II" or "MM" + magic 42
1223    if header.len() >= 4 {
1224        let is_le =
1225            header[0] == b'I' && header[1] == b'I' && header[2] == 0x2A && header[3] == 0x00;
1226        let is_be =
1227            header[0] == b'M' && header[1] == b'M' && header[2] == 0x00 && header[3] == 0x2A;
1228        if is_le || is_be {
1229            // CR2: "II" + "CR" at offset 8
1230            if header.len() >= 10 && is_le && header[8] == b'C' && header[9] == b'R' {
1231                return Some(FileType::Cr2);
1232            }
1233            // IIQ: "IIII" (LE) or "MMMM" (BE) at offset 8
1234            if header.len() >= 12 && is_le && &header[8..12] == b"IIII" {
1235                return Some(FileType::Iiq);
1236            }
1237            if header.len() >= 12 && is_be && &header[8..12] == b"MMMM" {
1238                return Some(FileType::Iiq);
1239            }
1240            // ORF: "IIRO" or "IIRS" (Olympus)
1241            if header.len() >= 4
1242                && is_le
1243                && header[0] == b'I'
1244                && header[1] == b'I'
1245                && header.len() >= 8
1246            {
1247                // Check for ORF signature at specific offsets (Olympus uses standard TIFF with specific patterns)
1248                // For now, fall through to generic TIFF and detect by extension
1249            }
1250            // BigTIFF: magic 43 instead of 42
1251            // (handled below)
1252            return Some(FileType::Tiff);
1253        }
1254        // BigTIFF: "II" + 0x2B or "MM" + 0x002B
1255        let is_btf_le =
1256            header[0] == b'I' && header[1] == b'I' && header[2] == 0x2B && header[3] == 0x00;
1257        let is_btf_be =
1258            header[0] == b'M' && header[1] == b'M' && header[2] == 0x00 && header[3] == 0x2B;
1259        if is_btf_le || is_btf_be {
1260            return Some(FileType::Btf);
1261        }
1262    }
1263
1264    // BMP: "BM"
1265    if header.starts_with(b"BM") && header.len() >= 6 {
1266        return Some(FileType::Bmp);
1267    }
1268
1269    // RIFF container: WebP, AVI, WAV
1270    if header.len() >= 12 && header.starts_with(b"RIFF") {
1271        match &header[8..12] {
1272            b"WEBP" => return Some(FileType::WebP),
1273            b"AVI " => return Some(FileType::Avi),
1274            b"WAVE" => return Some(FileType::Wav),
1275            _ => {}
1276        }
1277    }
1278
1279    // PSD: "8BPS"
1280    if header.starts_with(b"8BPS") {
1281        return Some(FileType::Psd);
1282    }
1283
1284    // JPEG 2000: 00 00 00 0C 6A 50 20 20 (jp2 signature box)
1285    if header.len() >= 12 && header.starts_with(&[0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20]) {
1286        return Some(FileType::Jp2);
1287    }
1288
1289    // JPEG 2000 codestream: FF 4F FF 51
1290    if header.starts_with(&[0xFF, 0x4F, 0xFF, 0x51]) {
1291        return Some(FileType::J2c);
1292    }
1293
1294    // JPEG XL: FF 0A (bare codestream) or 00 00 00 0C 4A 58 4C 20 (container)
1295    if header.len() >= 2 && header[0] == 0xFF && header[1] == 0x0A {
1296        return Some(FileType::Jxl);
1297    }
1298    if header.len() >= 12 && header.starts_with(&[0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20]) {
1299        return Some(FileType::Jxl);
1300    }
1301
1302    // FLIF: "FLIF"
1303    if header.starts_with(b"FLIF") {
1304        return Some(FileType::Flif);
1305    }
1306
1307    // BPG: 0x425047FB
1308    if header.starts_with(&[0x42, 0x50, 0x47, 0xFB]) {
1309        return Some(FileType::Bpg);
1310    }
1311
1312    // OpenEXR: 76 2F 31 01
1313    if header.starts_with(&[0x76, 0x2F, 0x31, 0x01]) {
1314        return Some(FileType::Exr);
1315    }
1316
1317    // ICO: 00 00 01 00 (icon) or 00 00 02 00 (cursor)
1318    if header.len() >= 4
1319        && header[0] == 0
1320        && header[1] == 0
1321        && (header[2] == 1 || header[2] == 2)
1322        && header[3] == 0
1323    {
1324        return Some(FileType::Ico);
1325    }
1326
1327    // DjVu: "AT&TFORM"
1328    if header.len() >= 8 && header.starts_with(b"AT&TFORM") {
1329        return Some(FileType::DjVu);
1330    }
1331
1332    // GIMP XCF: "gimp xcf"
1333    if header.starts_with(b"gimp xcf") {
1334        return Some(FileType::Xcf);
1335    }
1336
1337    // MNG: 8A 4D 4E 47 0D 0A 1A 0A
1338    if header.starts_with(&[0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
1339        return Some(FileType::Mng);
1340    }
1341
1342    // JNG: 8B 4A 4E 47 0D 0A 1A 0A
1343    if header.starts_with(&[0x8B, 0x4A, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
1344        return Some(FileType::Mng);
1345    }
1346
1347    // Radiance HDR: "#?RADIANCE"
1348    if header.len() >= 10 && header.starts_with(b"#?RADIANCE") {
1349        return Some(FileType::Hdr);
1350    }
1351
1352    // Portable Float Map: "PF\n" (color) or "Pf\n" (grayscale)
1353    if header.len() >= 3
1354        && header[0] == b'P'
1355        && (header[1] == b'F' || header[1] == b'f')
1356        && header[2] == b'\n'
1357    {
1358        return Some(FileType::PortableFloatMap);
1359    }
1360
1361    // FLIR FPF: "FPF Public Image Format\0"
1362    if header.starts_with(b"FPF Public Image Format\0") {
1363        return Some(FileType::Fpf);
1364    }
1365
1366    // LIF (Leica Image Format): 0x70 0x00 0x00 0x00 + 4 bytes size + 0x2A + 4 bytes + '<' 0x00
1367    if header.len() >= 15
1368        && header[0] == 0x70
1369        && header[1] == 0x00
1370        && header[2] == 0x00
1371        && header[3] == 0x00
1372        && header[8] == 0x2A
1373        && header[13] == b'<'
1374        && header[14] == 0x00
1375    {
1376        return Some(FileType::Lif);
1377    }
1378
1379    // Rawzor: "rawzor"
1380    if header.starts_with(b"rawzor") {
1381        return Some(FileType::Rwz);
1382    }
1383
1384    // JPEG XR / HD Photo: "II" + 0xBC byte at offset 2 (TIFF-like but identifier 0xBC)
1385    if header.len() >= 4 && header[0] == b'I' && header[1] == b'I' && header[2] == 0xBC {
1386        return Some(FileType::Jxr);
1387    }
1388
1389    // ===== RAW formats with unique magic =====
1390
1391    // Fujifilm RAF: "FUJIFILMCCD-RAW"
1392    if header.len() >= 15 && header.starts_with(b"FUJIFILMCCD-RAW") {
1393        return Some(FileType::Raf);
1394    }
1395
1396    // Canon CRW: "II" + 0x1A00 + "HEAPCCDR"
1397    if header.len() >= 14
1398        && header[0] == b'I'
1399        && header[1] == b'I'
1400        && header[2] == 0x1A
1401        && header[3] == 0x00
1402        && &header[6..14] == b"HEAPCCDR"
1403    {
1404        return Some(FileType::Crw);
1405    }
1406
1407    // Minolta MRW: 00 4D 52 4D
1408    if header.starts_with(&[0x00, 0x4D, 0x52, 0x4D]) {
1409        return Some(FileType::Mrw);
1410    }
1411
1412    // Sigma X3F: "FOVb"
1413    if header.starts_with(b"FOVb") {
1414        return Some(FileType::X3f);
1415    }
1416
1417    // Panasonic RW2: "IIU" (special TIFF variant)
1418    if header.len() >= 4
1419        && header[0] == b'I'
1420        && header[1] == b'I'
1421        && header[2] == 0x55
1422        && header[3] == 0x00
1423    {
1424        return Some(FileType::Rw2);
1425    }
1426
1427    // ===== Video / QuickTime container =====
1428
1429    // QuickTime / MP4 / HEIF / AVIF / CR3: check for ftyp box
1430    if header.len() >= 12 && &header[4..8] == b"ftyp" {
1431        let brand = &header[8..12];
1432        // HEIF/HEIC
1433        if brand == b"heic"
1434            || brand == b"mif1"
1435            || brand == b"heim"
1436            || brand == b"heis"
1437            || brand == b"msf1"
1438        {
1439            return Some(FileType::Heif);
1440        }
1441        // AVIF
1442        if brand == b"avif" || brand == b"avis" {
1443            return Some(FileType::Avif);
1444        }
1445        // Canon CR3
1446        if brand == b"crx " {
1447            return Some(FileType::Cr3);
1448        }
1449        // QuickTime
1450        if brand == b"qt  " {
1451            return Some(FileType::QuickTime);
1452        }
1453        // 3GP
1454        if brand == b"3gp4" || brand == b"3gp5" || brand == b"3gp6" || brand == b"3g2a" {
1455            return Some(FileType::ThreeGP);
1456        }
1457        // M4A/M4V
1458        if brand == b"M4A " || brand == b"M4B " || brand == b"M4P " {
1459            return Some(FileType::M4a);
1460        }
1461        if brand == b"M4V " || brand == b"M4VH" || brand == b"M4VP" {
1462            return Some(FileType::Mp4);
1463        }
1464        // F4V
1465        if brand == b"F4V " || brand == b"F4P " {
1466            return Some(FileType::F4v);
1467        }
1468        // Default ftyp → MP4
1469        return Some(FileType::Mp4);
1470    }
1471
1472    // QuickTime without ftyp: check for common atom types at offset 4
1473    if header.len() >= 8 {
1474        let atom_type = &header[4..8];
1475        if atom_type == b"moov"
1476            || atom_type == b"mdat"
1477            || atom_type == b"wide"
1478            || atom_type == b"free"
1479            || atom_type == b"pnot"
1480            || atom_type == b"skip"
1481        {
1482            return Some(FileType::QuickTime);
1483        }
1484    }
1485
1486    // Matroska/WebM: EBML header 0x1A45DFA3
1487    if header.starts_with(&[0x1A, 0x45, 0xDF, 0xA3]) {
1488        return Some(FileType::Mkv);
1489        // Note: WebM vs MKV distinction requires reading the DocType element inside EBML
1490    }
1491
1492    // FLV: "FLV\x01"
1493    if header.starts_with(b"FLV\x01") {
1494        return Some(FileType::Flv);
1495    }
1496
1497    // ASF/WMV/WMA: 30 26 B2 75 8E 66 CF 11
1498    if header.len() >= 16 && header.starts_with(&[0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11]) {
1499        return Some(FileType::Asf);
1500        // WMV/WMA distinction is done by content/extension
1501    }
1502
1503    // MXF: 06 0E 2B 34 02 05 01 01
1504    if header.len() >= 8 && header.starts_with(&[0x06, 0x0E, 0x2B, 0x34]) {
1505        return Some(FileType::Mxf);
1506    }
1507
1508    // ZISRAW/CZI: "ZISRAWFILE" magic
1509    if header.len() >= 10 && header.starts_with(b"ZISRAWFILE") {
1510        return Some(FileType::Czi);
1511    }
1512
1513    // ICC Profile: "acsp" at offset 36 (must be before MPEG check which has loose matching)
1514    if header.len() >= 40 && &header[36..40] == b"acsp" {
1515        return Some(FileType::Icc);
1516    }
1517
1518    // MPEG: 00 00 01 Bx (system header) or 00 00 01 BA (pack start)
1519    if header.len() >= 4
1520        && header[0] == 0
1521        && header[1] == 0
1522        && header[2] == 1
1523        && (header[3] == 0xBA || header[3] == 0xBB || (header[3] & 0xF0) == 0xE0)
1524    {
1525        return Some(FileType::Mpeg);
1526    }
1527
1528    // MPEG-2 TS: 0x47 sync byte every 188 or 192 bytes
1529    if !header.is_empty() && header[0] == 0x47 {
1530        if header.len() >= 376 && header[188] == 0x47 {
1531            return Some(FileType::M2ts);
1532        }
1533        if header.len() >= 384 && header[192] == 0x47 {
1534            return Some(FileType::M2ts);
1535        }
1536    }
1537
1538    // RealMedia: ".RMF"
1539    if header.starts_with(b".RMF") {
1540        return Some(FileType::RealMedia);
1541    }
1542
1543    // RED R3D: "RED1" or "RED2"
1544    if header.starts_with(b"RED1") || header.starts_with(b"RED2") {
1545        return Some(FileType::R3d);
1546    }
1547
1548    // ===== Audio =====
1549
1550    // MP3: ID3 tag or MPEG sync word
1551    if header.starts_with(b"ID3") {
1552        return Some(FileType::Mp3);
1553    }
1554    // AAC ADTS: sync=0xFFF (12 bits), then layer bits 13-14 must be 00
1555    // 0xFF F0 or 0xFF F1 (MPEG-2 AAC) or 0xFF F8/F9 (MPEG-4 AAC with CRC/no-CRC)
1556    // Distinguishing from MP3: layer bits are 00 for AAC, non-zero for MP3
1557    if header.len() >= 2
1558        && header[0] == 0xFF
1559        && (header[1] == 0xF0 || header[1] == 0xF1 || header[1] == 0xF8 || header[1] == 0xF9)
1560    {
1561        return Some(FileType::Aac);
1562    }
1563    // MPEG audio sync: 0xFF + 0xE0 mask (after other FF-starting formats)
1564    if header.len() >= 2 && header[0] == 0xFF && (header[1] & 0xE0) == 0xE0 {
1565        return Some(FileType::Mp3);
1566    }
1567
1568    // FLAC: "fLaC"
1569    if header.starts_with(b"fLaC") {
1570        return Some(FileType::Flac);
1571    }
1572
1573    // OGG: "OggS"
1574    if header.starts_with(b"OggS") {
1575        return Some(FileType::Ogg);
1576    }
1577
1578    // AIFF: "FORM" + "AIFF" or "AIFC"
1579    if header.len() >= 12
1580        && header.starts_with(b"FORM")
1581        && (&header[8..12] == b"AIFF" || &header[8..12] == b"AIFC")
1582    {
1583        return Some(FileType::Aiff);
1584    }
1585
1586    // APE: "MAC "
1587    if header.starts_with(b"MAC ") {
1588        return Some(FileType::Ape);
1589    }
1590
1591    // Kyocera Contax N RAW: 'ARECOYK' at offset 0x19
1592    if header.len() >= 0x20 && &header[0x19..0x20] == b"ARECOYK" {
1593        return Some(FileType::KyoceraRaw);
1594    }
1595
1596    // Musepack: "MP+" or "MPCK"
1597    if header.starts_with(b"MP+") || header.starts_with(b"MPCK") {
1598        return Some(FileType::Mpc);
1599    }
1600
1601    // WavPack: "wvpk"
1602    if header.starts_with(b"wvpk") {
1603        return Some(FileType::WavPack);
1604    }
1605
1606    // DSD/DSF: "DSD "
1607    if header.starts_with(b"DSD ") {
1608        return Some(FileType::Dsf);
1609    }
1610
1611    // OptimFROG: "OFR "
1612    if header.starts_with(b"OFR ") {
1613        return Some(FileType::Ofr);
1614    }
1615
1616    // RealAudio: ".ra\xFD"
1617    if header.len() >= 4
1618        && header[0] == b'.'
1619        && header[1] == b'r'
1620        && header[2] == b'a'
1621        && header[3] == 0xFD
1622    {
1623        return Some(FileType::RealAudio);
1624    }
1625
1626    // DSS (Olympus Digital Speech Standard): "\x02dss" or "\x03ds2"
1627    if header.len() >= 4
1628        && (header[0] == 0x02 || header[0] == 0x03)
1629        && (header[1] == b'd')
1630        && (header[2] == b's')
1631        && (header[3] == b's' || header[3] == b'2')
1632    {
1633        return Some(FileType::Dss);
1634    }
1635
1636    // ===== Documents =====
1637
1638    // PDF: "%PDF-"
1639    if header.starts_with(b"%PDF-") {
1640        return Some(FileType::Pdf);
1641    }
1642
1643    // AFM (Adobe Font Metrics): "StartFontMetrics" / "StartCompFontMetrics" / "StartMasterFontMetrics"
1644    if header.starts_with(b"StartFontMetrics")
1645        || header.starts_with(b"StartCompFontMetrics")
1646        || header.starts_with(b"StartMasterFontMetrics")
1647    {
1648        return Some(FileType::Afm);
1649    }
1650
1651    // PFA (PostScript Type 1 ASCII font): %!PS-AdobeFont- / %!PS-Bitstream  / %!FontType1-
1652    // (must precede the generic PostScript "%!PS" test)
1653    if header.starts_with(b"%!PS-AdobeFont-")
1654        || header.starts_with(b"%!PS-Bitstream ")
1655        || header.starts_with(b"%!FontType1-")
1656    {
1657        return Some(FileType::Pfa);
1658    }
1659
1660    // PFB (PostScript Type 1 Binary font): 0x80 0x01 <len32> then the PFA text at offset 6
1661    if header.len() >= 21
1662        && header[0] == 0x80
1663        && header[1] == 0x01
1664        && (header[6..].starts_with(b"%!PS-AdobeFont-")
1665            || header[6..].starts_with(b"%!PS-Bitstream ")
1666            || header[6..].starts_with(b"%!FontType1-"))
1667    {
1668        return Some(FileType::Pfb);
1669    }
1670
1671    // EPS: DOS binary EPS header (C5 D0 D3 C6), or "%!PS-Adobe-N.N EPSF-N.N"
1672    if header.starts_with(&[0xC5, 0xD0, 0xD3, 0xC6]) {
1673        return Some(FileType::Eps);
1674    }
1675    if header.starts_with(b"%!PS-Adobe-")
1676        && header[..header.len().min(32)]
1677            .windows(4)
1678            .any(|w| w == b"EPSF")
1679    {
1680        return Some(FileType::Eps);
1681    }
1682
1683    // PostScript: "%!PS" or "%!Adobe"
1684    if header.starts_with(b"%!PS") || header.starts_with(b"%!Adobe") {
1685        return Some(FileType::PostScript);
1686    }
1687
1688    // MIFF (Magick Image File Format): "id=ImageMagick"
1689    if header.starts_with(b"id=ImageMagick") {
1690        return Some(FileType::Miff);
1691    }
1692
1693    // TNEF (Transport Neutral Encapsulation Format): signature 0x223e9f78 (little-endian)
1694    if header.starts_with(&[0x78, 0x9f, 0x3e, 0x22]) {
1695        return Some(FileType::Tnef);
1696    }
1697
1698    // WPG (WordPerfect Graphics): 0xff "WPC"
1699    if header.starts_with(&[0xff, 0x57, 0x50, 0x43]) {
1700        return Some(FileType::Wpg);
1701    }
1702
1703    // DV (Digital Video): 1F 07 00 [3F|BF]
1704    if header.len() >= 4
1705        && header[0] == 0x1f
1706        && header[1] == 0x07
1707        && header[2] == 0x00
1708        && (header[3] == 0x3f || header[3] == 0xbf)
1709    {
1710        return Some(FileType::Dv);
1711    }
1712
1713    // ITC (iTunes Cover Flow): ".{4}itch"
1714    if header.len() >= 8 && &header[4..8] == b"itch" {
1715        return Some(FileType::Itc);
1716    }
1717
1718    // MS Office legacy (DOC/XLS/PPT): OLE2 compound binary D0 CF 11 E0
1719    if header.starts_with(&[0xD0, 0xCF, 0x11, 0xE0]) {
1720        return Some(FileType::Doc); // Distinguishing DOC/XLS/PPT requires deeper parsing
1721    }
1722
1723    // RTF: "{\rtf"
1724    if header.starts_with(b"{\\rtf") {
1725        return Some(FileType::Rtf);
1726    }
1727
1728    // InDesign: 06 06 ED F5 D8 1D 46 E5
1729    if header.len() >= 8 && header.starts_with(&[0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5]) {
1730        return Some(FileType::InDesign);
1731    }
1732
1733    // ===== Archives =====
1734
1735    // ZIP (and DOCX/XLSX/PPTX/EPUB/etc.): "PK\x03\x04"
1736    if header.starts_with(&[0x50, 0x4B, 0x03, 0x04]) {
1737        return Some(FileType::Zip);
1738        // DOCX/XLSX/PPTX are ZIP files; distinguishing requires checking [Content_Types].xml
1739    }
1740
1741    // RAR: "Rar!\x1A\x07"
1742    if header.len() >= 6 && header.starts_with(b"Rar!\x1A\x07") {
1743        return Some(FileType::Rar);
1744    }
1745
1746    // 7-Zip: "7z\xBC\xAF\x27\x1C"
1747    if header.len() >= 6 && header.starts_with(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]) {
1748        return Some(FileType::SevenZ);
1749    }
1750
1751    // GZIP: 1F 8B
1752    if header.len() >= 2 && header[0] == 0x1F && header[1] == 0x8B {
1753        return Some(FileType::Gzip);
1754    }
1755
1756    // PCAPNG: 0x0A 0x0D 0x0D 0x0A (Section Header Block)
1757    if header.len() >= 4
1758        && header[0] == 0x0A
1759        && header[1] == 0x0D
1760        && header[2] == 0x0D
1761        && header[3] == 0x0A
1762    {
1763        return Some(FileType::Pcapng);
1764    }
1765
1766    // PCAP: D4 C3 B2 A1 (little-endian) or A1 B2 C3 D4 (big-endian)
1767    if header.len() >= 4
1768        && ((header[0] == 0xD4 && header[1] == 0xC3 && header[2] == 0xB2 && header[3] == 0xA1)
1769            || (header[0] == 0xA1 && header[1] == 0xB2 && header[2] == 0xC3 && header[3] == 0xD4))
1770    {
1771        return Some(FileType::Pcap);
1772    }
1773
1774    // ===== Other =====
1775
1776    // SWF: "FWS" (uncompressed) or "CWS" (compressed) or "ZWS"
1777    if header.len() >= 3
1778        && ((header[0] == b'F' || header[0] == b'C' || header[0] == b'Z')
1779            && header[1] == b'W'
1780            && header[2] == b'S')
1781    {
1782        return Some(FileType::Swf);
1783    }
1784
1785    // DICOM: "DICM" at offset 128
1786    if header.len() >= 132 && &header[128..132] == b"DICM" {
1787        return Some(FileType::Dicom);
1788    }
1789
1790    // MRC: axis values at offset 64-75 (each 1/2/3) and "MAP" at offset 208
1791    if header.len() >= 214 {
1792        let ax1 = u32::from_le_bytes([header[64], header[65], header[66], header[67]]);
1793        let ax2 = u32::from_le_bytes([header[68], header[69], header[70], header[71]]);
1794        let ax3 = u32::from_le_bytes([header[72], header[73], header[74], header[75]]);
1795        if (1..=3).contains(&ax1)
1796            && (1..=3).contains(&ax2)
1797            && (1..=3).contains(&ax3)
1798            && &header[208..211] == b"MAP"
1799        {
1800            let ms0 = header[212];
1801            let ms1 = header[213];
1802            if (ms1 == 0x41 || ms1 == 0x44) && ms0 == 0x44 || (ms0 == 0x11 && ms1 == 0x11) {
1803                return Some(FileType::Mrc);
1804            }
1805        }
1806    }
1807
1808    // FITS: "SIMPLE  ="
1809    if header.len() >= 9 && header.starts_with(b"SIMPLE  =") {
1810        return Some(FileType::Fits);
1811    }
1812
1813    // MIE: "~\x10\x04" + version
1814    if header.len() >= 4 && header[0] == 0x7E && header[1] == 0x10 && header[2] == 0x04 {
1815        return Some(FileType::Mie);
1816    }
1817
1818    // XMP sidecar (starts with XML PI or xpacket)
1819    if header.starts_with(b"<?xpacket") || header.starts_with(b"<x:xmpmeta") {
1820        return Some(FileType::Xmp);
1821    }
1822
1823    // XML-based formats: look deeper to classify
1824    if header.starts_with(b"<?xml") || header.starts_with(b"<svg") {
1825        let preview = &header[..header.len().min(512)];
1826        if preview.windows(4).any(|w| w == b"<svg") {
1827            return Some(FileType::Svg);
1828        }
1829        if preview.windows(5).any(|w| w == b"<html" || w == b"<HTML") {
1830            return Some(FileType::Html); // XHTML
1831        }
1832        if preview.windows(10).any(|w| w == b"<x:xmpmeta")
1833            || preview.windows(9).any(|w| w == b"<?xpacket")
1834        {
1835            return Some(FileType::Xmp);
1836        }
1837        if preview.windows(4).any(|w| w == b"<rdf" || w == b"<RDF") {
1838            return Some(FileType::Xmp);
1839        }
1840        // Apple PLIST
1841        if preview.windows(7).any(|w| w == b"<plist")
1842            || preview.windows(20).any(|w| w == b"DTD PLIST")
1843        {
1844            return Some(FileType::Plist);
1845        }
1846        // Adobe InDesign Interchange: "<?aid" processing instruction
1847        if preview.windows(5).any(|w| w == b"<?aid") {
1848            return Some(FileType::Inx);
1849        }
1850        // Default XML → XML (ExifTool reports plain XML; XMP needs rdf/xmpmeta)
1851        return Some(FileType::Xml);
1852    }
1853
1854    // HTML
1855    if header.starts_with(b"<!DOCTYPE html")
1856        || header.starts_with(b"<!doctype html")
1857        || header.starts_with(b"<!DOCTYPE HTML")
1858        || header.starts_with(b"<html")
1859        || header.starts_with(b"<HTML")
1860    {
1861        return Some(FileType::Html);
1862    }
1863
1864    // ELF executable: \x7FELF
1865    if header.starts_with(&[0x7F, b'E', b'L', b'F']) {
1866        return Some(FileType::Exe);
1867    }
1868
1869    // Mach-O 32-bit: FEEDFACE (big) or CEFAEDFE (little)
1870    if header.starts_with(&[0xFE, 0xED, 0xFA, 0xCE])
1871        || header.starts_with(&[0xCE, 0xFA, 0xED, 0xFE])
1872    {
1873        return Some(FileType::Exe);
1874    }
1875
1876    // Mach-O 64-bit: FEEDFACF (big) or CFFAEDFE (little)
1877    if header.starts_with(&[0xFE, 0xED, 0xFA, 0xCF])
1878        || header.starts_with(&[0xCF, 0xFA, 0xED, 0xFE])
1879    {
1880        return Some(FileType::Exe);
1881    }
1882
1883    // Mach-O Universal/Fat binary: CAFEBABE
1884    if header.starts_with(&[0xCA, 0xFE, 0xBA, 0xBE]) {
1885        return Some(FileType::Exe);
1886    }
1887
1888    // PE executable: "MZ"
1889    if header.starts_with(b"MZ") {
1890        return Some(FileType::Exe);
1891    }
1892
1893    // TrueType font: 00 01 00 00 or "true" or "typ1"
1894    if (header.starts_with(&[0x00, 0x01, 0x00, 0x00])
1895        || header.starts_with(b"true")
1896        || header.starts_with(b"typ1"))
1897        && header.len() >= 12
1898    {
1899        return Some(FileType::Font);
1900    }
1901
1902    // TrueType Collection: "ttcf"
1903    if header.starts_with(b"ttcf") {
1904        return Some(FileType::Font);
1905    }
1906
1907    // OpenType font: "OTTO"
1908    if header.starts_with(b"OTTO") {
1909        return Some(FileType::Font);
1910    }
1911
1912    // WOFF: "wOFF"
1913    if header.starts_with(b"wOFF") {
1914        return Some(FileType::Font);
1915    }
1916
1917    // WOFF2: "wOF2"
1918    if header.starts_with(b"wOF2") {
1919        return Some(FileType::Font);
1920    }
1921
1922    // MOI: starts with "V6" (camcorder info file)
1923    if header.len() >= 2 && header[0] == b'V' && header[1] == b'6' {
1924        return Some(FileType::Moi);
1925    }
1926
1927    // PGF: "PGF"
1928    if header.starts_with(b"PGF") {
1929        return Some(FileType::Pgf);
1930    }
1931
1932    // XISF: "XISF0100"
1933    if header.starts_with(b"XISF0100") {
1934        return Some(FileType::Xisf);
1935    }
1936
1937    // Paint Shop Pro: "Paint Shop Pro Image File\n\x1a\0\0\0\0\0"
1938    if header.len() >= 27 && header.starts_with(b"Paint Shop Pro Image File\n\x1a") {
1939        return Some(FileType::Psp);
1940    }
1941
1942    // Sony PMP: magic at offset 8-11 is 00 00 00 7C (header length = 124)
1943    // and byte 4 is 0x00 (part of file size field)
1944    if header.len() >= 12
1945        && header[8] == 0x00
1946        && header[9] == 0x00
1947        && header[10] == 0x00
1948        && header[11] == 0x7C
1949    {
1950        return Some(FileType::SonyPmp);
1951    }
1952
1953    None
1954}
1955
1956/// Detect file type from file extension.
1957pub fn detect_from_extension(ext: &str) -> Option<FileType> {
1958    let ext_lower = ext.to_ascii_lowercase();
1959    let ext_lower = ext_lower.trim_start_matches('.');
1960
1961    for &ft in ALL_FILE_TYPES {
1962        for known_ext in ft.extensions() {
1963            if ext_lower == *known_ext {
1964                return Some(ft);
1965            }
1966        }
1967    }
1968
1969    None
1970}
1971
1972impl std::fmt::Display for FileType {
1973    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1974        write!(f, "{}", self.description())
1975    }
1976}
1977
1978impl std::fmt::Display for Support {
1979    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1980        match self {
1981            Support::Read => write!(f, "R"),
1982            Support::ReadWrite => write!(f, "R/W"),
1983            Support::ReadWriteCreate => write!(f, "R/W/C"),
1984        }
1985    }
1986}
1987
1988#[cfg(test)]
1989mod tests {
1990    use super::*;
1991
1992    #[test]
1993    fn test_detect_jpeg() {
1994        let header = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, b'J', b'F', b'I', b'F'];
1995        assert_eq!(detect_from_magic(&header), Some(FileType::Jpeg));
1996    }
1997
1998    #[test]
1999    fn test_detect_png() {
2000        let header = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
2001        assert_eq!(detect_from_magic(&header), Some(FileType::Png));
2002    }
2003
2004    #[test]
2005    fn test_detect_tiff_le() {
2006        let header = [b'I', b'I', 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00];
2007        assert_eq!(detect_from_magic(&header), Some(FileType::Tiff));
2008    }
2009
2010    #[test]
2011    fn test_detect_tiff_be() {
2012        let header = [b'M', b'M', 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08];
2013        assert_eq!(detect_from_magic(&header), Some(FileType::Tiff));
2014    }
2015
2016    #[test]
2017    fn test_detect_cr2() {
2018        let header = [b'I', b'I', 0x2A, 0x00, 0x10, 0x00, 0x00, 0x00, b'C', b'R'];
2019        assert_eq!(detect_from_magic(&header), Some(FileType::Cr2));
2020    }
2021
2022    #[test]
2023    fn test_detect_pdf() {
2024        let header = b"%PDF-1.7 some more data here";
2025        assert_eq!(detect_from_magic(header), Some(FileType::Pdf));
2026    }
2027
2028    #[test]
2029    fn test_detect_webp() {
2030        let header = b"RIFF\x00\x00\x00\x00WEBP";
2031        assert_eq!(detect_from_magic(header), Some(FileType::WebP));
2032    }
2033
2034    #[test]
2035    fn test_detect_heif() {
2036        let mut header = [0u8; 16];
2037        header[4..8].copy_from_slice(b"ftyp");
2038        header[8..12].copy_from_slice(b"heic");
2039        assert_eq!(detect_from_magic(&header), Some(FileType::Heif));
2040    }
2041
2042    #[test]
2043    fn test_detect_avif() {
2044        let mut header = [0u8; 16];
2045        header[4..8].copy_from_slice(b"ftyp");
2046        header[8..12].copy_from_slice(b"avif");
2047        assert_eq!(detect_from_magic(&header), Some(FileType::Avif));
2048    }
2049
2050    #[test]
2051    fn test_detect_cr3() {
2052        let mut header = [0u8; 16];
2053        header[4..8].copy_from_slice(b"ftyp");
2054        header[8..12].copy_from_slice(b"crx ");
2055        assert_eq!(detect_from_magic(&header), Some(FileType::Cr3));
2056    }
2057
2058    #[test]
2059    fn test_detect_flac() {
2060        assert_eq!(detect_from_magic(b"fLaC\x00\x00"), Some(FileType::Flac));
2061    }
2062
2063    #[test]
2064    fn test_detect_ogg() {
2065        assert_eq!(detect_from_magic(b"OggS\x00\x02"), Some(FileType::Ogg));
2066    }
2067
2068    #[test]
2069    fn test_detect_mp3_id3() {
2070        assert_eq!(detect_from_magic(b"ID3\x04\x00"), Some(FileType::Mp3));
2071    }
2072
2073    #[test]
2074    fn test_detect_rar() {
2075        assert_eq!(
2076            detect_from_magic(b"Rar!\x1A\x07\x01\x00"),
2077            Some(FileType::Rar)
2078        );
2079    }
2080
2081    #[test]
2082    fn test_detect_7z() {
2083        assert_eq!(
2084            detect_from_magic(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]),
2085            Some(FileType::SevenZ)
2086        );
2087    }
2088
2089    #[test]
2090    fn test_detect_gzip() {
2091        assert_eq!(
2092            detect_from_magic(&[0x1F, 0x8B, 0x08, 0x00]),
2093            Some(FileType::Gzip)
2094        );
2095    }
2096
2097    #[test]
2098    fn test_detect_raf() {
2099        assert_eq!(
2100            detect_from_magic(b"FUJIFILMCCD-RAW 0201"),
2101            Some(FileType::Raf)
2102        );
2103    }
2104
2105    #[test]
2106    fn test_detect_psd() {
2107        assert_eq!(detect_from_magic(b"8BPS\x00\x01"), Some(FileType::Psd));
2108    }
2109
2110    #[test]
2111    fn test_detect_from_extension() {
2112        assert_eq!(detect_from_extension("jpg"), Some(FileType::Jpeg));
2113        assert_eq!(detect_from_extension(".JPEG"), Some(FileType::Jpeg));
2114        assert_eq!(detect_from_extension("cr2"), Some(FileType::Cr2));
2115        assert_eq!(detect_from_extension("cr3"), Some(FileType::Cr3));
2116        assert_eq!(detect_from_extension("nef"), Some(FileType::Nef));
2117        assert_eq!(detect_from_extension("arw"), Some(FileType::Arw));
2118        assert_eq!(detect_from_extension("dng"), Some(FileType::Dng));
2119        assert_eq!(detect_from_extension("raf"), Some(FileType::Raf));
2120        assert_eq!(detect_from_extension("mp4"), Some(FileType::Mp4));
2121        assert_eq!(detect_from_extension("mov"), Some(FileType::QuickTime));
2122        assert_eq!(detect_from_extension("flac"), Some(FileType::Flac));
2123        assert_eq!(detect_from_extension("docx"), Some(FileType::Docx));
2124        assert_eq!(detect_from_extension("xlsx"), Some(FileType::Xlsx));
2125        assert_eq!(detect_from_extension("3fr"), Some(FileType::ThreeFR));
2126        assert_eq!(detect_from_extension("xyz"), None);
2127    }
2128
2129    #[test]
2130    fn test_all_types_have_extensions() {
2131        for &ft in FileType::all() {
2132            assert!(!ft.extensions().is_empty(), "{:?} has no extensions", ft);
2133        }
2134    }
2135
2136    #[test]
2137    fn test_all_types_have_mime() {
2138        for &ft in FileType::all() {
2139            assert!(!ft.mime_type().is_empty(), "{:?} has no MIME type", ft);
2140        }
2141    }
2142
2143    #[test]
2144    fn test_total_format_count() {
2145        assert!(
2146            FileType::all().len() >= 100,
2147            "Expected 100+ formats, got {}",
2148            FileType::all().len()
2149        );
2150    }
2151
2152    #[test]
2153    fn test_detect_mp4_ftyp_isom() {
2154        let mut header = [0u8; 16];
2155        // size=16, type=ftyp, brand=isom
2156        header[0..4].copy_from_slice(&[0x00, 0x00, 0x00, 0x10]);
2157        header[4..8].copy_from_slice(b"ftyp");
2158        header[8..12].copy_from_slice(b"isom");
2159        assert_eq!(detect_from_magic(&header), Some(FileType::Mp4));
2160    }
2161
2162    #[test]
2163    fn test_detect_mp4_ftyp_mp42() {
2164        let mut header = [0u8; 16];
2165        header[0..4].copy_from_slice(&[0x00, 0x00, 0x00, 0x10]);
2166        header[4..8].copy_from_slice(b"ftyp");
2167        header[8..12].copy_from_slice(b"mp42");
2168        assert_eq!(detect_from_magic(&header), Some(FileType::Mp4));
2169    }
2170
2171    #[test]
2172    fn test_detect_mkv_ebml() {
2173        // Matroska/WebM EBML header
2174        let header = [0x1A, 0x45, 0xDF, 0xA3, 0x93, 0x42, 0x82, 0x88];
2175        assert_eq!(detect_from_magic(&header), Some(FileType::Mkv));
2176    }
2177
2178    #[test]
2179    fn test_detect_flif() {
2180        assert_eq!(
2181            detect_from_magic(b"FLIF\x30\x00\x01\x00"),
2182            Some(FileType::Flif)
2183        );
2184    }
2185
2186    #[test]
2187    fn test_detect_bpg() {
2188        assert_eq!(
2189            detect_from_magic(&[0x42, 0x50, 0x47, 0xFB, 0x00, 0x00]),
2190            Some(FileType::Bpg)
2191        );
2192    }
2193
2194    #[test]
2195    fn test_detect_exe_mz() {
2196        // PE/MZ header
2197        assert_eq!(
2198            detect_from_magic(b"MZ\x90\x00\x03\x00\x00\x00"),
2199            Some(FileType::Exe)
2200        );
2201    }
2202
2203    #[test]
2204    fn test_detect_elf() {
2205        assert_eq!(
2206            detect_from_magic(&[0x7F, b'E', b'L', b'F', 0x02, 0x01]),
2207            Some(FileType::Exe)
2208        );
2209    }
2210
2211    #[test]
2212    fn test_detect_macho_64_le() {
2213        // Mach-O 64-bit little-endian
2214        assert_eq!(
2215            detect_from_magic(&[0xCF, 0xFA, 0xED, 0xFE, 0x07, 0x00]),
2216            Some(FileType::Exe)
2217        );
2218    }
2219
2220    #[test]
2221    fn test_detect_macho_32_be() {
2222        // Mach-O 32-bit big-endian
2223        assert_eq!(
2224            detect_from_magic(&[0xFE, 0xED, 0xFA, 0xCE, 0x00, 0x00]),
2225            Some(FileType::Exe)
2226        );
2227    }
2228
2229    #[test]
2230    fn test_detect_macho_fat() {
2231        // Mach-O Universal/Fat binary
2232        assert_eq!(
2233            detect_from_magic(&[0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00]),
2234            Some(FileType::Exe)
2235        );
2236    }
2237
2238    #[test]
2239    fn test_detect_dicom() {
2240        // DICOM: "DICM" at offset 128
2241        let mut header = vec![0u8; 136];
2242        header[128..132].copy_from_slice(b"DICM");
2243        assert_eq!(detect_from_magic(&header), Some(FileType::Dicom));
2244    }
2245}