mirror of
https://github.com/immich-app/immich.git
synced 2025-03-11 02:23:09 -05:00
fix(web): tag people in video (#16351)
This commit is contained in:
parent
2969e25ff7
commit
c778516ce2
3 changed files with 79 additions and 28 deletions
|
@ -12,13 +12,13 @@
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
imgElement: HTMLImageElement;
|
htmlElement: HTMLImageElement | HTMLVideoElement;
|
||||||
containerWidth: number;
|
containerWidth: number;
|
||||||
containerHeight: number;
|
containerHeight: number;
|
||||||
assetId: string;
|
assetId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { imgElement, containerWidth, containerHeight, assetId }: Props = $props();
|
let { htmlElement, containerWidth, containerHeight, assetId }: Props = $props();
|
||||||
|
|
||||||
let canvasEl: HTMLCanvasElement | undefined = $state();
|
let canvasEl: HTMLCanvasElement | undefined = $state();
|
||||||
let canvas: Canvas | undefined = $state();
|
let canvas: Canvas | undefined = $state();
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupCanvas = () => {
|
const setupCanvas = () => {
|
||||||
if (!canvasEl || !imgElement) {
|
if (!canvasEl || !htmlElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const { actualWidth, actualHeight } = getContainedSize(imgElement);
|
const { actualWidth, actualHeight } = getContainedSize(htmlElement);
|
||||||
const offsetArea = {
|
const offsetArea = {
|
||||||
width: (containerWidth - actualWidth) / 2,
|
width: (containerWidth - actualWidth) / 2,
|
||||||
height: (containerHeight - actualHeight) / 2,
|
height: (containerHeight - actualHeight) / 2,
|
||||||
|
@ -103,15 +103,30 @@
|
||||||
positionFaceSelector();
|
positionFaceSelector();
|
||||||
});
|
});
|
||||||
|
|
||||||
const getContainedSize = (img: HTMLImageElement): { actualWidth: number; actualHeight: number } => {
|
const getContainedSize = (
|
||||||
const ratio = img.naturalWidth / img.naturalHeight;
|
img: HTMLImageElement | HTMLVideoElement,
|
||||||
let actualWidth = img.height * ratio;
|
): { actualWidth: number; actualHeight: number } => {
|
||||||
let actualHeight = img.height;
|
if (img instanceof HTMLImageElement) {
|
||||||
if (actualWidth > img.width) {
|
const ratio = img.naturalWidth / img.naturalHeight;
|
||||||
actualWidth = img.width;
|
let actualWidth = img.height * ratio;
|
||||||
actualHeight = img.width / ratio;
|
let actualHeight = img.height;
|
||||||
|
if (actualWidth > img.width) {
|
||||||
|
actualWidth = img.width;
|
||||||
|
actualHeight = img.width / ratio;
|
||||||
|
}
|
||||||
|
return { actualWidth, actualHeight };
|
||||||
|
} else if (img instanceof HTMLVideoElement) {
|
||||||
|
const ratio = img.videoWidth / img.videoHeight;
|
||||||
|
let actualWidth = img.clientHeight * ratio;
|
||||||
|
let actualHeight = img.clientHeight;
|
||||||
|
if (actualWidth > img.clientWidth) {
|
||||||
|
actualWidth = img.clientWidth;
|
||||||
|
actualHeight = img.clientWidth / ratio;
|
||||||
|
}
|
||||||
|
return { actualWidth, actualHeight };
|
||||||
}
|
}
|
||||||
return { actualWidth, actualHeight };
|
|
||||||
|
return { actualWidth: 0, actualHeight: 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
|
@ -202,12 +217,12 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const getFaceCroppedCoordinates = () => {
|
const getFaceCroppedCoordinates = () => {
|
||||||
if (!faceRect || !imgElement) {
|
if (!faceRect || !htmlElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { left, top, width, height } = faceRect.getBoundingRect();
|
const { left, top, width, height } = faceRect.getBoundingRect();
|
||||||
const { actualWidth, actualHeight } = getContainedSize(imgElement);
|
const { actualWidth, actualHeight } = getContainedSize(htmlElement);
|
||||||
|
|
||||||
const offsetArea = {
|
const offsetArea = {
|
||||||
width: (containerWidth - actualWidth) / 2,
|
width: (containerWidth - actualWidth) / 2,
|
||||||
|
@ -220,19 +235,35 @@
|
||||||
const y2Coeff = (top + height - offsetArea.height) / actualHeight;
|
const y2Coeff = (top + height - offsetArea.height) / actualHeight;
|
||||||
|
|
||||||
// transpose to the natural image location
|
// transpose to the natural image location
|
||||||
const x1 = x1Coeff * imgElement.naturalWidth;
|
if (htmlElement instanceof HTMLImageElement) {
|
||||||
const y1 = y1Coeff * imgElement.naturalHeight;
|
const x1 = x1Coeff * htmlElement.naturalWidth;
|
||||||
const x2 = x2Coeff * imgElement.naturalWidth;
|
const y1 = y1Coeff * htmlElement.naturalHeight;
|
||||||
const y2 = y2Coeff * imgElement.naturalHeight;
|
const x2 = x2Coeff * htmlElement.naturalWidth;
|
||||||
|
const y2 = y2Coeff * htmlElement.naturalHeight;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
imageWidth: imgElement.naturalWidth,
|
imageWidth: htmlElement.naturalWidth,
|
||||||
imageHeight: imgElement.naturalHeight,
|
imageHeight: htmlElement.naturalHeight,
|
||||||
x: Math.floor(x1),
|
x: Math.floor(x1),
|
||||||
y: Math.floor(y1),
|
y: Math.floor(y1),
|
||||||
width: Math.floor(x2 - x1),
|
width: Math.floor(x2 - x1),
|
||||||
height: Math.floor(y2 - y1),
|
height: Math.floor(y2 - y1),
|
||||||
};
|
};
|
||||||
|
} else if (htmlElement instanceof HTMLVideoElement) {
|
||||||
|
const x1 = x1Coeff * htmlElement.videoWidth;
|
||||||
|
const y1 = y1Coeff * htmlElement.videoHeight;
|
||||||
|
const x2 = x2Coeff * htmlElement.videoWidth;
|
||||||
|
const y2 = y2Coeff * htmlElement.videoHeight;
|
||||||
|
|
||||||
|
return {
|
||||||
|
imageWidth: htmlElement.videoWidth,
|
||||||
|
imageHeight: htmlElement.videoHeight,
|
||||||
|
x: Math.floor(x1),
|
||||||
|
y: Math.floor(y1),
|
||||||
|
width: Math.floor(x2 - x1),
|
||||||
|
height: Math.floor(y2 - y1),
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagFace = async (person: PersonResponseDto) => {
|
const tagFace = async (person: PersonResponseDto) => {
|
||||||
|
|
|
@ -234,7 +234,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isFaceEditMode.value}
|
{#if isFaceEditMode.value}
|
||||||
<FaceEditor imgElement={$photoViewerImgElement} {containerWidth} {containerHeight} assetId={asset.id} />
|
<FaceEditor htmlElement={$photoViewerImgElement} {containerWidth} {containerHeight} assetId={asset.id} />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
import type { SwipeCustomEvent } from 'svelte-gestures';
|
import type { SwipeCustomEvent } from 'svelte-gestures';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||||
|
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assetId: string;
|
assetId: string;
|
||||||
|
@ -84,9 +86,23 @@
|
||||||
onPreviousAsset();
|
onPreviousAsset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let containerWidth = $state(0);
|
||||||
|
let containerHeight = $state(0);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (isFaceEditMode.value) {
|
||||||
|
videoPlayer?.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div transition:fade={{ duration: 150 }} class="flex h-full select-none place-content-center place-items-center">
|
<div
|
||||||
|
transition:fade={{ duration: 150 }}
|
||||||
|
class="flex h-full select-none place-content-center place-items-center"
|
||||||
|
bind:clientWidth={containerWidth}
|
||||||
|
bind:clientHeight={containerHeight}
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
bind:this={videoPlayer}
|
bind:this={videoPlayer}
|
||||||
loop={$loopVideoPreference && loopVideo}
|
loop={$loopVideoPreference && loopVideo}
|
||||||
|
@ -116,4 +132,8 @@
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if isFaceEditMode.value}
|
||||||
|
<FaceEditor htmlElement={videoPlayer} {containerWidth} {containerHeight} {assetId} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue