mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
feat: exif metadata workflow filter (#29295)
This commit is contained in:
parent
2d3cf8ffaa
commit
8bbc252703
2 changed files with 117 additions and 33 deletions
|
|
@ -308,6 +308,76 @@
|
||||||
},
|
},
|
||||||
"uiHints": ["Filter"]
|
"uiHints": ["Filter"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "assetExifFilter",
|
||||||
|
"title": "Filter by EXIF metadata",
|
||||||
|
"description": "Filter assets by their EXIF properties",
|
||||||
|
"types": ["AssetV1"],
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"property": {
|
||||||
|
"title": "Property",
|
||||||
|
"description": "EXIF property to match",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"make",
|
||||||
|
"model",
|
||||||
|
"exifImageWidth",
|
||||||
|
"exifImageHeight",
|
||||||
|
"fileSizeInByte",
|
||||||
|
"orientation",
|
||||||
|
"lensModel",
|
||||||
|
"fNumber",
|
||||||
|
"focalLength",
|
||||||
|
"iso",
|
||||||
|
"description",
|
||||||
|
"fps",
|
||||||
|
"exposureTime",
|
||||||
|
"livePhotoCID",
|
||||||
|
"timeZone",
|
||||||
|
"projectionType",
|
||||||
|
"profileDescription",
|
||||||
|
"colorspace",
|
||||||
|
"bitsPerSample",
|
||||||
|
"rating"
|
||||||
|
],
|
||||||
|
"uiHint": {
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Pattern",
|
||||||
|
"description": "Text or regex pattern to match against property value",
|
||||||
|
"uiHint": {
|
||||||
|
"order": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"matchType": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Match type",
|
||||||
|
"enum": ["contains", "startsWith", "exact", "regex"],
|
||||||
|
"default": "contains",
|
||||||
|
"description": "Type of pattern matching to perform",
|
||||||
|
"uiHint": {
|
||||||
|
"order": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"caseSensitive": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"title": "Case sensitive",
|
||||||
|
"description": "Whether matching should be case-sensitive",
|
||||||
|
"uiHint": {
|
||||||
|
"order": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["property", "pattern"]
|
||||||
|
},
|
||||||
|
"uiHints": ["Filter"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "assetArchive",
|
"name": "assetArchive",
|
||||||
"title": "Archive asset",
|
"title": "Archive asset",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,42 @@ import { wrapper } from '@immich/plugin-sdk';
|
||||||
import { AssetVisibility } from '@immich/sdk';
|
import { AssetVisibility } from '@immich/sdk';
|
||||||
import type { Manifest } from '../dist/index.d.ts';
|
import type { Manifest } from '../dist/index.d.ts';
|
||||||
|
|
||||||
|
type MatchValueConfig = {
|
||||||
|
pattern: string;
|
||||||
|
matchType?: 'contains' | 'exact' | 'regex' | 'startsWith';
|
||||||
|
caseSensitive?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchValueResult = (value: string, config: MatchValueConfig) => {
|
||||||
|
const { pattern, matchType = 'contains', caseSensitive = false } = config;
|
||||||
|
const searchName = caseSensitive ? value : value.toLowerCase();
|
||||||
|
const searchPattern = caseSensitive ? pattern : pattern.toLowerCase();
|
||||||
|
|
||||||
|
switch (matchType) {
|
||||||
|
case 'contains': {
|
||||||
|
return { workflow: { continue: searchName.includes(searchPattern) } };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'exact': {
|
||||||
|
return { workflow: { continue: searchName === searchPattern } };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'startsWith': {
|
||||||
|
return { workflow: { continue: searchName.startsWith(searchPattern) } };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'regex': {
|
||||||
|
const flags = caseSensitive ? '' : 'i';
|
||||||
|
const regex = new RegExp(searchPattern, flags);
|
||||||
|
return { workflow: { continue: regex.test(value) } };
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const methods = wrapper<Manifest>({
|
const methods = wrapper<Manifest>({
|
||||||
assetAddToAlbums: ({ config, data, functions }) => {
|
assetAddToAlbums: ({ config, data, functions }) => {
|
||||||
const assetId = data.asset.id;
|
const assetId = data.asset.id;
|
||||||
|
|
@ -53,39 +89,7 @@ const methods = wrapper<Manifest>({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
assetFileFilter: ({ data, config }) => {
|
assetFileFilter: ({ data, config }) => matchValueResult(data.asset.originalFileName || '', config),
|
||||||
const { pattern, matchType = 'contains', caseSensitive = false, usePath = false } = config;
|
|
||||||
|
|
||||||
const { asset } = data;
|
|
||||||
|
|
||||||
const fileName = usePath ? asset.originalPath : asset.originalFileName;
|
|
||||||
const searchName = caseSensitive ? fileName : fileName.toLowerCase();
|
|
||||||
const searchPattern = caseSensitive ? pattern : pattern.toLowerCase();
|
|
||||||
|
|
||||||
switch (matchType) {
|
|
||||||
case 'contains': {
|
|
||||||
return { workflow: { continue: searchName.includes(searchPattern) } };
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'exact': {
|
|
||||||
return { workflow: { continue: searchName === searchPattern } };
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'startsWith': {
|
|
||||||
return { workflow: { continue: searchName.startsWith(searchPattern) } };
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'regex': {
|
|
||||||
const flags = caseSensitive ? '' : 'i';
|
|
||||||
const regex = new RegExp(searchPattern, flags);
|
|
||||||
return { workflow: { continue: regex.test(fileName) } };
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
assetLocationFilter: ({ config, data }) => {
|
assetLocationFilter: ({ config, data }) => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -124,6 +128,14 @@ const methods = wrapper<Manifest>({
|
||||||
return { workflow: { continue: earthDiameter * delta <= (config.coordinate?.radius ?? 0) } };
|
return { workflow: { continue: earthDiameter * delta <= (config.coordinate?.radius ?? 0) } };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
assetExifFilter: ({ config, data }) => {
|
||||||
|
if (!data.asset.exifInfo || data.asset.exifInfo[config.property] === null) {
|
||||||
|
return { workflow: { continue: false } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchValueResult(String(data.asset.exifInfo[config.property]), config);
|
||||||
|
},
|
||||||
|
|
||||||
assetDateFilter: ({ config, data }) => {
|
assetDateFilter: ({ config, data }) => {
|
||||||
const assetDate = new Date(data.asset.localDateTime);
|
const assetDate = new Date(data.asset.localDateTime);
|
||||||
let startDate = new Date(config.startDate.year, config.startDate.month - 1, config.startDate.day);
|
let startDate = new Date(config.startDate.year, config.startDate.month - 1, config.startDate.day);
|
||||||
|
|
@ -200,6 +212,7 @@ const {
|
||||||
assetFavorite,
|
assetFavorite,
|
||||||
assetFileFilter,
|
assetFileFilter,
|
||||||
assetLocationFilter,
|
assetLocationFilter,
|
||||||
|
assetExifFilter,
|
||||||
assetDateFilter,
|
assetDateFilter,
|
||||||
assetLock,
|
assetLock,
|
||||||
assetMissingTimeZoneFilter,
|
assetMissingTimeZoneFilter,
|
||||||
|
|
@ -217,6 +230,7 @@ export {
|
||||||
assetFavorite,
|
assetFavorite,
|
||||||
assetFileFilter,
|
assetFileFilter,
|
||||||
assetLocationFilter,
|
assetLocationFilter,
|
||||||
|
assetExifFilter,
|
||||||
assetDateFilter,
|
assetDateFilter,
|
||||||
assetLock,
|
assetLock,
|
||||||
assetMissingTimeZoneFilter,
|
assetMissingTimeZoneFilter,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue