atproto user agency toolkit for individuals and groups
1/**
2 * Detect content type from file magic bytes.
3 * Returns the detected MIME type or null if unknown.
4 */
5export function detectContentType(bytes: Uint8Array): string | null {
6 // MP4/M4V/MOV - check for ftyp box
7 if (bytes.length >= 12) {
8 const ftyp = String.fromCharCode(
9 bytes[4]!,
10 bytes[5]!,
11 bytes[6]!,
12 bytes[7]!,
13 );
14 if (ftyp === "ftyp") {
15 const brand = String.fromCharCode(
16 bytes[8]!,
17 bytes[9]!,
18 bytes[10]!,
19 bytes[11]!,
20 );
21 if (
22 brand === "isom" ||
23 brand === "iso2" ||
24 brand === "mp41" ||
25 brand === "mp42" ||
26 brand === "avc1"
27 ) {
28 return "video/mp4";
29 }
30 if (brand === "M4V " || brand === "M4VH" || brand === "M4VP") {
31 return "video/x-m4v";
32 }
33 if (brand === "qt ") {
34 return "video/quicktime";
35 }
36 return "video/mp4";
37 }
38 }
39
40 // JPEG
41 if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
42 return "image/jpeg";
43 }
44
45 // PNG
46 if (
47 bytes[0] === 0x89 &&
48 bytes[1] === 0x50 &&
49 bytes[2] === 0x4e &&
50 bytes[3] === 0x47
51 ) {
52 return "image/png";
53 }
54
55 // GIF
56 if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {
57 return "image/gif";
58 }
59
60 // WebP
61 if (
62 bytes[0] === 0x52 &&
63 bytes[1] === 0x49 &&
64 bytes[2] === 0x46 &&
65 bytes[3] === 0x46 &&
66 bytes[8] === 0x57 &&
67 bytes[9] === 0x45 &&
68 bytes[10] === 0x42 &&
69 bytes[11] === 0x50
70 ) {
71 return "image/webp";
72 }
73
74 // WebM
75 if (
76 bytes[0] === 0x1a &&
77 bytes[1] === 0x45 &&
78 bytes[2] === 0xdf &&
79 bytes[3] === 0xa3
80 ) {
81 return "video/webm";
82 }
83
84 return null;
85}