feat: facial recognition (#2180)

This commit is contained in:
Jason Rasmussen
2023-05-17 13:07:17 -04:00
committed by GitHub
parent 115a47d4c6
commit 93863b0629
107 changed files with 3943 additions and 133 deletions

View File

@@ -36,6 +36,10 @@
title: 'Encode Clip',
subtitle: 'Run machine learning to generate clip embeddings'
},
[JobName.RecognizeFacesQueue]: {
title: 'Recognize Faces',
subtitle: 'Run machine learning to recognize faces'
},
[JobName.VideoConversionQueue]: {
title: 'Transcode Videos',
subtitle: 'Transcode videos not in the desired format'

View File

@@ -120,10 +120,6 @@
}
});
const clearMultiSelectAssetAssetHandler = () => {
multiSelectAsset = new Set();
};
// Update Album Name
$: {
if (!isEditingTitle && currentAlbumName != album.albumName && isOwned) {
@@ -340,7 +336,7 @@
{#if isMultiSelectionMode}
<AssetSelectControlBar
assets={multiSelectAsset}
clearSelect={clearMultiSelectAssetAssetHandler}
clearSelect={() => (multiSelectAsset = new Set())}
>
<DownloadFiles filename={album.albumName} sharedLinkKey={sharedLink?.key} />
{#if isOwned}

View File

@@ -91,6 +91,11 @@
}
};
const handleCloseViewer = () => {
isShowDetail = false;
closeViewer();
};
const closeViewer = () => {
dispatch('close');
};
@@ -398,6 +403,7 @@
{asset}
albums={appearsInAlbums}
on:close={() => (isShowDetail = false)}
on:close-viewer={handleCloseViewer}
on:description-focus-in={disableKeyDownEvent}
on:description-focus-out={enableKeyDownEvent}
/>

View File

@@ -1,16 +1,17 @@
<script lang="ts">
import Close from 'svelte-material-icons/Close.svelte';
import { page } from '$app/stores';
import { locale } from '$lib/stores/preferences.store';
import type { LatLngTuple } from 'leaflet';
import { DateTime } from 'luxon';
import Calendar from 'svelte-material-icons/Calendar.svelte';
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
import CameraIris from 'svelte-material-icons/CameraIris.svelte';
import Close from 'svelte-material-icons/Close.svelte';
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte';
import { createEventDispatcher } from 'svelte';
import { AssetResponseDto, AlbumResponseDto, api, ThumbnailFormat } from '@api';
import { asByteUnitString } from '../../utils/byte-units';
import { locale } from '$lib/stores/preferences.store';
import { DateTime } from 'luxon';
import type { LatLngTuple } from 'leaflet';
import { page } from '$app/stores';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
export let asset: AssetResponseDto;
export let albums: AlbumResponseDto[] = [];
@@ -20,9 +21,10 @@
$: {
// Get latest description from server
if (asset.id) {
api.assetApi
.getAssetById(asset.id)
.then((res) => (textarea.value = res.data?.exifInfo?.description || ''));
api.assetApi.getAssetById(asset.id).then((res) => {
people = res.data?.people || [];
textarea.value = res.data?.exifInfo?.description || '';
});
}
}
@@ -35,6 +37,8 @@
}
})();
$: people = asset.people || [];
const dispatch = createEventDispatcher();
const getMegapixel = (width: number, height: number): number | undefined => {
@@ -81,7 +85,7 @@
<p class="text-immich-fg dark:text-immich-dark-fg text-lg">Info</p>
</div>
<div class="mx-4 mt-10">
<section class="mx-4 mt-10">
<textarea
bind:this={textarea}
class="max-h-[500px]
@@ -96,13 +100,35 @@
bind:value={description}
disabled={$page?.data?.user?.id !== asset.ownerId}
/>
</div>
</section>
{#if people.length > 0}
<section class="px-4 py-4 text-sm">
<h2>PEOPLE</h2>
<div class="flex flex-wrap gap-2 mt-4">
{#each people as person (person.id)}
<a href="/people/{person.id}" class="w-[90px]" on:click={() => dispatch('close-viewer')}>
<ImageThumbnail
curve
shadow
url={api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
widthStyle="90px"
heightStyle="90px"
/>
<p class="font-medium mt-1 truncate">{person.name}</p>
</a>
{/each}
</div>
</section>
{/if}
<div class="px-4 py-4">
{#if !asset.exifInfo}
<p class="text-sm pb-4">NO EXIF INFO AVAILABLE</p>
<p class="text-sm">NO EXIF INFO AVAILABLE</p>
{:else}
<p class="text-sm pb-4">DETAILS</p>
<p class="text-sm">DETAILS</p>
{/if}
{#if asset.exifInfo?.dateTimeOriginal}

View File

@@ -1,9 +1,13 @@
<script lang="ts">
import { imageLoad } from '$lib/utils/image-load';
export let url: string;
export let altText: string;
export let heightStyle: string;
export let heightStyle: string | undefined = undefined;
export let widthStyle: string;
export let curve = false;
export let shadow = false;
export let circle = false;
let loading = true;
</script>
@@ -13,7 +17,11 @@
src={url}
alt={altText}
class="object-cover transition-opacity duration-300"
class:rounded-lg={curve}
class:shadow-lg={shadow}
class:rounded-full={circle}
class:opacity-0={loading}
draggable="false"
on:load|once={() => (loading = false)}
use:imageLoad
on:image-load|once={() => (loading = false)}
/>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import { PersonResponseDto, api } from '@api';
import { createEventDispatcher } from 'svelte';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import Button from '../elements/buttons/button.svelte';
export let person: PersonResponseDto;
let name = person.name;
const dispatch = createEventDispatcher<{ change: string }>();
const handleNameChange = () => dispatch('change', name);
</script>
<div
class="flex place-items-center max-w-lg rounded-lg border dark:border-transparent p-2 bg-gray-100 dark:bg-gray-700"
>
<ImageThumbnail
circle
shadow
url={api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
widthStyle="2rem"
heightStyle="2rem"
/>
<form
class="ml-4 flex justify-between w-full gap-16"
autocomplete="off"
on:submit|preventDefault={handleNameChange}
>
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
class="gap-2 w-full bg-gray-100 dark:bg-gray-700 dark:text-white"
type="text"
placeholder="New name or nickname"
required
bind:value={name}
on:blur
/>
<Button size="sm" type="submit">Done</Button>
</form>
</div>