1// -- file extension rules --
2
3/** Extensions that must have EXIF/etc data stripped */
4export const extsScrubExif = new Set([
5 ".jpg",
6 ".jpeg",
7 ".png",
8 ".mov",
9 ".mp4",
10 ".m4a",
11]);
12/** Extensions that rendered syntax-highlighted code */
13export const extsCode = new Map<string, highlight.Language>(Object.entries({
14 ".json": "json",
15 ".toml": "toml",
16 ".ts": "ts",
17 ".js": "ts",
18 ".tsx": "tsx",
19 ".jsx": "tsx",
20 ".css": "css",
21 ".py": "python",
22 ".lua": "lua",
23 ".sh": "shell",
24 ".bat": "dosbatch",
25 ".ps1": "powershell",
26 ".cmd": "dosbatch",
27 ".yaml": "yaml",
28 ".yml": "yaml",
29 ".zig": "zig",
30 ".astro": "astro",
31 ".mdx": "mdx",
32 ".xml": "xml",
33 ".jsonc": "json",
34 ".php": "php",
35 ".patch": "diff",
36 ".diff": "diff",
37}));
38/** These files show an audio embed. */
39export const extsAudio = new Set([
40 ".mp3",
41 ".flac",
42 ".wav",
43 ".ogg",
44 ".m4a",
45]);
46/** These files show a video embed. */
47export const extsVideo = new Set([
48 ".mp4",
49 ".mkv",
50 ".webm",
51 ".avi",
52 ".mov",
53]);
54/** These files show an image embed */
55export const extsImage = new Set([
56 ".jpg",
57 ".jpeg",
58 ".png",
59 ".webp",
60 ".avif",
61 ".heic",
62]);
63/** These files show an image embed, but aren't optimized */
64export const extsImageLike = new Set([
65 ...extsImage,
66 ".svg",
67 ".gif",
68]);
69
70/** These files populate `duration` using `ffprobe` */
71export const extsDuration = new Set([...extsAudio, ...extsVideo]);
72/** These files populate `dimensions` using `ffprobe` */
73export const extsDimensions = new Set([...extsImage, ...extsVideo, ".gif"]);
74
75/** These files read file contents into `contents`, as-is */
76export const extsReadContents = new Set([".txt", ".chat", ".dirsort", ".date"]);
77
78export const extsArchive = new Set([
79 ".zip",
80 ".rar",
81 ".7z",
82 ".tar",
83 ".gz",
84 ".bz2",
85 ".xz",
86]);
87
88/**
89 * Formats which are already compression formats, meaning a pass
90 * through zstd would offer little to negative benefits
91 */
92export const extsPreCompressed = new Set([
93 ...extsAudio,
94 ...extsVideo,
95 ...extsImageLike,
96 ...extsArchive,
97 ".docx",
98 ".xlsx",
99 ".pptx",
100 ".psd",
101 ".sketch",
102 ".ai",
103 ".3ds",
104 ".fbx",
105 ".blend",
106 ".dng",
107]);
108extsPreCompressed.delete(".svg");
109
110// files that should never have media processors (transcodes) run on them,
111// usually because the file is intentionally cursed.
112export const processDenyList = new Set([
113 "/2021/top-10000-bread/output.mp4",
114]);
115
116export function fileIcon(
117 file: Pick<MediaFile, "kind" | "basename" | "path">,
118 dirOpen?: boolean,
119) {
120 const { kind, basename } = file;
121 if (kind === MediaFileKind.directory) return dirOpen ? "dir-open" : "dir";
122
123 // -- special cases --
124 if (file.path === "/2024/for everyone") return "snow";
125
126 // -- basename cases --
127 if (basename === "readme.txt") return "readme";
128
129 // -- extension cases --
130 const ext = path.extname(file.basename).toLowerCase();
131 if ([".comp", ".fuse", ".setting"].includes(ext)) return "fusion";
132 if ([".json", ".toml", ".yaml", ".yml"].includes(ext)) return "json";
133 if (ext === ".blend") return "blend";
134 if (ext === ".chat") return "chat";
135 if (ext === ".html") return "webpage";
136 if (ext === ".lnk") return "link";
137 if (ext === ".txt" || ext === ".md") return "text";
138
139 // -- extension categories --
140 if (extsVideo.has(ext)) return "video";
141 if (extsAudio.has(ext)) return "audio";
142 if (extsImage.has(ext)) return "image";
143 if (extsArchive.has(ext)) return "archive";
144 if (extsCode.has(ext)) return "code";
145
146 return "file";
147}
148
149import { MediaFile, MediaFileKind } from "#src/file-viewer/models/MediaFile.ts";
150import * as path from "node:path";
151import type * as highlight from "./highlight.ts";