2023-07-12 05:56:30 +02:00
|
|
|
import { AssetType } from '@app/infra/entities';
|
2023-08-24 21:28:50 +02:00
|
|
|
import { Duration } from 'luxon';
|
2024-01-27 23:39:33 +01:00
|
|
|
import { readFileSync } from 'node:fs';
|
2023-12-16 18:15:30 +01:00
|
|
|
import { extname, join } from 'node:path';
|
2024-01-27 23:39:33 +01:00
|
|
|
|
2023-08-24 21:28:50 +02:00
|
|
|
export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 });
|
2023-10-24 17:05:42 +02:00
|
|
|
export const ONE_HOUR = Duration.fromObject({ hours: 1 });
|
2022-03-22 07:22:04 +01:00
|
|
|
|
2023-12-21 17:06:26 +01:00
|
|
|
export interface IVersion {
|
2022-07-09 04:26:50 +02:00
|
|
|
major: number;
|
|
|
|
minor: number;
|
|
|
|
patch: number;
|
|
|
|
}
|
|
|
|
|
2024-02-07 03:46:38 +01:00
|
|
|
export enum VersionType {
|
|
|
|
EQUAL = 0,
|
|
|
|
PATCH = 1,
|
|
|
|
MINOR = 2,
|
|
|
|
MAJOR = 3,
|
|
|
|
}
|
|
|
|
|
2023-12-21 17:06:26 +01:00
|
|
|
export class Version implements IVersion {
|
2024-02-07 03:46:38 +01:00
|
|
|
public readonly types = ['major', 'minor', 'patch'] as const;
|
|
|
|
|
2023-10-24 17:05:42 +02:00
|
|
|
constructor(
|
2024-02-07 03:46:38 +01:00
|
|
|
public major: number,
|
|
|
|
public minor: number = 0,
|
|
|
|
public patch: number = 0,
|
2023-10-24 17:05:42 +02:00
|
|
|
) {}
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
return `${this.major}.${this.minor}.${this.patch}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
toJSON() {
|
|
|
|
const { major, minor, patch } = this;
|
|
|
|
return { major, minor, patch };
|
|
|
|
}
|
|
|
|
|
2023-12-21 17:06:26 +01:00
|
|
|
static fromString(version: string): Version {
|
2024-02-02 04:18:00 +01:00
|
|
|
const regex = /v?(?<major>\d+)(?:\.(?<minor>\d+))?(?:[.-](?<patch>\d+))?/i;
|
2023-10-24 17:05:42 +02:00
|
|
|
const matchResult = version.match(regex);
|
|
|
|
if (matchResult) {
|
2023-12-21 17:06:26 +01:00
|
|
|
const { major, minor = '0', patch = '0' } = matchResult.groups as { [K in keyof IVersion]: string };
|
|
|
|
return new Version(Number(major), Number(minor), Number(patch));
|
2023-10-24 17:05:42 +02:00
|
|
|
} else {
|
|
|
|
throw new Error(`Invalid version format: ${version}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 03:46:38 +01:00
|
|
|
private compare(version: Version): [number, VersionType] {
|
|
|
|
for (const [i, key] of this.types.entries()) {
|
2023-12-21 17:06:26 +01:00
|
|
|
const diff = this[key] - version[key];
|
|
|
|
if (diff !== 0) {
|
2024-02-07 03:46:38 +01:00
|
|
|
return [diff > 0 ? 1 : -1, (VersionType.MAJOR - i) as VersionType];
|
2023-12-21 17:06:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 03:46:38 +01:00
|
|
|
return [0, VersionType.EQUAL];
|
2023-12-21 17:06:26 +01:00
|
|
|
}
|
|
|
|
|
2024-02-07 03:46:38 +01:00
|
|
|
isOlderThan(version: Version): VersionType {
|
|
|
|
const [bool, type] = this.compare(version);
|
|
|
|
return bool < 0 ? type : VersionType.EQUAL;
|
2023-12-21 17:06:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
isEqual(version: Version): boolean {
|
2024-02-07 03:46:38 +01:00
|
|
|
const [bool] = this.compare(version);
|
|
|
|
return bool === 0;
|
2023-12-21 17:06:26 +01:00
|
|
|
}
|
2023-10-24 17:05:42 +02:00
|
|
|
|
2024-02-07 03:46:38 +01:00
|
|
|
isNewerThan(version: Version): VersionType {
|
|
|
|
const [bool, type] = this.compare(version);
|
|
|
|
return bool > 0 ? type : VersionType.EQUAL;
|
2023-10-24 17:05:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const envName = (process.env.NODE_ENV || 'development').toUpperCase();
|
|
|
|
export const isDev = process.env.NODE_ENV === 'development';
|
2022-12-08 16:53:18 +01:00
|
|
|
|
2024-02-02 04:18:00 +01:00
|
|
|
const { version } = JSON.parse(readFileSync('./package.json', 'utf8'));
|
|
|
|
export const serverVersion = Version.fromString(version);
|
2023-03-22 03:49:19 +01:00
|
|
|
|
2023-03-25 15:50:57 +01:00
|
|
|
export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload';
|
2023-03-24 05:55:15 +01:00
|
|
|
|
2023-12-16 18:15:30 +01:00
|
|
|
export const WEB_ROOT_PATH = join(process.env.IMMICH_WEB_ROOT || '/usr/src/app/www', 'index.html');
|
|
|
|
|
2024-01-04 14:36:52 +01:00
|
|
|
const GEODATA_ROOT_PATH = process.env.IMMICH_REVERSE_GEOCODING_ROOT || '/usr/src/resources';
|
|
|
|
|
|
|
|
export const citiesFile = 'cities500.txt';
|
|
|
|
export const geodataDatePath = join(GEODATA_ROOT_PATH, 'geodata-date.txt');
|
|
|
|
export const geodataAdmin1Path = join(GEODATA_ROOT_PATH, 'admin1CodesASCII.txt');
|
|
|
|
export const geodataAdmin2Path = join(GEODATA_ROOT_PATH, 'admin2Codes.txt');
|
2024-02-24 01:42:37 +01:00
|
|
|
export const geodataCities500Path = join(GEODATA_ROOT_PATH, citiesFile);
|
2024-01-04 14:36:52 +01:00
|
|
|
|
2023-07-19 16:27:25 +02:00
|
|
|
const image: Record<string, string[]> = {
|
|
|
|
'.3fr': ['image/3fr', 'image/x-hasselblad-3fr'],
|
|
|
|
'.ari': ['image/ari', 'image/x-arriflex-ari'],
|
|
|
|
'.arw': ['image/arw', 'image/x-sony-arw'],
|
|
|
|
'.avif': ['image/avif'],
|
2023-11-20 20:47:24 +01:00
|
|
|
'.bmp': ['image/bmp'],
|
2023-07-19 16:27:25 +02:00
|
|
|
'.cap': ['image/cap', 'image/x-phaseone-cap'],
|
|
|
|
'.cin': ['image/cin', 'image/x-phantom-cin'],
|
|
|
|
'.cr2': ['image/cr2', 'image/x-canon-cr2'],
|
|
|
|
'.cr3': ['image/cr3', 'image/x-canon-cr3'],
|
|
|
|
'.crw': ['image/crw', 'image/x-canon-crw'],
|
|
|
|
'.dcr': ['image/dcr', 'image/x-kodak-dcr'],
|
|
|
|
'.dng': ['image/dng', 'image/x-adobe-dng'],
|
|
|
|
'.erf': ['image/erf', 'image/x-epson-erf'],
|
|
|
|
'.fff': ['image/fff', 'image/x-hasselblad-fff'],
|
|
|
|
'.gif': ['image/gif'],
|
|
|
|
'.heic': ['image/heic'],
|
|
|
|
'.heif': ['image/heif'],
|
2024-01-18 18:18:56 +01:00
|
|
|
'.hif': ['image/hif'],
|
2023-07-19 16:27:25 +02:00
|
|
|
'.iiq': ['image/iiq', 'image/x-phaseone-iiq'],
|
2023-08-15 05:14:52 +02:00
|
|
|
'.insp': ['image/jpeg'],
|
2023-10-06 22:55:20 +02:00
|
|
|
'.jpe': ['image/jpeg'],
|
2023-07-19 16:27:25 +02:00
|
|
|
'.jpeg': ['image/jpeg'],
|
|
|
|
'.jpg': ['image/jpeg'],
|
|
|
|
'.jxl': ['image/jxl'],
|
|
|
|
'.k25': ['image/k25', 'image/x-kodak-k25'],
|
|
|
|
'.kdc': ['image/kdc', 'image/x-kodak-kdc'],
|
|
|
|
'.mrw': ['image/mrw', 'image/x-minolta-mrw'],
|
|
|
|
'.nef': ['image/nef', 'image/x-nikon-nef'],
|
|
|
|
'.orf': ['image/orf', 'image/x-olympus-orf'],
|
|
|
|
'.ori': ['image/ori', 'image/x-olympus-ori'],
|
|
|
|
'.pef': ['image/pef', 'image/x-pentax-pef'],
|
|
|
|
'.png': ['image/png'],
|
2023-09-24 17:03:14 +02:00
|
|
|
'.psd': ['image/psd', 'image/vnd.adobe.photoshop'],
|
2023-07-19 16:27:25 +02:00
|
|
|
'.raf': ['image/raf', 'image/x-fuji-raf'],
|
|
|
|
'.raw': ['image/raw', 'image/x-panasonic-raw'],
|
2024-01-07 00:58:04 +01:00
|
|
|
'.rw2': ['image/rw2', 'image/x-panasonic-rw2'],
|
2023-07-19 16:27:25 +02:00
|
|
|
'.rwl': ['image/rwl', 'image/x-leica-rwl'],
|
|
|
|
'.sr2': ['image/sr2', 'image/x-sony-sr2'],
|
|
|
|
'.srf': ['image/srf', 'image/x-sony-srf'],
|
|
|
|
'.srw': ['image/srw', 'image/x-samsung-srw'],
|
2023-08-17 17:27:29 +02:00
|
|
|
'.tif': ['image/tiff'],
|
2023-07-19 16:27:25 +02:00
|
|
|
'.tiff': ['image/tiff'],
|
|
|
|
'.webp': ['image/webp'],
|
|
|
|
'.x3f': ['image/x3f', 'image/x-sigma-x3f'],
|
2023-07-10 19:56:45 +02:00
|
|
|
};
|
|
|
|
|
2024-02-02 04:18:00 +01:00
|
|
|
const profileExtensions = new Set(['.avif', '.dng', '.heic', '.heif', '.jpeg', '.jpg', '.png', '.webp']);
|
2023-07-19 16:27:25 +02:00
|
|
|
const profile: Record<string, string[]> = Object.fromEntries(
|
2024-02-02 04:18:00 +01:00
|
|
|
Object.entries(image).filter(([key]) => profileExtensions.has(key)),
|
2023-07-19 16:27:25 +02:00
|
|
|
);
|
2023-07-10 19:56:45 +02:00
|
|
|
|
2023-07-19 16:27:25 +02:00
|
|
|
const video: Record<string, string[]> = {
|
|
|
|
'.3gp': ['video/3gpp'],
|
|
|
|
'.avi': ['video/avi', 'video/msvideo', 'video/vnd.avi', 'video/x-msvideo'],
|
|
|
|
'.flv': ['video/x-flv'],
|
2023-08-15 05:14:52 +02:00
|
|
|
'.insv': ['video/mp4'],
|
2023-07-19 16:27:25 +02:00
|
|
|
'.m2ts': ['video/mp2t'],
|
2023-09-18 19:39:42 +02:00
|
|
|
'.m4v': ['video/x-m4v'],
|
2023-07-19 16:27:25 +02:00
|
|
|
'.mkv': ['video/x-matroska'],
|
|
|
|
'.mov': ['video/quicktime'],
|
|
|
|
'.mp4': ['video/mp4'],
|
|
|
|
'.mpg': ['video/mpeg'],
|
|
|
|
'.mts': ['video/mp2t'],
|
|
|
|
'.webm': ['video/webm'],
|
|
|
|
'.wmv': ['video/x-ms-wmv'],
|
2023-07-10 19:56:45 +02:00
|
|
|
};
|
|
|
|
|
2023-07-19 16:27:25 +02:00
|
|
|
const sidecar: Record<string, string[]> = {
|
|
|
|
'.xmp': ['application/xml', 'text/xml'],
|
2023-07-10 19:56:45 +02:00
|
|
|
};
|
|
|
|
|
2023-07-19 16:27:25 +02:00
|
|
|
const isType = (filename: string, r: Record<string, string[]>) => extname(filename).toLowerCase() in r;
|
|
|
|
|
2023-07-12 05:56:30 +02:00
|
|
|
const lookup = (filename: string) =>
|
2023-08-28 21:41:57 +02:00
|
|
|
({ ...image, ...video, ...sidecar })[extname(filename).toLowerCase()]?.[0] ?? 'application/octet-stream';
|
2023-07-10 19:56:45 +02:00
|
|
|
|
|
|
|
export const mimeTypes = {
|
|
|
|
image,
|
|
|
|
profile,
|
|
|
|
sidecar,
|
|
|
|
video,
|
|
|
|
|
|
|
|
isAsset: (filename: string) => isType(filename, image) || isType(filename, video),
|
2023-09-20 13:16:33 +02:00
|
|
|
isImage: (filename: string) => isType(filename, image),
|
2023-07-10 19:56:45 +02:00
|
|
|
isProfile: (filename: string) => isType(filename, profile),
|
|
|
|
isSidecar: (filename: string) => isType(filename, sidecar),
|
|
|
|
isVideo: (filename: string) => isType(filename, video),
|
2023-07-12 05:56:30 +02:00
|
|
|
lookup,
|
|
|
|
assetType: (filename: string) => {
|
2023-07-19 16:27:25 +02:00
|
|
|
const contentType = lookup(filename);
|
|
|
|
if (contentType.startsWith('image/')) {
|
|
|
|
return AssetType.IMAGE;
|
|
|
|
} else if (contentType.startsWith('video/')) {
|
|
|
|
return AssetType.VIDEO;
|
2023-07-12 05:56:30 +02:00
|
|
|
}
|
2023-07-19 16:27:25 +02:00
|
|
|
return AssetType.OTHER;
|
2023-07-12 05:56:30 +02:00
|
|
|
},
|
2024-02-02 04:18:00 +01:00
|
|
|
getSupportedFileExtensions: () => [...Object.keys(image), ...Object.keys(video)],
|
2023-07-10 19:56:45 +02:00
|
|
|
};
|