Compare commits

..

8 Commits

Author SHA1 Message Date
Alex The Bot
429ad28810 Version v1.90.2 2023-12-08 14:23:04 +00:00
martin
7b3465621f fix(web): don't limit merge face selector to 10 people (#5551)
* fix: don't limit merge face selector to 10 people

* fix: don't use class to hide people in detail-panel

* fix: map faces and person in asset response
2023-12-08 08:21:29 -06:00
Alex The Bot
d2fbbe790b Version v1.90.1 2023-12-08 04:20:27 +00:00
martin
bc65bbfcc4 fix(web): create face from video (#5544)
* fix: create face from video

* fix: remove comment

* fix: inaccurate bounding boxes
2023-12-07 22:18:33 -06:00
Alex
e4b24b6e04 fix(web): cannot edit bulk metadata (#5543) 2023-12-07 17:21:51 -06:00
Luca Andrea Rossi
68d467f0e9 Update README_it_IT.md (#5541) 2023-12-07 22:20:41 +00:00
martin
866aaf00ff fix(web): name truncation on detail panel (#5542) 2023-12-07 22:04:59 +00:00
Alex
e086fa6931 chore: post release tasks 2023-12-07 12:48:43 -06:00
31 changed files with 129 additions and 96 deletions

View File

@@ -32,7 +32,7 @@
## Declino di responsabilità
- ⚠️ Il progetto è in fase di sviluppo **molto avanzato**.
- ⚠️ Il progetto è in una fase **molto intensa** di sviluppo.
- ⚠️ Possibilità di bug e cambiamenti rilevanti.
- ⚠️ **Non utilizzare l'app come unico salvataggio delle tue foto e dei tuoi video.**
- ⚠️ Utilizza sempre una tecnica [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) di backup per le foto e i video a cui tieni!
@@ -73,8 +73,8 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Funzionalità | Mobile | Web |
| ---------------------------------------------- | ------ | --- |
| Caricamento e visualizzazione di foto e video | Sì | Sì |
| Backup automatico quando l'app è in esecuzione | Sì | N/A |
| Selezione degli album per backup | Sì | N/A |
| Backup automatico quando l'app è in esecuzione | Sì | N/D |
| Selezione degli album per backup | Sì | N/D |
| Download foto e video sul dispositivo | Sì | Sì |
| Supporto multi utente | Sì | Sì |
| Album e album condivisi | Sì | Sì |
@@ -83,10 +83,10 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Visualizzazione metadata (EXIF, map) | Sì | Sì |
| Ricerca per metadata, oggetti, volti e CLIP | Sì | Sì |
| Funzioni di amministrazione degli utenti | No | Sì |
| Backup in background | Sì | N/A |
| Backup in background | Sì | N/D |
| Scroll virtuale | Sì | Sì |
| Supporto OAuth | Sì | Sì |
| API Keys | N/A | Sì |
| API Keys | N/D | Sì |
| Backup e riproduzione di LivePhoto | iOS | Sì |
| Archiviazione impostata dall'utente | Sì | Sì |
| Condivisione pubblica | No | Sì |
@@ -97,6 +97,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Ricordi (x anni fa) | Sì | Sì |
| Supporto offline | Sì | No |
| Galleria sola lettura | Sì | Sì |
| Foto raggruppate | Sì | Sì |
# Supporta il progetto

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.90.0"
version = "1.90.2"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View File

@@ -36,7 +36,7 @@ platform :android do
build_type: 'Release',
properties: {
"android.injected.version.code" => 114,
"android.injected.version.name" => "1.90.0",
"android.injected.version.name" => "1.90.2",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000263">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000235">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="80.37488">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="27.74518">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="25.830358">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="25.612783">
</testcase>

View File

@@ -379,7 +379,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 128;
CURRENT_PROJECT_VERSION = 130;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -515,7 +515,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 128;
CURRENT_PROJECT_VERSION = 130;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -543,7 +543,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 128;
CURRENT_PROJECT_VERSION = 130;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -54,11 +54,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.88.0</string>
<string>1.90.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>128</string>
<string>130</string>
<key>FLTEnableImpeller</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.90.0"
version_number: "1.90.2"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -5,32 +5,32 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000267">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000234">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.193021">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.207521">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.987435">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="18.516191">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.181886">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.23018">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="105.510332">
<testcase classname="fastlane.lanes" name="4: build_app" time="104.984834">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="65.714015">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="61.879749">
</testcase>

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.90.0
- API version: 1.90.2
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: "none"
version: 1.90.0+114
version: 1.90.2+114
isar_version: &isar_version 3.1.0+1
environment:

View File

@@ -6279,7 +6279,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "1.90.0",
"version": "1.90.2",
"contact": {}
},
"tags": [],

View File

@@ -1,12 +1,12 @@
{
"name": "immich",
"version": "1.90.0",
"version": "1.90.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "immich",
"version": "1.90.0",
"version": "1.90.2",
"license": "UNLICENSED",
"dependencies": {
"@babel/runtime": "^7.22.11",

View File

@@ -1,6 +1,6 @@
{
"name": "immich",
"version": "1.90.0",
"version": "1.90.2",
"description": "",
"author": "",
"private": true,

View File

@@ -1,6 +1,6 @@
import { AssetEntity, AssetFaceEntity, AssetType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { PersonWithFacesResponseDto } from '../../person/person.dto';
import { PersonWithFacesResponseDto, mapFacesWithoutPerson, mapPerson } from '../../person/person.dto';
import { TagResponseDto, mapTag } from '../../tag';
import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto';
import { ExifResponseDto, mapExif } from './exif-response.dto';
@@ -62,7 +62,7 @@ const peopleWithFaces = (faces: AssetFaceEntity[]): PersonWithFacesResponseDto[]
if (existingPersonEntry) {
existingPersonEntry.faces.push(face);
} else {
result.push({ ...face.person!, faces: [face] });
result.push({ ...mapPerson(face.person!), faces: [mapFacesWithoutPerson(face)] });
}
}
});

View File

@@ -144,7 +144,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
};
}
export function mapFaces(face: AssetFaceEntity, authUser: AuthUserDto): AssetFaceResponseDto {
export function mapFacesWithoutPerson(face: AssetFaceEntity): AssetFaceWithoutPersonResponseDto {
return {
id: face.id,
imageHeight: face.imageHeight,
@@ -153,6 +153,12 @@ export function mapFaces(face: AssetFaceEntity, authUser: AuthUserDto): AssetFac
boundingBoxX2: face.boundingBoxX2,
boundingBoxY1: face.boundingBoxY1,
boundingBoxY2: face.boundingBoxY2,
};
}
export function mapFaces(face: AssetFaceEntity, authUser: AuthUserDto): AssetFaceResponseDto {
return {
...mapFacesWithoutPerson(face),
person: face.person?.ownerId === authUser.id ? mapPerson(face.person) : null,
};
}

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.0
* The version of the OpenAPI document: 1.90.2
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -228,50 +228,52 @@
<div class="mt-2 flex flex-wrap gap-2">
{#each people as person, index (person.id)}
<div
role="button"
tabindex={index}
on:focus={() => ($boundingBoxesArray = people[index].faces)}
on:mouseover={() => ($boundingBoxesArray = people[index].faces)}
on:mouseleave={() => ($boundingBoxesArray = [])}
>
<a
href="/people/{person.id}?previousRoute={albumId ? `${AppRoute.ALBUMS}/${albumId}` : AppRoute.PHOTOS}"
class="w-[90px] {!showingHiddenPeople && person.isHidden ? 'hidden' : ''}"
on:click={() => dispatch('close-viewer')}
{#if showingHiddenPeople || !person.isHidden}
<div
class="w-[90px]"
role="button"
tabindex={index}
on:focus={() => ($boundingBoxesArray = people[index].faces)}
on:mouseover={() => ($boundingBoxesArray = people[index].faces)}
on:mouseleave={() => ($boundingBoxesArray = [])}
>
<div class="relative">
<ImageThumbnail
curve
shadow
url={api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
title={person.name}
widthStyle="90px"
heightStyle="90px"
thumbhash={null}
hidden={person.isHidden}
/>
</div>
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
{#if person.birthDate}
{@const personBirthDate = DateTime.fromISO(person.birthDate)}
<p
class="font-light"
title={personBirthDate.toLocaleString(
{
month: 'long',
day: 'numeric',
year: 'numeric',
},
{ locale: $locale },
)}
>
Age {Math.floor(DateTime.fromISO(asset.fileCreatedAt).diff(personBirthDate, 'years').years)}
</p>
{/if}
</a>
</div>
<a
href="/people/{person.id}?previousRoute={albumId ? `${AppRoute.ALBUMS}/${albumId}` : AppRoute.PHOTOS}"
on:click={() => dispatch('close-viewer')}
>
<div class="relative">
<ImageThumbnail
curve
shadow
url={api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
title={person.name}
widthStyle="90px"
heightStyle="90px"
thumbhash={null}
hidden={person.isHidden}
/>
</div>
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
{#if person.birthDate}
{@const personBirthDate = DateTime.fromISO(person.birthDate)}
<p
class="font-light"
title={personBirthDate.toLocaleString(
{
month: 'long',
day: 'numeric',
year: 'numeric',
},
{ locale: $locale },
)}
>
Age {Math.floor(DateTime.fromISO(asset.fileCreatedAt).diff(personBirthDate, 'years').years)}
</p>
{/if}
</a>
</div>
{/if}
{/each}
</div>
</section>
@@ -634,6 +636,7 @@
{#if showEditFaces}
<PersonSidePanel
assetId={asset.id}
assetType={asset.type}
on:close={() => {
showEditFaces = false;
}}

View File

@@ -143,7 +143,7 @@
/>
{#each getBoundingBox($boundingBoxesArray, $photoZoomState, $photoViewer) as boundingbox}
<div
class="absolute border-solid border-white border-[3px] rounded-lg p-3"
class="absolute border-solid border-white border-[3px] rounded-lg"
style="top: {boundingbox.top}px; left: {boundingbox.left}px; height: {boundingbox.height}px; width: {boundingbox.width}px;"
/>
{/each}

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { api, type AssetFaceResponseDto, type PersonResponseDto } from '@api';
import { api, AssetTypeEnum, type AssetFaceResponseDto, type PersonResponseDto, ThumbnailFormat } from '@api';
import { createEventDispatcher } from 'svelte';
import { linear } from 'svelte/easing';
import { fly } from 'svelte/transition';
@@ -14,6 +14,8 @@
export let peopleWithFaces: AssetFaceResponseDto[];
export let allPeople: PersonResponseDto[];
export let editedPersonIndex: number;
export let assetType: AssetTypeEnum;
export let assetId: string;
// loading spinners
let isShowLoadingNewPerson = false;
@@ -31,23 +33,38 @@
dispatch('close');
};
const zoomImageToBase64 = async (face: AssetFaceResponseDto): Promise<string | null> => {
if ($photoViewer === null) {
let image: HTMLImageElement | null = null;
if (assetType === AssetTypeEnum.Image) {
image = $photoViewer;
} else if (assetType === AssetTypeEnum.Video) {
const data = await api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp);
const img: HTMLImageElement = new Image();
img.src = data;
await new Promise<void>((resolve) => {
img.onload = () => resolve();
img.onerror = () => resolve();
});
image = img;
}
if (image === null) {
return null;
}
const { boundingBoxX1: x1, boundingBoxX2: x2, boundingBoxY1: y1, boundingBoxY2: y2 } = face;
const coordinates = {
x1: ($photoViewer.naturalWidth / face.imageWidth) * x1,
x2: ($photoViewer.naturalWidth / face.imageWidth) * x2,
y1: ($photoViewer.naturalHeight / face.imageHeight) * y1,
y2: ($photoViewer.naturalHeight / face.imageHeight) * y2,
x1: (image.naturalWidth / face.imageWidth) * x1,
x2: (image.naturalWidth / face.imageWidth) * x2,
y1: (image.naturalHeight / face.imageHeight) * y1,
y2: (image.naturalHeight / face.imageHeight) * y2,
};
const faceWidth = coordinates.x2 - coordinates.x1;
const faceHeight = coordinates.y2 - coordinates.y1;
const faceImage = new Image();
faceImage.src = $photoViewer.src;
faceImage.src = image.src;
await new Promise((resolve) => {
faceImage.onload = resolve;

View File

@@ -30,7 +30,9 @@
people = peopleCopy.filter(
(person) => !unselectedPeople.some((unselectedPerson) => unselectedPerson.id === person.id),
);
people = searchNameLocal(name, people, 10);
if (name) {
people = searchNameLocal(name, people, 10);
}
}
const searchPeople = async (force: boolean) => {

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { linear } from 'svelte/easing';
import { api, type PersonResponseDto, AssetFaceResponseDto } from '@api';
import { api, type PersonResponseDto, AssetFaceResponseDto, AssetTypeEnum } from '@api';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import { handleError } from '$lib/utils/handle-error';
import { createEventDispatcher, onMount } from 'svelte';
@@ -15,6 +15,7 @@
import { getPersonNameWithHiddenValue } from '$lib/utils/person';
export let assetId: string;
export let assetType: AssetTypeEnum;
// keep track of the changes
let numberOfPersonToCreate: string[] = [];
@@ -271,6 +272,8 @@
{peopleWithFaces}
{allPeople}
{editedPersonIndex}
{assetType}
{assetId}
on:close={() => (showSeletecFaces = false)}
on:createPerson={(event) => handleCreatePerson(event.detail)}
on:reassign={(event) => handleReassignFace(event.detail)}

View File

@@ -214,9 +214,10 @@ export const getAssetType = (type: AssetTypeEnum) => {
export const getSelectedAssets = (assets: Set<AssetResponseDto>, user: UserResponseDto | null): string[] => {
const ids = Array.from(assets)
.filter((a) => !a.isExternal && user && a.ownerId !== user.id)
.filter((a) => !a.isExternal && user && a.ownerId === user.id)
.map((a) => a.id);
const numberOfIssues = Array.from(assets).filter((a) => a.isExternal || (user && a.ownerId === user.id)).length;
const numberOfIssues = Array.from(assets).filter((a) => a.isExternal || (user && a.ownerId !== user.id)).length;
if (numberOfIssues > 0) {
notificationController.show({
message: `Can't change metadata of ${numberOfIssues} asset${numberOfIssues > 1 ? 's' : ''}`,